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/net/wan/Kconfig b/drivers/net/wan/Kconfig
new file mode 100644
index 0000000..3579193
--- /dev/null
+++ b/drivers/net/wan/Kconfig
@@ -0,0 +1,607 @@
+#
+# wan devices configuration
+#
+
+menu "Wan interfaces"
+	depends on NETDEVICES
+
+config WAN
+	bool "Wan interfaces support"
+	---help---
+	  Wide Area Networks (WANs), such as X.25, Frame Relay and leased
+	  lines, are used to interconnect Local Area Networks (LANs) over vast
+	  distances with data transfer rates significantly higher than those
+	  achievable with commonly used asynchronous modem connections.
+
+	  Usually, a quite expensive external device called a `WAN router' is
+	  needed to connect to a WAN. As an alternative, a relatively
+	  inexpensive WAN interface card can allow your Linux box to directly
+	  connect to a WAN.
+
+	  If you have one of those cards and wish to use it under Linux,
+	  say Y here and also to the WAN driver for your card.
+
+	  If unsure, say N.
+
+# There is no way to detect a comtrol sv11 - force it modular for now.
+config HOSTESS_SV11
+	tristate "Comtrol Hostess SV-11 support"
+	depends on WAN && ISA && m
+	help
+	  Driver for Comtrol Hostess SV-11 network card which
+	  operates on low speed synchronous serial links at up to
+	  256Kbps, supporting PPP and Cisco HDLC.
+
+	  The driver will be compiled as a module: the
+	  module will be called hostess_sv11.
+
+# The COSA/SRP driver has not been tested as non-modular yet.
+config COSA
+	tristate "COSA/SRP sync serial boards support"
+	depends on WAN && ISA && m
+	---help---
+	  Driver for COSA and SRP synchronous serial boards.
+
+	  These boards allow to connect synchronous serial devices (for example
+	  base-band modems, or any other device with the X.21, V.24, V.35 or
+	  V.36 interface) to your Linux box. The cards can work as the
+	  character device, synchronous PPP network device, or the Cisco HDLC
+	  network device.
+
+	  You will need user-space utilities COSA or SRP boards for downloading
+ 	  the firmware to the cards and to set them up. Look at the
+	  <http://www.fi.muni.cz/~kas/cosa/> for more information. You can also
+	  read the comment at the top of the <file:drivers/net/wan/cosa.c> for
+	  details about the cards and the driver itself.
+
+	  The driver will be compiled as a module: the
+	  module will be called cosa.
+
+config DSCC4
+	tristate "Etinc PCISYNC serial board support"
+	depends on WAN && PCI && m
+	help
+	  Driver for Etinc PCISYNC boards based on the Infineon (ex. Siemens)
+	  DSCC4 chipset.
+
+	  This is supposed to work with the four port card. Take a look at
+	  <http://www.cogenit.fr/dscc4/> for further information about the
+	  driver.
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called dscc4.
+
+config DSCC4_PCISYNC
+	bool "Etinc PCISYNC features"
+	depends on DSCC4
+	help
+	  Due to Etinc's design choice for its PCISYNC cards, some operations
+	  are only allowed on specific ports of the DSCC4. This option is the
+	  only way for the driver to know that it shouldn't return a success
+	  code for these operations.
+
+	  Please say Y if your card is an Etinc's PCISYNC.
+
+config DSCC4_PCI_RST
+	bool "Hard reset support"
+	depends on DSCC4
+	help
+	  Various DSCC4 bugs forbid any reliable software reset of the ASIC.
+	  As a replacement, some vendors provide a way to assert the PCI #RST
+	  pin of DSCC4 through the GPIO port of the card. If you choose Y,
+	  the driver will make use of this feature before module removal
+	  (i.e. rmmod). The feature is known to be available on Commtech's
+	  cards. Contact your manufacturer for details.
+
+	  Say Y if your card supports this feature.
+
+#
+# Lan Media's board. Currently 1000, 1200, 5200, 5245
+#
+config LANMEDIA
+	tristate "LanMedia Corp. SSI/V.35, T1/E1, HSSI, T3 boards"
+	depends on WAN && PCI
+	---help---
+	  Driver for the following Lan Media family of serial boards:
+
+	  - LMC 1000 board allows you to connect synchronous serial devices
+	  (for example base-band modems, or any other device with the X.21,
+	  V.24, V.35 or V.36 interface) to your Linux box.
+
+	  - LMC 1200 with on board DSU board allows you to connect your Linux
+	  box directly to a T1 or E1 circuit.
+
+	  - LMC 5200 board provides a HSSI interface capable of running up to
+	  52 Mbits per second.
+
+	  - LMC 5245 board connects directly to a T3 circuit saving the
+	  additional external hardware.
+
+	  To change setting such as syncPPP vs Cisco HDLC or clock source you
+	  will need lmcctl.  It is available at <ftp://ftp.lanmedia.com/>
+	  (broken link).
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called lmc.
+
+# There is no way to detect a Sealevel board. Force it modular
+config SEALEVEL_4021
+	tristate "Sealevel Systems 4021 support"
+	depends on WAN && ISA && m
+	help
+	  This is a driver for the Sealevel Systems ACB 56 serial I/O adapter.
+
+	  The driver will be compiled as a module: the
+	  module will be called sealevel.
+
+config SYNCLINK_SYNCPPP
+	tristate "SyncLink HDLC/SYNCPPP support"
+	depends on WAN
+	help
+	  Enables HDLC/SYNCPPP support for the SyncLink WAN driver.
+
+	  Normally the SyncLink WAN driver works with the main PPP driver
+	  <file:drivers/net/ppp_generic.c> and pppd program.
+	  HDLC/SYNCPPP support allows use of the Cisco HDLC/PPP driver
+	  <file:drivers/net/wan/syncppp.c>. The SyncLink WAN driver (in
+	  character devices) must also be enabled.
+
+# Generic HDLC
+config HDLC
+	tristate "Generic HDLC layer"
+	depends on WAN
+	help
+	  Say Y to this option if your Linux box contains a WAN (Wide Area
+	  Network) card supported by this driver and you are planning to
+	  connect the box to a WAN.
+
+	  You will need supporting software from
+	  <http://www.kernel.org/pub/linux/utils/net/hdlc/>.
+	  Generic HDLC driver currently supports raw HDLC, Cisco HDLC, Frame
+	  Relay, synchronous Point-to-Point Protocol (PPP) and X.25.
+
+ 	  To compile this driver as a module, choose M here: the
+	  module will be called hdlc.
+
+	  If unsure, say N.
+
+config HDLC_RAW
+	bool "Raw HDLC support"
+	depends on HDLC
+	help
+	  Generic HDLC driver supporting raw HDLC over WAN connections.
+
+	  If unsure, say N.
+
+config HDLC_RAW_ETH
+	bool "Raw HDLC Ethernet device support"
+	depends on HDLC
+	help
+	  Generic HDLC driver supporting raw HDLC Ethernet device emulation
+	  over WAN connections.
+
+	  You will need it for Ethernet over HDLC bridges.
+
+	  If unsure, say N.
+
+config HDLC_CISCO
+	bool "Cisco HDLC support"
+	depends on HDLC
+	help
+	  Generic HDLC driver supporting Cisco HDLC over WAN connections.
+
+	  If unsure, say N.
+
+config HDLC_FR
+	bool "Frame Relay support"
+	depends on HDLC
+	help
+	  Generic HDLC driver supporting Frame Relay over WAN connections.
+
+	  If unsure, say N.
+
+config HDLC_PPP
+	bool "Synchronous Point-to-Point Protocol (PPP) support"
+	depends on HDLC
+	help
+	  Generic HDLC driver supporting PPP over WAN connections.
+
+	  If unsure, say N.
+
+config HDLC_X25
+	bool "X.25 protocol support"
+	depends on HDLC && (LAPB=m && HDLC=m || LAPB=y)
+	help
+	  Generic HDLC driver supporting X.25 over WAN connections.
+
+	  If unsure, say N.
+
+comment "X.25/LAPB support is disabled"
+	depends on WAN && HDLC && (LAPB!=m || HDLC!=m) && LAPB!=y
+
+config PCI200SYN
+	tristate "Goramo PCI200SYN support"
+	depends on HDLC && PCI
+	help
+	  Driver for PCI200SYN cards by Goramo sp. j.
+
+	  If you have such a card, say Y here and see
+	  <http://www.kernel.org/pub/linux/utils/net/hdlc/>.
+
+	  To compile this as a module, choose M here: the
+	  module will be called pci200syn.
+
+	  If unsure, say N.
+
+config WANXL
+	tristate "SBE Inc. wanXL support"
+	depends on HDLC && PCI
+	help
+	  Driver for wanXL PCI cards by SBE Inc.
+
+	  If you have such a card, say Y here and see
+	  <http://www.kernel.org/pub/linux/utils/net/hdlc/>.
+
+	  To compile this as a module, choose M here: the
+	  module will be called wanxl.
+
+	  If unsure, say N.
+
+config WANXL_BUILD_FIRMWARE
+	bool "rebuild wanXL firmware"
+	depends on WANXL && !PREVENT_FIRMWARE_BUILD
+	help
+	  Allows you to rebuild firmware run by the QUICC processor.
+	  It requires as68k, ld68k and hexdump programs.
+
+	  You should never need this option, say N.
+
+config PC300
+	tristate "Cyclades-PC300 support (RS-232/V.35, X.21, T1/E1 boards)"
+	depends on HDLC && PCI
+	---help---
+	  Driver for the Cyclades-PC300 synchronous communication boards.
+
+	  These boards provide synchronous serial interfaces to your
+	  Linux box (interfaces currently available are RS-232/V.35, X.21 and
+	  T1/E1). If you wish to support Multilink PPP, please select the
+	  option later and read the file README.mlppp provided by PC300
+	  package.
+
+	  To compile this as a module, choose M here: the module
+	  will be called pc300.
+
+	  If unsure, say N.
+
+config PC300_MLPPP
+	bool "Cyclades-PC300 MLPPP support"
+	depends on PC300 && PPP_MULTILINK && PPP_SYNC_TTY && HDLC_PPP
+	help
+	  Multilink PPP over the PC300 synchronous communication boards.
+
+comment "Cyclades-PC300 MLPPP support is disabled."
+	depends on WAN && HDLC && PC300 && (PPP=n || !PPP_MULTILINK || PPP_SYNC_TTY=n || !HDLC_PPP)
+
+comment "Refer to the file README.mlppp, provided by PC300 package."
+	depends on WAN && HDLC && PC300 && (PPP=n || !PPP_MULTILINK || PPP_SYNC_TTY=n || !HDLC_PPP)
+
+config N2
+	tristate "SDL RISCom/N2 support"
+	depends on HDLC && ISA
+	help
+	  Driver for RISCom/N2 single or dual channel ISA cards by
+	  SDL Communications Inc.
+
+	  If you have such a card, say Y here and see
+	  <http://www.kernel.org/pub/linux/utils/net/hdlc/>.
+
+	  Note that N2csu and N2dds cards are not supported by this driver.
+
+	  To compile this driver as a module, choose M here: the module
+	  will be called n2.
+
+	  If unsure, say N.
+
+config C101
+	tristate "Moxa C101 support"
+	depends on HDLC && ISA
+	help
+	  Driver for C101 SuperSync ISA cards by Moxa Technologies Co., Ltd.
+
+	  If you have such a card, say Y here and see
+	  <http://www.kernel.org/pub/linux/utils/net/hdlc/>.
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called c101.
+
+	  If unsure, say N.
+
+config FARSYNC
+	tristate "FarSync T-Series support"
+	depends on HDLC && PCI
+	---help---
+	  Support for the FarSync T-Series X.21 (and V.35/V.24) cards by
+	  FarSite Communications Ltd.
+
+	  Synchronous communication is supported on all ports at speeds up to
+	  8Mb/s (128K on V.24) using synchronous PPP, Cisco HDLC, raw HDLC,
+	  Frame Relay or X.25/LAPB.
+
+	  If you want the module to be automatically loaded when the interface
+	  is referenced then you should add "alias hdlcX farsync" to
+	  /etc/modprobe.conf for each interface, where X is 0, 1, 2, ..., or
+	  simply use "alias hdlc* farsync" to indicate all of them.
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called farsync.
+
+config DLCI
+	tristate "Frame Relay DLCI support"
+	depends on WAN
+	---help---
+	  Support for the Frame Relay protocol.
+
+	  Frame Relay is a fast low-cost way to connect to a remote Internet
+	  access provider or to form a private wide area network. The one
+	  physical line from your box to the local "switch" (i.e. the entry
+	  point to the Frame Relay network, usually at the phone company) can
+	  carry several logical point-to-point connections to other computers
+	  connected to the Frame Relay network. For a general explanation of
+	  the protocol, check out <http://www.mplsforum.org/>.
+
+	  To use frame relay, you need supporting hardware (called FRAD) and
+	  certain programs from the net-tools package as explained in
+	  <file:Documentation/networking/framerelay.txt>.
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called dlci.
+
+config DLCI_COUNT
+	int "Max open DLCI"
+	depends on DLCI
+	default "24"
+	help
+	  Maximal number of logical point-to-point frame relay connections
+	  (the identifiers of which are called DCLIs) that the driver can
+	  handle.
+
+	  The default is probably fine.
+
+config DLCI_MAX
+	int "Max DLCI per device"
+	depends on DLCI
+	default "8"
+	help
+	  How many logical point-to-point frame relay connections (the
+	  identifiers of which are called DCLIs) should be handled by each
+	  of your hardware frame relay access devices.
+
+	  Go with the default.
+
+config SDLA
+	tristate "SDLA (Sangoma S502/S508) support"
+	depends on DLCI && ISA
+	help
+	  Driver for the Sangoma S502A, S502E, and S508 Frame Relay Access
+	  Devices.
+
+	  These are multi-protocol cards, but only Frame Relay is supported
+	  by the driver at this time. Please read
+	  <file:Documentation/networking/framerelay.txt>.
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called sdla.
+
+# Wan router core.
+config WAN_ROUTER_DRIVERS
+	bool "WAN router drivers"
+	depends on WAN && WAN_ROUTER
+	---help---
+	  Connect LAN to WAN via Linux box.
+
+	  Select driver your card and remember to say Y to "Wan Router."
+	  You will need the wan-tools package which is available from
+	  <ftp://ftp.sangoma.com/>. For more information read:
+	  <file:Documentation/networking/wan-router.txt>.
+
+	  Note that the answer to this question won't directly affect the
+	  kernel: saying N will just cause the configurator to skip all
+	  the questions about WAN router drivers.
+
+	  If unsure, say N.
+
+config VENDOR_SANGOMA
+	tristate "Sangoma WANPIPE(tm) multiprotocol cards"
+	depends on WAN_ROUTER_DRIVERS && WAN_ROUTER && (PCI || ISA) && BROKEN
+	---help---
+	  Driver for S514-PCI/ISA Synchronous Data Link Adapters (SDLA).
+
+	  WANPIPE from Sangoma Technologies Inc. <http://www.sangoma.com/>
+	  is a family of intelligent multiprotocol WAN adapters with data
+	  transfer rates up to 4Mbps. Cards support:
+
+	  - X.25, Frame Relay, PPP, Cisco HDLC protocols.
+
+	  - API for protocols like HDLC (LAPB), HDLC Streaming, X.25,
+	  Frame Relay and BiSync.
+
+	  - Ethernet Bridging over Frame Relay protocol.
+
+	  - MULTILINK PPP
+
+	  - Async PPP (Modem Dialup)
+
+	  The next questions will ask you about the protocols you want
+	  the driver to support.
+
+	  If you have one or more of these cards, say M to this option;
+	  and read <file:Documentation/networking/wanpipe.txt>.
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called wanpipe.
+
+config WANPIPE_CHDLC
+	bool "WANPIPE Cisco HDLC support"
+	depends on VENDOR_SANGOMA
+	---help---
+	  Connect a WANPIPE card to a leased line using the Cisco HDLC.
+
+	  - Supports Dual Port Cisco HDLC on the S514-PCI/S508-ISA cards
+	  which allows user to build applications using the HDLC streaming API.
+
+	  - CHDLC Streaming MULTILINK PPP that can bind multiple WANPIPE T1
+	  cards into a single logical channel.
+
+	  Say Y and the Cisco HDLC support, HDLC streaming API and
+	  MULTILINK PPP will be included in the driver.
+
+config WANPIPE_FR
+	bool "WANPIPE Frame Relay support"
+	depends on VENDOR_SANGOMA
+	help
+	  Connect a WANPIPE card to a Frame Relay network, or use Frame Felay
+	  API to develop custom applications.
+
+	  Contains the Ethernet Bridging over Frame Relay feature, where
+	  a WANPIPE frame relay link can be directly connected to the Linux
+	  kernel bridge. The Frame Relay option is supported on S514-PCI
+	  and S508-ISA cards.
+
+	  Say Y and the Frame Relay support will be included in the driver.
+
+config WANPIPE_X25
+	bool "WANPIPE X.25 support"
+	depends on VENDOR_SANGOMA
+	help
+	  Connect a WANPIPE card to an X.25 network.
+
+	  Includes the X.25 API support for custom applications over the
+	  X.25 protocol. The X.25 option is supported on S514-PCI and
+	  S508-ISA cards.
+
+	  Say Y and the X.25 support will be included in the driver.
+
+config WANPIPE_PPP
+	bool "WANPIPE PPP support"
+	depends on VENDOR_SANGOMA
+	help
+	  Connect a WANPIPE card to a leased line using Point-to-Point
+	  Protocol (PPP).
+
+	  The PPP option is supported on S514-PCI/S508-ISA cards.
+
+	  Say Y and the PPP support will be included in the driver.
+
+config WANPIPE_MULTPPP
+	bool "WANPIPE Multi-Port PPP support"
+	depends on VENDOR_SANGOMA
+	help
+	  Connect a WANPIPE card to a leased line using Point-to-Point
+	  Protocol (PPP).
+
+	  Uses in-kernel SyncPPP protocol over the Sangoma HDLC Streaming
+	  adapter. In this case each Sangoma adapter port can support an
+	  independent PPP connection. For example, a single Quad-Port PCI
+	  adapter can support up to four independent PPP links. The PPP
+	  option is supported on S514-PCI/S508-ISA cards.
+
+	  Say Y and the Multi-Port PPP support will be included in the driver.
+
+config CYCLADES_SYNC
+	tristate "Cyclom 2X(tm) cards (EXPERIMENTAL)"
+	depends on WAN_ROUTER_DRIVERS && (PCI || ISA)
+	---help---
+	  Cyclom 2X from Cyclades Corporation <http://www.cyclades.com/> is an
+	  intelligent multiprotocol WAN adapter with data transfer rates up to
+	  512 Kbps. These cards support the X.25 and SNA related protocols.
+
+	  While no documentation is available at this time please grab the
+	  wanconfig tarball in
+	  <http://www.conectiva.com.br/~acme/cycsyn-devel/> (with minor changes
+	  to make it compile with the current wanrouter include files; efforts
+	  are being made to use the original package available at
+	  <ftp://ftp.sangoma.com/>).
+
+	  Feel free to contact me or the cycsyn-devel mailing list at
+	  <acme@conectiva.com.br> and <cycsyn-devel@bazar.conectiva.com.br> for
+	  additional details, I hope to have documentation available as soon as
+	  possible. (Cyclades Brazil is writing the Documentation).
+
+	  The next questions will ask you about the protocols you want the
+	  driver to support (for now only X.25 is supported).
+
+	  If you have one or more of these cards, say Y to this option.
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called cyclomx.
+
+config CYCLOMX_X25
+	bool "Cyclom 2X X.25 support (EXPERIMENTAL)"
+	depends on CYCLADES_SYNC
+	help
+	  Connect a Cyclom 2X card to an X.25 network.
+
+	  Enabling X.25 support will enlarge your kernel by about 11 kB.
+
+# X.25 network drivers
+config LAPBETHER
+	tristate "LAPB over Ethernet driver (EXPERIMENTAL)"
+	depends on WAN && LAPB && X25
+	---help---
+	  Driver for a pseudo device (typically called /dev/lapb0) which allows
+	  you to open an LAPB point-to-point connection to some other computer
+	  on your Ethernet network.
+
+	  In order to do this, you need to say Y or M to the driver for your
+	  Ethernet card as well as to "LAPB Data Link Driver".
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called lapbether.
+
+	  If unsure, say N.
+
+config X25_ASY
+	tristate "X.25 async driver (EXPERIMENTAL)"
+	depends on WAN && LAPB && X25
+	---help---
+	  Send and receive X.25 frames over regular asynchronous serial
+	  lines such as telephone lines equipped with ordinary modems.
+
+	  Experts should note that this driver doesn't currently comply with
+	  the asynchronous HDLS framing protocols in CCITT recommendation X.25.
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called x25_asy.
+
+	  If unsure, say N.
+
+config SBNI
+	tristate "Granch SBNI12 Leased Line adapter support"
+	depends on WAN && X86
+	---help---
+	  Driver for ISA SBNI12-xx cards which are low cost alternatives to
+	  leased line modems.
+
+	  You can find more information and last versions of drivers and
+	  utilities at <http://www.granch.ru/>. If you have any question you
+	  can send email to <sbni@granch.ru>.
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called sbni.
+
+	  If unsure, say N.
+
+config SBNI_MULTILINE
+	bool "Multiple line feature support"
+	depends on SBNI
+	help
+	  Schedule traffic for some parallel lines, via SBNI12 adapters.
+
+	  If you have two computers connected with two parallel lines it's
+	  possible to increase transfer rate nearly twice. You should have
+	  a program named 'sbniconfig' to configure adapters.
+
+	  If unsure, say N.
+
+endmenu
+
diff --git a/drivers/net/wan/Makefile b/drivers/net/wan/Makefile
new file mode 100644
index 0000000..ce6c56b
--- /dev/null
+++ b/drivers/net/wan/Makefile
@@ -0,0 +1,86 @@
+#
+# Makefile for the Linux network (wan) device drivers.
+#
+# 3 Aug 2000, Christoph Hellwig <hch@infradead.org>
+# Rewritten to use lists instead of if-statements.
+#
+
+wanpipe-y			:= sdlamain.o sdla_ft1.o
+wanpipe-$(CONFIG_WANPIPE_X25)	+= sdla_x25.o
+wanpipe-$(CONFIG_WANPIPE_FR)	+= sdla_fr.o
+wanpipe-$(CONFIG_WANPIPE_CHDLC)	+= sdla_chdlc.o
+wanpipe-$(CONFIG_WANPIPE_PPP)	+= sdla_ppp.o
+wanpipe-$(CONFIG_WANPIPE_MULTPPP) += wanpipe_multppp.o
+wanpipe-objs			:= $(wanpipe-y)
+
+cyclomx-y                       := cycx_main.o
+cyclomx-$(CONFIG_CYCLOMX_X25)	+= cycx_x25.o
+cyclomx-objs			:= $(cyclomx-y)  
+
+hdlc-y				:= hdlc_generic.o
+hdlc-$(CONFIG_HDLC_RAW)		+= hdlc_raw.o
+hdlc-$(CONFIG_HDLC_RAW_ETH)	+= hdlc_raw_eth.o
+hdlc-$(CONFIG_HDLC_CISCO)	+= hdlc_cisco.o
+hdlc-$(CONFIG_HDLC_FR)		+= hdlc_fr.o
+hdlc-$(CONFIG_HDLC_PPP)		+= hdlc_ppp.o
+hdlc-$(CONFIG_HDLC_X25)		+= hdlc_x25.o
+hdlc-objs			:= $(hdlc-y)
+
+pc300-y				:= pc300_drv.o
+pc300-$(CONFIG_PC300_MLPPP)	+= pc300_tty.o
+pc300-objs			:= $(pc300-y)
+
+obj-$(CONFIG_HOSTESS_SV11)	+= z85230.o	syncppp.o	hostess_sv11.o
+obj-$(CONFIG_SEALEVEL_4021)	+= z85230.o	syncppp.o	sealevel.o
+obj-$(CONFIG_COSA)		+=		syncppp.o	cosa.o
+obj-$(CONFIG_FARSYNC)		+=		syncppp.o	farsync.o
+obj-$(CONFIG_DSCC4)             +=				dscc4.o
+obj-$(CONFIG_LANMEDIA)		+=		syncppp.o
+obj-$(CONFIG_SYNCLINK_SYNCPPP)	+=		syncppp.o
+obj-$(CONFIG_X25_ASY)		+= x25_asy.o
+
+obj-$(CONFIG_LANMEDIA)		+= lmc/
+
+obj-$(CONFIG_DLCI)		+= dlci.o 
+obj-$(CONFIG_SDLA)		+= sdla.o
+ifeq ($(CONFIG_WANPIPE_MULTPPP),y)
+  obj-$(CONFIG_VENDOR_SANGOMA) += sdladrv.o wanpipe.o syncppp.o	
+else
+  obj-$(CONFIG_VENDOR_SANGOMA) += sdladrv.o wanpipe.o
+endif
+obj-$(CONFIG_CYCLADES_SYNC)	+= cycx_drv.o cyclomx.o
+obj-$(CONFIG_LAPBETHER)		+= lapbether.o
+obj-$(CONFIG_SBNI)		+= sbni.o
+obj-$(CONFIG_PC300)		+= pc300.o
+obj-$(CONFIG_HDLC)		+= hdlc.o
+ifeq ($(CONFIG_HDLC_PPP),y)
+  obj-$(CONFIG_HDLC)		+= syncppp.o
+endif
+obj-$(CONFIG_N2)		+= n2.o
+obj-$(CONFIG_C101)		+= c101.o
+obj-$(CONFIG_WANXL)		+= wanxl.o
+obj-$(CONFIG_PCI200SYN)		+= pci200syn.o
+
+clean-files := wanxlfw.inc
+$(obj)/wanxl.o:	$(obj)/wanxlfw.inc
+
+ifeq ($(CONFIG_WANXL_BUILD_FIRMWARE),y)
+ifeq ($(ARCH),m68k)
+  AS68K = $(AS)
+  LD68K = $(LD)
+else
+  AS68K = as68k
+  LD68K = ld68k
+endif
+
+quiet_cmd_build_wanxlfw = BLD FW  $@
+      cmd_build_wanxlfw = \
+	$(CPP) -Wp,-MD,$(depfile) -I$(srctree)/include $< | $(AS68K) -m68360 -o $(obj)/wanxlfw.o; \
+	$(LD68K) --oformat binary -Ttext 0x1000 $(obj)/wanxlfw.o -o $(obj)/wanxlfw.bin; \
+	hexdump -ve '"\n" 16/1 "0x%02X,"' $(obj)/wanxlfw.bin | sed 's/0x  ,//g;1s/^/static u8 firmware[]={/;$$s/,$$/\n};\n/' >$(obj)/wanxlfw.inc; \
+	rm -f $(obj)/wanxlfw.bin $(obj)/wanxlfw.o
+
+$(obj)/wanxlfw.inc:	$(src)/wanxlfw.S
+	$(call if_changed_dep,build_wanxlfw)
+targets += wanxlfw.inc
+endif
diff --git a/drivers/net/wan/c101.c b/drivers/net/wan/c101.c
new file mode 100644
index 0000000..43d854a
--- /dev/null
+++ b/drivers/net/wan/c101.c
@@ -0,0 +1,446 @@
+/*
+ * Moxa C101 synchronous serial card driver for Linux
+ *
+ * Copyright (C) 2000-2003 Krzysztof Halasa <khc@pm.waw.pl>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of version 2 of the GNU General Public License
+ * as published by the Free Software Foundation.
+ *
+ * For information see http://hq.pm.waw.pl/hdlc/
+ *
+ * Sources of information:
+ *    Hitachi HD64570 SCA User's Manual
+ *    Moxa C101 User's Manual
+ */
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include <linux/types.h>
+#include <linux/string.h>
+#include <linux/errno.h>
+#include <linux/init.h>
+#include <linux/moduleparam.h>
+#include <linux/netdevice.h>
+#include <linux/hdlc.h>
+#include <linux/delay.h>
+#include <asm/io.h>
+
+#include "hd64570.h"
+
+
+static const char* version = "Moxa C101 driver version: 1.15";
+static const char* devname = "C101";
+
+#undef DEBUG_PKT
+#define DEBUG_RINGS
+
+#define C101_PAGE 0x1D00
+#define C101_DTR 0x1E00
+#define C101_SCA 0x1F00
+#define C101_WINDOW_SIZE 0x2000
+#define C101_MAPPED_RAM_SIZE 0x4000
+
+#define RAM_SIZE (256 * 1024)
+#define TX_RING_BUFFERS 10
+#define RX_RING_BUFFERS ((RAM_SIZE - C101_WINDOW_SIZE) /		\
+			 (sizeof(pkt_desc) + HDLC_MAX_MRU) - TX_RING_BUFFERS)
+
+#define CLOCK_BASE 9830400	/* 9.8304 MHz */
+#define PAGE0_ALWAYS_MAPPED
+
+static char *hw;		/* pointer to hw=xxx command line string */
+
+
+typedef struct card_s {
+	struct net_device *dev;
+	spinlock_t lock;	/* TX lock */
+	u8 __iomem *win0base;	/* ISA window base address */
+	u32 phy_winbase;	/* ISA physical base address */
+	sync_serial_settings settings;
+	int rxpart;		/* partial frame received, next frame invalid*/
+	unsigned short encoding;
+	unsigned short parity;
+	u16 rx_ring_buffers;	/* number of buffers in a ring */
+	u16 tx_ring_buffers;
+	u16 buff_offset;	/* offset of first buffer of first channel */
+	u16 rxin;		/* rx ring buffer 'in' pointer */
+	u16 txin;		/* tx ring buffer 'in' and 'last' pointers */
+	u16 txlast;
+	u8 rxs, txs, tmc;	/* SCA registers */
+	u8 irq;			/* IRQ (3-15) */
+	u8 page;
+
+	struct card_s *next_card;
+}card_t;
+
+typedef card_t port_t;
+
+static card_t *first_card;
+static card_t **new_card = &first_card;
+
+
+#define sca_in(reg, card)	   readb((card)->win0base + C101_SCA + (reg))
+#define sca_out(value, reg, card)  writeb(value, (card)->win0base + C101_SCA + (reg))
+#define sca_inw(reg, card)	   readw((card)->win0base + C101_SCA + (reg))
+
+/* EDA address register must be set in EDAL, EDAH order - 8 bit ISA bus */
+#define sca_outw(value, reg, card) do { \
+	writeb(value & 0xFF, (card)->win0base + C101_SCA + (reg)); \
+	writeb((value >> 8 ) & 0xFF, (card)->win0base + C101_SCA + (reg+1));\
+} while(0)
+
+#define port_to_card(port)	   (port)
+#define log_node(port)		   (0)
+#define phy_node(port)		   (0)
+#define winsize(card)		   (C101_WINDOW_SIZE)
+#define win0base(card)		   ((card)->win0base)
+#define winbase(card)      	   ((card)->win0base + 0x2000)
+#define get_port(card, port)	   (card)
+static void sca_msci_intr(port_t *port);
+
+
+static inline u8 sca_get_page(card_t *card)
+{
+	return card->page;
+}
+
+static inline void openwin(card_t *card, u8 page)
+{
+	card->page = page;
+	writeb(page, card->win0base + C101_PAGE);
+}
+
+
+#include "hd6457x.c"
+
+
+static void sca_msci_intr(port_t *port)
+{
+	struct net_device *dev = port_to_dev(port);
+	card_t* card = port_to_card(port);
+	u8 stat = sca_in(MSCI1_OFFSET + ST1, card); /* read MSCI ST1 status */
+
+	/* Reset MSCI TX underrun status bit */
+	sca_out(stat & ST1_UDRN, MSCI0_OFFSET + ST1, card);
+
+	if (stat & ST1_UDRN) {
+		struct net_device_stats *stats = hdlc_stats(dev);
+		stats->tx_errors++; /* TX Underrun error detected */
+		stats->tx_fifo_errors++;
+	}
+
+	/* Reset MSCI CDCD status bit - uses ch#2 DCD input */
+	sca_out(stat & ST1_CDCD, MSCI1_OFFSET + ST1, card);
+
+	if (stat & ST1_CDCD)
+		hdlc_set_carrier(!(sca_in(MSCI1_OFFSET + ST3, card) & ST3_DCD),
+				 dev);
+}
+
+
+static void c101_set_iface(port_t *port)
+{
+	u8 rxs = port->rxs & CLK_BRG_MASK;
+	u8 txs = port->txs & CLK_BRG_MASK;
+
+	switch(port->settings.clock_type) {
+	case CLOCK_INT:
+		rxs |= CLK_BRG_RX; /* TX clock */
+		txs |= CLK_RXCLK_TX; /* BRG output */
+		break;
+
+	case CLOCK_TXINT:
+		rxs |= CLK_LINE_RX; /* RXC input */
+		txs |= CLK_BRG_TX; /* BRG output */
+		break;
+
+	case CLOCK_TXFROMRX:
+		rxs |= CLK_LINE_RX; /* RXC input */
+		txs |= CLK_RXCLK_TX; /* RX clock */
+		break;
+
+	default:	/* EXTernal clock */
+		rxs |= CLK_LINE_RX; /* RXC input */
+		txs |= CLK_LINE_TX; /* TXC input */
+	}
+
+	port->rxs = rxs;
+	port->txs = txs;
+	sca_out(rxs, MSCI1_OFFSET + RXS, port);
+	sca_out(txs, MSCI1_OFFSET + TXS, port);
+	sca_set_port(port);
+}
+
+
+static int c101_open(struct net_device *dev)
+{
+	port_t *port = dev_to_port(dev);
+	int result;
+
+	result = hdlc_open(dev);
+	if (result)
+		return result;
+
+	writeb(1, port->win0base + C101_DTR);
+	sca_out(0, MSCI1_OFFSET + CTL, port); /* RTS uses ch#2 output */
+	sca_open(dev);
+	/* DCD is connected to port 2 !@#$%^& - disable MSCI0 CDCD interrupt */
+	sca_out(IE1_UDRN, MSCI0_OFFSET + IE1, port);
+	sca_out(IE0_TXINT, MSCI0_OFFSET + IE0, port);
+
+	hdlc_set_carrier(!(sca_in(MSCI1_OFFSET + ST3, port) & ST3_DCD), dev);
+	printk(KERN_DEBUG "0x%X\n", sca_in(MSCI1_OFFSET + ST3, port));
+
+	/* enable MSCI1 CDCD interrupt */
+	sca_out(IE1_CDCD, MSCI1_OFFSET + IE1, port);
+	sca_out(IE0_RXINTA, MSCI1_OFFSET + IE0, port);
+	sca_out(0x48, IER0, port); /* TXINT #0 and RXINT #1 */
+	c101_set_iface(port);
+	return 0;
+}
+
+
+static int c101_close(struct net_device *dev)
+{
+	port_t *port = dev_to_port(dev);
+
+	sca_close(dev);
+	writeb(0, port->win0base + C101_DTR);
+	sca_out(CTL_NORTS, MSCI1_OFFSET + CTL, port);
+	hdlc_close(dev);
+	return 0;
+}
+
+
+static int c101_ioctl(struct net_device *dev, struct ifreq *ifr, int cmd)
+{
+	const size_t size = sizeof(sync_serial_settings);
+	sync_serial_settings new_line;
+	sync_serial_settings __user *line = ifr->ifr_settings.ifs_ifsu.sync;
+	port_t *port = dev_to_port(dev);
+
+#ifdef DEBUG_RINGS
+	if (cmd == SIOCDEVPRIVATE) {
+		sca_dump_rings(dev);
+		printk(KERN_DEBUG "MSCI1: ST: %02x %02x %02x %02x\n",
+		       sca_in(MSCI1_OFFSET + ST0, port),
+		       sca_in(MSCI1_OFFSET + ST1, port),
+		       sca_in(MSCI1_OFFSET + ST2, port),
+		       sca_in(MSCI1_OFFSET + ST3, port));
+		return 0;
+	}
+#endif
+	if (cmd != SIOCWANDEV)
+		return hdlc_ioctl(dev, ifr, cmd);
+
+	switch(ifr->ifr_settings.type) {
+	case IF_GET_IFACE:
+		ifr->ifr_settings.type = IF_IFACE_SYNC_SERIAL;
+		if (ifr->ifr_settings.size < size) {
+			ifr->ifr_settings.size = size; /* data size wanted */
+			return -ENOBUFS;
+		}
+		if (copy_to_user(line, &port->settings, size))
+			return -EFAULT;
+		return 0;
+
+	case IF_IFACE_SYNC_SERIAL:
+		if(!capable(CAP_NET_ADMIN))
+			return -EPERM;
+
+		if (copy_from_user(&new_line, line, size))
+			return -EFAULT;
+
+		if (new_line.clock_type != CLOCK_EXT &&
+		    new_line.clock_type != CLOCK_TXFROMRX &&
+		    new_line.clock_type != CLOCK_INT &&
+		    new_line.clock_type != CLOCK_TXINT)
+		return -EINVAL;	/* No such clock setting */
+
+		if (new_line.loopback != 0 && new_line.loopback != 1)
+			return -EINVAL;
+
+		memcpy(&port->settings, &new_line, size); /* Update settings */
+		c101_set_iface(port);
+		return 0;
+
+	default:
+		return hdlc_ioctl(dev, ifr, cmd);
+	}
+}
+
+
+
+static void c101_destroy_card(card_t *card)
+{
+	readb(card->win0base + C101_PAGE); /* Resets SCA? */
+
+	if (card->irq)
+		free_irq(card->irq, card);
+
+	if (card->win0base) {
+		iounmap(card->win0base);
+		release_mem_region(card->phy_winbase, C101_MAPPED_RAM_SIZE);
+	}
+
+	free_netdev(card->dev);
+
+	kfree(card);
+}
+
+
+
+static int __init c101_run(unsigned long irq, unsigned long winbase)
+{
+	struct net_device *dev;
+	hdlc_device *hdlc;
+	card_t *card;
+	int result;
+
+	if (irq<3 || irq>15 || irq == 6) /* FIXME */ {
+		printk(KERN_ERR "c101: invalid IRQ value\n");
+		return -ENODEV;
+	}
+
+	if (winbase < 0xC0000 || winbase > 0xDFFFF || (winbase & 0x3FFF) !=0) {
+		printk(KERN_ERR "c101: invalid RAM value\n");
+		return -ENODEV;
+	}
+
+	card = kmalloc(sizeof(card_t), GFP_KERNEL);
+	if (card == NULL) {
+		printk(KERN_ERR "c101: unable to allocate memory\n");
+		return -ENOBUFS;
+	}
+	memset(card, 0, sizeof(card_t));
+
+	card->dev = alloc_hdlcdev(card);
+	if (!card->dev) {
+		printk(KERN_ERR "c101: unable to allocate memory\n");
+		kfree(card);
+		return -ENOBUFS;
+	}
+
+	if (request_irq(irq, sca_intr, 0, devname, card)) {
+		printk(KERN_ERR "c101: could not allocate IRQ\n");
+		c101_destroy_card(card);
+		return(-EBUSY);
+	}
+	card->irq = irq;
+
+	if (!request_mem_region(winbase, C101_MAPPED_RAM_SIZE, devname)) {
+		printk(KERN_ERR "c101: could not request RAM window\n");
+		c101_destroy_card(card);
+		return(-EBUSY);
+	}
+	card->phy_winbase = winbase;
+	card->win0base = ioremap(winbase, C101_MAPPED_RAM_SIZE);
+	if (!card->win0base) {
+		printk(KERN_ERR "c101: could not map I/O address\n");
+		c101_destroy_card(card);
+		return -EBUSY;
+	}
+
+	card->tx_ring_buffers = TX_RING_BUFFERS;
+	card->rx_ring_buffers = RX_RING_BUFFERS;
+	card->buff_offset = C101_WINDOW_SIZE; /* Bytes 1D00-1FFF reserved */
+
+	readb(card->win0base + C101_PAGE); /* Resets SCA? */
+	udelay(100);
+	writeb(0, card->win0base + C101_PAGE);
+	writeb(0, card->win0base + C101_DTR); /* Power-up for RAM? */
+
+	sca_init(card, 0);
+
+	dev = port_to_dev(card);
+	hdlc = dev_to_hdlc(dev);
+
+	spin_lock_init(&card->lock);
+	SET_MODULE_OWNER(dev);
+	dev->irq = irq;
+	dev->mem_start = winbase;
+	dev->mem_end = winbase + C101_MAPPED_RAM_SIZE - 1;
+	dev->tx_queue_len = 50;
+	dev->do_ioctl = c101_ioctl;
+	dev->open = c101_open;
+	dev->stop = c101_close;
+	hdlc->attach = sca_attach;
+	hdlc->xmit = sca_xmit;
+	card->settings.clock_type = CLOCK_EXT;
+
+	result = register_hdlc_device(dev);
+	if (result) {
+		printk(KERN_WARNING "c101: unable to register hdlc device\n");
+		c101_destroy_card(card);
+		return result;
+	}
+
+	sca_init_sync_port(card); /* Set up C101 memory */
+	hdlc_set_carrier(!(sca_in(MSCI1_OFFSET + ST3, card) & ST3_DCD), dev);
+
+	printk(KERN_INFO "%s: Moxa C101 on IRQ%u,"
+	       " using %u TX + %u RX packets rings\n",
+	       dev->name, card->irq,
+	       card->tx_ring_buffers, card->rx_ring_buffers);
+
+	*new_card = card;
+	new_card = &card->next_card;
+	return 0;
+}
+
+
+
+static int __init c101_init(void)
+{
+	if (hw == NULL) {
+#ifdef MODULE
+		printk(KERN_INFO "c101: no card initialized\n");
+#endif
+		return -ENOSYS;	/* no parameters specified, abort */
+	}
+
+	printk(KERN_INFO "%s\n", version);
+
+	do {
+		unsigned long irq, ram;
+
+		irq = simple_strtoul(hw, &hw, 0);
+
+		if (*hw++ != ',')
+			break;
+		ram = simple_strtoul(hw, &hw, 0);
+
+		if (*hw == ':' || *hw == '\x0')
+			c101_run(irq, ram);
+
+		if (*hw == '\x0')
+			return first_card ? 0 : -ENOSYS;
+	}while(*hw++ == ':');
+
+	printk(KERN_ERR "c101: invalid hardware parameters\n");
+	return first_card ? 0 : -ENOSYS;
+}
+
+
+static void __exit c101_cleanup(void)
+{
+	card_t *card = first_card;
+
+	while (card) {
+		card_t *ptr = card;
+		card = card->next_card;
+		unregister_hdlc_device(port_to_dev(ptr));
+		c101_destroy_card(ptr);
+	}
+}
+
+
+module_init(c101_init);
+module_exit(c101_cleanup);
+
+MODULE_AUTHOR("Krzysztof Halasa <khc@pm.waw.pl>");
+MODULE_DESCRIPTION("Moxa C101 serial port driver");
+MODULE_LICENSE("GPL v2");
+module_param(hw, charp, 0444);	/* hw=irq,ram:irq,... */
diff --git a/drivers/net/wan/cosa.c b/drivers/net/wan/cosa.c
new file mode 100644
index 0000000..921a573
--- /dev/null
+++ b/drivers/net/wan/cosa.c
@@ -0,0 +1,2100 @@
+/* $Id: cosa.c,v 1.31 2000/03/08 17:47:16 kas Exp $ */
+
+/*
+ *  Copyright (C) 1995-1997  Jan "Yenya" Kasprzak <kas@fi.muni.cz>
+ *
+ *  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.
+ */
+
+/*
+ * The driver for the SRP and COSA synchronous serial cards.
+ *
+ * HARDWARE INFO
+ *
+ * Both cards are developed at the Institute of Computer Science,
+ * Masaryk University (http://www.ics.muni.cz/). The hardware is
+ * developed by Jiri Novotny <novotny@ics.muni.cz>. More information
+ * and the photo of both cards is available at
+ * http://www.pavoucek.cz/cosa.html. The card documentation, firmwares
+ * and other goods can be downloaded from ftp://ftp.ics.muni.cz/pub/cosa/.
+ * For Linux-specific utilities, see below in the "Software info" section.
+ * If you want to order the card, contact Jiri Novotny.
+ *
+ * The SRP (serial port?, the Czech word "srp" means "sickle") card
+ * is a 2-port intelligent (with its own 8-bit CPU) synchronous serial card
+ * with V.24 interfaces up to 80kb/s each.
+ *
+ * The COSA (communication serial adapter?, the Czech word "kosa" means
+ * "scythe") is a next-generation sync/async board with two interfaces
+ * - currently any of V.24, X.21, V.35 and V.36 can be selected.
+ * It has a 16-bit SAB80166 CPU and can do up to 10 Mb/s per channel.
+ * The 8-channels version is in development.
+ *
+ * Both types have downloadable firmware and communicate via ISA DMA.
+ * COSA can be also a bus-mastering device.
+ *
+ * SOFTWARE INFO
+ *
+ * The homepage of the Linux driver is at http://www.fi.muni.cz/~kas/cosa/.
+ * The CVS tree of Linux driver can be viewed there, as well as the
+ * firmware binaries and user-space utilities for downloading the firmware
+ * into the card and setting up the card.
+ *
+ * The Linux driver (unlike the present *BSD drivers :-) can work even
+ * for the COSA and SRP in one computer and allows each channel to work
+ * in one of the three modes (character device, Cisco HDLC, Sync PPP).
+ *
+ * AUTHOR
+ *
+ * The Linux driver was written by Jan "Yenya" Kasprzak <kas@fi.muni.cz>.
+ *
+ * You can mail me bugfixes and even success reports. I am especially
+ * interested in the SMP and/or muliti-channel success/failure reports
+ * (I wonder if I did the locking properly :-).
+ *
+ * THE AUTHOR USED THE FOLLOWING SOURCES WHEN PROGRAMMING THE DRIVER
+ *
+ * The COSA/SRP NetBSD driver by Zdenek Salvet and Ivos Cernohlavek
+ * The skeleton.c by Donald Becker
+ * The SDL Riscom/N2 driver by Mike Natale
+ * The Comtrol Hostess SV11 driver by Alan Cox
+ * The Sync PPP/Cisco HDLC layer (syncppp.c) ported to Linux by Alan Cox
+ */
+/*
+ *     5/25/1999 : Marcelo Tosatti <marcelo@conectiva.com.br>
+ *             fixed a deadlock in cosa_sppp_open
+ */
+
+/* ---------- Headers, macros, data structures ---------- */
+
+#include <linux/config.h>
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include <linux/poll.h>
+#include <linux/fs.h>
+#include <linux/devfs_fs_kernel.h>
+#include <linux/interrupt.h>
+#include <linux/delay.h>
+#include <linux/errno.h>
+#include <linux/ioport.h>
+#include <linux/netdevice.h>
+#include <linux/spinlock.h>
+#include <linux/smp_lock.h>
+#include <linux/device.h>
+
+#undef COSA_SLOW_IO	/* for testing purposes only */
+#undef REALLY_SLOW_IO
+
+#include <asm/io.h>
+#include <asm/dma.h>
+#include <asm/byteorder.h>
+
+#include <net/syncppp.h>
+#include "cosa.h"
+
+/* Maximum length of the identification string. */
+#define COSA_MAX_ID_STRING	128
+
+/* Maximum length of the channel name */
+#define COSA_MAX_NAME		(sizeof("cosaXXXcXXX")+1)
+
+/* Per-channel data structure */
+
+struct channel_data {
+	void *if_ptr;	/* General purpose pointer (used by SPPP) */
+	int usage;	/* Usage count; >0 for chrdev, -1 for netdev */
+	int num;	/* Number of the channel */
+	struct cosa_data *cosa;	/* Pointer to the per-card structure */
+	int txsize;	/* Size of transmitted data */
+	char *txbuf;	/* Transmit buffer */
+	char name[COSA_MAX_NAME];	/* channel name */
+
+	/* The HW layer interface */
+	/* routine called from the RX interrupt */
+	char *(*setup_rx)(struct channel_data *channel, int size);
+	/* routine called when the RX is done (from the EOT interrupt) */
+	int (*rx_done)(struct channel_data *channel);
+	/* routine called when the TX is done (from the EOT interrupt) */
+	int (*tx_done)(struct channel_data *channel, int size);
+
+	/* Character device parts */
+	struct semaphore rsem, wsem;
+	char *rxdata;
+	int rxsize;
+	wait_queue_head_t txwaitq, rxwaitq;
+	int tx_status, rx_status;
+
+	/* SPPP/HDLC device parts */
+	struct ppp_device pppdev;
+	struct sk_buff *rx_skb, *tx_skb;
+	struct net_device_stats stats;
+};
+
+/* cosa->firmware_status bits */
+#define COSA_FW_RESET		(1<<0)	/* Is the ROM monitor active? */
+#define COSA_FW_DOWNLOAD	(1<<1)	/* Is the microcode downloaded? */
+#define COSA_FW_START		(1<<2)	/* Is the microcode running? */
+
+struct cosa_data {
+	int num;			/* Card number */
+	char name[COSA_MAX_NAME];	/* Card name - e.g "cosa0" */
+	unsigned int datareg, statusreg;	/* I/O ports */
+	unsigned short irq, dma;	/* IRQ and DMA number */
+	unsigned short startaddr;	/* Firmware start address */
+	unsigned short busmaster;	/* Use busmastering? */
+	int nchannels;			/* # of channels on this card */
+	int driver_status;		/* For communicating with firmware */
+	int firmware_status;		/* Downloaded, reseted, etc. */
+	long int rxbitmap, txbitmap;	/* Bitmap of channels who are willing to send/receive data */
+	long int rxtx;			/* RX or TX in progress? */
+	int enabled;
+	int usage;				/* usage count */
+	int txchan, txsize, rxsize;
+	struct channel_data *rxchan;
+	char *bouncebuf;
+	char *txbuf, *rxbuf;
+	struct channel_data *chan;
+	spinlock_t lock;	/* For exclusive operations on this structure */
+	char id_string[COSA_MAX_ID_STRING];	/* ROM monitor ID string */
+	char *type;				/* card type */
+};
+
+/*
+ * Define this if you want all the possible ports to be autoprobed.
+ * It is here but it probably is not a good idea to use this.
+ */
+/* #define COSA_ISA_AUTOPROBE	1 */
+
+/*
+ * Character device major number. 117 was allocated for us.
+ * The value of 0 means to allocate a first free one.
+ */
+static int cosa_major = 117;
+
+/*
+ * Encoding of the minor numbers:
+ * The lowest CARD_MINOR_BITS bits means the channel on the single card,
+ * the highest bits means the card number.
+ */
+#define CARD_MINOR_BITS	4	/* How many bits in minor number are reserved
+				 * for the single card */
+/*
+ * The following depends on CARD_MINOR_BITS. Unfortunately, the "MODULE_STRING"
+ * macro doesn't like anything other than the raw number as an argument :-(
+ */
+#define MAX_CARDS	16
+/* #define MAX_CARDS	(1 << (8-CARD_MINOR_BITS)) */
+
+#define DRIVER_RX_READY		0x0001
+#define DRIVER_TX_READY		0x0002
+#define DRIVER_TXMAP_SHIFT	2
+#define DRIVER_TXMAP_MASK	0x0c	/* FIXME: 0xfc for 8-channel version */
+
+/*
+ * for cosa->rxtx - indicates whether either transmit or receive is
+ * in progress. These values are mean number of the bit.
+ */
+#define TXBIT 0
+#define RXBIT 1
+#define IRQBIT 2
+
+#define COSA_MTU 2000	/* FIXME: I don't know this exactly */
+
+#undef DEBUG_DATA //1	/* Dump the data read or written to the channel */
+#undef DEBUG_IRQS //1	/* Print the message when the IRQ is received */
+#undef DEBUG_IO   //1	/* Dump the I/O traffic */
+
+#define TX_TIMEOUT	(5*HZ)
+
+/* Maybe the following should be allocated dynamically */
+static struct cosa_data cosa_cards[MAX_CARDS];
+static int nr_cards;
+
+#ifdef COSA_ISA_AUTOPROBE
+static int io[MAX_CARDS+1]  = { 0x220, 0x228, 0x210, 0x218, 0, };
+/* NOTE: DMA is not autoprobed!!! */
+static int dma[MAX_CARDS+1] = { 1, 7, 1, 7, 1, 7, 1, 7, 0, };
+#else
+static int io[MAX_CARDS+1];
+static int dma[MAX_CARDS+1];
+#endif
+/* IRQ can be safely autoprobed */
+static int irq[MAX_CARDS+1] = { -1, -1, -1, -1, -1, -1, 0, };
+
+/* for class stuff*/
+static struct class_simple *cosa_class;
+
+#ifdef MODULE
+module_param_array(io, int, NULL, 0);
+MODULE_PARM_DESC(io, "The I/O bases of the COSA or SRP cards");
+module_param_array(irq, int, NULL, 0);
+MODULE_PARM_DESC(irq, "The IRQ lines of the COSA or SRP cards");
+module_param_array(dma, int, NULL, 0);
+MODULE_PARM_DESC(dma, "The DMA channels of the COSA or SRP cards");
+
+MODULE_AUTHOR("Jan \"Yenya\" Kasprzak, <kas@fi.muni.cz>");
+MODULE_DESCRIPTION("Modular driver for the COSA or SRP synchronous card");
+MODULE_LICENSE("GPL");
+#endif
+
+/* I use this mainly for testing purposes */
+#ifdef COSA_SLOW_IO
+#define cosa_outb outb_p
+#define cosa_outw outw_p
+#define cosa_inb  inb_p
+#define cosa_inw  inw_p
+#else
+#define cosa_outb outb
+#define cosa_outw outw
+#define cosa_inb  inb
+#define cosa_inw  inw
+#endif
+
+#define is_8bit(cosa)		(!(cosa->datareg & 0x08))
+
+#define cosa_getstatus(cosa)	(cosa_inb(cosa->statusreg))
+#define cosa_putstatus(cosa, stat)	(cosa_outb(stat, cosa->statusreg))
+#define cosa_getdata16(cosa)	(cosa_inw(cosa->datareg))
+#define cosa_getdata8(cosa)	(cosa_inb(cosa->datareg))
+#define cosa_putdata16(cosa, dt)	(cosa_outw(dt, cosa->datareg))
+#define cosa_putdata8(cosa, dt)	(cosa_outb(dt, cosa->datareg))
+
+/* Initialization stuff */
+static int cosa_probe(int ioaddr, int irq, int dma);
+
+/* HW interface */
+static void cosa_enable_rx(struct channel_data *chan);
+static void cosa_disable_rx(struct channel_data *chan);
+static int cosa_start_tx(struct channel_data *channel, char *buf, int size);
+static void cosa_kick(struct cosa_data *cosa);
+static int cosa_dma_able(struct channel_data *chan, char *buf, int data);
+
+/* SPPP/HDLC stuff */
+static void sppp_channel_init(struct channel_data *chan);
+static void sppp_channel_delete(struct channel_data *chan);
+static int cosa_sppp_open(struct net_device *d);
+static int cosa_sppp_close(struct net_device *d);
+static void cosa_sppp_timeout(struct net_device *d);
+static int cosa_sppp_tx(struct sk_buff *skb, struct net_device *d);
+static char *sppp_setup_rx(struct channel_data *channel, int size);
+static int sppp_rx_done(struct channel_data *channel);
+static int sppp_tx_done(struct channel_data *channel, int size);
+static int cosa_sppp_ioctl(struct net_device *dev, struct ifreq *ifr, int cmd);
+static struct net_device_stats *cosa_net_stats(struct net_device *dev);
+
+/* Character device */
+static void chardev_channel_init(struct channel_data *chan);
+static char *chrdev_setup_rx(struct channel_data *channel, int size);
+static int chrdev_rx_done(struct channel_data *channel);
+static int chrdev_tx_done(struct channel_data *channel, int size);
+static ssize_t cosa_read(struct file *file,
+	char __user *buf, size_t count, loff_t *ppos);
+static ssize_t cosa_write(struct file *file,
+	const char __user *buf, size_t count, loff_t *ppos);
+static unsigned int cosa_poll(struct file *file, poll_table *poll);
+static int cosa_open(struct inode *inode, struct file *file);
+static int cosa_release(struct inode *inode, struct file *file);
+static int cosa_chardev_ioctl(struct inode *inode, struct file *file,
+	unsigned int cmd, unsigned long arg);
+#ifdef COSA_FASYNC_WORKING
+static int cosa_fasync(struct inode *inode, struct file *file, int on);
+#endif
+
+static struct file_operations cosa_fops = {
+	.owner		= THIS_MODULE,
+	.llseek		= no_llseek,
+	.read		= cosa_read,
+	.write		= cosa_write,
+	.poll		= cosa_poll,
+	.ioctl		= cosa_chardev_ioctl,
+	.open		= cosa_open,
+	.release	= cosa_release,
+#ifdef COSA_FASYNC_WORKING
+	.fasync		= cosa_fasync,
+#endif
+};
+
+/* Ioctls */
+static int cosa_start(struct cosa_data *cosa, int address);
+static int cosa_reset(struct cosa_data *cosa);
+static int cosa_download(struct cosa_data *cosa, void __user *a);
+static int cosa_readmem(struct cosa_data *cosa, void __user *a);
+
+/* COSA/SRP ROM monitor */
+static int download(struct cosa_data *cosa, const char __user *data, int addr, int len);
+static int startmicrocode(struct cosa_data *cosa, int address);
+static int readmem(struct cosa_data *cosa, char __user *data, int addr, int len);
+static int cosa_reset_and_read_id(struct cosa_data *cosa, char *id);
+
+/* Auxilliary functions */
+static int get_wait_data(struct cosa_data *cosa);
+static int put_wait_data(struct cosa_data *cosa, int data);
+static int puthexnumber(struct cosa_data *cosa, int number);
+static void put_driver_status(struct cosa_data *cosa);
+static void put_driver_status_nolock(struct cosa_data *cosa);
+
+/* Interrupt handling */
+static irqreturn_t cosa_interrupt(int irq, void *cosa, struct pt_regs *regs);
+
+/* I/O ops debugging */
+#ifdef DEBUG_IO
+static void debug_data_in(struct cosa_data *cosa, int data);
+static void debug_data_out(struct cosa_data *cosa, int data);
+static void debug_data_cmd(struct cosa_data *cosa, int data);
+static void debug_status_in(struct cosa_data *cosa, int status);
+static void debug_status_out(struct cosa_data *cosa, int status);
+#endif
+
+
+/* ---------- Initialization stuff ---------- */
+
+static int __init cosa_init(void)
+{
+	int i, err = 0;
+
+	printk(KERN_INFO "cosa v1.08 (c) 1997-2000 Jan Kasprzak <kas@fi.muni.cz>\n");
+#ifdef CONFIG_SMP
+	printk(KERN_INFO "cosa: SMP found. Please mail any success/failure reports to the author.\n");
+#endif
+	if (cosa_major > 0) {
+		if (register_chrdev(cosa_major, "cosa", &cosa_fops)) {
+			printk(KERN_WARNING "cosa: unable to get major %d\n",
+				cosa_major);
+			err = -EIO;
+			goto out;
+		}
+	} else {
+		if (!(cosa_major=register_chrdev(0, "cosa", &cosa_fops))) {
+			printk(KERN_WARNING "cosa: unable to register chardev\n");
+			err = -EIO;
+			goto out;
+		}
+	}
+	for (i=0; i<MAX_CARDS; i++)
+		cosa_cards[i].num = -1;
+	for (i=0; io[i] != 0 && i < MAX_CARDS; i++)
+		cosa_probe(io[i], irq[i], dma[i]);
+	if (!nr_cards) {
+		printk(KERN_WARNING "cosa: no devices found.\n");
+		unregister_chrdev(cosa_major, "cosa");
+		err = -ENODEV;
+		goto out;
+	}
+	devfs_mk_dir("cosa");
+	cosa_class = class_simple_create(THIS_MODULE, "cosa");
+	if (IS_ERR(cosa_class)) {
+		err = PTR_ERR(cosa_class);
+		goto out_chrdev;
+	}
+	for (i=0; i<nr_cards; i++) {
+		class_simple_device_add(cosa_class, MKDEV(cosa_major, i),
+				NULL, "cosa%d", i);
+		err = devfs_mk_cdev(MKDEV(cosa_major, i),
+				S_IFCHR|S_IRUSR|S_IWUSR,
+				"cosa/%d", i);
+		if (err) {
+			class_simple_device_remove(MKDEV(cosa_major, i));
+			goto out_chrdev;		
+		}
+	}
+	err = 0;
+	goto out;
+	
+out_chrdev:
+	unregister_chrdev(cosa_major, "cosa");
+out:
+	return err;
+}
+module_init(cosa_init);
+
+static void __exit cosa_exit(void)
+{
+	struct cosa_data *cosa;
+	int i;
+	printk(KERN_INFO "Unloading the cosa module\n");
+
+	for (i=0; i<nr_cards; i++) {
+		class_simple_device_remove(MKDEV(cosa_major, i));
+		devfs_remove("cosa/%d", i);
+	}
+	class_simple_destroy(cosa_class);
+	devfs_remove("cosa");
+	for (cosa=cosa_cards; nr_cards--; cosa++) {
+		/* Clean up the per-channel data */
+		for (i=0; i<cosa->nchannels; i++) {
+			/* Chardev driver has no alloc'd per-channel data */
+			sppp_channel_delete(cosa->chan+i);
+		}
+		/* Clean up the per-card data */
+		kfree(cosa->chan);
+		kfree(cosa->bouncebuf);
+		free_irq(cosa->irq, cosa);
+		free_dma(cosa->dma);
+		release_region(cosa->datareg,is_8bit(cosa)?2:4);
+	}
+	unregister_chrdev(cosa_major, "cosa");
+}
+module_exit(cosa_exit);
+
+/*
+ * This function should register all the net devices needed for the
+ * single channel.
+ */
+static __inline__ void channel_init(struct channel_data *chan)
+{
+	sprintf(chan->name, "cosa%dc%d", chan->cosa->num, chan->num);
+
+	/* Initialize the chardev data structures */
+	chardev_channel_init(chan);
+
+	/* Register the sppp interface */
+	sppp_channel_init(chan);
+}
+	
+static int cosa_probe(int base, int irq, int dma)
+{
+	struct cosa_data *cosa = cosa_cards+nr_cards;
+	int i, err = 0;
+
+	memset(cosa, 0, sizeof(struct cosa_data));
+
+	/* Checking validity of parameters: */
+	/* IRQ should be 2-7 or 10-15; negative IRQ means autoprobe */
+	if ((irq >= 0  && irq < 2) || irq > 15 || (irq < 10 && irq > 7)) {
+		printk (KERN_INFO "cosa_probe: invalid IRQ %d\n", irq);
+		return -1;
+	}
+	/* I/O address should be between 0x100 and 0x3ff and should be
+	 * multiple of 8. */
+	if (base < 0x100 || base > 0x3ff || base & 0x7) {
+		printk (KERN_INFO "cosa_probe: invalid I/O address 0x%x\n",
+			base);
+		return -1;
+	}
+	/* DMA should be 0,1 or 3-7 */
+	if (dma < 0 || dma == 4 || dma > 7) {
+		printk (KERN_INFO "cosa_probe: invalid DMA %d\n", dma);
+		return -1;
+	}
+	/* and finally, on 16-bit COSA DMA should be 4-7 and 
+	 * I/O base should not be multiple of 0x10 */
+	if (((base & 0x8) && dma < 4) || (!(base & 0x8) && dma > 3)) {
+		printk (KERN_INFO "cosa_probe: 8/16 bit base and DMA mismatch"
+			" (base=0x%x, dma=%d)\n", base, dma);
+		return -1;
+	}
+
+	cosa->dma = dma;
+	cosa->datareg = base;
+	cosa->statusreg = is_8bit(cosa)?base+1:base+2;
+	spin_lock_init(&cosa->lock);
+
+	if (!request_region(base, is_8bit(cosa)?2:4,"cosa"))
+		return -1;
+	
+	if (cosa_reset_and_read_id(cosa, cosa->id_string) < 0) {
+		printk(KERN_DEBUG "cosa: probe at 0x%x failed.\n", base);
+		err = -1;
+		goto err_out;
+	}
+
+	/* Test the validity of identification string */
+	if (!strncmp(cosa->id_string, "SRP", 3))
+		cosa->type = "srp";
+	else if (!strncmp(cosa->id_string, "COSA", 4))
+		cosa->type = is_8bit(cosa)? "cosa8": "cosa16";
+	else {
+/* Print a warning only if we are not autoprobing */
+#ifndef COSA_ISA_AUTOPROBE
+		printk(KERN_INFO "cosa: valid signature not found at 0x%x.\n",
+			base);
+#endif
+		err = -1;
+		goto err_out;
+	}
+	/* Update the name of the region now we know the type of card */ 
+	release_region(base, is_8bit(cosa)?2:4);
+	if (!request_region(base, is_8bit(cosa)?2:4, cosa->type)) {
+		printk(KERN_DEBUG "cosa: changing name at 0x%x failed.\n", base);
+		return -1;
+	}
+
+	/* Now do IRQ autoprobe */
+	if (irq < 0) {
+		unsigned long irqs;
+/*		printk(KERN_INFO "IRQ autoprobe\n"); */
+		irqs = probe_irq_on();
+		/* 
+		 * Enable interrupt on tx buffer empty (it sure is) 
+		 * really sure ?
+		 * FIXME: When this code is not used as module, we should
+		 * probably call udelay() instead of the interruptible sleep.
+		 */
+		set_current_state(TASK_INTERRUPTIBLE);
+		cosa_putstatus(cosa, SR_TX_INT_ENA);
+		schedule_timeout(30);
+		irq = probe_irq_off(irqs);
+		/* Disable all IRQs from the card */
+		cosa_putstatus(cosa, 0);
+		/* Empty the received data register */
+		cosa_getdata8(cosa);
+
+		if (irq < 0) {
+			printk (KERN_INFO "cosa IRQ autoprobe: multiple interrupts obtained (%d, board at 0x%x)\n",
+				irq, cosa->datareg);
+			err = -1;
+			goto err_out;
+		}
+		if (irq == 0) {
+			printk (KERN_INFO "cosa IRQ autoprobe: no interrupt obtained (board at 0x%x)\n",
+				cosa->datareg);
+		/*	return -1; */
+		}
+	}
+
+	cosa->irq = irq;
+	cosa->num = nr_cards;
+	cosa->usage = 0;
+	cosa->nchannels = 2;	/* FIXME: how to determine this? */
+
+	if (request_irq(cosa->irq, cosa_interrupt, 0, cosa->type, cosa)) {
+		err = -1;
+		goto err_out;
+	}
+	if (request_dma(cosa->dma, cosa->type)) {
+		err = -1;
+		goto err_out1;
+	}
+	
+	cosa->bouncebuf = kmalloc(COSA_MTU, GFP_KERNEL|GFP_DMA);
+	if (!cosa->bouncebuf) {
+		err = -ENOMEM;
+		goto err_out2;
+	}
+	sprintf(cosa->name, "cosa%d", cosa->num);
+
+	/* Initialize the per-channel data */
+	cosa->chan = kmalloc(sizeof(struct channel_data)*cosa->nchannels,
+			     GFP_KERNEL);
+	if (!cosa->chan) {
+	        err = -ENOMEM;
+		goto err_out3;
+	}
+	memset(cosa->chan, 0, sizeof(struct channel_data)*cosa->nchannels);
+	for (i=0; i<cosa->nchannels; i++) {
+		cosa->chan[i].cosa = cosa;
+		cosa->chan[i].num = i;
+		channel_init(cosa->chan+i);
+	}
+
+	printk (KERN_INFO "cosa%d: %s (%s at 0x%x irq %d dma %d), %d channels\n",
+		cosa->num, cosa->id_string, cosa->type,
+		cosa->datareg, cosa->irq, cosa->dma, cosa->nchannels);
+
+	return nr_cards++;
+err_out3:
+	kfree(cosa->bouncebuf);
+err_out2:
+	free_dma(cosa->dma);
+err_out1:
+	free_irq(cosa->irq, cosa);
+err_out:	
+	release_region(cosa->datareg,is_8bit(cosa)?2:4);
+	printk(KERN_NOTICE "cosa%d: allocating resources failed\n",
+	       cosa->num);
+	return err;
+}
+
+
+/*---------- SPPP/HDLC netdevice ---------- */
+
+static void cosa_setup(struct net_device *d)
+{
+	d->open = cosa_sppp_open;
+	d->stop = cosa_sppp_close;
+	d->hard_start_xmit = cosa_sppp_tx;
+	d->do_ioctl = cosa_sppp_ioctl;
+	d->get_stats = cosa_net_stats;
+	d->tx_timeout = cosa_sppp_timeout;
+	d->watchdog_timeo = TX_TIMEOUT;
+}
+
+static void sppp_channel_init(struct channel_data *chan)
+{
+	struct net_device *d;
+	chan->if_ptr = &chan->pppdev;
+	d = alloc_netdev(0, chan->name, cosa_setup);
+	if (!d) {
+		printk(KERN_WARNING "%s: alloc_netdev failed.\n", chan->name);
+		return;
+	}
+	chan->pppdev.dev = d;
+	d->base_addr = chan->cosa->datareg;
+	d->irq = chan->cosa->irq;
+	d->dma = chan->cosa->dma;
+	d->priv = chan;
+	sppp_attach(&chan->pppdev);
+	if (register_netdev(d)) {
+		printk(KERN_WARNING "%s: register_netdev failed.\n", d->name);
+		sppp_detach(d);
+		free_netdev(d);
+		chan->pppdev.dev = NULL;
+		return;
+	}
+}
+
+static void sppp_channel_delete(struct channel_data *chan)
+{
+	unregister_netdev(chan->pppdev.dev);
+	sppp_detach(chan->pppdev.dev);
+	free_netdev(chan->pppdev.dev);
+	chan->pppdev.dev = NULL;
+}
+
+static int cosa_sppp_open(struct net_device *d)
+{
+	struct channel_data *chan = d->priv;
+	int err;
+	unsigned long flags;
+
+	if (!(chan->cosa->firmware_status & COSA_FW_START)) {
+		printk(KERN_NOTICE "%s: start the firmware first (status %d)\n",
+			chan->cosa->name, chan->cosa->firmware_status);
+		return -EPERM;
+	}
+	spin_lock_irqsave(&chan->cosa->lock, flags);
+	if (chan->usage != 0) {
+		printk(KERN_WARNING "%s: sppp_open called with usage count %d\n",
+			chan->name, chan->usage);
+		spin_unlock_irqrestore(&chan->cosa->lock, flags);
+		return -EBUSY;
+	}
+	chan->setup_rx = sppp_setup_rx;
+	chan->tx_done = sppp_tx_done;
+	chan->rx_done = sppp_rx_done;
+	chan->usage=-1;
+	chan->cosa->usage++;
+	spin_unlock_irqrestore(&chan->cosa->lock, flags);
+
+	err = sppp_open(d);
+	if (err) {
+		spin_lock_irqsave(&chan->cosa->lock, flags);
+		chan->usage=0;
+		chan->cosa->usage--;
+		
+		spin_unlock_irqrestore(&chan->cosa->lock, flags);
+		return err;
+	}
+
+	netif_start_queue(d);
+	cosa_enable_rx(chan);
+	return 0;
+}
+
+static int cosa_sppp_tx(struct sk_buff *skb, struct net_device *dev)
+{
+	struct channel_data *chan = dev->priv;
+
+	netif_stop_queue(dev);
+
+	chan->tx_skb = skb;
+	cosa_start_tx(chan, skb->data, skb->len);
+	return 0;
+}
+
+static void cosa_sppp_timeout(struct net_device *dev)
+{
+	struct channel_data *chan = dev->priv;
+
+	if (test_bit(RXBIT, &chan->cosa->rxtx)) {
+		chan->stats.rx_errors++;
+		chan->stats.rx_missed_errors++;
+	} else {
+		chan->stats.tx_errors++;
+		chan->stats.tx_aborted_errors++;
+	}
+	cosa_kick(chan->cosa);
+	if (chan->tx_skb) {
+		dev_kfree_skb(chan->tx_skb);
+		chan->tx_skb = NULL;
+	}
+	netif_wake_queue(dev);
+}
+
+static int cosa_sppp_close(struct net_device *d)
+{
+	struct channel_data *chan = d->priv;
+	unsigned long flags;
+
+	netif_stop_queue(d);
+	sppp_close(d);
+	cosa_disable_rx(chan);
+	spin_lock_irqsave(&chan->cosa->lock, flags);
+	if (chan->rx_skb) {
+		kfree_skb(chan->rx_skb);
+		chan->rx_skb = NULL;
+	}
+	if (chan->tx_skb) {
+		kfree_skb(chan->tx_skb);
+		chan->tx_skb = NULL;
+	}
+	chan->usage=0;
+	chan->cosa->usage--;
+	spin_unlock_irqrestore(&chan->cosa->lock, flags);
+	return 0;
+}
+
+static char *sppp_setup_rx(struct channel_data *chan, int size)
+{
+	/*
+	 * We can safely fall back to non-dma-able memory, because we have
+	 * the cosa->bouncebuf pre-allocated.
+	 */
+	if (chan->rx_skb)
+		kfree_skb(chan->rx_skb);
+	chan->rx_skb = dev_alloc_skb(size);
+	if (chan->rx_skb == NULL) {
+		printk(KERN_NOTICE "%s: Memory squeeze, dropping packet\n",
+			chan->name);
+		chan->stats.rx_dropped++;
+		return NULL;
+	}
+	chan->pppdev.dev->trans_start = jiffies;
+	return skb_put(chan->rx_skb, size);
+}
+
+static int sppp_rx_done(struct channel_data *chan)
+{
+	if (!chan->rx_skb) {
+		printk(KERN_WARNING "%s: rx_done with empty skb!\n",
+			chan->name);
+		chan->stats.rx_errors++;
+		chan->stats.rx_frame_errors++;
+		return 0;
+	}
+	chan->rx_skb->protocol = htons(ETH_P_WAN_PPP);
+	chan->rx_skb->dev = chan->pppdev.dev;
+	chan->rx_skb->mac.raw = chan->rx_skb->data;
+	chan->stats.rx_packets++;
+	chan->stats.rx_bytes += chan->cosa->rxsize;
+	netif_rx(chan->rx_skb);
+	chan->rx_skb = NULL;
+	chan->pppdev.dev->last_rx = jiffies;
+	return 0;
+}
+
+/* ARGSUSED */
+static int sppp_tx_done(struct channel_data *chan, int size)
+{
+	if (!chan->tx_skb) {
+		printk(KERN_WARNING "%s: tx_done with empty skb!\n",
+			chan->name);
+		chan->stats.tx_errors++;
+		chan->stats.tx_aborted_errors++;
+		return 1;
+	}
+	dev_kfree_skb_irq(chan->tx_skb);
+	chan->tx_skb = NULL;
+	chan->stats.tx_packets++;
+	chan->stats.tx_bytes += size;
+	netif_wake_queue(chan->pppdev.dev);
+	return 1;
+}
+
+static struct net_device_stats *cosa_net_stats(struct net_device *dev)
+{
+	struct channel_data *chan = dev->priv;
+	return &chan->stats;
+}
+
+
+/*---------- Character device ---------- */
+
+static void chardev_channel_init(struct channel_data *chan)
+{
+	init_MUTEX(&chan->rsem);
+	init_MUTEX(&chan->wsem);
+}
+
+static ssize_t cosa_read(struct file *file,
+	char __user *buf, size_t count, loff_t *ppos)
+{
+	DECLARE_WAITQUEUE(wait, current);
+	unsigned long flags;
+	struct channel_data *chan = file->private_data;
+	struct cosa_data *cosa = chan->cosa;
+	char *kbuf;
+
+	if (!(cosa->firmware_status & COSA_FW_START)) {
+		printk(KERN_NOTICE "%s: start the firmware first (status %d)\n",
+			cosa->name, cosa->firmware_status);
+		return -EPERM;
+	}
+	if (down_interruptible(&chan->rsem))
+		return -ERESTARTSYS;
+	
+	if ((chan->rxdata = kmalloc(COSA_MTU, GFP_DMA|GFP_KERNEL)) == NULL) {
+		printk(KERN_INFO "%s: cosa_read() - OOM\n", cosa->name);
+		up(&chan->rsem);
+		return -ENOMEM;
+	}
+
+	chan->rx_status = 0;
+	cosa_enable_rx(chan);
+	spin_lock_irqsave(&cosa->lock, flags);
+	add_wait_queue(&chan->rxwaitq, &wait);
+	while(!chan->rx_status) {
+		current->state = TASK_INTERRUPTIBLE;
+		spin_unlock_irqrestore(&cosa->lock, flags);
+		schedule();
+		spin_lock_irqsave(&cosa->lock, flags);
+		if (signal_pending(current) && chan->rx_status == 0) {
+			chan->rx_status = 1;
+			remove_wait_queue(&chan->rxwaitq, &wait);
+			current->state = TASK_RUNNING;
+			spin_unlock_irqrestore(&cosa->lock, flags);
+			up(&chan->rsem);
+			return -ERESTARTSYS;
+		}
+	}
+	remove_wait_queue(&chan->rxwaitq, &wait);
+	current->state = TASK_RUNNING;
+	kbuf = chan->rxdata;
+	count = chan->rxsize;
+	spin_unlock_irqrestore(&cosa->lock, flags);
+	up(&chan->rsem);
+
+	if (copy_to_user(buf, kbuf, count)) {
+		kfree(kbuf);
+		return -EFAULT;
+	}
+	kfree(kbuf);
+	return count;
+}
+
+static char *chrdev_setup_rx(struct channel_data *chan, int size)
+{
+	/* Expect size <= COSA_MTU */
+	chan->rxsize = size;
+	return chan->rxdata;
+}
+
+static int chrdev_rx_done(struct channel_data *chan)
+{
+	if (chan->rx_status) { /* Reader has died */
+		kfree(chan->rxdata);
+		up(&chan->wsem);
+	}
+	chan->rx_status = 1;
+	wake_up_interruptible(&chan->rxwaitq);
+	return 1;
+}
+
+
+static ssize_t cosa_write(struct file *file,
+	const char __user *buf, size_t count, loff_t *ppos)
+{
+	DECLARE_WAITQUEUE(wait, current);
+	struct channel_data *chan = file->private_data;
+	struct cosa_data *cosa = chan->cosa;
+	unsigned long flags;
+	char *kbuf;
+
+	if (!(cosa->firmware_status & COSA_FW_START)) {
+		printk(KERN_NOTICE "%s: start the firmware first (status %d)\n",
+			cosa->name, cosa->firmware_status);
+		return -EPERM;
+	}
+	if (down_interruptible(&chan->wsem))
+		return -ERESTARTSYS;
+
+	if (count > COSA_MTU)
+		count = COSA_MTU;
+	
+	/* Allocate the buffer */
+	if ((kbuf = kmalloc(count, GFP_KERNEL|GFP_DMA)) == NULL) {
+		printk(KERN_NOTICE "%s: cosa_write() OOM - dropping packet\n",
+			cosa->name);
+		up(&chan->wsem);
+		return -ENOMEM;
+	}
+	if (copy_from_user(kbuf, buf, count)) {
+		up(&chan->wsem);
+		kfree(kbuf);
+		return -EFAULT;
+	}
+	chan->tx_status=0;
+	cosa_start_tx(chan, kbuf, count);
+
+	spin_lock_irqsave(&cosa->lock, flags);
+	add_wait_queue(&chan->txwaitq, &wait);
+	while(!chan->tx_status) {
+		current->state = TASK_INTERRUPTIBLE;
+		spin_unlock_irqrestore(&cosa->lock, flags);
+		schedule();
+		spin_lock_irqsave(&cosa->lock, flags);
+		if (signal_pending(current) && chan->tx_status == 0) {
+			chan->tx_status = 1;
+			remove_wait_queue(&chan->txwaitq, &wait);
+			current->state = TASK_RUNNING;
+			chan->tx_status = 1;
+			spin_unlock_irqrestore(&cosa->lock, flags);
+			return -ERESTARTSYS;
+		}
+	}
+	remove_wait_queue(&chan->txwaitq, &wait);
+	current->state = TASK_RUNNING;
+	up(&chan->wsem);
+	spin_unlock_irqrestore(&cosa->lock, flags);
+	kfree(kbuf);
+	return count;
+}
+
+static int chrdev_tx_done(struct channel_data *chan, int size)
+{
+	if (chan->tx_status) { /* Writer was interrupted */
+		kfree(chan->txbuf);
+		up(&chan->wsem);
+	}
+	chan->tx_status = 1;
+	wake_up_interruptible(&chan->txwaitq);
+	return 1;
+}
+
+static unsigned int cosa_poll(struct file *file, poll_table *poll)
+{
+	printk(KERN_INFO "cosa_poll is here\n");
+	return 0;
+}
+
+static int cosa_open(struct inode *inode, struct file *file)
+{
+	struct cosa_data *cosa;
+	struct channel_data *chan;
+	unsigned long flags;
+	int n;
+
+	if ((n=iminor(file->f_dentry->d_inode)>>CARD_MINOR_BITS)
+		>= nr_cards)
+		return -ENODEV;
+	cosa = cosa_cards+n;
+
+	if ((n=iminor(file->f_dentry->d_inode)
+		& ((1<<CARD_MINOR_BITS)-1)) >= cosa->nchannels)
+		return -ENODEV;
+	chan = cosa->chan + n;
+	
+	file->private_data = chan;
+
+	spin_lock_irqsave(&cosa->lock, flags);
+
+	if (chan->usage < 0) { /* in netdev mode */
+		spin_unlock_irqrestore(&cosa->lock, flags);
+		return -EBUSY;
+	}
+	cosa->usage++;
+	chan->usage++;
+
+	chan->tx_done = chrdev_tx_done;
+	chan->setup_rx = chrdev_setup_rx;
+	chan->rx_done = chrdev_rx_done;
+	spin_unlock_irqrestore(&cosa->lock, flags);
+	return 0;
+}
+
+static int cosa_release(struct inode *inode, struct file *file)
+{
+	struct channel_data *channel = file->private_data;
+	struct cosa_data *cosa;
+	unsigned long flags;
+
+	cosa = channel->cosa;
+	spin_lock_irqsave(&cosa->lock, flags);
+	cosa->usage--;
+	channel->usage--;
+	spin_unlock_irqrestore(&cosa->lock, flags);
+	return 0;
+}
+
+#ifdef COSA_FASYNC_WORKING
+static struct fasync_struct *fasync[256] = { NULL, };
+
+/* To be done ... */
+static int cosa_fasync(struct inode *inode, struct file *file, int on)
+{
+        int port = iminor(inode);
+        int rv = fasync_helper(inode, file, on, &fasync[port]);
+        return rv < 0 ? rv : 0;
+}
+#endif
+
+
+/* ---------- Ioctls ---------- */
+
+/*
+ * Ioctl subroutines can safely be made inline, because they are called
+ * only from cosa_ioctl().
+ */
+static inline int cosa_reset(struct cosa_data *cosa)
+{
+	char idstring[COSA_MAX_ID_STRING];
+	if (cosa->usage > 1)
+		printk(KERN_INFO "cosa%d: WARNING: reset requested with cosa->usage > 1 (%d). Odd things may happen.\n",
+			cosa->num, cosa->usage);
+	cosa->firmware_status &= ~(COSA_FW_RESET|COSA_FW_START);
+	if (cosa_reset_and_read_id(cosa, idstring) < 0) {
+		printk(KERN_NOTICE "cosa%d: reset failed\n", cosa->num);
+		return -EIO;
+	}
+	printk(KERN_INFO "cosa%d: resetting device: %s\n", cosa->num,
+		idstring);
+	cosa->firmware_status |= COSA_FW_RESET;
+	return 0;
+}
+
+/* High-level function to download data into COSA memory. Calls download() */
+static inline int cosa_download(struct cosa_data *cosa, void __user *arg)
+{
+	struct cosa_download d;
+	int i;
+
+	if (cosa->usage > 1)
+		printk(KERN_INFO "%s: WARNING: download of microcode requested with cosa->usage > 1 (%d). Odd things may happen.\n",
+			cosa->name, cosa->usage);
+	if (!(cosa->firmware_status & COSA_FW_RESET)) {
+		printk(KERN_NOTICE "%s: reset the card first (status %d).\n",
+			cosa->name, cosa->firmware_status);
+		return -EPERM;
+	}
+	
+	if (copy_from_user(&d, arg, sizeof(d)))
+		return -EFAULT;
+
+	if (d.addr < 0 || d.addr > COSA_MAX_FIRMWARE_SIZE)
+		return -EINVAL;
+	if (d.len < 0 || d.len > COSA_MAX_FIRMWARE_SIZE)
+		return -EINVAL;
+
+
+	/* If something fails, force the user to reset the card */
+	cosa->firmware_status &= ~(COSA_FW_RESET|COSA_FW_DOWNLOAD);
+
+	i = download(cosa, d.code, d.len, d.addr);
+	if (i < 0) {
+		printk(KERN_NOTICE "cosa%d: microcode download failed: %d\n",
+			cosa->num, i);
+		return -EIO;
+	}
+	printk(KERN_INFO "cosa%d: downloading microcode - 0x%04x bytes at 0x%04x\n",
+		cosa->num, d.len, d.addr);
+	cosa->firmware_status |= COSA_FW_RESET|COSA_FW_DOWNLOAD;
+	return 0;
+}
+
+/* High-level function to read COSA memory. Calls readmem() */
+static inline int cosa_readmem(struct cosa_data *cosa, void __user *arg)
+{
+	struct cosa_download d;
+	int i;
+
+	if (cosa->usage > 1)
+		printk(KERN_INFO "cosa%d: WARNING: readmem requested with "
+			"cosa->usage > 1 (%d). Odd things may happen.\n",
+			cosa->num, cosa->usage);
+	if (!(cosa->firmware_status & COSA_FW_RESET)) {
+		printk(KERN_NOTICE "%s: reset the card first (status %d).\n",
+			cosa->name, cosa->firmware_status);
+		return -EPERM;
+	}
+
+	if (copy_from_user(&d, arg, sizeof(d)))
+		return -EFAULT;
+
+	/* If something fails, force the user to reset the card */
+	cosa->firmware_status &= ~COSA_FW_RESET;
+
+	i = readmem(cosa, d.code, d.len, d.addr);
+	if (i < 0) {
+		printk(KERN_NOTICE "cosa%d: reading memory failed: %d\n",
+			cosa->num, i);
+		return -EIO;
+	}
+	printk(KERN_INFO "cosa%d: reading card memory - 0x%04x bytes at 0x%04x\n",
+		cosa->num, d.len, d.addr);
+	cosa->firmware_status |= COSA_FW_RESET;
+	return 0;
+}
+
+/* High-level function to start microcode. Calls startmicrocode(). */
+static inline int cosa_start(struct cosa_data *cosa, int address)
+{
+	int i;
+
+	if (cosa->usage > 1)
+		printk(KERN_INFO "cosa%d: WARNING: start microcode requested with cosa->usage > 1 (%d). Odd things may happen.\n",
+			cosa->num, cosa->usage);
+
+	if ((cosa->firmware_status & (COSA_FW_RESET|COSA_FW_DOWNLOAD))
+		!= (COSA_FW_RESET|COSA_FW_DOWNLOAD)) {
+		printk(KERN_NOTICE "%s: download the microcode and/or reset the card first (status %d).\n",
+			cosa->name, cosa->firmware_status);
+		return -EPERM;
+	}
+	cosa->firmware_status &= ~COSA_FW_RESET;
+	if ((i=startmicrocode(cosa, address)) < 0) {
+		printk(KERN_NOTICE "cosa%d: start microcode at 0x%04x failed: %d\n",
+			cosa->num, address, i);
+		return -EIO;
+	}
+	printk(KERN_INFO "cosa%d: starting microcode at 0x%04x\n",
+		cosa->num, address);
+	cosa->startaddr = address;
+	cosa->firmware_status |= COSA_FW_START;
+	return 0;
+}
+		
+/* Buffer of size at least COSA_MAX_ID_STRING is expected */
+static inline int cosa_getidstr(struct cosa_data *cosa, char __user *string)
+{
+	int l = strlen(cosa->id_string)+1;
+	if (copy_to_user(string, cosa->id_string, l))
+		return -EFAULT;
+	return l;
+}
+
+/* Buffer of size at least COSA_MAX_ID_STRING is expected */
+static inline int cosa_gettype(struct cosa_data *cosa, char __user *string)
+{
+	int l = strlen(cosa->type)+1;
+	if (copy_to_user(string, cosa->type, l))
+		return -EFAULT;
+	return l;
+}
+
+static int cosa_ioctl_common(struct cosa_data *cosa,
+	struct channel_data *channel, unsigned int cmd, unsigned long arg)
+{
+	void __user *argp = (void __user *)arg;
+	switch(cmd) {
+	case COSAIORSET:	/* Reset the device */
+		if (!capable(CAP_NET_ADMIN))
+			return -EACCES;
+		return cosa_reset(cosa);
+	case COSAIOSTRT:	/* Start the firmware */
+		if (!capable(CAP_SYS_RAWIO))
+			return -EACCES;
+		return cosa_start(cosa, arg);
+	case COSAIODOWNLD:	/* Download the firmware */
+		if (!capable(CAP_SYS_RAWIO))
+			return -EACCES;
+		
+		return cosa_download(cosa, argp);
+	case COSAIORMEM:
+		if (!capable(CAP_SYS_RAWIO))
+			return -EACCES;
+		return cosa_readmem(cosa, argp);
+	case COSAIORTYPE:
+		return cosa_gettype(cosa, argp);
+	case COSAIORIDSTR:
+		return cosa_getidstr(cosa, argp);
+	case COSAIONRCARDS:
+		return nr_cards;
+	case COSAIONRCHANS:
+		return cosa->nchannels;
+	case COSAIOBMSET:
+		if (!capable(CAP_SYS_RAWIO))
+			return -EACCES;
+		if (is_8bit(cosa))
+			return -EINVAL;
+		if (arg != COSA_BM_OFF && arg != COSA_BM_ON)
+			return -EINVAL;
+		cosa->busmaster = arg;
+		return 0;
+	case COSAIOBMGET:
+		return cosa->busmaster;
+	}
+	return -ENOIOCTLCMD;
+}
+
+static int cosa_sppp_ioctl(struct net_device *dev, struct ifreq *ifr,
+	int cmd)
+{
+	int rv;
+	struct channel_data *chan = dev->priv;
+	rv = cosa_ioctl_common(chan->cosa, chan, cmd, (unsigned long)ifr->ifr_data);
+	if (rv == -ENOIOCTLCMD) {
+		return sppp_do_ioctl(dev, ifr, cmd);
+	}
+	return rv;
+}
+
+static int cosa_chardev_ioctl(struct inode *inode, struct file *file,
+	unsigned int cmd, unsigned long arg)
+{
+	struct channel_data *channel = file->private_data;
+	struct cosa_data *cosa = channel->cosa;
+	return cosa_ioctl_common(cosa, channel, cmd, arg);
+}
+
+
+/*---------- HW layer interface ---------- */
+
+/*
+ * The higher layer can bind itself to the HW layer by setting the callbacks
+ * in the channel_data structure and by using these routines.
+ */
+static void cosa_enable_rx(struct channel_data *chan)
+{
+	struct cosa_data *cosa = chan->cosa;
+
+	if (!test_and_set_bit(chan->num, &cosa->rxbitmap))
+		put_driver_status(cosa);
+}
+
+static void cosa_disable_rx(struct channel_data *chan)
+{
+	struct cosa_data *cosa = chan->cosa;
+
+	if (test_and_clear_bit(chan->num, &cosa->rxbitmap))
+		put_driver_status(cosa);
+}
+
+/*
+ * FIXME: This routine probably should check for cosa_start_tx() called when
+ * the previous transmit is still unfinished. In this case the non-zero
+ * return value should indicate to the caller that the queuing(sp?) up
+ * the transmit has failed.
+ */
+static int cosa_start_tx(struct channel_data *chan, char *buf, int len)
+{
+	struct cosa_data *cosa = chan->cosa;
+	unsigned long flags;
+#ifdef DEBUG_DATA
+	int i;
+
+	printk(KERN_INFO "cosa%dc%d: starting tx(0x%x)", chan->cosa->num,
+		chan->num, len);
+	for (i=0; i<len; i++)
+		printk(" %02x", buf[i]&0xff);
+	printk("\n");
+#endif
+	spin_lock_irqsave(&cosa->lock, flags);
+	chan->txbuf = buf;
+	chan->txsize = len;
+	if (len > COSA_MTU)
+		chan->txsize = COSA_MTU;
+	spin_unlock_irqrestore(&cosa->lock, flags);
+
+	/* Tell the firmware we are ready */
+	set_bit(chan->num, &cosa->txbitmap);
+	put_driver_status(cosa);
+
+	return 0;
+}
+
+static void put_driver_status(struct cosa_data *cosa)
+{
+	unsigned long flags;
+	int status;
+
+	spin_lock_irqsave(&cosa->lock, flags);
+
+	status = (cosa->rxbitmap ? DRIVER_RX_READY : 0)
+		| (cosa->txbitmap ? DRIVER_TX_READY : 0)
+		| (cosa->txbitmap? ~(cosa->txbitmap<<DRIVER_TXMAP_SHIFT)
+			&DRIVER_TXMAP_MASK : 0);
+	if (!cosa->rxtx) {
+		if (cosa->rxbitmap|cosa->txbitmap) {
+			if (!cosa->enabled) {
+				cosa_putstatus(cosa, SR_RX_INT_ENA);
+#ifdef DEBUG_IO
+				debug_status_out(cosa, SR_RX_INT_ENA);
+#endif
+				cosa->enabled = 1;
+			}
+		} else if (cosa->enabled) {
+			cosa->enabled = 0;
+			cosa_putstatus(cosa, 0);
+#ifdef DEBUG_IO
+			debug_status_out(cosa, 0);
+#endif
+		}
+		cosa_putdata8(cosa, status);
+#ifdef DEBUG_IO
+		debug_data_cmd(cosa, status);
+#endif
+	}
+	spin_unlock_irqrestore(&cosa->lock, flags);
+}
+
+static void put_driver_status_nolock(struct cosa_data *cosa)
+{
+	int status;
+
+	status = (cosa->rxbitmap ? DRIVER_RX_READY : 0)
+		| (cosa->txbitmap ? DRIVER_TX_READY : 0)
+		| (cosa->txbitmap? ~(cosa->txbitmap<<DRIVER_TXMAP_SHIFT)
+			&DRIVER_TXMAP_MASK : 0);
+
+	if (cosa->rxbitmap|cosa->txbitmap) {
+		cosa_putstatus(cosa, SR_RX_INT_ENA);
+#ifdef DEBUG_IO
+		debug_status_out(cosa, SR_RX_INT_ENA);
+#endif
+		cosa->enabled = 1;
+	} else {
+		cosa_putstatus(cosa, 0);
+#ifdef DEBUG_IO
+		debug_status_out(cosa, 0);
+#endif
+		cosa->enabled = 0;
+	}
+	cosa_putdata8(cosa, status);
+#ifdef DEBUG_IO
+	debug_data_cmd(cosa, status);
+#endif
+}
+
+/*
+ * The "kickme" function: When the DMA times out, this is called to
+ * clean up the driver status.
+ * FIXME: Preliminary support, the interface is probably wrong.
+ */
+static void cosa_kick(struct cosa_data *cosa)
+{
+	unsigned long flags, flags1;
+	char *s = "(probably) IRQ";
+
+	if (test_bit(RXBIT, &cosa->rxtx))
+		s = "RX DMA";
+	if (test_bit(TXBIT, &cosa->rxtx))
+		s = "TX DMA";
+
+	printk(KERN_INFO "%s: %s timeout - restarting.\n", cosa->name, s); 
+	spin_lock_irqsave(&cosa->lock, flags);
+	cosa->rxtx = 0;
+
+	flags1 = claim_dma_lock();
+	disable_dma(cosa->dma);
+	clear_dma_ff(cosa->dma);
+	release_dma_lock(flags1);
+
+	/* FIXME: Anything else? */
+	udelay(100);
+	cosa_putstatus(cosa, 0);
+	udelay(100);
+	(void) cosa_getdata8(cosa);
+	udelay(100);
+	cosa_putdata8(cosa, 0);
+	udelay(100);
+	put_driver_status_nolock(cosa);
+	spin_unlock_irqrestore(&cosa->lock, flags);
+}
+
+/*
+ * Check if the whole buffer is DMA-able. It means it is below the 16M of
+ * physical memory and doesn't span the 64k boundary. For now it seems
+ * SKB's never do this, but we'll check this anyway.
+ */
+static int cosa_dma_able(struct channel_data *chan, char *buf, int len)
+{
+	static int count;
+	unsigned long b = (unsigned long)buf;
+	if (b+len >= MAX_DMA_ADDRESS)
+		return 0;
+	if ((b^ (b+len)) & 0x10000) {
+		if (count++ < 5)
+			printk(KERN_INFO "%s: packet spanning a 64k boundary\n",
+				chan->name);
+		return 0;
+	}
+	return 1;
+}
+
+
+/* ---------- The SRP/COSA ROM monitor functions ---------- */
+
+/*
+ * Downloading SRP microcode: say "w" to SRP monitor, it answers by "w=",
+ * drivers need to say 4-digit hex number meaning start address of the microcode
+ * separated by a single space. Monitor replies by saying " =". Now driver
+ * has to write 4-digit hex number meaning the last byte address ended
+ * by a single space. Monitor has to reply with a space. Now the download
+ * begins. After the download monitor replies with "\r\n." (CR LF dot).
+ */
+static int download(struct cosa_data *cosa, const char __user *microcode, int length, int address)
+{
+	int i;
+
+	if (put_wait_data(cosa, 'w') == -1) return -1;
+	if ((i=get_wait_data(cosa)) != 'w') { printk("dnld: 0x%04x\n",i); return -2;}
+	if (get_wait_data(cosa) != '=') return -3;
+
+	if (puthexnumber(cosa, address) < 0) return -4;
+	if (put_wait_data(cosa, ' ') == -1) return -10;
+	if (get_wait_data(cosa) != ' ') return -11;
+	if (get_wait_data(cosa) != '=') return -12;
+
+	if (puthexnumber(cosa, address+length-1) < 0) return -13;
+	if (put_wait_data(cosa, ' ') == -1) return -18;
+	if (get_wait_data(cosa) != ' ') return -19;
+
+	while (length--) {
+		char c;
+#ifndef SRP_DOWNLOAD_AT_BOOT
+		if (get_user(c, microcode))
+			return -23; /* ??? */
+#else
+		c = *microcode;
+#endif
+		if (put_wait_data(cosa, c) == -1)
+			return -20;
+		microcode++;
+	}
+
+	if (get_wait_data(cosa) != '\r') return -21;
+	if (get_wait_data(cosa) != '\n') return -22;
+	if (get_wait_data(cosa) != '.') return -23;
+#if 0
+	printk(KERN_DEBUG "cosa%d: download completed.\n", cosa->num);
+#endif
+	return 0;
+}
+
+
+/*
+ * Starting microcode is done via the "g" command of the SRP monitor.
+ * The chat should be the following: "g" "g=" "<addr><CR>"
+ * "<CR><CR><LF><CR><LF>".
+ */
+static int startmicrocode(struct cosa_data *cosa, int address)
+{
+	if (put_wait_data(cosa, 'g') == -1) return -1;
+	if (get_wait_data(cosa) != 'g') return -2;
+	if (get_wait_data(cosa) != '=') return -3;
+
+	if (puthexnumber(cosa, address) < 0) return -4;
+	if (put_wait_data(cosa, '\r') == -1) return -5;
+	
+	if (get_wait_data(cosa) != '\r') return -6;
+	if (get_wait_data(cosa) != '\r') return -7;
+	if (get_wait_data(cosa) != '\n') return -8;
+	if (get_wait_data(cosa) != '\r') return -9;
+	if (get_wait_data(cosa) != '\n') return -10;
+#if 0
+	printk(KERN_DEBUG "cosa%d: microcode started\n", cosa->num);
+#endif
+	return 0;
+}
+
+/*
+ * Reading memory is done via the "r" command of the SRP monitor.
+ * The chat is the following "r" "r=" "<addr> " " =" "<last_byte> " " "
+ * Then driver can read the data and the conversation is finished
+ * by SRP monitor sending "<CR><LF>." (dot at the end).
+ *
+ * This routine is not needed during the normal operation and serves
+ * for debugging purposes only.
+ */
+static int readmem(struct cosa_data *cosa, char __user *microcode, int length, int address)
+{
+	if (put_wait_data(cosa, 'r') == -1) return -1;
+	if ((get_wait_data(cosa)) != 'r') return -2;
+	if ((get_wait_data(cosa)) != '=') return -3;
+
+	if (puthexnumber(cosa, address) < 0) return -4;
+	if (put_wait_data(cosa, ' ') == -1) return -5;
+	if (get_wait_data(cosa) != ' ') return -6;
+	if (get_wait_data(cosa) != '=') return -7;
+
+	if (puthexnumber(cosa, address+length-1) < 0) return -8;
+	if (put_wait_data(cosa, ' ') == -1) return -9;
+	if (get_wait_data(cosa) != ' ') return -10;
+
+	while (length--) {
+		char c;
+		int i;
+		if ((i=get_wait_data(cosa)) == -1) {
+			printk (KERN_INFO "cosa: 0x%04x bytes remaining\n",
+				length);
+			return -11;
+		}
+		c=i;
+#if 1
+		if (put_user(c, microcode))
+			return -23; /* ??? */
+#else
+		*microcode = c;
+#endif
+		microcode++;
+	}
+
+	if (get_wait_data(cosa) != '\r') return -21;
+	if (get_wait_data(cosa) != '\n') return -22;
+	if (get_wait_data(cosa) != '.') return -23;
+#if 0
+	printk(KERN_DEBUG "cosa%d: readmem completed.\n", cosa->num);
+#endif
+	return 0;
+}
+
+/*
+ * This function resets the device and reads the initial prompt
+ * of the device's ROM monitor.
+ */
+static int cosa_reset_and_read_id(struct cosa_data *cosa, char *idstring)
+{
+	int i=0, id=0, prev=0, curr=0;
+
+	/* Reset the card ... */
+	cosa_putstatus(cosa, 0);
+	cosa_getdata8(cosa);
+	cosa_putstatus(cosa, SR_RST);
+#ifdef MODULE
+	msleep(500);
+#else
+	udelay(5*100000);
+#endif
+	/* Disable all IRQs from the card */
+	cosa_putstatus(cosa, 0);
+
+	/*
+	 * Try to read the ID string. The card then prints out the
+	 * identification string ended by the "\n\x2e".
+	 *
+	 * The following loop is indexed through i (instead of id)
+	 * to avoid looping forever when for any reason
+	 * the port returns '\r', '\n' or '\x2e' permanently.
+	 */
+	for (i=0; i<COSA_MAX_ID_STRING-1; i++, prev=curr) {
+		if ((curr = get_wait_data(cosa)) == -1) {
+			return -1;
+		}
+		curr &= 0xff;
+		if (curr != '\r' && curr != '\n' && curr != 0x2e)
+			idstring[id++] = curr;
+		if (curr == 0x2e && prev == '\n')
+			break;
+	}
+	/* Perhaps we should fail when i==COSA_MAX_ID_STRING-1 ? */
+	idstring[id] = '\0';
+	return id;
+}
+
+
+/* ---------- Auxiliary routines for COSA/SRP monitor ---------- */
+
+/*
+ * This routine gets the data byte from the card waiting for the SR_RX_RDY
+ * bit to be set in a loop. It should be used in the exceptional cases
+ * only (for example when resetting the card or downloading the firmware.
+ */
+static int get_wait_data(struct cosa_data *cosa)
+{
+	int retries = 1000;
+
+	while (--retries) {
+		/* read data and return them */
+		if (cosa_getstatus(cosa) & SR_RX_RDY) {
+			short r;
+			r = cosa_getdata8(cosa);
+#if 0
+			printk(KERN_INFO "cosa: get_wait_data returning after %d retries\n", 999-retries);
+#endif
+			return r;
+		}
+		/* sleep if not ready to read */
+		set_current_state(TASK_INTERRUPTIBLE);
+		schedule_timeout(1);
+	}
+	printk(KERN_INFO "cosa: timeout in get_wait_data (status 0x%x)\n",
+		cosa_getstatus(cosa));
+	return -1;
+}
+
+/*
+ * This routine puts the data byte to the card waiting for the SR_TX_RDY
+ * bit to be set in a loop. It should be used in the exceptional cases
+ * only (for example when resetting the card or downloading the firmware).
+ */
+static int put_wait_data(struct cosa_data *cosa, int data)
+{
+	int retries = 1000;
+	while (--retries) {
+		/* read data and return them */
+		if (cosa_getstatus(cosa) & SR_TX_RDY) {
+			cosa_putdata8(cosa, data);
+#if 0
+			printk(KERN_INFO "Putdata: %d retries\n", 999-retries);
+#endif
+			return 0;
+		}
+#if 0
+		/* sleep if not ready to read */
+		current->state = TASK_INTERRUPTIBLE;
+		schedule_timeout(1);
+#endif
+	}
+	printk(KERN_INFO "cosa%d: timeout in put_wait_data (status 0x%x)\n",
+		cosa->num, cosa_getstatus(cosa));
+	return -1;
+}
+	
+/* 
+ * The following routine puts the hexadecimal number into the SRP monitor
+ * and verifies the proper echo of the sent bytes. Returns 0 on success,
+ * negative number on failure (-1,-3,-5,-7) means that put_wait_data() failed,
+ * (-2,-4,-6,-8) means that reading echo failed.
+ */
+static int puthexnumber(struct cosa_data *cosa, int number)
+{
+	char temp[5];
+	int i;
+
+	/* Well, I should probably replace this by something faster. */
+	sprintf(temp, "%04X", number);
+	for (i=0; i<4; i++) {
+		if (put_wait_data(cosa, temp[i]) == -1) {
+			printk(KERN_NOTICE "cosa%d: puthexnumber failed to write byte %d\n",
+				cosa->num, i);
+			return -1-2*i;
+		}
+		if (get_wait_data(cosa) != temp[i]) {
+			printk(KERN_NOTICE "cosa%d: puthexhumber failed to read echo of byte %d\n",
+				cosa->num, i);
+			return -2-2*i;
+		}
+	}
+	return 0;
+}
+
+
+/* ---------- Interrupt routines ---------- */
+
+/*
+ * There are three types of interrupt:
+ * At the beginning of transmit - this handled is in tx_interrupt(),
+ * at the beginning of receive - it is in rx_interrupt() and
+ * at the end of transmit/receive - it is the eot_interrupt() function.
+ * These functions are multiplexed by cosa_interrupt() according to the
+ * COSA status byte. I have moved the rx/tx/eot interrupt handling into
+ * separate functions to make it more readable. These functions are inline,
+ * so there should be no overhead of function call.
+ * 
+ * In the COSA bus-master mode, we need to tell the card the address of a
+ * buffer. Unfortunately, COSA may be too slow for us, so we must busy-wait.
+ * It's time to use the bottom half :-(
+ */
+
+/*
+ * Transmit interrupt routine - called when COSA is willing to obtain
+ * data from the OS. The most tricky part of the routine is selection
+ * of channel we (OS) want to send packet for. For SRP we should probably
+ * use the round-robin approach. The newer COSA firmwares have a simple
+ * flow-control - in the status word has bits 2 and 3 set to 1 means that the
+ * channel 0 or 1 doesn't want to receive data.
+ *
+ * It seems there is a bug in COSA firmware (need to trace it further):
+ * When the driver status says that the kernel has no more data for transmit
+ * (e.g. at the end of TX DMA) and then the kernel changes its mind
+ * (e.g. new packet is queued to hard_start_xmit()), the card issues
+ * the TX interrupt but does not mark the channel as ready-to-transmit.
+ * The fix seems to be to push the packet to COSA despite its request.
+ * We first try to obey the card's opinion, and then fall back to forced TX.
+ */
+static inline void tx_interrupt(struct cosa_data *cosa, int status)
+{
+	unsigned long flags, flags1;
+#ifdef DEBUG_IRQS
+	printk(KERN_INFO "cosa%d: SR_DOWN_REQUEST status=0x%04x\n",
+		cosa->num, status);
+#endif
+	spin_lock_irqsave(&cosa->lock, flags);
+	set_bit(TXBIT, &cosa->rxtx);
+	if (!test_bit(IRQBIT, &cosa->rxtx)) {
+		/* flow control, see the comment above */
+		int i=0;
+		if (!cosa->txbitmap) {
+			printk(KERN_WARNING "%s: No channel wants data "
+				"in TX IRQ. Expect DMA timeout.",
+				cosa->name);
+			put_driver_status_nolock(cosa);
+			clear_bit(TXBIT, &cosa->rxtx);
+			spin_unlock_irqrestore(&cosa->lock, flags);
+			return;
+		}
+		while(1) {
+			cosa->txchan++;
+			i++;
+			if (cosa->txchan >= cosa->nchannels)
+				cosa->txchan = 0;
+			if (!(cosa->txbitmap & (1<<cosa->txchan)))
+				continue;
+			if (~status & (1 << (cosa->txchan+DRIVER_TXMAP_SHIFT)))
+				break;
+			/* in second pass, accept first ready-to-TX channel */
+			if (i > cosa->nchannels) {
+				/* Can be safely ignored */
+#ifdef DEBUG_IRQS
+				printk(KERN_DEBUG "%s: Forcing TX "
+					"to not-ready channel %d\n",
+					cosa->name, cosa->txchan);
+#endif
+				break;
+			}
+		}
+
+		cosa->txsize = cosa->chan[cosa->txchan].txsize;
+		if (cosa_dma_able(cosa->chan+cosa->txchan,
+			cosa->chan[cosa->txchan].txbuf, cosa->txsize)) {
+			cosa->txbuf = cosa->chan[cosa->txchan].txbuf;
+		} else {
+			memcpy(cosa->bouncebuf, cosa->chan[cosa->txchan].txbuf,
+				cosa->txsize);
+			cosa->txbuf = cosa->bouncebuf;
+		}
+	}
+
+	if (is_8bit(cosa)) {
+		if (!test_bit(IRQBIT, &cosa->rxtx)) {
+			cosa_putstatus(cosa, SR_TX_INT_ENA);
+			cosa_putdata8(cosa, ((cosa->txchan << 5) & 0xe0)|
+				((cosa->txsize >> 8) & 0x1f));
+#ifdef DEBUG_IO
+			debug_status_out(cosa, SR_TX_INT_ENA);
+			debug_data_out(cosa, ((cosa->txchan << 5) & 0xe0)|
+                                ((cosa->txsize >> 8) & 0x1f));
+			debug_data_in(cosa, cosa_getdata8(cosa));
+#else
+			cosa_getdata8(cosa);
+#endif
+			set_bit(IRQBIT, &cosa->rxtx);
+			spin_unlock_irqrestore(&cosa->lock, flags);
+			return;
+		} else {
+			clear_bit(IRQBIT, &cosa->rxtx);
+			cosa_putstatus(cosa, 0);
+			cosa_putdata8(cosa, cosa->txsize&0xff);
+#ifdef DEBUG_IO
+			debug_status_out(cosa, 0);
+			debug_data_out(cosa, cosa->txsize&0xff);
+#endif
+		}
+	} else {
+		cosa_putstatus(cosa, SR_TX_INT_ENA);
+		cosa_putdata16(cosa, ((cosa->txchan<<13) & 0xe000)
+			| (cosa->txsize & 0x1fff));
+#ifdef DEBUG_IO
+		debug_status_out(cosa, SR_TX_INT_ENA);
+		debug_data_out(cosa, ((cosa->txchan<<13) & 0xe000)
+                        | (cosa->txsize & 0x1fff));
+		debug_data_in(cosa, cosa_getdata8(cosa));
+		debug_status_out(cosa, 0);
+#else
+		cosa_getdata8(cosa);
+#endif
+		cosa_putstatus(cosa, 0);
+	}
+
+	if (cosa->busmaster) {
+		unsigned long addr = virt_to_bus(cosa->txbuf);
+		int count=0;
+		printk(KERN_INFO "busmaster IRQ\n");
+		while (!(cosa_getstatus(cosa)&SR_TX_RDY)) {
+			count++;
+			udelay(10);
+			if (count > 1000) break;
+		}
+		printk(KERN_INFO "status %x\n", cosa_getstatus(cosa));
+		printk(KERN_INFO "ready after %d loops\n", count);
+		cosa_putdata16(cosa, (addr >> 16)&0xffff);
+
+		count = 0;
+		while (!(cosa_getstatus(cosa)&SR_TX_RDY)) {
+			count++;
+			if (count > 1000) break;
+			udelay(10);
+		}
+		printk(KERN_INFO "ready after %d loops\n", count);
+		cosa_putdata16(cosa, addr &0xffff);
+		flags1 = claim_dma_lock();
+		set_dma_mode(cosa->dma, DMA_MODE_CASCADE);
+		enable_dma(cosa->dma);
+		release_dma_lock(flags1);
+	} else {
+		/* start the DMA */
+		flags1 = claim_dma_lock();
+		disable_dma(cosa->dma);
+		clear_dma_ff(cosa->dma);
+		set_dma_mode(cosa->dma, DMA_MODE_WRITE);
+		set_dma_addr(cosa->dma, virt_to_bus(cosa->txbuf));
+		set_dma_count(cosa->dma, cosa->txsize);
+		enable_dma(cosa->dma);
+		release_dma_lock(flags1);
+	}
+	cosa_putstatus(cosa, SR_TX_DMA_ENA|SR_USR_INT_ENA);
+#ifdef DEBUG_IO
+	debug_status_out(cosa, SR_TX_DMA_ENA|SR_USR_INT_ENA);
+#endif
+	spin_unlock_irqrestore(&cosa->lock, flags);
+}
+
+static inline void rx_interrupt(struct cosa_data *cosa, int status)
+{
+	unsigned long flags;
+#ifdef DEBUG_IRQS
+	printk(KERN_INFO "cosa%d: SR_UP_REQUEST\n", cosa->num);
+#endif
+
+	spin_lock_irqsave(&cosa->lock, flags);
+	set_bit(RXBIT, &cosa->rxtx);
+
+	if (is_8bit(cosa)) {
+		if (!test_bit(IRQBIT, &cosa->rxtx)) {
+			set_bit(IRQBIT, &cosa->rxtx);
+			put_driver_status_nolock(cosa);
+			cosa->rxsize = cosa_getdata8(cosa) <<8;
+#ifdef DEBUG_IO
+			debug_data_in(cosa, cosa->rxsize >> 8);
+#endif
+			spin_unlock_irqrestore(&cosa->lock, flags);
+			return;
+		} else {
+			clear_bit(IRQBIT, &cosa->rxtx);
+			cosa->rxsize |= cosa_getdata8(cosa) & 0xff;
+#ifdef DEBUG_IO
+			debug_data_in(cosa, cosa->rxsize & 0xff);
+#endif
+#if 0
+			printk(KERN_INFO "cosa%d: receive rxsize = (0x%04x).\n",
+				cosa->num, cosa->rxsize);
+#endif
+		}
+	} else {
+		cosa->rxsize = cosa_getdata16(cosa);
+#ifdef DEBUG_IO
+		debug_data_in(cosa, cosa->rxsize);
+#endif
+#if 0
+		printk(KERN_INFO "cosa%d: receive rxsize = (0x%04x).\n",
+			cosa->num, cosa->rxsize);
+#endif
+	}
+	if (((cosa->rxsize & 0xe000) >> 13) >= cosa->nchannels) {
+		printk(KERN_WARNING "%s: rx for unknown channel (0x%04x)\n",
+			cosa->name, cosa->rxsize);
+		spin_unlock_irqrestore(&cosa->lock, flags);
+		goto reject;
+	}
+	cosa->rxchan = cosa->chan + ((cosa->rxsize & 0xe000) >> 13);
+	cosa->rxsize &= 0x1fff;
+	spin_unlock_irqrestore(&cosa->lock, flags);
+
+	cosa->rxbuf = NULL;
+	if (cosa->rxchan->setup_rx)
+		cosa->rxbuf = cosa->rxchan->setup_rx(cosa->rxchan, cosa->rxsize);
+
+	if (!cosa->rxbuf) {
+reject:		/* Reject the packet */
+		printk(KERN_INFO "cosa%d: rejecting packet on channel %d\n",
+			cosa->num, cosa->rxchan->num);
+		cosa->rxbuf = cosa->bouncebuf;
+	}
+
+	/* start the DMA */
+	flags = claim_dma_lock();
+	disable_dma(cosa->dma);
+	clear_dma_ff(cosa->dma);
+	set_dma_mode(cosa->dma, DMA_MODE_READ);
+	if (cosa_dma_able(cosa->rxchan, cosa->rxbuf, cosa->rxsize & 0x1fff)) {
+		set_dma_addr(cosa->dma, virt_to_bus(cosa->rxbuf));
+	} else {
+		set_dma_addr(cosa->dma, virt_to_bus(cosa->bouncebuf));
+	}
+	set_dma_count(cosa->dma, (cosa->rxsize&0x1fff));
+	enable_dma(cosa->dma);
+	release_dma_lock(flags);
+	spin_lock_irqsave(&cosa->lock, flags);
+	cosa_putstatus(cosa, SR_RX_DMA_ENA|SR_USR_INT_ENA);
+	if (!is_8bit(cosa) && (status & SR_TX_RDY))
+		cosa_putdata8(cosa, DRIVER_RX_READY);
+#ifdef DEBUG_IO
+	debug_status_out(cosa, SR_RX_DMA_ENA|SR_USR_INT_ENA);
+	if (!is_8bit(cosa) && (status & SR_TX_RDY))
+		debug_data_cmd(cosa, DRIVER_RX_READY);
+#endif
+	spin_unlock_irqrestore(&cosa->lock, flags);
+}
+
+static inline void eot_interrupt(struct cosa_data *cosa, int status)
+{
+	unsigned long flags, flags1;
+	spin_lock_irqsave(&cosa->lock, flags);
+	flags1 = claim_dma_lock();
+	disable_dma(cosa->dma);
+	clear_dma_ff(cosa->dma);
+	release_dma_lock(flags1);
+	if (test_bit(TXBIT, &cosa->rxtx)) {
+		struct channel_data *chan = cosa->chan+cosa->txchan;
+		if (chan->tx_done)
+			if (chan->tx_done(chan, cosa->txsize))
+				clear_bit(chan->num, &cosa->txbitmap);
+	} else if (test_bit(RXBIT, &cosa->rxtx)) {
+#ifdef DEBUG_DATA
+	{
+		int i;
+		printk(KERN_INFO "cosa%dc%d: done rx(0x%x)", cosa->num, 
+			cosa->rxchan->num, cosa->rxsize);
+		for (i=0; i<cosa->rxsize; i++)
+			printk (" %02x", cosa->rxbuf[i]&0xff);
+		printk("\n");
+	}
+#endif
+		/* Packet for unknown channel? */
+		if (cosa->rxbuf == cosa->bouncebuf)
+			goto out;
+		if (!cosa_dma_able(cosa->rxchan, cosa->rxbuf, cosa->rxsize))
+			memcpy(cosa->rxbuf, cosa->bouncebuf, cosa->rxsize);
+		if (cosa->rxchan->rx_done)
+			if (cosa->rxchan->rx_done(cosa->rxchan))
+				clear_bit(cosa->rxchan->num, &cosa->rxbitmap);
+	} else {
+		printk(KERN_NOTICE "cosa%d: unexpected EOT interrupt\n",
+			cosa->num);
+	}
+	/*
+	 * Clear the RXBIT, TXBIT and IRQBIT (the latest should be
+	 * cleared anyway). We should do it as soon as possible
+	 * so that we can tell the COSA we are done and to give it a time
+	 * for recovery.
+	 */
+out:
+	cosa->rxtx = 0;
+	put_driver_status_nolock(cosa);
+	spin_unlock_irqrestore(&cosa->lock, flags);
+}
+
+static irqreturn_t cosa_interrupt(int irq, void *cosa_, struct pt_regs *regs)
+{
+	unsigned status;
+	int count = 0;
+	struct cosa_data *cosa = cosa_;
+again:
+	status = cosa_getstatus(cosa);
+#ifdef DEBUG_IRQS
+	printk(KERN_INFO "cosa%d: got IRQ, status 0x%02x\n", cosa->num,
+		status & 0xff);
+#endif
+#ifdef DEBUG_IO
+	debug_status_in(cosa, status);
+#endif
+	switch (status & SR_CMD_FROM_SRP_MASK) {
+	case SR_DOWN_REQUEST:
+		tx_interrupt(cosa, status);
+		break;
+	case SR_UP_REQUEST:
+		rx_interrupt(cosa, status);
+		break;
+	case SR_END_OF_TRANSFER:
+		eot_interrupt(cosa, status);
+		break;
+	default:
+		/* We may be too fast for SRP. Try to wait a bit more. */
+		if (count++ < 100) {
+			udelay(100);
+			goto again;
+		}
+		printk(KERN_INFO "cosa%d: unknown status 0x%02x in IRQ after %d retries\n",
+			cosa->num, status & 0xff, count);
+	}
+#ifdef DEBUG_IRQS
+	if (count)
+		printk(KERN_INFO "%s: %d-times got unknown status in IRQ\n",
+			cosa->name, count);
+	else
+		printk(KERN_INFO "%s: returning from IRQ\n", cosa->name);
+#endif
+	return IRQ_HANDLED;
+}
+
+
+/* ---------- I/O debugging routines ---------- */
+/*
+ * These routines can be used to monitor COSA/SRP I/O and to printk()
+ * the data being transferred on the data and status I/O port in a
+ * readable way.
+ */
+
+#ifdef DEBUG_IO
+static void debug_status_in(struct cosa_data *cosa, int status)
+{
+	char *s;
+	switch(status & SR_CMD_FROM_SRP_MASK) {
+	case SR_UP_REQUEST:
+		s = "RX_REQ";
+		break;
+	case SR_DOWN_REQUEST:
+		s = "TX_REQ";
+		break;
+	case SR_END_OF_TRANSFER:
+		s = "ET_REQ";
+		break;
+	default:
+		s = "NO_REQ";
+		break;
+	}
+	printk(KERN_INFO "%s: IO: status -> 0x%02x (%s%s%s%s)\n",
+		cosa->name,
+		status,
+		status & SR_USR_RQ ? "USR_RQ|":"",
+		status & SR_TX_RDY ? "TX_RDY|":"",
+		status & SR_RX_RDY ? "RX_RDY|":"",
+		s);
+}
+
+static void debug_status_out(struct cosa_data *cosa, int status)
+{
+	printk(KERN_INFO "%s: IO: status <- 0x%02x (%s%s%s%s%s%s)\n",
+		cosa->name,
+		status,
+		status & SR_RX_DMA_ENA  ? "RXDMA|":"!rxdma|",
+		status & SR_TX_DMA_ENA  ? "TXDMA|":"!txdma|",
+		status & SR_RST         ? "RESET|":"",
+		status & SR_USR_INT_ENA ? "USRINT|":"!usrint|",
+		status & SR_TX_INT_ENA  ? "TXINT|":"!txint|",
+		status & SR_RX_INT_ENA  ? "RXINT":"!rxint");
+}
+
+static void debug_data_in(struct cosa_data *cosa, int data)
+{
+	printk(KERN_INFO "%s: IO: data -> 0x%04x\n", cosa->name, data);
+}
+
+static void debug_data_out(struct cosa_data *cosa, int data)
+{
+	printk(KERN_INFO "%s: IO: data <- 0x%04x\n", cosa->name, data);
+}
+
+static void debug_data_cmd(struct cosa_data *cosa, int data)
+{
+	printk(KERN_INFO "%s: IO: data <- 0x%04x (%s|%s)\n",
+		cosa->name, data,
+		data & SR_RDY_RCV ? "RX_RDY" : "!rx_rdy",
+		data & SR_RDY_SND ? "TX_RDY" : "!tx_rdy");
+}
+#endif
+
+/* EOF -- this file has not been truncated */
diff --git a/drivers/net/wan/cosa.h b/drivers/net/wan/cosa.h
new file mode 100644
index 0000000..028f3d9
--- /dev/null
+++ b/drivers/net/wan/cosa.h
@@ -0,0 +1,117 @@
+/* $Id: cosa.h,v 1.6 1999/01/06 14:02:44 kas Exp $ */
+
+/*
+ *  Copyright (C) 1995-1997  Jan "Yenya" Kasprzak <kas@fi.muni.cz>
+ *
+ *  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.
+ */
+
+#ifndef COSA_H__
+#define COSA_H__
+
+#include <linux/ioctl.h>
+
+#ifdef __KERNEL__
+/* status register - output bits */
+#define SR_RX_DMA_ENA   0x04    /* receiver DMA enable bit */
+#define SR_TX_DMA_ENA   0x08    /* transmitter DMA enable bit */
+#define SR_RST          0x10    /* SRP reset */
+#define SR_USR_INT_ENA  0x20    /* user interrupt enable bit */
+#define SR_TX_INT_ENA   0x40    /* transmitter interrupt enable bit */
+#define SR_RX_INT_ENA   0x80    /* receiver interrupt enable bit */
+
+/* status register - input bits */
+#define SR_USR_RQ       0x20    /* user interrupt request pending */
+#define SR_TX_RDY       0x40    /* transmitter empty (ready) */
+#define SR_RX_RDY       0x80    /* receiver data ready */
+
+#define SR_UP_REQUEST   0x02    /* request from SRP to transfer data
+                                   up to PC */
+#define SR_DOWN_REQUEST 0x01    /* SRP is able to transfer data down
+                                   from PC to SRP */
+#define SR_END_OF_TRANSFER      0x03    /* SRP signalize end of
+                                           transfer (up or down) */
+
+#define SR_CMD_FROM_SRP_MASK    0x03    /* mask to get SRP command */
+
+/* bits in driver status byte definitions : */
+#define SR_RDY_RCV      0x01    /* ready to receive packet */
+#define SR_RDY_SND      0x02    /* ready to send packet */
+#define SR_CMD_PND      0x04    /* command pending */ /* not currently used */
+
+/* ???? */
+#define SR_PKT_UP       0x01    /* transfer of packet up in progress */
+#define SR_PKT_DOWN     0x02    /* transfer of packet down in progress */
+
+#endif /* __KERNEL__ */
+
+#define SR_LOAD_ADDR    0x4400  /* SRP microcode load address */
+#define SR_START_ADDR   0x4400  /* SRP microcode start address */
+
+#define COSA_LOAD_ADDR    0x400  /* SRP microcode load address */
+#define COSA_MAX_FIRMWARE_SIZE	0x10000
+
+/* ioctls */
+struct cosa_download {
+	int addr, len;
+	char __user *code;
+};
+
+/* Reset the device */
+#define COSAIORSET	_IO('C',0xf0)
+
+/* Start microcode at given address */
+#define COSAIOSTRT	_IOW('C',0xf1, int)
+
+/* Read the block from the device memory */
+#define COSAIORMEM	_IOWR('C',0xf2, struct cosa_download *)
+	/* actually the struct cosa_download itself; this is to keep
+	 * the ioctl number same as in 2.4 in order to keep the user-space
+	 * utils compatible. */
+
+/* Write the block to the device memory (i.e. download the microcode) */
+#define COSAIODOWNLD	_IOW('C',0xf2, struct cosa_download *)
+	/* actually the struct cosa_download itself; this is to keep
+	 * the ioctl number same as in 2.4 in order to keep the user-space
+	 * utils compatible. */
+
+/* Read the device type (one of "srp", "cosa", and "cosa8" for now) */
+#define COSAIORTYPE	_IOR('C',0xf3, char *)
+
+/* Read the device identification string */
+#define COSAIORIDSTR	_IOR('C',0xf4, char *)
+/* Maximum length of the identification string. */
+#define COSA_MAX_ID_STRING 128
+
+/* Increment/decrement the module usage count :-) */
+/* #define COSAIOMINC	_IO('C',0xf5) */
+/* #define COSAIOMDEC	_IO('C',0xf6) */
+
+/* Get the total number of cards installed */
+#define COSAIONRCARDS	_IO('C',0xf7)
+
+/* Get the number of channels on this card */
+#define COSAIONRCHANS	_IO('C',0xf8)
+
+/* Set the driver for the bus-master operations */
+#define COSAIOBMSET	_IOW('C', 0xf9, unsigned short)
+
+#define COSA_BM_OFF	0	/* Bus-mastering off - use ISA DMA (default) */
+#define COSA_BM_ON	1	/* Bus-mastering on - faster but untested */
+
+/* Gets the busmaster status */
+#define COSAIOBMGET	_IO('C', 0xfa)
+
+#endif /* !COSA_H__ */
diff --git a/drivers/net/wan/cycx_drv.c b/drivers/net/wan/cycx_drv.c
new file mode 100644
index 0000000..6e74af6
--- /dev/null
+++ b/drivers/net/wan/cycx_drv.c
@@ -0,0 +1,586 @@
+/*
+* cycx_drv.c	Cyclom 2X Support Module.
+*
+*		This module is a library of common hardware specific
+*		functions used by the Cyclades Cyclom 2X sync card.
+*
+* Author:	Arnaldo Carvalho de Melo <acme@conectiva.com.br>
+*
+* Copyright:	(c) 1998-2003 Arnaldo Carvalho de Melo
+*
+* Based on sdladrv.c by Gene Kozin <genek@compuserve.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.
+* ============================================================================
+* 1999/11/11	acme		set_current_state(TASK_INTERRUPTIBLE), code
+*				cleanup
+* 1999/11/08	acme		init_cyc2x deleted, doing nothing
+* 1999/11/06	acme		back to read[bw], write[bw] and memcpy_to and
+*				fromio to use dpmbase ioremaped
+* 1999/10/26	acme		use isa_read[bw], isa_write[bw] & isa_memcpy_to
+*				& fromio
+* 1999/10/23	acme		cleanup to only supports cyclom2x: all the other
+*				boards are no longer manufactured by cyclades,
+*				if someone wants to support them... be my guest!
+* 1999/05/28    acme		cycx_intack & cycx_intde gone for good
+* 1999/05/18	acme		lots of unlogged work, submitting to Linus...
+* 1999/01/03	acme		more judicious use of data types
+* 1999/01/03	acme		judicious use of data types :>
+*				cycx_inten trying to reset pending interrupts
+*				from cyclom 2x - I think this isn't the way to
+*				go, but for now...
+* 1999/01/02	acme		cycx_intack ok, I think there's nothing to do
+*				to ack an int in cycx_drv.c, only handle it in
+*				cyx_isr (or in the other protocols: cyp_isr,
+*				cyf_isr, when they get implemented.
+* Dec 31, 1998	acme		cycx_data_boot & cycx_code_boot fixed, crossing
+*				fingers to see x25_configure in cycx_x25.c
+*				work... :)
+* Dec 26, 1998	acme		load implementation fixed, seems to work! :)
+*				cycx_2x_dpmbase_options with all the possible
+*				DPM addresses (20).
+*				cycx_intr implemented (test this!)
+*				general code cleanup
+* Dec  8, 1998	Ivan Passos	Cyclom-2X firmware load implementation.
+* Aug  8, 1998	acme		Initial version.
+*/
+
+#include <linux/init.h>		/* __init */
+#include <linux/module.h>
+#include <linux/kernel.h>	/* printk(), and other useful stuff */
+#include <linux/stddef.h>	/* offsetof(), etc. */
+#include <linux/errno.h>	/* return codes */
+#include <linux/sched.h>	/* for jiffies, HZ, etc. */
+#include <linux/cycx_drv.h>	/* API definitions */
+#include <linux/cycx_cfm.h>	/* CYCX firmware module definitions */
+#include <linux/delay.h>	/* udelay */
+#include <asm/io.h>		/* read[wl], write[wl], ioremap, iounmap */
+
+#define	MOD_VERSION	0
+#define	MOD_RELEASE	6
+
+MODULE_AUTHOR("Arnaldo Carvalho de Melo");
+MODULE_DESCRIPTION("Cyclom 2x Sync Card Driver");
+MODULE_LICENSE("GPL");
+
+/* Hardware-specific functions */
+static int load_cyc2x(struct cycx_hw *hw, struct cycx_firmware *cfm, u32 len);
+static void cycx_bootcfg(struct cycx_hw *hw);
+
+static int reset_cyc2x(void __iomem *addr);
+static int detect_cyc2x(void __iomem *addr);
+
+/* Miscellaneous functions */
+static void delay_cycx(int sec);
+static int get_option_index(long *optlist, long optval);
+static u16 checksum(u8 *buf, u32 len);
+
+#define wait_cyc(addr) cycx_exec(addr + CMD_OFFSET)
+
+/* Global Data */
+
+/* private data */
+static char modname[] = "cycx_drv";
+static char fullname[] = "Cyclom 2X Support Module";
+static char copyright[] = "(c) 1998-2003 Arnaldo Carvalho de Melo "
+			  "<acme@conectiva.com.br>";
+
+/* Hardware configuration options.
+ * These are arrays of configuration options used by verification routines.
+ * The first element of each array is its size (i.e. number of options).
+ */
+static long cyc2x_dpmbase_options[] = {
+	20,
+	0xA0000, 0xA4000, 0xA8000, 0xAC000, 0xB0000, 0xB4000, 0xB8000,
+	0xBC000, 0xC0000, 0xC4000, 0xC8000, 0xCC000, 0xD0000, 0xD4000,
+	0xD8000, 0xDC000, 0xE0000, 0xE4000, 0xE8000, 0xEC000
+};
+
+static long cycx_2x_irq_options[]  = { 7, 3, 5, 9, 10, 11, 12, 15 };
+
+/* Kernel Loadable Module Entry Points */
+/* Module 'insert' entry point.
+ * o print announcement
+ * o initialize static data
+ *
+ * Return:	0	Ok
+ *		< 0	error.
+ * Context:	process */
+
+int __init cycx_drv_init(void)
+{
+	printk(KERN_INFO "%s v%u.%u %s\n", fullname, MOD_VERSION, MOD_RELEASE,
+			 copyright);
+
+	return 0;
+}
+
+/* Module 'remove' entry point.
+ * o release all remaining system resources */
+void cycx_drv_cleanup(void)
+{
+}
+
+/* Kernel APIs */
+/* Set up adapter.
+ * o detect adapter type
+ * o verify hardware configuration options
+ * o check for hardware conflicts
+ * o set up adapter shared memory
+ * o test adapter memory
+ * o load firmware
+ * Return:	0	ok.
+ *		< 0	error */
+EXPORT_SYMBOL(cycx_setup);
+int cycx_setup(struct cycx_hw *hw, void *cfm, u32 len, unsigned long dpmbase)
+{
+	int err;
+
+	/* Verify IRQ configuration options */
+	if (!get_option_index(cycx_2x_irq_options, hw->irq)) {
+		printk(KERN_ERR "%s: IRQ %d is invalid!\n", modname, hw->irq);
+		return -EINVAL;
+	}
+
+	/* Setup adapter dual-port memory window and test memory */
+	if (!dpmbase) {
+		printk(KERN_ERR "%s: you must specify the dpm address!\n",
+				modname);
+ 		return -EINVAL;
+	} else if (!get_option_index(cyc2x_dpmbase_options, dpmbase)) {
+		printk(KERN_ERR "%s: memory address 0x%lX is invalid!\n",
+				modname, dpmbase);
+		return -EINVAL;
+	}
+
+	hw->dpmbase = ioremap(dpmbase, CYCX_WINDOWSIZE);
+	hw->dpmsize = CYCX_WINDOWSIZE;
+
+	if (!detect_cyc2x(hw->dpmbase)) {
+		printk(KERN_ERR "%s: adapter Cyclom 2X not found at "
+				"address 0x%lX!\n", modname, dpmbase);
+		return -EINVAL;
+	}
+
+	printk(KERN_INFO "%s: found Cyclom 2X card at address 0x%lX.\n",
+			 modname, dpmbase);
+
+	/* Load firmware. If loader fails then shut down adapter */
+	err = load_cyc2x(hw, cfm, len);
+
+	if (err)
+		cycx_down(hw);         /* shutdown adapter */
+
+	return err;
+}
+
+EXPORT_SYMBOL(cycx_down);
+int cycx_down(struct cycx_hw *hw)
+{
+	iounmap(hw->dpmbase);
+	return 0;
+}
+
+/* Enable interrupt generation.  */
+EXPORT_SYMBOL(cycx_inten);
+void cycx_inten(struct cycx_hw *hw)
+{
+	writeb(0, hw->dpmbase);
+}
+
+/* Generate an interrupt to adapter's CPU. */
+EXPORT_SYMBOL(cycx_intr);
+void cycx_intr(struct cycx_hw *hw)
+{
+	writew(0, hw->dpmbase + GEN_CYCX_INTR);
+}
+
+/* Execute Adapter Command.
+ * o Set exec flag.
+ * o Busy-wait until flag is reset. */
+EXPORT_SYMBOL(cycx_exec);
+int cycx_exec(void __iomem *addr)
+{
+	u16 i = 0;
+	/* wait till addr content is zeroed */
+
+	while (readw(addr)) {
+		udelay(1000);
+
+		if (++i > 50)
+			return -1;
+	}
+
+	return 0;
+}
+
+/* Read absolute adapter memory.
+ * Transfer data from adapter's memory to data buffer. */
+EXPORT_SYMBOL(cycx_peek);
+int cycx_peek(struct cycx_hw *hw, u32 addr, void *buf, u32 len)
+{
+	if (len == 1)
+		*(u8*)buf = readb(hw->dpmbase + addr);
+	else
+		memcpy_fromio(buf, hw->dpmbase + addr, len);
+
+	return 0;
+}
+
+/* Write Absolute Adapter Memory.
+ * Transfer data from data buffer to adapter's memory. */
+EXPORT_SYMBOL(cycx_poke);
+int cycx_poke(struct cycx_hw *hw, u32 addr, void *buf, u32 len)
+{
+	if (len == 1)
+		writeb(*(u8*)buf, hw->dpmbase + addr);
+	else
+		memcpy_toio(hw->dpmbase + addr, buf, len);
+
+	return 0;
+}
+
+/* Hardware-Specific Functions */
+
+/* Load Aux Routines */
+/* Reset board hardware.
+   return 1 if memory exists at addr and 0 if not. */
+static int memory_exists(void __iomem *addr)
+{
+	int tries = 0;
+
+	for (; tries < 3 ; tries++) {
+		writew(TEST_PATTERN, addr + 0x10);
+
+		if (readw(addr + 0x10) == TEST_PATTERN)
+			if (readw(addr + 0x10) == TEST_PATTERN)
+				return 1;
+
+		delay_cycx(1);
+	}
+
+	return 0;
+}
+
+/* Load reset code. */
+static void reset_load(void __iomem *addr, u8 *buffer, u32 cnt)
+{
+	void __iomem *pt_code = addr + RESET_OFFSET;
+	u16 i; /*, j; */
+
+	for (i = 0 ; i < cnt ; i++) {
+/*		for (j = 0 ; j < 50 ; j++); Delay - FIXME busy waiting... */
+		writeb(*buffer++, pt_code++);
+	}
+}
+
+/* Load buffer using boot interface.
+ * o copy data from buffer to Cyclom-X memory
+ * o wait for reset code to copy it to right portion of memory */
+static int buffer_load(void __iomem *addr, u8 *buffer, u32 cnt)
+{
+	memcpy_toio(addr + DATA_OFFSET, buffer, cnt);
+	writew(GEN_BOOT_DAT, addr + CMD_OFFSET);
+
+	return wait_cyc(addr);
+}
+
+/* Set up entry point and kick start Cyclom-X CPU. */
+static void cycx_start(void __iomem *addr)
+{
+	/* put in 0x30 offset the jump instruction to the code entry point */
+	writeb(0xea, addr + 0x30);
+	writeb(0x00, addr + 0x31);
+	writeb(0xc4, addr + 0x32);
+	writeb(0x00, addr + 0x33);
+	writeb(0x00, addr + 0x34);
+
+	/* cmd to start executing code */
+	writew(GEN_START, addr + CMD_OFFSET);
+}
+
+/* Load and boot reset code. */
+static void cycx_reset_boot(void __iomem *addr, u8 *code, u32 len)
+{
+	void __iomem *pt_start = addr + START_OFFSET;
+
+	writeb(0xea, pt_start++); /* jmp to f000:3f00 */
+	writeb(0x00, pt_start++);
+	writeb(0xfc, pt_start++);
+	writeb(0x00, pt_start++);
+	writeb(0xf0, pt_start);
+	reset_load(addr, code, len);
+
+	/* 80186 was in hold, go */
+	writeb(0, addr + START_CPU);
+	delay_cycx(1);
+}
+
+/* Load data.bin file through boot (reset) interface. */
+static int cycx_data_boot(void __iomem *addr, u8 *code, u32 len)
+{
+	void __iomem *pt_boot_cmd = addr + CMD_OFFSET;
+	u32 i;
+
+	/* boot buffer lenght */
+	writew(CFM_LOAD_BUFSZ, pt_boot_cmd + sizeof(u16));
+	writew(GEN_DEFPAR, pt_boot_cmd);
+
+	if (wait_cyc(addr) < 0)
+		return -1;
+
+	writew(0, pt_boot_cmd + sizeof(u16));
+	writew(0x4000, pt_boot_cmd + 2 * sizeof(u16));
+	writew(GEN_SET_SEG, pt_boot_cmd);
+
+	if (wait_cyc(addr) < 0)
+		return -1;
+
+	for (i = 0 ; i < len ; i += CFM_LOAD_BUFSZ)
+		if (buffer_load(addr, code + i,
+				min_t(u32, CFM_LOAD_BUFSZ, (len - i))) < 0) {
+			printk(KERN_ERR "%s: Error !!\n", modname);
+			return -1;
+		}
+
+	return 0;
+}
+
+
+/* Load code.bin file through boot (reset) interface. */
+static int cycx_code_boot(void __iomem *addr, u8 *code, u32 len)
+{
+	void __iomem *pt_boot_cmd = addr + CMD_OFFSET;
+	u32 i;
+
+	/* boot buffer lenght */
+	writew(CFM_LOAD_BUFSZ, pt_boot_cmd + sizeof(u16));
+	writew(GEN_DEFPAR, pt_boot_cmd);
+
+	if (wait_cyc(addr) < 0)
+		return -1;
+
+	writew(0x0000, pt_boot_cmd + sizeof(u16));
+	writew(0xc400, pt_boot_cmd + 2 * sizeof(u16));
+	writew(GEN_SET_SEG, pt_boot_cmd);
+
+	if (wait_cyc(addr) < 0)
+		return -1;
+
+	for (i = 0 ; i < len ; i += CFM_LOAD_BUFSZ)
+		if (buffer_load(addr, code + i,
+				min_t(u32, CFM_LOAD_BUFSZ, (len - i)))) {
+			printk(KERN_ERR "%s: Error !!\n", modname);
+			return -1;
+		}
+
+	return 0;
+}
+
+/* Load adapter from the memory image of the CYCX firmware module.
+ * o verify firmware integrity and compatibility
+ * o start adapter up */
+static int load_cyc2x(struct cycx_hw *hw, struct cycx_firmware *cfm, u32 len)
+{
+	int i, j;
+	struct cycx_fw_header *img_hdr;
+	u8 *reset_image,
+	   *data_image,
+	   *code_image;
+	void __iomem *pt_cycld = hw->dpmbase + 0x400;
+	u16 cksum;
+
+	/* Announce */
+	printk(KERN_INFO "%s: firmware signature=\"%s\"\n", modname,
+							    cfm->signature);
+
+	/* Verify firmware signature */
+	if (strcmp(cfm->signature, CFM_SIGNATURE)) {
+		printk(KERN_ERR "%s:load_cyc2x: not Cyclom-2X firmware!\n",
+				modname);
+		return -EINVAL;
+	}
+
+	printk(KERN_INFO "%s: firmware version=%u\n", modname, cfm->version);
+
+	/* Verify firmware module format version */
+	if (cfm->version != CFM_VERSION) {
+		printk(KERN_ERR "%s:%s: firmware format %u rejected! "
+				"Expecting %u.\n",
+				modname, __FUNCTION__, cfm->version, CFM_VERSION);
+		return -EINVAL;
+	}
+
+	/* Verify firmware module length and checksum */
+	cksum = checksum((u8*)&cfm->info, sizeof(struct cycx_fw_info) +
+					  cfm->info.codesize);
+/*
+	FIXME cfm->info.codesize is off by 2
+	if (((len - sizeof(struct cycx_firmware) - 1) != cfm->info.codesize) ||
+*/
+	if (cksum != cfm->checksum) {
+		printk(KERN_ERR "%s:%s: firmware corrupted!\n",
+				modname, __FUNCTION__);
+		printk(KERN_ERR " cdsize = 0x%x (expected 0x%lx)\n",
+				len - (int)sizeof(struct cycx_firmware) - 1,
+				cfm->info.codesize);
+		printk(KERN_ERR " chksum = 0x%x (expected 0x%x)\n",
+				cksum, cfm->checksum);
+		return -EINVAL;
+	}
+
+	/* If everything is ok, set reset, data and code pointers */
+	img_hdr = (struct cycx_fw_header *)&cfm->image;
+#ifdef FIRMWARE_DEBUG
+	printk(KERN_INFO "%s:%s: image sizes\n", __FUNCTION__, modname);
+	printk(KERN_INFO " reset=%lu\n", img_hdr->reset_size);
+	printk(KERN_INFO "  data=%lu\n", img_hdr->data_size);
+	printk(KERN_INFO "  code=%lu\n", img_hdr->code_size);
+#endif
+	reset_image = ((u8 *)img_hdr) + sizeof(struct cycx_fw_header);
+	data_image = reset_image + img_hdr->reset_size;
+	code_image = data_image + img_hdr->data_size;
+
+	/*---- Start load ----*/
+	/* Announce */
+	printk(KERN_INFO "%s: loading firmware %s (ID=%u)...\n", modname,
+			 cfm->descr[0] ? cfm->descr : "unknown firmware",
+			 cfm->info.codeid);
+
+	for (i = 0 ; i < 5 ; i++) {
+		/* Reset Cyclom hardware */
+		if (!reset_cyc2x(hw->dpmbase)) {
+			printk(KERN_ERR "%s: dpm problem or board not found\n",
+					modname);
+			return -EINVAL;
+		}
+
+		/* Load reset.bin */
+		cycx_reset_boot(hw->dpmbase, reset_image, img_hdr->reset_size);
+		/* reset is waiting for boot */
+		writew(GEN_POWER_ON, pt_cycld);
+		delay_cycx(1);
+
+		for (j = 0 ; j < 3 ; j++)
+			if (!readw(pt_cycld))
+				goto reset_loaded;
+			else
+				delay_cycx(1);
+	}
+
+	printk(KERN_ERR "%s: reset not started.\n", modname);
+	return -EINVAL;
+
+reset_loaded:
+	/* Load data.bin */
+	if (cycx_data_boot(hw->dpmbase, data_image, img_hdr->data_size)) {
+		printk(KERN_ERR "%s: cannot load data file.\n", modname);
+		return -EINVAL;
+	}
+
+	/* Load code.bin */
+	if (cycx_code_boot(hw->dpmbase, code_image, img_hdr->code_size)) {
+		printk(KERN_ERR "%s: cannot load code file.\n", modname);
+		return -EINVAL;
+	}
+
+	/* Prepare boot-time configuration data */
+	cycx_bootcfg(hw);
+
+	/* kick-off CPU */
+	cycx_start(hw->dpmbase);
+
+	/* Arthur Ganzert's tip: wait a while after the firmware loading...
+	   seg abr 26 17:17:12 EST 1999 - acme */
+	delay_cycx(7);
+	printk(KERN_INFO "%s: firmware loaded!\n", modname);
+
+	/* enable interrupts */
+	cycx_inten(hw);
+
+	return 0;
+}
+
+/* Prepare boot-time firmware configuration data.
+ * o initialize configuration data area
+   From async.doc - V_3.4.0 - 07/18/1994
+   - As of now, only static buffers are available to the user.
+     So, the bit VD_RXDIRC must be set in 'valid'. That means that user
+     wants to use the static transmission and reception buffers. */
+static void cycx_bootcfg(struct cycx_hw *hw)
+{
+	/* use fixed buffers */
+	writeb(FIXED_BUFFERS, hw->dpmbase + CONF_OFFSET);
+}
+
+/* Detect Cyclom 2x adapter.
+ *	Following tests are used to detect Cyclom 2x adapter:
+ *       to be completed based on the tests done below
+ *	Return 1 if detected o.k. or 0 if failed.
+ *	Note:	This test is destructive! Adapter will be left in shutdown
+ *		state after the test. */
+static int detect_cyc2x(void __iomem *addr)
+{
+	reset_cyc2x(addr);
+
+	return memory_exists(addr);
+}
+
+/* Miscellaneous */
+/* Get option's index into the options list.
+ *	Return option's index (1 .. N) or zero if option is invalid. */
+static int get_option_index(long *optlist, long optval)
+{
+	int i = 1;
+
+	for (; i <= optlist[0]; ++i)
+		if (optlist[i] == optval)
+			return i;
+
+	return 0;
+}
+
+/* Reset adapter's CPU. */
+static int reset_cyc2x(void __iomem *addr)
+{
+	writeb(0, addr + RST_ENABLE);
+	delay_cycx(2);
+	writeb(0, addr + RST_DISABLE);
+	delay_cycx(2);
+
+	return memory_exists(addr);
+}
+
+/* Delay */
+static void delay_cycx(int sec)
+{
+	set_current_state(TASK_INTERRUPTIBLE);
+	schedule_timeout(sec * HZ);
+}
+
+/* Calculate 16-bit CRC using CCITT polynomial. */
+static u16 checksum(u8 *buf, u32 len)
+{
+	u16 crc = 0;
+	u16 mask, flag;
+
+	for (; len; --len, ++buf)
+		for (mask = 0x80; mask; mask >>= 1) {
+			flag = (crc & 0x8000);
+			crc <<= 1;
+			crc |= ((*buf & mask) ? 1 : 0);
+
+			if (flag)
+				crc ^= 0x1021;
+		}
+
+	return crc;
+}
+
+module_init(cycx_drv_init);
+module_exit(cycx_drv_cleanup);
+
+/* End */
diff --git a/drivers/net/wan/cycx_main.c b/drivers/net/wan/cycx_main.c
new file mode 100644
index 0000000..7b48064
--- /dev/null
+++ b/drivers/net/wan/cycx_main.c
@@ -0,0 +1,351 @@
+/*
+* cycx_main.c	Cyclades Cyclom 2X WAN Link Driver. Main module.
+*
+* Author:	Arnaldo Carvalho de Melo <acme@conectiva.com.br>
+*
+* Copyright:	(c) 1998-2003 Arnaldo Carvalho de Melo
+*
+* Based on sdlamain.c by Gene Kozin <genek@compuserve.com> &
+*			 Jaspreet Singh	<jaspreet@sangoma.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.
+* ============================================================================
+* Please look at the bitkeeper changelog (or any other scm tool that ends up
+* importing bitkeeper changelog or that replaces bitkeeper in the future as
+* main tool for linux development).
+* 
+* 2001/05/09	acme		Fix MODULE_DESC for debug, .bss nitpicks,
+* 				some cleanups
+* 2000/07/13	acme		remove useless #ifdef MODULE and crap
+*				#if KERNEL_VERSION > blah
+* 2000/07/06	acme		__exit at cyclomx_cleanup
+* 2000/04/02	acme		dprintk and cycx_debug
+* 				module_init/module_exit
+* 2000/01/21	acme		rename cyclomx_open to cyclomx_mod_inc_use_count
+*				and cyclomx_close to cyclomx_mod_dec_use_count
+* 2000/01/08	acme		cleanup
+* 1999/11/06	acme		cycx_down back to life (it needs to be
+*				called to iounmap the dpmbase)
+* 1999/08/09	acme		removed references to enable_tx_int
+*				use spinlocks instead of cli/sti in
+*				cyclomx_set_state
+* 1999/05/19	acme		works directly linked into the kernel
+*				init_waitqueue_head for 2.3.* kernel
+* 1999/05/18	acme		major cleanup (polling not needed), etc
+* 1998/08/28	acme		minor cleanup (ioctls for firmware deleted)
+*				queue_task activated
+* 1998/08/08	acme		Initial version.
+*/
+
+#include <linux/config.h>	/* OS configuration options */
+#include <linux/stddef.h>	/* offsetof(), etc. */
+#include <linux/errno.h>	/* return codes */
+#include <linux/string.h>	/* inline memset(), etc. */
+#include <linux/slab.h>		/* kmalloc(), kfree() */
+#include <linux/kernel.h>	/* printk(), and other useful stuff */
+#include <linux/module.h>	/* support for loadable modules */
+#include <linux/ioport.h>	/* request_region(), release_region() */
+#include <linux/wanrouter.h>	/* WAN router definitions */
+#include <linux/cyclomx.h>	/* cyclomx common user API definitions */
+#include <linux/init.h>         /* __init (when not using as a module) */
+
+unsigned int cycx_debug;
+
+MODULE_AUTHOR("Arnaldo Carvalho de Melo");
+MODULE_DESCRIPTION("Cyclom 2X Sync Card Driver.");
+MODULE_LICENSE("GPL");
+module_param(cycx_debug, int, 0);
+MODULE_PARM_DESC(cycx_debug, "cyclomx debug level");
+
+/* Defines & Macros */
+
+#define	CYCX_DRV_VERSION	0	/* version number */
+#define	CYCX_DRV_RELEASE	11	/* release (minor version) number */
+#define	CYCX_MAX_CARDS		1	/* max number of adapters */
+
+#define	CONFIG_CYCX_CARDS 1
+
+/* Function Prototypes */
+
+/* WAN link driver entry points */
+static int cycx_wan_setup(struct wan_device *wandev, wandev_conf_t *conf);
+static int cycx_wan_shutdown(struct wan_device *wandev);
+
+/* Miscellaneous functions */
+static irqreturn_t cycx_isr(int irq, void *dev_id, struct pt_regs *regs);
+
+/* Global Data
+ * Note: All data must be explicitly initialized!!!
+ */
+
+/* private data */
+static char cycx_drvname[] = "cyclomx";
+static char cycx_fullname[] = "CYCLOM 2X(tm) Sync Card Driver";
+static char cycx_copyright[] = "(c) 1998-2003 Arnaldo Carvalho de Melo "
+			  "<acme@conectiva.com.br>";
+static int cycx_ncards = CONFIG_CYCX_CARDS;
+static struct cycx_device *cycx_card_array;	/* adapter data space */
+
+/* Kernel Loadable Module Entry Points */
+
+/*
+ * Module 'insert' entry point.
+ * o print announcement
+ * o allocate adapter data space
+ * o initialize static data
+ * o register all cards with WAN router
+ * o calibrate Cyclom 2X shared memory access delay.
+ *
+ * Return:	0	Ok
+ *		< 0	error.
+ * Context:	process
+ */
+int __init cycx_init(void)
+{
+	int cnt, err = -ENOMEM;
+
+	printk(KERN_INFO "%s v%u.%u %s\n",
+		cycx_fullname, CYCX_DRV_VERSION, CYCX_DRV_RELEASE,
+		cycx_copyright);
+
+	/* Verify number of cards and allocate adapter data space */
+	cycx_ncards = min_t(int, cycx_ncards, CYCX_MAX_CARDS);
+	cycx_ncards = max_t(int, cycx_ncards, 1);
+	cycx_card_array = kmalloc(sizeof(struct cycx_device) * cycx_ncards,
+				  GFP_KERNEL);
+	if (!cycx_card_array)
+		goto out;
+
+	memset(cycx_card_array, 0, sizeof(struct cycx_device) * cycx_ncards);
+
+	/* Register adapters with WAN router */
+	for (cnt = 0; cnt < cycx_ncards; ++cnt) {
+		struct cycx_device *card = &cycx_card_array[cnt];
+		struct wan_device *wandev = &card->wandev;
+
+		sprintf(card->devname, "%s%d", cycx_drvname, cnt + 1);
+		wandev->magic    = ROUTER_MAGIC;
+		wandev->name     = card->devname;
+		wandev->private  = card;
+		wandev->setup    = cycx_wan_setup;
+		wandev->shutdown = cycx_wan_shutdown;
+		err = register_wan_device(wandev);
+
+		if (err) {
+			printk(KERN_ERR "%s: %s registration failed with "
+					"error %d!\n",
+					cycx_drvname, card->devname, err);
+			break;
+		}
+	}
+
+	err = -ENODEV;
+	if (!cnt) {
+		kfree(cycx_card_array);
+		goto out;
+	}
+	err = 0;
+	cycx_ncards = cnt;	/* adjust actual number of cards */
+out:	return err;
+}
+
+/*
+ * Module 'remove' entry point.
+ * o unregister all adapters from the WAN router
+ * o release all remaining system resources
+ */
+static void __exit cycx_exit(void)
+{
+	int i = 0;
+
+	for (; i < cycx_ncards; ++i) {
+		struct cycx_device *card = &cycx_card_array[i];
+		unregister_wan_device(card->devname);
+	}
+
+	kfree(cycx_card_array);
+}
+
+/* WAN Device Driver Entry Points */
+/*
+ * Setup/configure WAN link driver.
+ * o check adapter state
+ * o make sure firmware is present in configuration
+ * o allocate interrupt vector
+ * o setup Cyclom 2X hardware
+ * o call appropriate routine to perform protocol-specific initialization
+ *
+ * This function is called when router handles ROUTER_SETUP IOCTL. The
+ * configuration structure is in kernel memory (including extended data, if
+ * any).
+ */
+static int cycx_wan_setup(struct wan_device *wandev, wandev_conf_t *conf)
+{
+	int rc = -EFAULT;
+	struct cycx_device *card;
+	int irq;
+
+	/* Sanity checks */
+
+	if (!wandev || !wandev->private || !conf)
+		goto out;
+
+	card = wandev->private;
+	rc = -EBUSY;
+	if (wandev->state != WAN_UNCONFIGURED)
+		goto out;
+
+	rc = -EINVAL;
+	if (!conf->data_size || !conf->data) {
+		printk(KERN_ERR "%s: firmware not found in configuration "
+				"data!\n", wandev->name);
+		goto out;
+	}
+
+	if (conf->irq <= 0) {
+		printk(KERN_ERR "%s: can't configure without IRQ!\n",
+				wandev->name);
+		goto out;
+	}
+
+	/* Allocate IRQ */
+	irq = conf->irq == 2 ? 9 : conf->irq;	/* IRQ2 -> IRQ9 */
+
+	if (request_irq(irq, cycx_isr, 0, wandev->name, card)) {
+		printk(KERN_ERR "%s: can't reserve IRQ %d!\n",
+				wandev->name, irq);
+		goto out;
+	}
+
+	/* Configure hardware, load firmware, etc. */
+	memset(&card->hw, 0, sizeof(card->hw));
+	card->hw.irq	 = irq;
+	card->hw.dpmsize = CYCX_WINDOWSIZE;
+	card->hw.fwid	 = CFID_X25_2X;
+	spin_lock_init(&card->lock);
+	init_waitqueue_head(&card->wait_stats);
+
+	rc = cycx_setup(&card->hw, conf->data, conf->data_size, conf->maddr);
+	if (rc)
+		goto out_irq;
+
+	/* Initialize WAN device data space */
+	wandev->irq       = irq;
+	wandev->dma       = wandev->ioport = 0;
+	wandev->maddr     = (unsigned long)card->hw.dpmbase;
+	wandev->msize     = card->hw.dpmsize;
+	wandev->hw_opt[2] = 0;
+	wandev->hw_opt[3] = card->hw.fwid;
+
+	/* Protocol-specific initialization */
+	switch (card->hw.fwid) {
+#ifdef CONFIG_CYCLOMX_X25
+	case CFID_X25_2X:
+		rc = cycx_x25_wan_init(card, conf);
+		break;
+#endif
+	default:
+		printk(KERN_ERR "%s: this firmware is not supported!\n",
+				wandev->name);
+		rc = -EINVAL;
+	}
+
+	if (rc) {
+		cycx_down(&card->hw);
+		goto out_irq;
+	}
+
+	rc = 0;
+out:
+	return rc;
+out_irq:
+	free_irq(irq, card);
+	goto out;
+}
+
+/*
+ * Shut down WAN link driver.
+ * o shut down adapter hardware
+ * o release system resources.
+ *
+ * This function is called by the router when device is being unregistered or
+ * when it handles ROUTER_DOWN IOCTL.
+ */
+static int cycx_wan_shutdown(struct wan_device *wandev)
+{
+	int ret = -EFAULT;
+	struct cycx_device *card;
+
+	/* sanity checks */
+	if (!wandev || !wandev->private)
+		goto out;
+
+	ret = 0;
+	if (wandev->state == WAN_UNCONFIGURED)
+		goto out;
+
+	card = wandev->private;
+	wandev->state = WAN_UNCONFIGURED;
+	cycx_down(&card->hw);
+	printk(KERN_INFO "%s: irq %d being freed!\n", wandev->name,
+			wandev->irq);
+	free_irq(wandev->irq, card);
+out:	return ret;
+}
+
+/* Miscellaneous */
+/*
+ * Cyclom 2X Interrupt Service Routine.
+ * o acknowledge Cyclom 2X hardware interrupt.
+ * o call protocol-specific interrupt service routine, if any.
+ */
+static irqreturn_t cycx_isr(int irq, void *dev_id, struct pt_regs *regs)
+{
+	struct cycx_device *card = (struct cycx_device *)dev_id;
+
+	if (!card || card->wandev.state == WAN_UNCONFIGURED)
+		goto out;
+
+	if (card->in_isr) {
+		printk(KERN_WARNING "%s: interrupt re-entrancy on IRQ %d!\n",
+				    card->devname, card->wandev.irq);
+		goto out;
+	}
+
+	if (card->isr)
+		card->isr(card);
+	return IRQ_HANDLED;
+out:
+	return IRQ_NONE;
+}
+
+/* Set WAN device state.  */
+void cycx_set_state(struct cycx_device *card, int state)
+{
+	unsigned long flags;
+	char *string_state = NULL;
+
+	spin_lock_irqsave(&card->lock, flags);
+
+	if (card->wandev.state != state) {
+		switch (state) {
+		case WAN_CONNECTED:
+			string_state = "connected!";
+			break;
+		case WAN_DISCONNECTED:
+			string_state = "disconnected!";
+			break;
+		}
+		printk(KERN_INFO "%s: link %s\n", card->devname, string_state);
+		card->wandev.state = state;
+	}
+
+	card->state_tick = jiffies;
+	spin_unlock_irqrestore(&card->lock, flags);
+}
+
+module_init(cycx_init);
+module_exit(cycx_exit);
diff --git a/drivers/net/wan/cycx_x25.c b/drivers/net/wan/cycx_x25.c
new file mode 100644
index 0000000..5b48cd8
--- /dev/null
+++ b/drivers/net/wan/cycx_x25.c
@@ -0,0 +1,1609 @@
+/*
+* cycx_x25.c	Cyclom 2X WAN Link Driver.  X.25 module.
+*
+* Author:	Arnaldo Carvalho de Melo <acme@conectiva.com.br>
+*
+* Copyright:	(c) 1998-2003 Arnaldo Carvalho de Melo
+*
+* Based on sdla_x25.c by Gene Kozin <genek@compuserve.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.
+* ============================================================================
+* 2001/01/12	acme		use dev_kfree_skb_irq on interrupt context
+* 2000/04/02	acme		dprintk, cycx_debug
+* 				fixed the bug introduced in get_dev_by_lcn and
+* 				get_dev_by_dte_addr by the anonymous hacker
+* 				that converted this driver to softnet
+* 2000/01/08	acme		cleanup
+* 1999/10/27	acme		use ARPHRD_HWX25 so that the X.25 stack know
+*				that we have a X.25 stack implemented in
+*				firmware onboard
+* 1999/10/18	acme		support for X.25 sockets in if_send,
+*				beware: socket(AF_X25...) IS WORK IN PROGRESS,
+*				TCP/IP over X.25 via wanrouter not affected,
+*				working.
+* 1999/10/09	acme		chan_disc renamed to chan_disconnect,
+* 				began adding support for X.25 sockets:
+* 				conf->protocol in new_if
+* 1999/10/05	acme		fixed return E... to return -E...
+* 1999/08/10	acme		serialized access to the card thru a spinlock
+*				in x25_exec
+* 1999/08/09	acme		removed per channel spinlocks
+*				removed references to enable_tx_int
+* 1999/05/28	acme		fixed nibble_to_byte, ackvc now properly treated
+*				if_send simplified
+* 1999/05/25	acme		fixed t1, t2, t21 & t23 configuration
+*				use spinlocks instead of cli/sti in some points
+* 1999/05/24	acme		finished the x25_get_stat function
+* 1999/05/23	acme		dev->type = ARPHRD_X25 (tcpdump only works,
+*				AFAIT, with ARPHRD_ETHER). This seems to be
+*				needed to use socket(AF_X25)...
+*				Now the config file must specify a peer media
+*				address for svc channels over a crossover cable.
+*				Removed hold_timeout from x25_channel_t,
+*				not used.
+*				A little enhancement in the DEBUG processing
+* 1999/05/22	acme		go to DISCONNECTED in disconnect_confirm_intr,
+*				instead of chan_disc.
+* 1999/05/16	marcelo		fixed timer initialization in SVCs
+* 1999/01/05	acme		x25_configure now get (most of) all
+*				parameters...
+* 1999/01/05	acme		pktlen now (correctly) uses log2 (value
+*				configured)
+* 1999/01/03	acme		judicious use of data types (u8, u16, u32, etc)
+* 1999/01/03	acme		cyx_isr: reset dpmbase to acknowledge
+*				indication (interrupt from cyclom 2x)
+* 1999/01/02	acme		cyx_isr: first hackings...
+* 1999/01/0203  acme 		when initializing an array don't give less
+*				elements than declared...
+* 				example: char send_cmd[6] = "?\xFF\x10";
+*          			you'll gonna lose a couple hours, 'cause your
+*				brain won't admit that there's an error in the
+*				above declaration...  the side effect is that
+*				memset is put into the unresolved symbols
+*				instead of using the inline memset functions...
+* 1999/01/02    acme 		began chan_connect, chan_send, x25_send
+* 1998/12/31	acme		x25_configure
+*				this code can be compiled as non module
+* 1998/12/27	acme		code cleanup
+*				IPX code wiped out! let's decrease code
+*				complexity for now, remember: I'm learning! :)
+*                               bps_to_speed_code OK
+* 1998/12/26	acme		Minimal debug code cleanup
+* 1998/08/08	acme		Initial version.
+*/
+
+#define CYCLOMX_X25_DEBUG 1
+
+#include <linux/errno.h>	/* return codes */
+#include <linux/if_arp.h>       /* ARPHRD_HWX25 */
+#include <linux/kernel.h>	/* printk(), and other useful stuff */
+#include <linux/module.h>
+#include <linux/string.h>	/* inline memset(), etc. */
+#include <linux/slab.h>		/* kmalloc(), kfree() */
+#include <linux/stddef.h>	/* offsetof(), etc. */
+#include <linux/wanrouter.h>	/* WAN router definitions */
+
+#include <asm/byteorder.h>	/* htons(), etc. */
+
+#include <linux/cyclomx.h>	/* Cyclom 2X common user API definitions */
+#include <linux/cycx_x25.h>	/* X.25 firmware API definitions */
+
+#include <net/x25device.h>
+
+/* Defines & Macros */
+#define CYCX_X25_MAX_CMD_RETRY 5
+#define CYCX_X25_CHAN_MTU 2048	/* unfragmented logical channel MTU */
+
+/* Data Structures */
+/* This is an extension of the 'struct net_device' we create for each network
+   interface to keep the rest of X.25 channel-specific data. */
+struct cycx_x25_channel {
+	/* This member must be first. */
+	struct net_device *slave;	/* WAN slave */
+
+	char name[WAN_IFNAME_SZ+1];	/* interface name, ASCIIZ */
+	char addr[WAN_ADDRESS_SZ+1];	/* media address, ASCIIZ */
+	char *local_addr;		/* local media address, ASCIIZ -
+					   svc thru crossover cable */
+	s16 lcn;			/* logical channel number/conn.req.key*/
+	u8 link;
+	struct timer_list timer;	/* timer used for svc channel disc. */
+	u16 protocol;			/* ethertype, 0 - multiplexed */
+	u8 svc;				/* 0 - permanent, 1 - switched */
+	u8 state;			/* channel state */
+	u8 drop_sequence;		/* mark sequence for dropping */
+	u32 idle_tmout;			/* sec, before disconnecting */
+	struct sk_buff *rx_skb;		/* receive socket buffer */
+	struct cycx_device *card;	/* -> owner */
+	struct net_device_stats ifstats;/* interface statistics */
+};
+
+/* Function Prototypes */
+/* WAN link driver entry points. These are called by the WAN router module. */
+static int cycx_wan_update(struct wan_device *wandev),
+	   cycx_wan_new_if(struct wan_device *wandev, struct net_device *dev,
+			   wanif_conf_t *conf),
+	   cycx_wan_del_if(struct wan_device *wandev, struct net_device *dev);
+
+/* Network device interface */
+static int cycx_netdevice_init(struct net_device *dev),
+	   cycx_netdevice_open(struct net_device *dev),
+	   cycx_netdevice_stop(struct net_device *dev),
+	   cycx_netdevice_hard_header(struct sk_buff *skb,
+				     struct net_device *dev, u16 type,
+				     void *daddr, void *saddr, unsigned len),
+	   cycx_netdevice_rebuild_header(struct sk_buff *skb),
+	   cycx_netdevice_hard_start_xmit(struct sk_buff *skb,
+					  struct net_device *dev);
+
+static struct net_device_stats *
+			cycx_netdevice_get_stats(struct net_device *dev);
+
+/* Interrupt handlers */
+static void cycx_x25_irq_handler(struct cycx_device *card),
+	    cycx_x25_irq_tx(struct cycx_device *card, struct cycx_x25_cmd *cmd),
+	    cycx_x25_irq_rx(struct cycx_device *card, struct cycx_x25_cmd *cmd),
+	    cycx_x25_irq_log(struct cycx_device *card,
+			     struct cycx_x25_cmd *cmd),
+	    cycx_x25_irq_stat(struct cycx_device *card,
+			      struct cycx_x25_cmd *cmd),
+	    cycx_x25_irq_connect_confirm(struct cycx_device *card,
+					 struct cycx_x25_cmd *cmd),
+	    cycx_x25_irq_disconnect_confirm(struct cycx_device *card,
+					    struct cycx_x25_cmd *cmd),
+	    cycx_x25_irq_connect(struct cycx_device *card,
+				 struct cycx_x25_cmd *cmd),
+	    cycx_x25_irq_disconnect(struct cycx_device *card,
+				    struct cycx_x25_cmd *cmd),
+	    cycx_x25_irq_spurious(struct cycx_device *card,
+				  struct cycx_x25_cmd *cmd);
+
+/* X.25 firmware interface functions */
+static int cycx_x25_configure(struct cycx_device *card,
+			      struct cycx_x25_config *conf),
+	   cycx_x25_get_stats(struct cycx_device *card),
+	   cycx_x25_send(struct cycx_device *card, u8 link, u8 lcn, u8 bitm,
+			 int len, void *buf),
+	   cycx_x25_connect_response(struct cycx_device *card,
+				struct cycx_x25_channel *chan),
+	   cycx_x25_disconnect_response(struct cycx_device *card, u8 link,
+			   		u8 lcn);
+
+/* channel functions */
+static int cycx_x25_chan_connect(struct net_device *dev),
+	   cycx_x25_chan_send(struct net_device *dev, struct sk_buff *skb);
+
+static void cycx_x25_chan_disconnect(struct net_device *dev),
+	    cycx_x25_chan_send_event(struct net_device *dev, u8 event);
+
+/* Miscellaneous functions */
+static void cycx_x25_set_chan_state(struct net_device *dev, u8 state),
+	    cycx_x25_chan_timer(unsigned long d);
+
+static void nibble_to_byte(u8 *s, u8 *d, u8 len, u8 nibble),
+	    reset_timer(struct net_device *dev);
+
+static u8 bps_to_speed_code(u32 bps);
+static u8 cycx_log2(u32 n);
+
+static unsigned dec_to_uint(u8 *str, int len);
+
+static struct net_device *cycx_x25_get_dev_by_lcn(struct wan_device *wandev,
+						  s16 lcn);
+static struct net_device *
+	cycx_x25_get_dev_by_dte_addr(struct wan_device *wandev, char *dte);
+
+#ifdef CYCLOMX_X25_DEBUG
+static void hex_dump(char *msg, unsigned char *p, int len);
+static void cycx_x25_dump_config(struct cycx_x25_config *conf);
+static void cycx_x25_dump_stats(struct cycx_x25_stats *stats);
+static void cycx_x25_dump_devs(struct wan_device *wandev);
+#else
+#define hex_dump(msg, p, len)
+#define cycx_x25_dump_config(conf)
+#define cycx_x25_dump_stats(stats)
+#define cycx_x25_dump_devs(wandev)
+#endif
+/* Public Functions */
+
+/* X.25 Protocol Initialization routine.
+ *
+ * This routine is called by the main Cyclom 2X module during setup.  At this
+ * point adapter is completely initialized and X.25 firmware is running.
+ *  o configure adapter
+ *  o initialize protocol-specific fields of the adapter data space.
+ *
+ * Return:	0	o.k.
+ *		< 0	failure.  */
+int cycx_x25_wan_init(struct cycx_device *card, wandev_conf_t *conf)
+{
+	struct cycx_x25_config cfg;
+
+	/* Verify configuration ID */
+	if (conf->config_id != WANCONFIG_X25) {
+		printk(KERN_INFO "%s: invalid configuration ID %u!\n",
+				 card->devname, conf->config_id);
+		return -EINVAL;
+	}
+
+	/* Initialize protocol-specific fields */
+	card->mbox  = card->hw.dpmbase + X25_MBOX_OFFS;
+	card->u.x.connection_keys = 0;
+	spin_lock_init(&card->u.x.lock);
+
+	/* Configure adapter. Here we set reasonable defaults, then parse
+	 * device configuration structure and set configuration options.
+	 * Most configuration options are verified and corrected (if
+	 * necessary) since we can't rely on the adapter to do so and don't
+	 * want it to fail either. */
+	memset(&cfg, 0, sizeof(cfg));
+	cfg.link = 0;
+	cfg.clock = conf->clocking == WANOPT_EXTERNAL ? 8 : 55;
+	cfg.speed = bps_to_speed_code(conf->bps);
+	cfg.n3win = 7;
+	cfg.n2win = 2;
+	cfg.n2 = 5;
+	cfg.nvc = 1;
+	cfg.npvc = 1;
+	cfg.flags = 0x02; /* default = V35 */
+	cfg.t1 = 10;   /* line carrier timeout */
+	cfg.t2 = 29;   /* tx timeout */
+	cfg.t21 = 180; /* CALL timeout */
+	cfg.t23 = 180; /* CLEAR timeout */
+
+	/* adjust MTU */
+	if (!conf->mtu || conf->mtu >= 512)
+		card->wandev.mtu = 512;
+	else if (conf->mtu >= 256)
+		card->wandev.mtu = 256;
+	else if (conf->mtu >= 128)
+		card->wandev.mtu = 128;
+	else
+		card->wandev.mtu = 64;
+
+	cfg.pktlen = cycx_log2(card->wandev.mtu);
+
+	if (conf->station == WANOPT_DTE) {
+		cfg.locaddr = 3; /* DTE */
+		cfg.remaddr = 1; /* DCE */
+	} else {
+		cfg.locaddr = 1; /* DCE */
+		cfg.remaddr = 3; /* DTE */
+	}
+
+	if (conf->interface == WANOPT_RS232)
+	        cfg.flags = 0;      /* FIXME just reset the 2nd bit */
+
+	if (conf->u.x25.hi_pvc) {
+		card->u.x.hi_pvc = min_t(unsigned int, conf->u.x25.hi_pvc, 4095);
+		card->u.x.lo_pvc = min_t(unsigned int, conf->u.x25.lo_pvc, card->u.x.hi_pvc);
+	}
+
+	if (conf->u.x25.hi_svc) {
+		card->u.x.hi_svc = min_t(unsigned int, conf->u.x25.hi_svc, 4095);
+		card->u.x.lo_svc = min_t(unsigned int, conf->u.x25.lo_svc, card->u.x.hi_svc);
+	}
+
+	if (card->u.x.lo_pvc == 255)
+		cfg.npvc = 0;
+	else
+		cfg.npvc = card->u.x.hi_pvc - card->u.x.lo_pvc + 1;
+
+	cfg.nvc = card->u.x.hi_svc - card->u.x.lo_svc + 1 + cfg.npvc;
+
+	if (conf->u.x25.hdlc_window)
+		cfg.n2win = min_t(unsigned int, conf->u.x25.hdlc_window, 7);
+
+	if (conf->u.x25.pkt_window)
+		cfg.n3win = min_t(unsigned int, conf->u.x25.pkt_window, 7);
+
+	if (conf->u.x25.t1)
+		cfg.t1 = min_t(unsigned int, conf->u.x25.t1, 30);
+
+	if (conf->u.x25.t2)
+		cfg.t2 = min_t(unsigned int, conf->u.x25.t2, 30);
+
+	if (conf->u.x25.t11_t21)
+		cfg.t21 = min_t(unsigned int, conf->u.x25.t11_t21, 30);
+
+	if (conf->u.x25.t13_t23)
+		cfg.t23 = min_t(unsigned int, conf->u.x25.t13_t23, 30);
+
+	if (conf->u.x25.n2)
+		cfg.n2 = min_t(unsigned int, conf->u.x25.n2, 30);
+
+	/* initialize adapter */
+	if (cycx_x25_configure(card, &cfg))
+		return -EIO;
+
+	/* Initialize protocol-specific fields of adapter data space */
+	card->wandev.bps	= conf->bps;
+	card->wandev.interface	= conf->interface;
+	card->wandev.clocking	= conf->clocking;
+	card->wandev.station	= conf->station;
+	card->isr		= cycx_x25_irq_handler;
+	card->exec		= NULL;
+	card->wandev.update	= cycx_wan_update;
+	card->wandev.new_if	= cycx_wan_new_if;
+	card->wandev.del_if	= cycx_wan_del_if;
+	card->wandev.state	= WAN_DISCONNECTED;
+
+	return 0;
+}
+
+/* WAN Device Driver Entry Points */
+/* Update device status & statistics. */
+static int cycx_wan_update(struct wan_device *wandev)
+{
+	/* sanity checks */
+	if (!wandev || !wandev->private)
+		return -EFAULT;
+
+	if (wandev->state == WAN_UNCONFIGURED)
+		return -ENODEV;
+
+	cycx_x25_get_stats(wandev->private);
+
+	return 0;
+}
+
+/* Create new logical channel.
+ * This routine is called by the router when ROUTER_IFNEW IOCTL is being
+ * handled.
+ * o parse media- and hardware-specific configuration
+ * o make sure that a new channel can be created
+ * o allocate resources, if necessary
+ * o prepare network device structure for registration.
+ *
+ * Return:	0	o.k.
+ *		< 0	failure (channel will not be created) */
+static int cycx_wan_new_if(struct wan_device *wandev, struct net_device *dev,
+			   wanif_conf_t *conf)
+{
+	struct cycx_device *card = wandev->private;
+	struct cycx_x25_channel *chan;
+	int err = 0;
+
+	if (!conf->name[0] || strlen(conf->name) > WAN_IFNAME_SZ) {
+		printk(KERN_INFO "%s: invalid interface name!\n",
+		       card->devname);
+		return -EINVAL;
+	}
+
+	/* allocate and initialize private data */
+	chan = kmalloc(sizeof(struct cycx_x25_channel), GFP_KERNEL);
+	if (!chan)
+		return -ENOMEM;
+
+	memset(chan, 0, sizeof(*chan));
+	strcpy(chan->name, conf->name);
+	chan->card = card;
+	chan->link = conf->port;
+	chan->protocol = conf->protocol ? ETH_P_X25 : ETH_P_IP;
+	chan->rx_skb = NULL;
+	/* only used in svc connected thru crossover cable */
+	chan->local_addr = NULL;
+
+	if (conf->addr[0] == '@') {	/* SVC */
+		int len = strlen(conf->local_addr);
+
+		if (len) {
+			if (len > WAN_ADDRESS_SZ) {
+				printk(KERN_ERR "%s: %s local addr too long!\n",
+						wandev->name, chan->name);
+				kfree(chan);
+				return -EINVAL;
+			} else {
+				chan->local_addr = kmalloc(len + 1, GFP_KERNEL);
+
+				if (!chan->local_addr) {
+					kfree(chan);
+					return -ENOMEM;
+				}
+			}
+
+			strncpy(chan->local_addr, conf->local_addr,
+				WAN_ADDRESS_SZ);
+		}
+
+		chan->svc = 1;
+		strncpy(chan->addr, &conf->addr[1], WAN_ADDRESS_SZ);
+		init_timer(&chan->timer);
+		chan->timer.function	= cycx_x25_chan_timer;
+		chan->timer.data	= (unsigned long)dev;
+
+		/* Set channel timeouts (default if not specified) */
+		chan->idle_tmout = conf->idle_timeout ? conf->idle_timeout : 90;
+	} else if (is_digit(conf->addr[0])) {	/* PVC */
+		s16 lcn = dec_to_uint(conf->addr, 0);
+
+		if (lcn >= card->u.x.lo_pvc && lcn <= card->u.x.hi_pvc)
+			chan->lcn = lcn;
+		else {
+			printk(KERN_ERR
+				"%s: PVC %u is out of range on interface %s!\n",
+				wandev->name, lcn, chan->name);
+			err = -EINVAL;
+		}
+	} else {
+		printk(KERN_ERR "%s: invalid media address on interface %s!\n",
+				wandev->name, chan->name);
+		err = -EINVAL;
+	}
+
+	if (err) {
+		if (chan->local_addr)
+			kfree(chan->local_addr);
+
+		kfree(chan);
+		return err;
+	}
+
+	/* prepare network device data space for registration */
+	strcpy(dev->name, chan->name);
+	dev->init = cycx_netdevice_init;
+	dev->priv = chan;
+
+	return 0;
+}
+
+/* Delete logical channel. */
+static int cycx_wan_del_if(struct wan_device *wandev, struct net_device *dev)
+{
+	if (dev->priv) {
+		struct cycx_x25_channel *chan = dev->priv;
+
+		if (chan->svc) {
+			if (chan->local_addr)
+				kfree(chan->local_addr);
+
+			if (chan->state == WAN_CONNECTED)
+				del_timer(&chan->timer);
+		}
+
+		kfree(chan);
+		dev->priv = NULL;
+	}
+
+	return 0;
+}
+
+/* Network Device Interface */
+/* Initialize Linux network interface.
+ *
+ * This routine is called only once for each interface, during Linux network
+ * interface registration.  Returning anything but zero will fail interface
+ * registration. */
+static int cycx_netdevice_init(struct net_device *dev)
+{
+	struct cycx_x25_channel *chan = dev->priv;
+	struct cycx_device *card = chan->card;
+	struct wan_device *wandev = &card->wandev;
+
+	/* Initialize device driver entry points */
+	dev->open		= cycx_netdevice_open;
+	dev->stop		= cycx_netdevice_stop;
+	dev->hard_header	= cycx_netdevice_hard_header;
+	dev->rebuild_header	= cycx_netdevice_rebuild_header;
+	dev->hard_start_xmit	= cycx_netdevice_hard_start_xmit;
+	dev->get_stats		= cycx_netdevice_get_stats;
+
+	/* Initialize media-specific parameters */
+	dev->mtu		= CYCX_X25_CHAN_MTU;
+	dev->type		= ARPHRD_HWX25;	/* ARP h/w type */
+	dev->hard_header_len	= 0;		/* media header length */
+	dev->addr_len		= 0;		/* hardware address length */
+
+	if (!chan->svc)
+		*(u16*)dev->dev_addr = htons(chan->lcn);
+
+	/* Initialize hardware parameters (just for reference) */
+	dev->irq		= wandev->irq;
+	dev->dma		= wandev->dma;
+	dev->base_addr		= wandev->ioport;
+	dev->mem_start		= (unsigned long)wandev->maddr;
+	dev->mem_end		= (unsigned long)(wandev->maddr +
+						  wandev->msize - 1);
+	dev->flags		|= IFF_NOARP;
+
+	/* Set transmit buffer queue length */
+	dev->tx_queue_len	= 10;
+	SET_MODULE_OWNER(dev);
+
+	/* Initialize socket buffers */
+	cycx_x25_set_chan_state(dev, WAN_DISCONNECTED);
+
+	return 0;
+}
+
+/* Open network interface.
+ * o prevent module from unloading by incrementing use count
+ * o if link is disconnected then initiate connection
+ *
+ * Return 0 if O.k. or errno.  */
+static int cycx_netdevice_open(struct net_device *dev)
+{
+	if (netif_running(dev))
+		return -EBUSY; /* only one open is allowed */
+
+	netif_start_queue(dev);
+	return 0;
+}
+
+/* Close network interface.
+ * o reset flags.
+ * o if there's no more open channels then disconnect physical link. */
+static int cycx_netdevice_stop(struct net_device *dev)
+{
+	struct cycx_x25_channel *chan = dev->priv;
+
+	netif_stop_queue(dev);
+
+	if (chan->state == WAN_CONNECTED || chan->state == WAN_CONNECTING)
+		cycx_x25_chan_disconnect(dev);
+
+	return 0;
+}
+
+/* Build media header.
+ * o encapsulate packet according to encapsulation type.
+ *
+ * The trick here is to put packet type (Ethertype) into 'protocol' field of
+ * the socket buffer, so that we don't forget it.  If encapsulation fails,
+ * set skb->protocol to 0 and discard packet later.
+ *
+ * Return:	media header length. */
+static int cycx_netdevice_hard_header(struct sk_buff *skb,
+				      struct net_device *dev, u16 type,
+				      void *daddr, void *saddr, unsigned len)
+{
+	skb->protocol = type;
+
+	return dev->hard_header_len;
+}
+
+/* * Re-build media header.
+ * Return:	1	physical address resolved.
+ *		0	physical address not resolved */
+static int cycx_netdevice_rebuild_header(struct sk_buff *skb)
+{
+	return 1;
+}
+
+/* Send a packet on a network interface.
+ * o set busy flag (marks start of the transmission).
+ * o check link state. If link is not up, then drop the packet.
+ * o check channel status. If it's down then initiate a call.
+ * o pass a packet to corresponding WAN device.
+ * o free socket buffer
+ *
+ * Return:	0	complete (socket buffer must be freed)
+ *		non-0	packet may be re-transmitted (tbusy must be set)
+ *
+ * Notes:
+ * 1. This routine is called either by the protocol stack or by the "net
+ *    bottom half" (with interrupts enabled).
+ * 2. Setting tbusy flag will inhibit further transmit requests from the
+ *    protocol stack and can be used for flow control with protocol layer. */
+static int cycx_netdevice_hard_start_xmit(struct sk_buff *skb,
+					  struct net_device *dev)
+{
+	struct cycx_x25_channel *chan = dev->priv;
+	struct cycx_device *card = chan->card;
+
+	if (!chan->svc)
+		chan->protocol = skb->protocol;
+
+	if (card->wandev.state != WAN_CONNECTED)
+		++chan->ifstats.tx_dropped;
+	else if (chan->svc && chan->protocol &&
+		 chan->protocol != skb->protocol) {
+		printk(KERN_INFO
+		       "%s: unsupported Ethertype 0x%04X on interface %s!\n",
+		       card->devname, skb->protocol, dev->name);
+		++chan->ifstats.tx_errors;
+	} else if (chan->protocol == ETH_P_IP) {
+		switch (chan->state) {
+		case WAN_DISCONNECTED:
+			if (cycx_x25_chan_connect(dev)) {
+				netif_stop_queue(dev);
+				return -EBUSY;
+			}
+			/* fall thru */
+		case WAN_CONNECTED:
+			reset_timer(dev);
+			dev->trans_start = jiffies;
+			netif_stop_queue(dev);
+
+			if (cycx_x25_chan_send(dev, skb))
+				return -EBUSY;
+
+			break;
+		default:
+			++chan->ifstats.tx_dropped;
+			++card->wandev.stats.tx_dropped;
+	}
+	} else { /* chan->protocol == ETH_P_X25 */
+		switch (skb->data[0]) {
+		case 0: break;
+		case 1: /* Connect request */
+			cycx_x25_chan_connect(dev);
+			goto free_packet;
+		case 2: /* Disconnect request */
+			cycx_x25_chan_disconnect(dev);
+			goto free_packet;
+	        default:
+			printk(KERN_INFO
+			       "%s: unknown %d x25-iface request on %s!\n",
+			       card->devname, skb->data[0], dev->name);
+			++chan->ifstats.tx_errors;
+			goto free_packet;
+		}
+
+		skb_pull(skb, 1); /* Remove control byte */
+		reset_timer(dev);
+		dev->trans_start = jiffies;
+		netif_stop_queue(dev);
+
+		if (cycx_x25_chan_send(dev, skb)) {
+			/* prepare for future retransmissions */
+			skb_push(skb, 1);
+			return -EBUSY;
+		}
+	}
+
+free_packet:
+	dev_kfree_skb(skb);
+
+	return 0;
+}
+
+/* Get Ethernet-style interface statistics.
+ * Return a pointer to struct net_device_stats */
+static struct net_device_stats *cycx_netdevice_get_stats(struct net_device *dev)
+{
+	struct cycx_x25_channel *chan = dev->priv;
+
+	return chan ? &chan->ifstats : NULL;
+}
+
+/* Interrupt Handlers */
+/* X.25 Interrupt Service Routine. */
+static void cycx_x25_irq_handler(struct cycx_device *card)
+{
+	struct cycx_x25_cmd cmd;
+	u16 z = 0;
+
+	card->in_isr = 1;
+	card->buff_int_mode_unbusy = 0;
+	cycx_peek(&card->hw, X25_RXMBOX_OFFS, &cmd, sizeof(cmd));
+
+	switch (cmd.command) {
+	case X25_DATA_INDICATION:
+		cycx_x25_irq_rx(card, &cmd);
+		break;
+	case X25_ACK_FROM_VC:
+		cycx_x25_irq_tx(card, &cmd);
+		break;
+	case X25_LOG:
+		cycx_x25_irq_log(card, &cmd);
+		break;
+	case X25_STATISTIC:
+		cycx_x25_irq_stat(card, &cmd);
+		break;
+	case X25_CONNECT_CONFIRM:
+		cycx_x25_irq_connect_confirm(card, &cmd);
+		break;
+	case X25_CONNECT_INDICATION:
+		cycx_x25_irq_connect(card, &cmd);
+		break;
+	case X25_DISCONNECT_INDICATION:
+		cycx_x25_irq_disconnect(card, &cmd);
+		break;
+	case X25_DISCONNECT_CONFIRM:
+		cycx_x25_irq_disconnect_confirm(card, &cmd);
+		break;
+	case X25_LINE_ON:
+		cycx_set_state(card, WAN_CONNECTED);
+		break;
+	case X25_LINE_OFF:
+		cycx_set_state(card, WAN_DISCONNECTED);
+		break;
+	default:
+		cycx_x25_irq_spurious(card, &cmd);
+		break;
+	}
+
+	cycx_poke(&card->hw, 0, &z, sizeof(z));
+	cycx_poke(&card->hw, X25_RXMBOX_OFFS, &z, sizeof(z));
+	card->in_isr = 0;
+}
+
+/* Transmit interrupt handler.
+ *	o Release socket buffer
+ *	o Clear 'tbusy' flag */
+static void cycx_x25_irq_tx(struct cycx_device *card, struct cycx_x25_cmd *cmd)
+{
+	struct net_device *dev;
+	struct wan_device *wandev = &card->wandev;
+	u8 lcn;
+
+	cycx_peek(&card->hw, cmd->buf, &lcn, sizeof(lcn));
+
+	/* unbusy device and then dev_tint(); */
+	dev = cycx_x25_get_dev_by_lcn(wandev, lcn);
+	if (dev) {
+		card->buff_int_mode_unbusy = 1;
+		netif_wake_queue(dev);
+	} else
+		printk(KERN_ERR "%s:ackvc for inexistent lcn %d\n",
+				 card->devname, lcn);
+}
+
+/* Receive interrupt handler.
+ * This routine handles fragmented IP packets using M-bit according to the
+ * RFC1356.
+ * o map logical channel number to network interface.
+ * o allocate socket buffer or append received packet to the existing one.
+ * o if M-bit is reset (i.e. it's the last packet in a sequence) then
+ *   decapsulate packet and pass socket buffer to the protocol stack.
+ *
+ * Notes:
+ * 1. When allocating a socket buffer, if M-bit is set then more data is
+ *    coming and we have to allocate buffer for the maximum IP packet size
+ *    expected on this channel.
+ * 2. If something goes wrong and X.25 packet has to be dropped (e.g. no
+ *    socket buffers available) the whole packet sequence must be discarded. */
+static void cycx_x25_irq_rx(struct cycx_device *card, struct cycx_x25_cmd *cmd)
+{
+	struct wan_device *wandev = &card->wandev;
+	struct net_device *dev;
+	struct cycx_x25_channel *chan;
+	struct sk_buff *skb;
+	u8 bitm, lcn;
+	int pktlen = cmd->len - 5;
+
+	cycx_peek(&card->hw, cmd->buf, &lcn, sizeof(lcn));
+	cycx_peek(&card->hw, cmd->buf + 4, &bitm, sizeof(bitm));
+	bitm &= 0x10;
+
+	dev = cycx_x25_get_dev_by_lcn(wandev, lcn);
+	if (!dev) {
+		/* Invalid channel, discard packet */
+		printk(KERN_INFO "%s: receiving on orphaned LCN %d!\n",
+				 card->devname, lcn);
+		return;
+	}
+
+	chan = dev->priv;
+	reset_timer(dev);
+
+	if (chan->drop_sequence) {
+		if (!bitm)
+			chan->drop_sequence = 0;
+		else
+			return;
+	}
+
+	if ((skb = chan->rx_skb) == NULL) {
+		/* Allocate new socket buffer */
+		int bufsize = bitm ? dev->mtu : pktlen;
+
+		if ((skb = dev_alloc_skb((chan->protocol == ETH_P_X25 ? 1 : 0) +
+					 bufsize +
+					 dev->hard_header_len)) == NULL) {
+			printk(KERN_INFO "%s: no socket buffers available!\n",
+					 card->devname);
+			chan->drop_sequence = 1;
+			++chan->ifstats.rx_dropped;
+			return;
+		}
+
+		if (chan->protocol == ETH_P_X25) /* X.25 socket layer control */
+			/* 0 = data packet (dev_alloc_skb zeroed skb->data) */
+			skb_put(skb, 1);
+
+		skb->dev = dev;
+		skb->protocol = htons(chan->protocol);
+		chan->rx_skb = skb;
+	}
+
+	if (skb_tailroom(skb) < pktlen) {
+		/* No room for the packet. Call off the whole thing! */
+		dev_kfree_skb_irq(skb);
+		chan->rx_skb = NULL;
+
+		if (bitm)
+			chan->drop_sequence = 1;
+
+		printk(KERN_INFO "%s: unexpectedly long packet sequence "
+			"on interface %s!\n", card->devname, dev->name);
+		++chan->ifstats.rx_length_errors;
+		return;
+	}
+
+	/* Append packet to the socket buffer  */
+	cycx_peek(&card->hw, cmd->buf + 5, skb_put(skb, pktlen), pktlen);
+
+	if (bitm)
+		return; /* more data is coming */
+
+	chan->rx_skb = NULL;		/* dequeue packet */
+
+	++chan->ifstats.rx_packets;
+	chan->ifstats.rx_bytes += pktlen;
+
+	skb->mac.raw = skb->data;
+	netif_rx(skb);
+	dev->last_rx = jiffies;		/* timestamp */
+}
+
+/* Connect interrupt handler. */
+static void cycx_x25_irq_connect(struct cycx_device *card,
+				 struct cycx_x25_cmd *cmd)
+{
+	struct wan_device *wandev = &card->wandev;
+	struct net_device *dev = NULL;
+	struct cycx_x25_channel *chan;
+	u8 d[32],
+	   loc[24],
+	   rem[24];
+	u8 lcn, sizeloc, sizerem;
+
+	cycx_peek(&card->hw, cmd->buf, &lcn, sizeof(lcn));
+	cycx_peek(&card->hw, cmd->buf + 5, &sizeloc, sizeof(sizeloc));
+	cycx_peek(&card->hw, cmd->buf + 6, d, cmd->len - 6);
+
+	sizerem = sizeloc >> 4;
+	sizeloc &= 0x0F;
+
+	loc[0] = rem[0] = '\0';
+
+	if (sizeloc)
+		nibble_to_byte(d, loc, sizeloc, 0);
+
+	if (sizerem)
+		nibble_to_byte(d + (sizeloc >> 1), rem, sizerem, sizeloc & 1);
+
+	dprintk(1, KERN_INFO "%s:lcn=%d, local=%s, remote=%s\n",
+			  __FUNCTION__, lcn, loc, rem);
+
+	dev = cycx_x25_get_dev_by_dte_addr(wandev, rem);
+	if (!dev) {
+		/* Invalid channel, discard packet */
+		printk(KERN_INFO "%s: connect not expected: remote %s!\n",
+				 card->devname, rem);
+		return;
+	}
+
+	chan = dev->priv;
+	chan->lcn = lcn;
+	cycx_x25_connect_response(card, chan);
+	cycx_x25_set_chan_state(dev, WAN_CONNECTED);
+}
+
+/* Connect confirm interrupt handler. */
+static void cycx_x25_irq_connect_confirm(struct cycx_device *card,
+					 struct cycx_x25_cmd *cmd)
+{
+	struct wan_device *wandev = &card->wandev;
+	struct net_device *dev;
+	struct cycx_x25_channel *chan;
+	u8 lcn, key;
+
+	cycx_peek(&card->hw, cmd->buf, &lcn, sizeof(lcn));
+	cycx_peek(&card->hw, cmd->buf + 1, &key, sizeof(key));
+	dprintk(1, KERN_INFO "%s: %s:lcn=%d, key=%d\n",
+			  card->devname, __FUNCTION__, lcn, key);
+
+	dev = cycx_x25_get_dev_by_lcn(wandev, -key);
+	if (!dev) {
+		/* Invalid channel, discard packet */
+		clear_bit(--key, (void*)&card->u.x.connection_keys);
+		printk(KERN_INFO "%s: connect confirm not expected: lcn %d, "
+				 "key=%d!\n", card->devname, lcn, key);
+		return;
+	}
+
+	clear_bit(--key, (void*)&card->u.x.connection_keys);
+	chan = dev->priv;
+	chan->lcn = lcn;
+	cycx_x25_set_chan_state(dev, WAN_CONNECTED);
+}
+
+/* Disconnect confirm interrupt handler. */
+static void cycx_x25_irq_disconnect_confirm(struct cycx_device *card,
+					    struct cycx_x25_cmd *cmd)
+{
+	struct wan_device *wandev = &card->wandev;
+	struct net_device *dev;
+	u8 lcn;
+
+	cycx_peek(&card->hw, cmd->buf, &lcn, sizeof(lcn));
+	dprintk(1, KERN_INFO "%s: %s:lcn=%d\n",
+			  card->devname, __FUNCTION__, lcn);
+	dev = cycx_x25_get_dev_by_lcn(wandev, lcn);
+	if (!dev) {
+		/* Invalid channel, discard packet */
+		printk(KERN_INFO "%s:disconnect confirm not expected!:lcn %d\n",
+				 card->devname, lcn);
+		return;
+	}
+
+	cycx_x25_set_chan_state(dev, WAN_DISCONNECTED);
+}
+
+/* disconnect interrupt handler. */
+static void cycx_x25_irq_disconnect(struct cycx_device *card,
+				    struct cycx_x25_cmd *cmd)
+{
+	struct wan_device *wandev = &card->wandev;
+	struct net_device *dev;
+	u8 lcn;
+
+	cycx_peek(&card->hw, cmd->buf, &lcn, sizeof(lcn));
+	dprintk(1, KERN_INFO "%s:lcn=%d\n", __FUNCTION__, lcn);
+
+	dev = cycx_x25_get_dev_by_lcn(wandev, lcn);
+	if (dev) {
+		struct cycx_x25_channel *chan = dev->priv;
+
+		cycx_x25_disconnect_response(card, chan->link, lcn);
+		cycx_x25_set_chan_state(dev, WAN_DISCONNECTED);
+	} else
+		cycx_x25_disconnect_response(card, 0, lcn);
+}
+
+/* LOG interrupt handler. */
+static void cycx_x25_irq_log(struct cycx_device *card, struct cycx_x25_cmd *cmd)
+{
+#if CYCLOMX_X25_DEBUG
+	char bf[20];
+	u16 size, toread, link, msg_code;
+	u8 code, routine;
+
+	cycx_peek(&card->hw, cmd->buf, &msg_code, sizeof(msg_code));
+	cycx_peek(&card->hw, cmd->buf + 2, &link, sizeof(link));
+	cycx_peek(&card->hw, cmd->buf + 4, &size, sizeof(size));
+	/* at most 20 bytes are available... thanks to Daniela :) */
+	toread = size < 20 ? size : 20;
+	cycx_peek(&card->hw, cmd->buf + 10, &bf, toread);
+	cycx_peek(&card->hw, cmd->buf + 10 + toread, &code, 1);
+	cycx_peek(&card->hw, cmd->buf + 10 + toread + 1, &routine, 1);
+
+	printk(KERN_INFO "cycx_x25_irq_handler: X25_LOG (0x4500) indic.:\n");
+	printk(KERN_INFO "cmd->buf=0x%X\n", cmd->buf);
+	printk(KERN_INFO "Log message code=0x%X\n", msg_code);
+	printk(KERN_INFO "Link=%d\n", link);
+	printk(KERN_INFO "log code=0x%X\n", code);
+	printk(KERN_INFO "log routine=0x%X\n", routine);
+	printk(KERN_INFO "Message size=%d\n", size);
+	hex_dump("Message", bf, toread);
+#endif
+}
+
+/* STATISTIC interrupt handler. */
+static void cycx_x25_irq_stat(struct cycx_device *card,
+			      struct cycx_x25_cmd *cmd)
+{
+	cycx_peek(&card->hw, cmd->buf, &card->u.x.stats,
+		  sizeof(card->u.x.stats));
+	hex_dump("cycx_x25_irq_stat", (unsigned char*)&card->u.x.stats,
+		 sizeof(card->u.x.stats));
+	cycx_x25_dump_stats(&card->u.x.stats);
+	wake_up_interruptible(&card->wait_stats);
+}
+
+/* Spurious interrupt handler.
+ * o print a warning
+ * If number of spurious interrupts exceeded some limit, then ??? */
+static void cycx_x25_irq_spurious(struct cycx_device *card,
+				  struct cycx_x25_cmd *cmd)
+{
+	printk(KERN_INFO "%s: spurious interrupt (0x%X)!\n",
+			 card->devname, cmd->command);
+}
+#ifdef CYCLOMX_X25_DEBUG
+static void hex_dump(char *msg, unsigned char *p, int len)
+{
+	unsigned char hex[1024],
+	    	* phex = hex;
+
+	if (len >= (sizeof(hex) / 2))
+		len = (sizeof(hex) / 2) - 1;
+
+	while (len--) {
+		sprintf(phex, "%02x", *p++);
+		phex += 2;
+	}
+
+	printk(KERN_INFO "%s: %s\n", msg, hex);
+}
+#endif
+
+/* Cyclom 2X Firmware-Specific Functions */
+/* Exec X.25 command. */
+static int x25_exec(struct cycx_device *card, int command, int link,
+		    void *d1, int len1, void *d2, int len2)
+{
+	struct cycx_x25_cmd c;
+	unsigned long flags;
+	u32 addr = 0x1200 + 0x2E0 * link + 0x1E2;
+	u8 retry = CYCX_X25_MAX_CMD_RETRY;
+	int err = 0;
+
+	c.command = command;
+	c.link = link;
+	c.len = len1 + len2;
+
+	spin_lock_irqsave(&card->u.x.lock, flags);
+
+	/* write command */
+	cycx_poke(&card->hw, X25_MBOX_OFFS, &c, sizeof(c) - sizeof(c.buf));
+
+	/* write X.25 data */
+	if (d1) {
+		cycx_poke(&card->hw, addr, d1, len1);
+
+		if (d2) {
+			if (len2 > 254) {
+				u32 addr1 = 0xA00 + 0x400 * link;
+
+				cycx_poke(&card->hw, addr + len1, d2, 249);
+				cycx_poke(&card->hw, addr1, ((u8*)d2) + 249,
+					  len2 - 249);
+			} else
+				cycx_poke(&card->hw, addr + len1, d2, len2);
+		}
+	}
+
+	/* generate interruption, executing command */
+	cycx_intr(&card->hw);
+
+	/* wait till card->mbox == 0 */
+	do {
+		err = cycx_exec(card->mbox);
+	} while (retry-- && err);
+
+	spin_unlock_irqrestore(&card->u.x.lock, flags);
+
+	return err;
+}
+
+/* Configure adapter. */
+static int cycx_x25_configure(struct cycx_device *card,
+			      struct cycx_x25_config *conf)
+{
+	struct {
+		u16 nlinks;
+		struct cycx_x25_config conf[2];
+	} x25_cmd_conf;
+
+	memset(&x25_cmd_conf, 0, sizeof(x25_cmd_conf));
+	x25_cmd_conf.nlinks = 2;
+	x25_cmd_conf.conf[0] = *conf;
+	/* FIXME: we need to find a way in the wanrouter framework
+		  to configure the second link, for now lets use it
+		  with the same config from the first link, fixing
+		  the interface type to RS232, the speed in 38400 and
+		  the clock to external */
+	x25_cmd_conf.conf[1] = *conf;
+	x25_cmd_conf.conf[1].link = 1;
+	x25_cmd_conf.conf[1].speed = 5; /* 38400 */
+	x25_cmd_conf.conf[1].clock = 8;
+	x25_cmd_conf.conf[1].flags = 0; /* default = RS232 */
+
+	cycx_x25_dump_config(&x25_cmd_conf.conf[0]);
+	cycx_x25_dump_config(&x25_cmd_conf.conf[1]);
+
+	return x25_exec(card, X25_CONFIG, 0,
+			&x25_cmd_conf, sizeof(x25_cmd_conf), NULL, 0);
+}
+
+/* Get protocol statistics. */
+static int cycx_x25_get_stats(struct cycx_device *card)
+{
+	/* the firmware expects 20 in the size field!!!
+	   thanks to Daniela */
+	int err = x25_exec(card, X25_STATISTIC, 0, NULL, 20, NULL, 0);
+
+	if (err)
+		return err;
+
+	interruptible_sleep_on(&card->wait_stats);
+
+	if (signal_pending(current))
+		return -EINTR;
+
+	card->wandev.stats.rx_packets = card->u.x.stats.n2_rx_frames;
+	card->wandev.stats.rx_over_errors = card->u.x.stats.rx_over_errors;
+	card->wandev.stats.rx_crc_errors = card->u.x.stats.rx_crc_errors;
+	card->wandev.stats.rx_length_errors = 0; /* not available from fw */
+	card->wandev.stats.rx_frame_errors = 0; /* not available from fw */
+	card->wandev.stats.rx_missed_errors = card->u.x.stats.rx_aborts;
+	card->wandev.stats.rx_dropped = 0; /* not available from fw */
+	card->wandev.stats.rx_errors = 0; /* not available from fw */
+	card->wandev.stats.tx_packets = card->u.x.stats.n2_tx_frames;
+	card->wandev.stats.tx_aborted_errors = card->u.x.stats.tx_aborts;
+	card->wandev.stats.tx_dropped = 0; /* not available from fw */
+	card->wandev.stats.collisions = 0; /* not available from fw */
+	card->wandev.stats.tx_errors = 0; /* not available from fw */
+
+	cycx_x25_dump_devs(&card->wandev);
+
+	return 0;
+}
+
+/* return the number of nibbles */
+static int byte_to_nibble(u8 *s, u8 *d, char *nibble)
+{
+	int i = 0;
+
+	if (*nibble && *s) {
+		d[i] |= *s++ - '0';
+		*nibble = 0;
+		++i;
+	}
+
+	while (*s) {
+		d[i] = (*s - '0') << 4;
+		if (*(s + 1))
+			d[i] |= *(s + 1) - '0';
+		else {
+			*nibble = 1;
+			break;
+		}
+		++i;
+		s += 2;
+	}
+
+	return i;
+}
+
+static void nibble_to_byte(u8 *s, u8 *d, u8 len, u8 nibble)
+{
+	if (nibble) {
+		*d++ = '0' + (*s++ & 0x0F);
+		--len;
+	}
+
+	while (len) {
+		*d++ = '0' + (*s >> 4);
+
+		if (--len) {
+			*d++ = '0' + (*s & 0x0F);
+			--len;
+		} else break;
+
+		++s;
+	}
+
+	*d = '\0';
+}
+
+/* Place X.25 call. */
+static int x25_place_call(struct cycx_device *card,
+			  struct cycx_x25_channel *chan)
+{
+	int err = 0,
+	    len;
+	char d[64],
+	     nibble = 0,
+	     mylen = chan->local_addr ? strlen(chan->local_addr) : 0,
+	     remotelen = strlen(chan->addr);
+	u8 key;
+
+	if (card->u.x.connection_keys == ~0U) {
+		printk(KERN_INFO "%s: too many simultaneous connection "
+				 "requests!\n", card->devname);
+		return -EAGAIN;
+	}
+
+	key = ffz(card->u.x.connection_keys);
+	set_bit(key, (void*)&card->u.x.connection_keys);
+	++key;
+	dprintk(1, KERN_INFO "%s:x25_place_call:key=%d\n", card->devname, key);
+	memset(d, 0, sizeof(d));
+	d[1] = key; /* user key */
+	d[2] = 0x10;
+	d[4] = 0x0B;
+
+	len = byte_to_nibble(chan->addr, d + 6, &nibble);
+
+	if (chan->local_addr)
+		len += byte_to_nibble(chan->local_addr, d + 6 + len, &nibble);
+
+	if (nibble)
+		++len;
+
+	d[5] = mylen << 4 | remotelen;
+	d[6 + len + 1] = 0xCC; /* TCP/IP over X.25, thanks to Daniela :) */
+
+	if ((err = x25_exec(card, X25_CONNECT_REQUEST, chan->link,
+			    &d, 7 + len + 1, NULL, 0)) != 0)
+		clear_bit(--key, (void*)&card->u.x.connection_keys);
+	else
+		chan->lcn = -key;
+
+	return err;
+}
+
+/* Place X.25 CONNECT RESPONSE. */
+static int cycx_x25_connect_response(struct cycx_device *card,
+				     struct cycx_x25_channel *chan)
+{
+	u8 d[8];
+
+	memset(d, 0, sizeof(d));
+	d[0] = d[3] = chan->lcn;
+	d[2] = 0x10;
+	d[4] = 0x0F;
+	d[7] = 0xCC; /* TCP/IP over X.25, thanks Daniela */
+
+	return x25_exec(card, X25_CONNECT_RESPONSE, chan->link, &d, 8, NULL, 0);
+}
+
+/* Place X.25 DISCONNECT RESPONSE.  */
+static int cycx_x25_disconnect_response(struct cycx_device *card, u8 link,
+					u8 lcn)
+{
+	char d[5];
+
+	memset(d, 0, sizeof(d));
+	d[0] = d[3] = lcn;
+	d[2] = 0x10;
+	d[4] = 0x17;
+
+	return x25_exec(card, X25_DISCONNECT_RESPONSE, link, &d, 5, NULL, 0);
+}
+
+/* Clear X.25 call.  */
+static int x25_clear_call(struct cycx_device *card, u8 link, u8 lcn, u8 cause,
+			  u8 diagn)
+{
+	u8 d[7];
+
+	memset(d, 0, sizeof(d));
+	d[0] = d[3] = lcn;
+	d[2] = 0x10;
+	d[4] = 0x13;
+	d[5] = cause;
+	d[6] = diagn;
+
+	return x25_exec(card, X25_DISCONNECT_REQUEST, link, d, 7, NULL, 0);
+}
+
+/* Send X.25 data packet. */
+static int cycx_x25_send(struct cycx_device *card, u8 link, u8 lcn, u8 bitm,
+			 int len, void *buf)
+{
+	u8 d[] = "?\xFF\x10??";
+
+	d[0] = d[3] = lcn;
+	d[4] = bitm;
+
+	return x25_exec(card, X25_DATA_REQUEST, link, &d, 5, buf, len);
+}
+
+/* Miscellaneous */
+/* Find network device by its channel number.  */
+static struct net_device *cycx_x25_get_dev_by_lcn(struct wan_device *wandev,
+						  s16 lcn)
+{
+	struct net_device *dev = wandev->dev;
+	struct cycx_x25_channel *chan;
+
+	while (dev) {
+		chan = (struct cycx_x25_channel*)dev->priv;
+
+		if (chan->lcn == lcn)
+			break;
+		dev = chan->slave;
+	}
+	return dev;
+}
+
+/* Find network device by its remote dte address. */
+static struct net_device *
+	cycx_x25_get_dev_by_dte_addr(struct wan_device *wandev, char *dte)
+{
+	struct net_device *dev = wandev->dev;
+	struct cycx_x25_channel *chan;
+
+	while (dev) {
+		chan = (struct cycx_x25_channel*)dev->priv;
+
+		if (!strcmp(chan->addr, dte))
+			break;
+		dev = chan->slave;
+	}
+	return dev;
+}
+
+/* Initiate connection on the logical channel.
+ * o for PVC we just get channel configuration
+ * o for SVCs place an X.25 call
+ *
+ * Return:	0	connected
+ *		>0	connection in progress
+ *		<0	failure */
+static int cycx_x25_chan_connect(struct net_device *dev)
+{
+	struct cycx_x25_channel *chan = dev->priv;
+	struct cycx_device *card = chan->card;
+
+	if (chan->svc) {
+		if (!chan->addr[0])
+			return -EINVAL; /* no destination address */
+
+		dprintk(1, KERN_INFO "%s: placing X.25 call to %s...\n",
+				  card->devname, chan->addr);
+
+		if (x25_place_call(card, chan))
+			return -EIO;
+
+		cycx_x25_set_chan_state(dev, WAN_CONNECTING);
+		return 1;
+	} else
+		cycx_x25_set_chan_state(dev, WAN_CONNECTED);
+
+	return 0;
+}
+
+/* Disconnect logical channel.
+ * o if SVC then clear X.25 call */
+static void cycx_x25_chan_disconnect(struct net_device *dev)
+{
+	struct cycx_x25_channel *chan = dev->priv;
+
+	if (chan->svc) {
+		x25_clear_call(chan->card, chan->link, chan->lcn, 0, 0);
+		cycx_x25_set_chan_state(dev, WAN_DISCONNECTING);
+	} else
+		cycx_x25_set_chan_state(dev, WAN_DISCONNECTED);
+}
+
+/* Called by kernel timer */
+static void cycx_x25_chan_timer(unsigned long d)
+{
+	struct net_device *dev = (struct net_device *)d;
+	struct cycx_x25_channel *chan = dev->priv;
+
+	if (chan->state == WAN_CONNECTED)
+		cycx_x25_chan_disconnect(dev);
+	else
+		printk(KERN_ERR "%s: %s for svc (%s) not connected!\n",
+				chan->card->devname, __FUNCTION__, dev->name);
+}
+
+/* Set logical channel state. */
+static void cycx_x25_set_chan_state(struct net_device *dev, u8 state)
+{
+	struct cycx_x25_channel *chan = dev->priv;
+	struct cycx_device *card = chan->card;
+	unsigned long flags;
+	char *string_state = NULL;
+
+	spin_lock_irqsave(&card->lock, flags);
+
+	if (chan->state != state) {
+		if (chan->svc && chan->state == WAN_CONNECTED)
+			del_timer(&chan->timer);
+
+		switch (state) {
+		case WAN_CONNECTED:
+			string_state = "connected!";
+			*(u16*)dev->dev_addr = htons(chan->lcn);
+			netif_wake_queue(dev);
+			reset_timer(dev);
+
+			if (chan->protocol == ETH_P_X25)
+				cycx_x25_chan_send_event(dev, 1);
+
+			break;
+		case WAN_CONNECTING:
+			string_state = "connecting...";
+			break;
+		case WAN_DISCONNECTING:
+			string_state = "disconnecting...";
+			break;
+		case WAN_DISCONNECTED:
+			string_state = "disconnected!";
+
+			if (chan->svc) {
+				*(unsigned short*)dev->dev_addr = 0;
+				chan->lcn = 0;
+			}
+
+			if (chan->protocol == ETH_P_X25)
+				cycx_x25_chan_send_event(dev, 2);
+
+			netif_wake_queue(dev);
+			break;
+		}
+
+		printk(KERN_INFO "%s: interface %s %s\n", card->devname,
+				  dev->name, string_state);
+		chan->state = state;
+	}
+
+	spin_unlock_irqrestore(&card->lock, flags);
+}
+
+/* Send packet on a logical channel.
+ *	When this function is called, tx_skb field of the channel data space
+ *	points to the transmit socket buffer.  When transmission is complete,
+ *	release socket buffer and reset 'tbusy' flag.
+ *
+ * Return:	0	- transmission complete
+ *		1	- busy
+ *
+ * Notes:
+ * 1. If packet length is greater than MTU for this channel, we'll fragment
+ *    the packet into 'complete sequence' using M-bit.
+ * 2. When transmission is complete, an event notification should be issued
+ *    to the router.  */
+static int cycx_x25_chan_send(struct net_device *dev, struct sk_buff *skb)
+{
+	struct cycx_x25_channel *chan = dev->priv;
+	struct cycx_device *card = chan->card;
+	int bitm = 0;		/* final packet */
+	unsigned len = skb->len;
+
+	if (skb->len > card->wandev.mtu) {
+		len = card->wandev.mtu;
+		bitm = 0x10;		/* set M-bit (more data) */
+	}
+
+	if (cycx_x25_send(card, chan->link, chan->lcn, bitm, len, skb->data))
+		return 1;
+
+	if (bitm) {
+		skb_pull(skb, len);
+		return 1;
+	}
+
+	++chan->ifstats.tx_packets;
+	chan->ifstats.tx_bytes += len;
+
+	return 0;
+}
+
+/* Send event (connection, disconnection, etc) to X.25 socket layer */
+
+static void cycx_x25_chan_send_event(struct net_device *dev, u8 event)
+{
+	struct sk_buff *skb;
+	unsigned char *ptr;
+
+	if ((skb = dev_alloc_skb(1)) == NULL) {
+		printk(KERN_ERR "%s: out of memory\n", __FUNCTION__);
+		return;
+	}
+
+	ptr  = skb_put(skb, 1);
+	*ptr = event;
+
+	skb->protocol = x25_type_trans(skb, dev);
+	netif_rx(skb);
+	dev->last_rx = jiffies;		/* timestamp */
+}
+
+/* Convert line speed in bps to a number used by cyclom 2x code. */
+static u8 bps_to_speed_code(u32 bps)
+{
+	u8 number = 0; /* defaults to the lowest (1200) speed ;> */
+
+	     if (bps >= 512000) number = 8;
+	else if (bps >= 256000) number = 7;
+	else if (bps >= 64000)  number = 6;
+	else if (bps >= 38400)  number = 5;
+	else if (bps >= 19200)  number = 4;
+	else if (bps >= 9600)   number = 3;
+	else if (bps >= 4800)   number = 2;
+	else if (bps >= 2400)   number = 1;
+
+	return number;
+}
+
+/* log base 2 */
+static u8 cycx_log2(u32 n)
+{
+	u8 log = 0;
+
+	if (!n)
+		return 0;
+
+	while (n > 1) {
+		n >>= 1;
+		++log;
+	}
+
+	return log;
+}
+
+/* Convert decimal string to unsigned integer.
+ * If len != 0 then only 'len' characters of the string are converted. */
+static unsigned dec_to_uint(u8 *str, int len)
+{
+	unsigned val = 0;
+
+	if (!len)
+		len = strlen(str);
+
+	for (; len && is_digit(*str); ++str, --len)
+		val = (val * 10) + (*str - (unsigned) '0');
+
+	return val;
+}
+
+static void reset_timer(struct net_device *dev)
+{
+	struct cycx_x25_channel *chan = dev->priv;
+
+	if (chan->svc)
+		mod_timer(&chan->timer, jiffies+chan->idle_tmout*HZ);
+}
+#ifdef CYCLOMX_X25_DEBUG
+static void cycx_x25_dump_config(struct cycx_x25_config *conf)
+{
+	printk(KERN_INFO "X.25 configuration\n");
+	printk(KERN_INFO "-----------------\n");
+	printk(KERN_INFO "link number=%d\n", conf->link);
+	printk(KERN_INFO "line speed=%d\n", conf->speed);
+	printk(KERN_INFO "clock=%sternal\n", conf->clock == 8 ? "Ex" : "In");
+	printk(KERN_INFO "# level 2 retransm.=%d\n", conf->n2);
+	printk(KERN_INFO "level 2 window=%d\n", conf->n2win);
+	printk(KERN_INFO "level 3 window=%d\n", conf->n3win);
+	printk(KERN_INFO "# logical channels=%d\n", conf->nvc);
+	printk(KERN_INFO "level 3 pkt len=%d\n", conf->pktlen);
+	printk(KERN_INFO "my address=%d\n", conf->locaddr);
+	printk(KERN_INFO "remote address=%d\n", conf->remaddr);
+	printk(KERN_INFO "t1=%d seconds\n", conf->t1);
+	printk(KERN_INFO "t2=%d seconds\n", conf->t2);
+	printk(KERN_INFO "t21=%d seconds\n", conf->t21);
+	printk(KERN_INFO "# PVCs=%d\n", conf->npvc);
+	printk(KERN_INFO "t23=%d seconds\n", conf->t23);
+	printk(KERN_INFO "flags=0x%x\n", conf->flags);
+}
+
+static void cycx_x25_dump_stats(struct cycx_x25_stats *stats)
+{
+	printk(KERN_INFO "X.25 statistics\n");
+	printk(KERN_INFO "--------------\n");
+	printk(KERN_INFO "rx_crc_errors=%d\n", stats->rx_crc_errors);
+	printk(KERN_INFO "rx_over_errors=%d\n", stats->rx_over_errors);
+	printk(KERN_INFO "n2_tx_frames=%d\n", stats->n2_tx_frames);
+	printk(KERN_INFO "n2_rx_frames=%d\n", stats->n2_rx_frames);
+	printk(KERN_INFO "tx_timeouts=%d\n", stats->tx_timeouts);
+	printk(KERN_INFO "rx_timeouts=%d\n", stats->rx_timeouts);
+	printk(KERN_INFO "n3_tx_packets=%d\n", stats->n3_tx_packets);
+	printk(KERN_INFO "n3_rx_packets=%d\n", stats->n3_rx_packets);
+	printk(KERN_INFO "tx_aborts=%d\n", stats->tx_aborts);
+	printk(KERN_INFO "rx_aborts=%d\n", stats->rx_aborts);
+}
+
+static void cycx_x25_dump_devs(struct wan_device *wandev)
+{
+	struct net_device *dev = wandev->dev;
+
+	printk(KERN_INFO "X.25 dev states\n");
+	printk(KERN_INFO "name: addr:           txoff:  protocol:\n");
+	printk(KERN_INFO "---------------------------------------\n");
+
+	while(dev) {
+		struct cycx_x25_channel *chan = dev->priv;
+
+		printk(KERN_INFO "%-5.5s %-15.15s   %d     ETH_P_%s\n",
+				 chan->name, chan->addr, netif_queue_stopped(dev),
+				 chan->protocol == ETH_P_IP ? "IP" : "X25");
+		dev = chan->slave;
+	}
+}
+
+#endif /* CYCLOMX_X25_DEBUG */
+/* End */
diff --git a/drivers/net/wan/dlci.c b/drivers/net/wan/dlci.c
new file mode 100644
index 0000000..6e1ec5b
--- /dev/null
+++ b/drivers/net/wan/dlci.c
@@ -0,0 +1,566 @@
+/*
+ * DLCI		Implementation of Frame Relay protocol for Linux, according to
+ *		RFC 1490.  This generic device provides en/decapsulation for an
+ *		underlying hardware driver.  Routes & IPs are assigned to these
+ *		interfaces.  Requires 'dlcicfg' program to create usable 
+ *		interfaces, the initial one, 'dlci' is for IOCTL use only.
+ *
+ * Version:	@(#)dlci.c	0.35	4 Jan 1997
+ *
+ * Author:	Mike McLagan <mike.mclagan@linux.org>
+ *
+ * Changes:
+ *
+ *		0.15	Mike Mclagan	Packet freeing, bug in kmalloc call
+ *					DLCI_RET handling
+ *		0.20	Mike McLagan	More conservative on which packets
+ *					are returned for retry and which are
+ *					are dropped.  If DLCI_RET_DROP is
+ *					returned from the FRAD, the packet is
+ *				 	sent back to Linux for re-transmission
+ *		0.25	Mike McLagan	Converted to use SIOC IOCTL calls
+ *		0.30	Jim Freeman	Fixed to allow IPX traffic
+ *		0.35	Michael Elizabeth	Fixed incorrect memcpy_fromfs
+ *
+ *		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.
+ */
+
+#include <linux/config.h> /* for CONFIG_DLCI_COUNT */
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/types.h>
+#include <linux/fcntl.h>
+#include <linux/interrupt.h>
+#include <linux/ptrace.h>
+#include <linux/ioport.h>
+#include <linux/in.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/string.h>
+#include <linux/errno.h>
+#include <linux/netdevice.h>
+#include <linux/skbuff.h>
+#include <linux/if_arp.h>
+#include <linux/if_frad.h>
+#include <linux/bitops.h>
+
+#include <net/sock.h>
+
+#include <asm/system.h>
+#include <asm/io.h>
+#include <asm/dma.h>
+#include <asm/uaccess.h>
+
+static const char version[] = "DLCI driver v0.35, 4 Jan 1997, mike.mclagan@linux.org";
+
+static LIST_HEAD(dlci_devs);
+
+static void dlci_setup(struct net_device *);
+
+/* 
+ * these encapsulate the RFC 1490 requirements as well as 
+ * deal with packet transmission and reception, working with
+ * the upper network layers 
+ */
+
+static int dlci_header(struct sk_buff *skb, struct net_device *dev, 
+                           unsigned short type, void *daddr, void *saddr, 
+                           unsigned len)
+{
+	struct frhdr		hdr;
+	struct dlci_local	*dlp;
+	unsigned int		hlen;
+	char			*dest;
+
+	dlp = dev->priv;
+
+	hdr.control = FRAD_I_UI;
+	switch(type)
+	{
+		case ETH_P_IP:
+			hdr.IP_NLPID = FRAD_P_IP;
+			hlen = sizeof(hdr.control) + sizeof(hdr.IP_NLPID);
+			break;
+
+		/* feel free to add other types, if necessary */
+
+		default:
+			hdr.pad = FRAD_P_PADDING;
+			hdr.NLPID = FRAD_P_SNAP;
+			memset(hdr.OUI, 0, sizeof(hdr.OUI));
+			hdr.PID = htons(type);
+			hlen = sizeof(hdr);
+			break;
+	}
+
+	dest = skb_push(skb, hlen);
+	if (!dest)
+		return(0);
+
+	memcpy(dest, &hdr, hlen);
+
+	return(hlen);
+}
+
+static void dlci_receive(struct sk_buff *skb, struct net_device *dev)
+{
+	struct dlci_local *dlp;
+	struct frhdr		*hdr;
+	int					process, header;
+
+	dlp = dev->priv;
+	if (!pskb_may_pull(skb, sizeof(*hdr))) {
+		printk(KERN_NOTICE "%s: invalid data no header\n",
+		       dev->name);
+		dlp->stats.rx_errors++;
+		kfree_skb(skb);
+		return;
+	}
+
+	hdr = (struct frhdr *) skb->data;
+	process = 0;
+	header = 0;
+	skb->dev = dev;
+
+	if (hdr->control != FRAD_I_UI)
+	{
+		printk(KERN_NOTICE "%s: Invalid header flag 0x%02X.\n", dev->name, hdr->control);
+		dlp->stats.rx_errors++;
+	}
+	else
+		switch(hdr->IP_NLPID)
+		{
+			case FRAD_P_PADDING:
+				if (hdr->NLPID != FRAD_P_SNAP)
+				{
+					printk(KERN_NOTICE "%s: Unsupported NLPID 0x%02X.\n", dev->name, hdr->NLPID);
+					dlp->stats.rx_errors++;
+					break;
+				}
+	 
+				if (hdr->OUI[0] + hdr->OUI[1] + hdr->OUI[2] != 0)
+				{
+					printk(KERN_NOTICE "%s: Unsupported organizationally unique identifier 0x%02X-%02X-%02X.\n", dev->name, hdr->OUI[0], hdr->OUI[1], hdr->OUI[2]);
+					dlp->stats.rx_errors++;
+					break;
+				}
+
+				/* at this point, it's an EtherType frame */
+				header = sizeof(struct frhdr);
+				/* Already in network order ! */
+				skb->protocol = hdr->PID;
+				process = 1;
+				break;
+
+			case FRAD_P_IP:
+				header = sizeof(hdr->control) + sizeof(hdr->IP_NLPID);
+				skb->protocol = htons(ETH_P_IP);
+				process = 1;
+				break;
+
+			case FRAD_P_SNAP:
+			case FRAD_P_Q933:
+			case FRAD_P_CLNP:
+				printk(KERN_NOTICE "%s: Unsupported NLPID 0x%02X.\n", dev->name, hdr->pad);
+				dlp->stats.rx_errors++;
+				break;
+
+			default:
+				printk(KERN_NOTICE "%s: Invalid pad byte 0x%02X.\n", dev->name, hdr->pad);
+				dlp->stats.rx_errors++;
+				break;				
+		}
+
+	if (process)
+	{
+		/* we've set up the protocol, so discard the header */
+		skb->mac.raw = skb->data; 
+		skb_pull(skb, header);
+		dlp->stats.rx_bytes += skb->len;
+		netif_rx(skb);
+		dlp->stats.rx_packets++;
+		dev->last_rx = jiffies;
+	}
+	else
+		dev_kfree_skb(skb);
+}
+
+static int dlci_transmit(struct sk_buff *skb, struct net_device *dev)
+{
+	struct dlci_local *dlp;
+	int					ret;
+
+	ret = 0;
+
+	if (!skb || !dev)
+		return(0);
+
+	dlp = dev->priv;
+
+	netif_stop_queue(dev);
+	
+	ret = dlp->slave->hard_start_xmit(skb, dlp->slave);
+	switch (ret)
+	{
+		case DLCI_RET_OK:
+			dlp->stats.tx_packets++;
+			ret = 0;
+			break;
+			case DLCI_RET_ERR:
+			dlp->stats.tx_errors++;
+			ret = 0;
+			break;
+			case DLCI_RET_DROP:
+			dlp->stats.tx_dropped++;
+			ret = 1;
+			break;
+	}
+	/* Alan Cox recommends always returning 0, and always freeing the packet */
+	/* experience suggest a slightly more conservative approach */
+
+	if (!ret)
+	{
+		dev_kfree_skb(skb);
+		netif_wake_queue(dev);
+	}
+	return(ret);
+}
+
+static int dlci_config(struct net_device *dev, struct dlci_conf __user *conf, int get)
+{
+	struct dlci_conf	config;
+	struct dlci_local	*dlp;
+	struct frad_local	*flp;
+	int			err;
+
+	dlp = dev->priv;
+
+	flp = dlp->slave->priv;
+
+	if (!get)
+	{
+		if(copy_from_user(&config, conf, sizeof(struct dlci_conf)))
+			return -EFAULT;
+		if (config.flags & ~DLCI_VALID_FLAGS)
+			return(-EINVAL);
+		memcpy(&dlp->config, &config, sizeof(struct dlci_conf));
+		dlp->configured = 1;
+	}
+
+	err = (*flp->dlci_conf)(dlp->slave, dev, get);
+	if (err)
+		return(err);
+
+	if (get)
+	{
+		if(copy_to_user(conf, &dlp->config, sizeof(struct dlci_conf)))
+			return -EFAULT;
+	}
+
+	return(0);
+}
+
+static int dlci_dev_ioctl(struct net_device *dev, struct ifreq *ifr, int cmd)
+{
+	struct dlci_local *dlp;
+
+	if (!capable(CAP_NET_ADMIN))
+		return(-EPERM);
+
+	dlp = dev->priv;
+
+	switch(cmd)
+	{
+		case DLCI_GET_SLAVE:
+			if (!*(short *)(dev->dev_addr))
+				return(-EINVAL);
+
+			strncpy(ifr->ifr_slave, dlp->slave->name, sizeof(ifr->ifr_slave));
+			break;
+
+		case DLCI_GET_CONF:
+		case DLCI_SET_CONF:
+			if (!*(short *)(dev->dev_addr))
+				return(-EINVAL);
+
+			return(dlci_config(dev, ifr->ifr_data, cmd == DLCI_GET_CONF));
+			break;
+
+		default: 
+			return(-EOPNOTSUPP);
+	}
+	return(0);
+}
+
+static int dlci_change_mtu(struct net_device *dev, int new_mtu)
+{
+	struct dlci_local *dlp;
+
+	dlp = dev->priv;
+
+	return((*dlp->slave->change_mtu)(dlp->slave, new_mtu));
+}
+
+static int dlci_open(struct net_device *dev)
+{
+	struct dlci_local	*dlp;
+	struct frad_local	*flp;
+	int			err;
+
+	dlp = dev->priv;
+
+	if (!*(short *)(dev->dev_addr))
+		return(-EINVAL);
+
+	if (!netif_running(dlp->slave))
+		return(-ENOTCONN);
+
+	flp = dlp->slave->priv;
+	err = (*flp->activate)(dlp->slave, dev);
+	if (err)
+		return(err);
+
+	netif_start_queue(dev);
+
+	return 0;
+}
+
+static int dlci_close(struct net_device *dev)
+{
+	struct dlci_local	*dlp;
+	struct frad_local	*flp;
+	int			err;
+
+	netif_stop_queue(dev);
+
+	dlp = dev->priv;
+
+	flp = dlp->slave->priv;
+	err = (*flp->deactivate)(dlp->slave, dev);
+
+	return 0;
+}
+
+static struct net_device_stats *dlci_get_stats(struct net_device *dev)
+{
+	struct dlci_local *dlp;
+
+	dlp = dev->priv;
+
+	return(&dlp->stats);
+}
+
+static int dlci_add(struct dlci_add *dlci)
+{
+	struct net_device	*master, *slave;
+	struct dlci_local	*dlp;
+	struct frad_local	*flp;
+	int			err = -EINVAL;
+
+
+	/* validate slave device */
+	slave = dev_get_by_name(dlci->devname);
+	if (!slave)
+		return -ENODEV;
+
+	if (slave->type != ARPHRD_FRAD || slave->priv == NULL)
+		goto err1;
+
+	/* create device name */
+	master = alloc_netdev( sizeof(struct dlci_local), "dlci%d",
+			      dlci_setup);
+	if (!master) {
+		err = -ENOMEM;
+		goto err1;
+	}
+
+	/* make sure same slave not already registered */
+	rtnl_lock();
+	list_for_each_entry(dlp, &dlci_devs, list) {
+		if (dlp->slave == slave) {
+			err = -EBUSY;
+			goto err2;
+		}
+	}
+
+	err = dev_alloc_name(master, master->name);
+	if (err < 0)
+		goto err2;
+
+	*(short *)(master->dev_addr) = dlci->dlci;
+
+	dlp = (struct dlci_local *) master->priv;
+	dlp->slave = slave;
+	dlp->master = master;
+
+	flp = slave->priv;
+	err = (*flp->assoc)(slave, master);
+	if (err < 0)
+		goto err2;
+
+	err = register_netdevice(master);
+	if (err < 0) 
+		goto err2;
+
+	strcpy(dlci->devname, master->name);
+
+	list_add(&dlp->list, &dlci_devs);
+	rtnl_unlock();
+
+	return(0);
+
+ err2:
+	rtnl_unlock();
+	free_netdev(master);
+ err1:
+	dev_put(slave);
+	return(err);
+}
+
+static int dlci_del(struct dlci_add *dlci)
+{
+	struct dlci_local	*dlp;
+	struct frad_local	*flp;
+	struct net_device	*master, *slave;
+	int			err;
+
+	/* validate slave device */
+	master = __dev_get_by_name(dlci->devname);
+	if (!master)
+		return(-ENODEV);
+
+	if (netif_running(master)) {
+		return(-EBUSY);
+	}
+
+	dlp = master->priv;
+	slave = dlp->slave;
+	flp = slave->priv;
+
+	rtnl_lock();
+	err = (*flp->deassoc)(slave, master);
+	if (!err) {
+		list_del(&dlp->list);
+
+		unregister_netdevice(master);
+
+		dev_put(slave);
+	}
+	rtnl_unlock();
+
+	return(err);
+}
+
+static int dlci_ioctl(unsigned int cmd, void __user *arg)
+{
+	struct dlci_add add;
+	int err;
+	
+	if (!capable(CAP_NET_ADMIN))
+		return(-EPERM);
+
+	if(copy_from_user(&add, arg, sizeof(struct dlci_add)))
+		return -EFAULT;
+
+	switch (cmd)
+	{
+		case SIOCADDDLCI:
+			err = dlci_add(&add);
+
+			if (!err)
+				if(copy_to_user(arg, &add, sizeof(struct dlci_add)))
+					return -EFAULT;
+			break;
+
+		case SIOCDELDLCI:
+			err = dlci_del(&add);
+			break;
+
+		default:
+			err = -EINVAL;
+	}
+
+	return(err);
+}
+
+static void dlci_setup(struct net_device *dev)
+{
+	struct dlci_local *dlp = dev->priv;
+
+	dev->flags		= 0;
+	dev->open		= dlci_open;
+	dev->stop		= dlci_close;
+	dev->do_ioctl		= dlci_dev_ioctl;
+	dev->hard_start_xmit	= dlci_transmit;
+	dev->hard_header	= dlci_header;
+	dev->get_stats		= dlci_get_stats;
+	dev->change_mtu		= dlci_change_mtu;
+	dev->destructor		= free_netdev;
+
+	dlp->receive		= dlci_receive;
+
+	dev->type		= ARPHRD_DLCI;
+	dev->hard_header_len	= sizeof(struct frhdr);
+	dev->addr_len		= sizeof(short);
+
+}
+
+/* if slave is unregistering, then cleanup master */
+static int dlci_dev_event(struct notifier_block *unused,
+			  unsigned long event, void *ptr)
+{
+	struct net_device *dev = (struct net_device *) ptr;
+
+	if (event == NETDEV_UNREGISTER) {
+		struct dlci_local *dlp;
+
+		list_for_each_entry(dlp, &dlci_devs, list) {
+			if (dlp->slave == dev) {
+				list_del(&dlp->list);
+				unregister_netdevice(dlp->master);
+				dev_put(dlp->slave);
+				break;
+			}
+		}
+	}
+	return NOTIFY_DONE;
+}
+
+static struct notifier_block dlci_notifier = {
+	.notifier_call = dlci_dev_event,
+};
+
+static int __init init_dlci(void)
+{
+	dlci_ioctl_set(dlci_ioctl);
+	register_netdevice_notifier(&dlci_notifier);
+
+	printk("%s.\n", version);
+
+	return 0;
+}
+
+static void __exit dlci_exit(void)
+{
+	struct dlci_local	*dlp, *nxt;
+	
+	dlci_ioctl_set(NULL);
+	unregister_netdevice_notifier(&dlci_notifier);
+
+	rtnl_lock();
+	list_for_each_entry_safe(dlp, nxt, &dlci_devs, list) {
+		unregister_netdevice(dlp->master);
+		dev_put(dlp->slave);
+	}
+	rtnl_unlock();
+}
+
+module_init(init_dlci);
+module_exit(dlci_exit);
+
+MODULE_AUTHOR("Mike McLagan");
+MODULE_DESCRIPTION("Frame Relay DLCI layer");
+MODULE_LICENSE("GPL");
diff --git a/drivers/net/wan/dscc4.c b/drivers/net/wan/dscc4.c
new file mode 100644
index 0000000..520a77a
--- /dev/null
+++ b/drivers/net/wan/dscc4.c
@@ -0,0 +1,2074 @@
+/*
+ * drivers/net/wan/dscc4/dscc4.c: a DSCC4 HDLC driver for Linux
+ *
+ * This software may be used and distributed according to the terms of the
+ * GNU General Public License.
+ *
+ * The author may be reached as romieu@cogenit.fr.
+ * Specific bug reports/asian food will be welcome.
+ *
+ * Special thanks to the nice people at CS-Telecom for the hardware and the
+ * access to the test/measure tools.
+ *
+ *
+ *                             Theory of Operation
+ *
+ * I. Board Compatibility
+ *
+ * This device driver is designed for the Siemens PEB20534 4 ports serial
+ * controller as found on Etinc PCISYNC cards. The documentation for the
+ * chipset is available at http://www.infineon.com:
+ * - Data Sheet "DSCC4, DMA Supported Serial Communication Controller with
+ * 4 Channels, PEB 20534 Version 2.1, PEF 20534 Version 2.1";
+ * - Application Hint "Management of DSCC4 on-chip FIFO resources".
+ * - Errata sheet DS5 (courtesy of Michael Skerritt).
+ * Jens David has built an adapter based on the same chipset. Take a look
+ * at http://www.afthd.tu-darmstadt.de/~dg1kjd/pciscc4 for a specific
+ * driver.
+ * Sample code (2 revisions) is available at Infineon.
+ *
+ * II. Board-specific settings
+ *
+ * Pcisync can transmit some clock signal to the outside world on the
+ * *first two* ports provided you put a quartz and a line driver on it and
+ * remove the jumpers. The operation is described on Etinc web site. If you
+ * go DCE on these ports, don't forget to use an adequate cable.
+ *
+ * Sharing of the PCI interrupt line for this board is possible.
+ *
+ * III. Driver operation
+ *
+ * The rx/tx operations are based on a linked list of descriptors. The driver
+ * doesn't use HOLD mode any more. HOLD mode is definitely buggy and the more
+ * I tried to fix it, the more it started to look like (convoluted) software
+ * mutation of LxDA method. Errata sheet DS5 suggests to use LxDA: consider
+ * this a rfc2119 MUST.
+ *
+ * Tx direction
+ * When the tx ring is full, the xmit routine issues a call to netdev_stop.
+ * The device is supposed to be enabled again during an ALLS irq (we could
+ * use HI but as it's easy to lose events, it's fscked).
+ *
+ * Rx direction
+ * The received frames aren't supposed to span over multiple receiving areas.
+ * I may implement it some day but it isn't the highest ranked item.
+ *
+ * IV. Notes
+ * The current error (XDU, RFO) recovery code is untested.
+ * So far, RDO takes his RX channel down and the right sequence to enable it
+ * again is still a mistery. If RDO happens, plan a reboot. More details
+ * in the code (NB: as this happens, TX still works).
+ * Don't mess the cables during operation, especially on DTE ports. I don't
+ * suggest it for DCE either but at least one can get some messages instead
+ * of a complete instant freeze.
+ * Tests are done on Rev. 20 of the silicium. The RDO handling changes with
+ * the documentation/chipset releases.
+ *
+ * TODO:
+ * - test X25.
+ * - use polling at high irq/s,
+ * - performance analysis,
+ * - endianness.
+ *
+ * 2001/12/10	Daniela Squassoni  <daniela@cyclades.com>
+ * - Contribution to support the new generic HDLC layer.
+ *
+ * 2002/01	Ueimor
+ * - old style interface removal
+ * - dscc4_release_ring fix (related to DMA mapping)
+ * - hard_start_xmit fix (hint: TxSizeMax)
+ * - misc crapectomy.
+ */
+
+#include <linux/module.h>
+#include <linux/types.h>
+#include <linux/errno.h>
+#include <linux/list.h>
+#include <linux/ioport.h>
+#include <linux/pci.h>
+#include <linux/kernel.h>
+#include <linux/mm.h>
+
+#include <asm/system.h>
+#include <asm/cache.h>
+#include <asm/byteorder.h>
+#include <asm/uaccess.h>
+#include <asm/io.h>
+#include <asm/irq.h>
+
+#include <linux/init.h>
+#include <linux/string.h>
+
+#include <linux/if_arp.h>
+#include <linux/netdevice.h>
+#include <linux/skbuff.h>
+#include <linux/delay.h>
+#include <net/syncppp.h>
+#include <linux/hdlc.h>
+
+/* Version */
+static const char version[] = "$Id: dscc4.c,v 1.173 2003/09/20 23:55:34 romieu Exp $ for Linux\n";
+static int debug;
+static int quartz;
+
+#ifdef CONFIG_DSCC4_PCI_RST
+static DECLARE_MUTEX(dscc4_sem);
+static u32 dscc4_pci_config_store[16];
+#endif
+
+#define	DRV_NAME	"dscc4"
+
+#undef DSCC4_POLLING
+
+/* Module parameters */
+
+MODULE_AUTHOR("Maintainer: Francois Romieu <romieu@cogenit.fr>");
+MODULE_DESCRIPTION("Siemens PEB20534 PCI Controler");
+MODULE_LICENSE("GPL");
+module_param(debug, int, 0);
+MODULE_PARM_DESC(debug,"Enable/disable extra messages");
+module_param(quartz, int, 0);
+MODULE_PARM_DESC(quartz,"If present, on-board quartz frequency (Hz)");
+
+/* Structures */
+
+struct thingie {
+	int define;
+	u32 bits;
+};
+
+struct TxFD {
+	u32 state;
+	u32 next;
+	u32 data;
+	u32 complete;
+	u32 jiffies; /* Allows sizeof(TxFD) == sizeof(RxFD) + extra hack */
+};
+
+struct RxFD {
+	u32 state1;
+	u32 next;
+	u32 data;
+	u32 state2;
+	u32 end;
+};
+
+#define DUMMY_SKB_SIZE		64
+#define TX_LOW			8
+#define TX_RING_SIZE		32
+#define RX_RING_SIZE		32
+#define TX_TOTAL_SIZE		TX_RING_SIZE*sizeof(struct TxFD)
+#define RX_TOTAL_SIZE		RX_RING_SIZE*sizeof(struct RxFD)
+#define IRQ_RING_SIZE		64		/* Keep it a multiple of 32 */
+#define TX_TIMEOUT		(HZ/10)
+#define DSCC4_HZ_MAX		33000000
+#define BRR_DIVIDER_MAX		64*0x00004000	/* Cf errata DS5 p.10 */
+#define dev_per_card		4
+#define SCC_REGISTERS_MAX	23		/* Cf errata DS5 p.4 */
+
+#define SOURCE_ID(flags)	(((flags) >> 28) & 0x03)
+#define TO_SIZE(state)		(((state) >> 16) & 0x1fff)
+
+/*
+ * Given the operating range of Linux HDLC, the 2 defines below could be
+ * made simpler. However they are a fine reminder for the limitations of
+ * the driver: it's better to stay < TxSizeMax and < RxSizeMax.
+ */
+#define TO_STATE_TX(len)	cpu_to_le32(((len) & TxSizeMax) << 16)
+#define TO_STATE_RX(len)	cpu_to_le32((RX_MAX(len) % RxSizeMax) << 16)
+#define RX_MAX(len)		((((len) >> 5) + 1) << 5)	/* Cf RLCR */
+#define SCC_REG_START(dpriv)	(SCC_START+(dpriv->dev_id)*SCC_OFFSET)
+
+struct dscc4_pci_priv {
+        u32 *iqcfg;
+        int cfg_cur;
+        spinlock_t lock;
+        struct pci_dev *pdev;
+
+        struct dscc4_dev_priv *root;
+        dma_addr_t iqcfg_dma;
+	u32 xtal_hz;
+};
+
+struct dscc4_dev_priv {
+        struct sk_buff *rx_skbuff[RX_RING_SIZE];
+        struct sk_buff *tx_skbuff[TX_RING_SIZE];
+
+        struct RxFD *rx_fd;
+        struct TxFD *tx_fd;
+        u32 *iqrx;
+        u32 *iqtx;
+
+	/* FIXME: check all the volatile are required */
+        volatile u32 tx_current;
+        u32 rx_current;
+        u32 iqtx_current;
+        u32 iqrx_current;
+
+        volatile u32 tx_dirty;
+        volatile u32 ltda;
+        u32 rx_dirty;
+        u32 lrda;
+
+        dma_addr_t tx_fd_dma;
+        dma_addr_t rx_fd_dma;
+        dma_addr_t iqtx_dma;
+        dma_addr_t iqrx_dma;
+
+	u32 scc_regs[SCC_REGISTERS_MAX]; /* Cf errata DS5 p.4 */
+
+	struct timer_list timer;
+
+        struct dscc4_pci_priv *pci_priv;
+        spinlock_t lock;
+
+        int dev_id;
+	volatile u32 flags;
+	u32 timer_help;
+
+	unsigned short encoding;
+	unsigned short parity;
+	struct net_device *dev;
+	sync_serial_settings settings;
+	void __iomem *base_addr;
+	u32 __pad __attribute__ ((aligned (4)));
+};
+
+/* GLOBAL registers definitions */
+#define GCMDR   0x00
+#define GSTAR   0x04
+#define GMODE   0x08
+#define IQLENR0 0x0C
+#define IQLENR1 0x10
+#define IQRX0   0x14
+#define IQTX0   0x24
+#define IQCFG   0x3c
+#define FIFOCR1 0x44
+#define FIFOCR2 0x48
+#define FIFOCR3 0x4c
+#define FIFOCR4 0x34
+#define CH0CFG  0x50
+#define CH0BRDA 0x54
+#define CH0BTDA 0x58
+#define CH0FRDA 0x98
+#define CH0FTDA 0xb0
+#define CH0LRDA 0xc8
+#define CH0LTDA 0xe0
+
+/* SCC registers definitions */
+#define SCC_START	0x0100
+#define SCC_OFFSET      0x80
+#define CMDR    0x00
+#define STAR    0x04
+#define CCR0    0x08
+#define CCR1    0x0c
+#define CCR2    0x10
+#define BRR     0x2C
+#define RLCR    0x40
+#define IMR     0x54
+#define ISR     0x58
+
+#define GPDIR	0x0400
+#define GPDATA	0x0404
+#define GPIM	0x0408
+
+/* Bit masks */
+#define EncodingMask	0x00700000
+#define CrcMask		0x00000003
+
+#define IntRxScc0	0x10000000
+#define IntTxScc0	0x01000000
+
+#define TxPollCmd	0x00000400
+#define RxActivate	0x08000000
+#define MTFi		0x04000000
+#define Rdr		0x00400000
+#define Rdt		0x00200000
+#define Idr		0x00100000
+#define Idt		0x00080000
+#define TxSccRes	0x01000000
+#define RxSccRes	0x00010000
+#define TxSizeMax	0x1fff		/* Datasheet DS1 - 11.1.1.1 */
+#define RxSizeMax	0x1ffc		/* Datasheet DS1 - 11.1.2.1 */
+
+#define Ccr0ClockMask	0x0000003f
+#define Ccr1LoopMask	0x00000200
+#define IsrMask		0x000fffff
+#define BrrExpMask	0x00000f00
+#define BrrMultMask	0x0000003f
+#define EncodingMask	0x00700000
+#define Hold		0x40000000
+#define SccBusy		0x10000000
+#define PowerUp		0x80000000
+#define Vis		0x00001000
+#define FrameOk		(FrameVfr | FrameCrc)
+#define FrameVfr	0x80
+#define FrameRdo	0x40
+#define FrameCrc	0x20
+#define FrameRab	0x10
+#define FrameAborted	0x00000200
+#define FrameEnd	0x80000000
+#define DataComplete	0x40000000
+#define LengthCheck	0x00008000
+#define SccEvt		0x02000000
+#define NoAck		0x00000200
+#define Action		0x00000001
+#define HiDesc		0x20000000
+
+/* SCC events */
+#define RxEvt		0xf0000000
+#define TxEvt		0x0f000000
+#define Alls		0x00040000
+#define Xdu		0x00010000
+#define Cts		0x00004000
+#define Xmr		0x00002000
+#define Xpr		0x00001000
+#define Rdo		0x00000080
+#define Rfs		0x00000040
+#define Cd		0x00000004
+#define Rfo		0x00000002
+#define Flex		0x00000001
+
+/* DMA core events */
+#define Cfg		0x00200000
+#define Hi		0x00040000
+#define Fi		0x00020000
+#define Err		0x00010000
+#define Arf		0x00000002
+#define ArAck		0x00000001
+
+/* State flags */
+#define Ready		0x00000000
+#define NeedIDR		0x00000001
+#define NeedIDT		0x00000002
+#define RdoSet		0x00000004
+#define FakeReset	0x00000008
+
+/* Don't mask RDO. Ever. */
+#ifdef DSCC4_POLLING
+#define EventsMask	0xfffeef7f
+#else
+#define EventsMask	0xfffa8f7a
+#endif
+
+/* Functions prototypes */
+static void dscc4_rx_irq(struct dscc4_pci_priv *, struct dscc4_dev_priv *);
+static void dscc4_tx_irq(struct dscc4_pci_priv *, struct dscc4_dev_priv *);
+static int dscc4_found1(struct pci_dev *, void __iomem *ioaddr);
+static int dscc4_init_one(struct pci_dev *, const struct pci_device_id *ent);
+static int dscc4_open(struct net_device *);
+static int dscc4_start_xmit(struct sk_buff *, struct net_device *);
+static int dscc4_close(struct net_device *);
+static int dscc4_ioctl(struct net_device *dev, struct ifreq *rq, int cmd);
+static int dscc4_init_ring(struct net_device *);
+static void dscc4_release_ring(struct dscc4_dev_priv *);
+static void dscc4_timer(unsigned long);
+static void dscc4_tx_timeout(struct net_device *);
+static irqreturn_t dscc4_irq(int irq, void *dev_id, struct pt_regs *ptregs);
+static int dscc4_hdlc_attach(struct net_device *, unsigned short, unsigned short);
+static int dscc4_set_iface(struct dscc4_dev_priv *, struct net_device *);
+#ifdef DSCC4_POLLING
+static int dscc4_tx_poll(struct dscc4_dev_priv *, struct net_device *);
+#endif
+
+static inline struct dscc4_dev_priv *dscc4_priv(struct net_device *dev)
+{
+	return dev_to_hdlc(dev)->priv;
+}
+
+static inline struct net_device *dscc4_to_dev(struct dscc4_dev_priv *p)
+{
+	return p->dev;
+}
+
+static void scc_patchl(u32 mask, u32 value, struct dscc4_dev_priv *dpriv,
+			struct net_device *dev, int offset)
+{
+	u32 state;
+
+	/* Cf scc_writel for concern regarding thread-safety */
+	state = dpriv->scc_regs[offset >> 2];
+	state &= ~mask;
+	state |= value;
+	dpriv->scc_regs[offset >> 2] = state;
+	writel(state, dpriv->base_addr + SCC_REG_START(dpriv) + offset);
+}
+
+static void scc_writel(u32 bits, struct dscc4_dev_priv *dpriv,
+		       struct net_device *dev, int offset)
+{
+	/*
+	 * Thread-UNsafe.
+	 * As of 2002/02/16, there are no thread racing for access.
+	 */
+	dpriv->scc_regs[offset >> 2] = bits;
+	writel(bits, dpriv->base_addr + SCC_REG_START(dpriv) + offset);
+}
+
+static inline u32 scc_readl(struct dscc4_dev_priv *dpriv, int offset)
+{
+	return dpriv->scc_regs[offset >> 2];
+}
+
+static u32 scc_readl_star(struct dscc4_dev_priv *dpriv, struct net_device *dev)
+{
+	/* Cf errata DS5 p.4 */
+	readl(dpriv->base_addr + SCC_REG_START(dpriv) + STAR);
+	return readl(dpriv->base_addr + SCC_REG_START(dpriv) + STAR);
+}
+
+static inline void dscc4_do_tx(struct dscc4_dev_priv *dpriv,
+			       struct net_device *dev)
+{
+	dpriv->ltda = dpriv->tx_fd_dma +
+                      ((dpriv->tx_current-1)%TX_RING_SIZE)*sizeof(struct TxFD);
+	writel(dpriv->ltda, dpriv->base_addr + CH0LTDA + dpriv->dev_id*4);
+	/* Flush posted writes *NOW* */
+	readl(dpriv->base_addr + CH0LTDA + dpriv->dev_id*4);
+}
+
+static inline void dscc4_rx_update(struct dscc4_dev_priv *dpriv,
+				   struct net_device *dev)
+{
+	dpriv->lrda = dpriv->rx_fd_dma +
+		      ((dpriv->rx_dirty - 1)%RX_RING_SIZE)*sizeof(struct RxFD);
+	writel(dpriv->lrda, dpriv->base_addr + CH0LRDA + dpriv->dev_id*4);
+}
+
+static inline unsigned int dscc4_tx_done(struct dscc4_dev_priv *dpriv)
+{
+	return dpriv->tx_current == dpriv->tx_dirty;
+}
+
+static inline unsigned int dscc4_tx_quiescent(struct dscc4_dev_priv *dpriv,
+					      struct net_device *dev)
+{
+	return readl(dpriv->base_addr + CH0FTDA + dpriv->dev_id*4) == dpriv->ltda;
+}
+
+int state_check(u32 state, struct dscc4_dev_priv *dpriv, struct net_device *dev,
+		const char *msg)
+{
+	int ret = 0;
+
+	if (debug > 1) {
+	if (SOURCE_ID(state) != dpriv->dev_id) {
+		printk(KERN_DEBUG "%s (%s): Source Id=%d, state=%08x\n",
+		       dev->name, msg, SOURCE_ID(state), state );
+			ret = -1;
+	}
+	if (state & 0x0df80c00) {
+		printk(KERN_DEBUG "%s (%s): state=%08x (UFO alert)\n",
+		       dev->name, msg, state);
+			ret = -1;
+	}
+	}
+	return ret;
+}
+
+void dscc4_tx_print(struct net_device *dev, struct dscc4_dev_priv *dpriv,
+		    char *msg)
+{
+	printk(KERN_DEBUG "%s: tx_current=%02d tx_dirty=%02d (%s)\n",
+	       dev->name, dpriv->tx_current, dpriv->tx_dirty, msg);
+}
+
+static void dscc4_release_ring(struct dscc4_dev_priv *dpriv)
+{
+	struct pci_dev *pdev = dpriv->pci_priv->pdev;
+	struct TxFD *tx_fd = dpriv->tx_fd;
+	struct RxFD *rx_fd = dpriv->rx_fd;
+	struct sk_buff **skbuff;
+	int i;
+
+	pci_free_consistent(pdev, TX_TOTAL_SIZE, tx_fd, dpriv->tx_fd_dma);
+	pci_free_consistent(pdev, RX_TOTAL_SIZE, rx_fd, dpriv->rx_fd_dma);
+
+	skbuff = dpriv->tx_skbuff;
+	for (i = 0; i < TX_RING_SIZE; i++) {
+		if (*skbuff) {
+			pci_unmap_single(pdev, tx_fd->data, (*skbuff)->len,
+				PCI_DMA_TODEVICE);
+			dev_kfree_skb(*skbuff);
+		}
+		skbuff++;
+		tx_fd++;
+	}
+
+	skbuff = dpriv->rx_skbuff;
+	for (i = 0; i < RX_RING_SIZE; i++) {
+		if (*skbuff) {
+			pci_unmap_single(pdev, rx_fd->data,
+				RX_MAX(HDLC_MAX_MRU), PCI_DMA_FROMDEVICE);
+			dev_kfree_skb(*skbuff);
+		}
+		skbuff++;
+		rx_fd++;
+	}
+}
+
+inline int try_get_rx_skb(struct dscc4_dev_priv *dpriv, struct net_device *dev)
+{
+	unsigned int dirty = dpriv->rx_dirty%RX_RING_SIZE;
+	struct RxFD *rx_fd = dpriv->rx_fd + dirty;
+	const int len = RX_MAX(HDLC_MAX_MRU);
+	struct sk_buff *skb;
+	int ret = 0;
+
+	skb = dev_alloc_skb(len);
+	dpriv->rx_skbuff[dirty] = skb;
+	if (skb) {
+		skb->protocol = hdlc_type_trans(skb, dev);
+		rx_fd->data = pci_map_single(dpriv->pci_priv->pdev, skb->data,
+					     len, PCI_DMA_FROMDEVICE);
+	} else {
+		rx_fd->data = (u32) NULL;
+		ret = -1;
+	}
+	return ret;
+}
+
+/*
+ * IRQ/thread/whatever safe
+ */
+static int dscc4_wait_ack_cec(struct dscc4_dev_priv *dpriv,
+			      struct net_device *dev, char *msg)
+{
+	s8 i = 0;
+
+	do {
+		if (!(scc_readl_star(dpriv, dev) & SccBusy)) {
+			printk(KERN_DEBUG "%s: %s ack (%d try)\n", dev->name,
+			       msg, i);
+			goto done;
+		}
+		set_current_state(TASK_UNINTERRUPTIBLE);
+		schedule_timeout(10);
+		rmb();
+	} while (++i > 0);
+	printk(KERN_ERR "%s: %s timeout\n", dev->name, msg);
+done:
+	return (i >= 0) ? i : -EAGAIN;
+}
+
+static int dscc4_do_action(struct net_device *dev, char *msg)
+{
+	void __iomem *ioaddr = dscc4_priv(dev)->base_addr;
+	s16 i = 0;
+
+	writel(Action, ioaddr + GCMDR);
+	ioaddr += GSTAR;
+	do {
+		u32 state = readl(ioaddr);
+
+		if (state & ArAck) {
+			printk(KERN_DEBUG "%s: %s ack\n", dev->name, msg);
+			writel(ArAck, ioaddr);
+			goto done;
+		} else if (state & Arf) {
+			printk(KERN_ERR "%s: %s failed\n", dev->name, msg);
+			writel(Arf, ioaddr);
+			i = -1;
+			goto done;
+	}
+		rmb();
+	} while (++i > 0);
+	printk(KERN_ERR "%s: %s timeout\n", dev->name, msg);
+done:
+	return i;
+}
+
+static inline int dscc4_xpr_ack(struct dscc4_dev_priv *dpriv)
+{
+	int cur = dpriv->iqtx_current%IRQ_RING_SIZE;
+	s8 i = 0;
+
+	do {
+		if (!(dpriv->flags & (NeedIDR | NeedIDT)) ||
+		    (dpriv->iqtx[cur] & Xpr))
+			break;
+		smp_rmb();
+		set_current_state(TASK_UNINTERRUPTIBLE);
+		schedule_timeout(10);
+	} while (++i > 0);
+
+	return (i >= 0 ) ? i : -EAGAIN;
+}
+
+#if 0 /* dscc4_{rx/tx}_reset are both unreliable - more tweak needed */
+static void dscc4_rx_reset(struct dscc4_dev_priv *dpriv, struct net_device *dev)
+{
+	unsigned long flags;
+
+	spin_lock_irqsave(&dpriv->pci_priv->lock, flags);
+	/* Cf errata DS5 p.6 */
+	writel(0x00000000, dpriv->base_addr + CH0LRDA + dpriv->dev_id*4);
+	scc_patchl(PowerUp, 0, dpriv, dev, CCR0);
+	readl(dpriv->base_addr + CH0LRDA + dpriv->dev_id*4);
+	writel(MTFi|Rdr, dpriv->base_addr + dpriv->dev_id*0x0c + CH0CFG);
+	writel(Action, dpriv->base_addr + GCMDR);
+	spin_unlock_irqrestore(&dpriv->pci_priv->lock, flags);
+}
+
+#endif
+
+#if 0
+static void dscc4_tx_reset(struct dscc4_dev_priv *dpriv, struct net_device *dev)
+{
+	u16 i = 0;
+
+	/* Cf errata DS5 p.7 */
+	scc_patchl(PowerUp, 0, dpriv, dev, CCR0);
+	scc_writel(0x00050000, dpriv, dev, CCR2);
+	/*
+	 * Must be longer than the time required to fill the fifo.
+	 */
+	while (!dscc4_tx_quiescent(dpriv, dev) && ++i) {
+		udelay(1);
+		wmb();
+	}
+
+	writel(MTFi|Rdt, dpriv->base_addr + dpriv->dev_id*0x0c + CH0CFG);
+	if (dscc4_do_action(dev, "Rdt") < 0)
+		printk(KERN_ERR "%s: Tx reset failed\n", dev->name);
+}
+#endif
+
+/* TODO: (ab)use this function to refill a completely depleted RX ring. */
+static inline void dscc4_rx_skb(struct dscc4_dev_priv *dpriv,
+				struct net_device *dev)
+{
+	struct RxFD *rx_fd = dpriv->rx_fd + dpriv->rx_current%RX_RING_SIZE;
+	struct net_device_stats *stats = hdlc_stats(dev);
+	struct pci_dev *pdev = dpriv->pci_priv->pdev;
+	struct sk_buff *skb;
+	int pkt_len;
+
+	skb = dpriv->rx_skbuff[dpriv->rx_current++%RX_RING_SIZE];
+	if (!skb) {
+		printk(KERN_DEBUG "%s: skb=0 (%s)\n", dev->name, __FUNCTION__);
+		goto refill;
+	}
+	pkt_len = TO_SIZE(rx_fd->state2);
+	pci_unmap_single(pdev, rx_fd->data, RX_MAX(HDLC_MAX_MRU), PCI_DMA_FROMDEVICE);
+	if ((skb->data[--pkt_len] & FrameOk) == FrameOk) {
+		stats->rx_packets++;
+		stats->rx_bytes += pkt_len;
+		skb_put(skb, pkt_len);
+		if (netif_running(dev))
+			skb->protocol = hdlc_type_trans(skb, dev);
+		skb->dev->last_rx = jiffies;
+		netif_rx(skb);
+	} else {
+		if (skb->data[pkt_len] & FrameRdo)
+			stats->rx_fifo_errors++;
+		else if (!(skb->data[pkt_len] | ~FrameCrc))
+			stats->rx_crc_errors++;
+		else if (!(skb->data[pkt_len] | ~(FrameVfr | FrameRab)))
+			stats->rx_length_errors++;
+		else
+			stats->rx_errors++;
+		dev_kfree_skb_irq(skb);
+	}
+refill:
+	while ((dpriv->rx_dirty - dpriv->rx_current) % RX_RING_SIZE) {
+		if (try_get_rx_skb(dpriv, dev) < 0)
+			break;
+		dpriv->rx_dirty++;
+	}
+	dscc4_rx_update(dpriv, dev);
+	rx_fd->state2 = 0x00000000;
+	rx_fd->end = 0xbabeface;
+}
+
+static void dscc4_free1(struct pci_dev *pdev)
+{
+	struct dscc4_pci_priv *ppriv;
+	struct dscc4_dev_priv *root;
+	int i;
+
+	ppriv = pci_get_drvdata(pdev);
+	root = ppriv->root;
+
+	for (i = 0; i < dev_per_card; i++)
+		unregister_hdlc_device(dscc4_to_dev(root + i));
+
+	pci_set_drvdata(pdev, NULL);
+
+	for (i = 0; i < dev_per_card; i++)
+		free_netdev(root[i].dev);
+	kfree(root);
+	kfree(ppriv);
+}
+
+static int __devinit dscc4_init_one(struct pci_dev *pdev,
+				  const struct pci_device_id *ent)
+{
+	struct dscc4_pci_priv *priv;
+	struct dscc4_dev_priv *dpriv;
+	void __iomem *ioaddr;
+	int i, rc;
+
+	printk(KERN_DEBUG "%s", version);
+
+	rc = pci_enable_device(pdev);
+	if (rc < 0)
+		goto out;
+
+	rc = pci_request_region(pdev, 0, "registers");
+	if (rc < 0) {
+	        printk(KERN_ERR "%s: can't reserve MMIO region (regs)\n",
+			DRV_NAME);
+	        goto err_disable_0;
+	}
+	rc = pci_request_region(pdev, 1, "LBI interface");
+	if (rc < 0) {
+	        printk(KERN_ERR "%s: can't reserve MMIO region (lbi)\n",
+			DRV_NAME);
+	        goto err_free_mmio_region_1;
+	}
+
+	ioaddr = ioremap(pci_resource_start(pdev, 0),
+					pci_resource_len(pdev, 0));
+	if (!ioaddr) {
+		printk(KERN_ERR "%s: cannot remap MMIO region %lx @ %lx\n",
+			DRV_NAME, pci_resource_len(pdev, 0),
+			pci_resource_start(pdev, 0));
+		rc = -EIO;
+		goto err_free_mmio_regions_2;
+	}
+	printk(KERN_DEBUG "Siemens DSCC4, MMIO at %#lx (regs), %#lx (lbi), IRQ %d\n",
+	        pci_resource_start(pdev, 0),
+	        pci_resource_start(pdev, 1), pdev->irq);
+
+	/* Cf errata DS5 p.2 */
+	pci_write_config_byte(pdev, PCI_LATENCY_TIMER, 0xf8);
+	pci_set_master(pdev);
+
+	rc = dscc4_found1(pdev, ioaddr);
+	if (rc < 0)
+	        goto err_iounmap_3;
+
+	priv = pci_get_drvdata(pdev);
+
+	rc = request_irq(pdev->irq, dscc4_irq, SA_SHIRQ, DRV_NAME, priv->root);
+	if (rc < 0) {
+		printk(KERN_WARNING "%s: IRQ %d busy\n", DRV_NAME, pdev->irq);
+		goto err_release_4;
+	}
+
+	/* power up/little endian/dma core controlled via lrda/ltda */
+	writel(0x00000001, ioaddr + GMODE);
+	/* Shared interrupt queue */
+	{
+		u32 bits;
+
+		bits = (IRQ_RING_SIZE >> 5) - 1;
+		bits |= bits << 4;
+		bits |= bits << 8;
+		bits |= bits << 16;
+		writel(bits, ioaddr + IQLENR0);
+	}
+	/* Global interrupt queue */
+	writel((u32)(((IRQ_RING_SIZE >> 5) - 1) << 20), ioaddr + IQLENR1);
+	priv->iqcfg = (u32 *) pci_alloc_consistent(pdev,
+		IRQ_RING_SIZE*sizeof(u32), &priv->iqcfg_dma);
+	if (!priv->iqcfg)
+		goto err_free_irq_5;
+	writel(priv->iqcfg_dma, ioaddr + IQCFG);
+
+	rc = -ENOMEM;
+
+	/*
+	 * SCC 0-3 private rx/tx irq structures
+	 * IQRX/TXi needs to be set soon. Learned it the hard way...
+	 */
+	for (i = 0; i < dev_per_card; i++) {
+		dpriv = priv->root + i;
+		dpriv->iqtx = (u32 *) pci_alloc_consistent(pdev,
+			IRQ_RING_SIZE*sizeof(u32), &dpriv->iqtx_dma);
+		if (!dpriv->iqtx)
+			goto err_free_iqtx_6;
+		writel(dpriv->iqtx_dma, ioaddr + IQTX0 + i*4);
+	}
+	for (i = 0; i < dev_per_card; i++) {
+		dpriv = priv->root + i;
+		dpriv->iqrx = (u32 *) pci_alloc_consistent(pdev,
+			IRQ_RING_SIZE*sizeof(u32), &dpriv->iqrx_dma);
+		if (!dpriv->iqrx)
+			goto err_free_iqrx_7;
+		writel(dpriv->iqrx_dma, ioaddr + IQRX0 + i*4);
+	}
+
+	/* Cf application hint. Beware of hard-lock condition on threshold. */
+	writel(0x42104000, ioaddr + FIFOCR1);
+	//writel(0x9ce69800, ioaddr + FIFOCR2);
+	writel(0xdef6d800, ioaddr + FIFOCR2);
+	//writel(0x11111111, ioaddr + FIFOCR4);
+	writel(0x18181818, ioaddr + FIFOCR4);
+	// FIXME: should depend on the chipset revision
+	writel(0x0000000e, ioaddr + FIFOCR3);
+
+	writel(0xff200001, ioaddr + GCMDR);
+
+	rc = 0;
+out:
+	return rc;
+
+err_free_iqrx_7:
+	while (--i >= 0) {
+		dpriv = priv->root + i;
+		pci_free_consistent(pdev, IRQ_RING_SIZE*sizeof(u32),
+				    dpriv->iqrx, dpriv->iqrx_dma);
+	}
+	i = dev_per_card;
+err_free_iqtx_6:
+	while (--i >= 0) {
+		dpriv = priv->root + i;
+		pci_free_consistent(pdev, IRQ_RING_SIZE*sizeof(u32),
+				    dpriv->iqtx, dpriv->iqtx_dma);
+	}
+	pci_free_consistent(pdev, IRQ_RING_SIZE*sizeof(u32), priv->iqcfg,
+			    priv->iqcfg_dma);
+err_free_irq_5:
+	free_irq(pdev->irq, priv->root);
+err_release_4:
+	dscc4_free1(pdev);
+err_iounmap_3:
+	iounmap (ioaddr);
+err_free_mmio_regions_2:
+	pci_release_region(pdev, 1);
+err_free_mmio_region_1:
+	pci_release_region(pdev, 0);
+err_disable_0:
+	pci_disable_device(pdev);
+	goto out;
+};
+
+/*
+ * Let's hope the default values are decent enough to protect my
+ * feet from the user's gun - Ueimor
+ */
+static void dscc4_init_registers(struct dscc4_dev_priv *dpriv,
+				 struct net_device *dev)
+{
+	/* No interrupts, SCC core disabled. Let's relax */
+	scc_writel(0x00000000, dpriv, dev, CCR0);
+
+	scc_writel(LengthCheck | (HDLC_MAX_MRU >> 5), dpriv, dev, RLCR);
+
+	/*
+	 * No address recognition/crc-CCITT/cts enabled
+	 * Shared flags transmission disabled - cf errata DS5 p.11
+	 * Carrier detect disabled - cf errata p.14
+	 * FIXME: carrier detection/polarity may be handled more gracefully.
+	 */
+	scc_writel(0x02408000, dpriv, dev, CCR1);
+
+	/* crc not forwarded - Cf errata DS5 p.11 */
+	scc_writel(0x00050008 & ~RxActivate, dpriv, dev, CCR2);
+	// crc forwarded
+	//scc_writel(0x00250008 & ~RxActivate, dpriv, dev, CCR2);
+}
+
+static inline int dscc4_set_quartz(struct dscc4_dev_priv *dpriv, int hz)
+{
+	int ret = 0;
+
+	if ((hz < 0) || (hz > DSCC4_HZ_MAX))
+		ret = -EOPNOTSUPP;
+	else
+		dpriv->pci_priv->xtal_hz = hz;
+
+	return ret;
+}
+
+static int dscc4_found1(struct pci_dev *pdev, void __iomem *ioaddr)
+{
+	struct dscc4_pci_priv *ppriv;
+	struct dscc4_dev_priv *root;
+	int i, ret = -ENOMEM;
+
+	root = kmalloc(dev_per_card*sizeof(*root), GFP_KERNEL);
+	if (!root) {
+		printk(KERN_ERR "%s: can't allocate data\n", DRV_NAME);
+		goto err_out;
+	}
+	memset(root, 0, dev_per_card*sizeof(*root));
+
+	for (i = 0; i < dev_per_card; i++) {
+		root[i].dev = alloc_hdlcdev(root + i);
+		if (!root[i].dev)
+			goto err_free_dev;
+	}
+
+	ppriv = kmalloc(sizeof(*ppriv), GFP_KERNEL);
+	if (!ppriv) {
+		printk(KERN_ERR "%s: can't allocate private data\n", DRV_NAME);
+		goto err_free_dev;
+	}
+	memset(ppriv, 0, sizeof(struct dscc4_pci_priv));
+
+	ppriv->root = root;
+	spin_lock_init(&ppriv->lock);
+
+	for (i = 0; i < dev_per_card; i++) {
+		struct dscc4_dev_priv *dpriv = root + i;
+		struct net_device *d = dscc4_to_dev(dpriv);
+		hdlc_device *hdlc = dev_to_hdlc(d);
+
+	        d->base_addr = (unsigned long)ioaddr;
+		d->init = NULL;
+	        d->irq = pdev->irq;
+	        d->open = dscc4_open;
+	        d->stop = dscc4_close;
+		d->set_multicast_list = NULL;
+	        d->do_ioctl = dscc4_ioctl;
+		d->tx_timeout = dscc4_tx_timeout;
+		d->watchdog_timeo = TX_TIMEOUT;
+		SET_MODULE_OWNER(d);
+		SET_NETDEV_DEV(d, &pdev->dev);
+
+		dpriv->dev_id = i;
+		dpriv->pci_priv = ppriv;
+		dpriv->base_addr = ioaddr;
+		spin_lock_init(&dpriv->lock);
+
+		hdlc->xmit = dscc4_start_xmit;
+		hdlc->attach = dscc4_hdlc_attach;
+
+		dscc4_init_registers(dpriv, d);
+		dpriv->parity = PARITY_CRC16_PR0_CCITT;
+		dpriv->encoding = ENCODING_NRZ;
+	
+		ret = dscc4_init_ring(d);
+		if (ret < 0)
+			goto err_unregister;
+
+		ret = register_hdlc_device(d);
+		if (ret < 0) {
+			printk(KERN_ERR "%s: unable to register\n", DRV_NAME);
+			dscc4_release_ring(dpriv);
+			goto err_unregister;
+	        }
+	}
+
+	ret = dscc4_set_quartz(root, quartz);
+	if (ret < 0)
+		goto err_unregister;
+
+	pci_set_drvdata(pdev, ppriv);
+	return ret;
+
+err_unregister:
+	while (i-- > 0) {
+		dscc4_release_ring(root + i);
+		unregister_hdlc_device(dscc4_to_dev(root + i));
+	}
+	kfree(ppriv);
+	i = dev_per_card;
+err_free_dev:
+	while (i-- > 0)
+		free_netdev(root[i].dev);
+	kfree(root);
+err_out:
+	return ret;
+};
+
+/* FIXME: get rid of the unneeded code */
+static void dscc4_timer(unsigned long data)
+{
+	struct net_device *dev = (struct net_device *)data;
+	struct dscc4_dev_priv *dpriv = dscc4_priv(dev);
+//	struct dscc4_pci_priv *ppriv;
+
+	goto done;
+done:
+        dpriv->timer.expires = jiffies + TX_TIMEOUT;
+        add_timer(&dpriv->timer);
+}
+
+static void dscc4_tx_timeout(struct net_device *dev)
+{
+	/* FIXME: something is missing there */
+}
+
+static int dscc4_loopback_check(struct dscc4_dev_priv *dpriv)
+{
+	sync_serial_settings *settings = &dpriv->settings;
+
+	if (settings->loopback && (settings->clock_type != CLOCK_INT)) {
+		struct net_device *dev = dscc4_to_dev(dpriv);
+
+		printk(KERN_INFO "%s: loopback requires clock\n", dev->name);
+		return -1;
+	}
+	return 0;
+}
+
+#ifdef CONFIG_DSCC4_PCI_RST
+/*
+ * Some DSCC4-based cards wires the GPIO port and the PCI #RST pin together
+ * so as to provide a safe way to reset the asic while not the whole machine
+ * rebooting.
+ *
+ * This code doesn't need to be efficient. Keep It Simple
+ */
+static void dscc4_pci_reset(struct pci_dev *pdev, void __iomem *ioaddr)
+{
+	int i;
+
+	down(&dscc4_sem);
+	for (i = 0; i < 16; i++)
+		pci_read_config_dword(pdev, i << 2, dscc4_pci_config_store + i);
+
+	/* Maximal LBI clock divider (who cares ?) and whole GPIO range. */
+	writel(0x001c0000, ioaddr + GMODE);
+	/* Configure GPIO port as output */
+	writel(0x0000ffff, ioaddr + GPDIR);
+	/* Disable interruption */
+	writel(0x0000ffff, ioaddr + GPIM);
+
+	writel(0x0000ffff, ioaddr + GPDATA);
+	writel(0x00000000, ioaddr + GPDATA);
+
+	/* Flush posted writes */
+	readl(ioaddr + GSTAR);
+
+	set_current_state(TASK_UNINTERRUPTIBLE);
+	schedule_timeout(10);
+
+	for (i = 0; i < 16; i++)
+		pci_write_config_dword(pdev, i << 2, dscc4_pci_config_store[i]);
+	up(&dscc4_sem);
+}
+#else
+#define dscc4_pci_reset(pdev,ioaddr)	do {} while (0)
+#endif /* CONFIG_DSCC4_PCI_RST */
+
+static int dscc4_open(struct net_device *dev)
+{
+	struct dscc4_dev_priv *dpriv = dscc4_priv(dev);
+	struct dscc4_pci_priv *ppriv;
+	int ret = -EAGAIN;
+
+	if ((dscc4_loopback_check(dpriv) < 0) || !dev->hard_start_xmit)
+		goto err;
+
+	if ((ret = hdlc_open(dev)))
+		goto err;
+
+	ppriv = dpriv->pci_priv;
+
+	/*
+	 * Due to various bugs, there is no way to reliably reset a
+	 * specific port (manufacturer's dependant special PCI #RST wiring
+	 * apart: it affects all ports). Thus the device goes in the best
+	 * silent mode possible at dscc4_close() time and simply claims to
+	 * be up if it's opened again. It still isn't possible to change
+	 * the HDLC configuration without rebooting but at least the ports
+	 * can be up/down ifconfig'ed without killing the host.
+	 */
+	if (dpriv->flags & FakeReset) {
+		dpriv->flags &= ~FakeReset;
+		scc_patchl(0, PowerUp, dpriv, dev, CCR0);
+		scc_patchl(0, 0x00050000, dpriv, dev, CCR2);
+		scc_writel(EventsMask, dpriv, dev, IMR);
+		printk(KERN_INFO "%s: up again.\n", dev->name);
+		goto done;
+	}
+
+	/* IDT+IDR during XPR */
+	dpriv->flags = NeedIDR | NeedIDT;
+
+	scc_patchl(0, PowerUp | Vis, dpriv, dev, CCR0);
+
+	/*
+	 * The following is a bit paranoid...
+	 *
+	 * NB: the datasheet "...CEC will stay active if the SCC is in
+	 * power-down mode or..." and CCR2.RAC = 1 are two different
+	 * situations.
+	 */
+	if (scc_readl_star(dpriv, dev) & SccBusy) {
+		printk(KERN_ERR "%s busy. Try later\n", dev->name);
+		ret = -EAGAIN;
+		goto err_out;
+	} else
+		printk(KERN_INFO "%s: available. Good\n", dev->name);
+
+	scc_writel(EventsMask, dpriv, dev, IMR);
+
+	/* Posted write is flushed in the wait_ack loop */
+	scc_writel(TxSccRes | RxSccRes, dpriv, dev, CMDR);
+
+	if ((ret = dscc4_wait_ack_cec(dpriv, dev, "Cec")) < 0)
+		goto err_disable_scc_events;
+
+	/*
+	 * I would expect XPR near CE completion (before ? after ?).
+	 * At worst, this code won't see a late XPR and people
+	 * will have to re-issue an ifconfig (this is harmless).
+	 * WARNING, a really missing XPR usually means a hardware
+	 * reset is needed. Suggestions anyone ?
+	 */
+	if ((ret = dscc4_xpr_ack(dpriv)) < 0) {
+		printk(KERN_ERR "%s: %s timeout\n", DRV_NAME, "XPR");
+		goto err_disable_scc_events;
+	}
+	
+	if (debug > 2)
+		dscc4_tx_print(dev, dpriv, "Open");
+
+done:
+	netif_start_queue(dev);
+
+        init_timer(&dpriv->timer);
+        dpriv->timer.expires = jiffies + 10*HZ;
+        dpriv->timer.data = (unsigned long)dev;
+        dpriv->timer.function = &dscc4_timer;
+        add_timer(&dpriv->timer);
+	netif_carrier_on(dev);
+
+	return 0;
+
+err_disable_scc_events:
+	scc_writel(0xffffffff, dpriv, dev, IMR);
+	scc_patchl(PowerUp | Vis, 0, dpriv, dev, CCR0);
+err_out:
+	hdlc_close(dev);
+err:
+	return ret;
+}
+
+#ifdef DSCC4_POLLING
+static int dscc4_tx_poll(struct dscc4_dev_priv *dpriv, struct net_device *dev)
+{
+	/* FIXME: it's gonna be easy (TM), for sure */
+}
+#endif /* DSCC4_POLLING */
+
+static int dscc4_start_xmit(struct sk_buff *skb, struct net_device *dev)
+{
+	struct dscc4_dev_priv *dpriv = dscc4_priv(dev);
+	struct dscc4_pci_priv *ppriv = dpriv->pci_priv;
+	struct TxFD *tx_fd;
+	int next;
+
+	next = dpriv->tx_current%TX_RING_SIZE;
+	dpriv->tx_skbuff[next] = skb;
+	tx_fd = dpriv->tx_fd + next;
+	tx_fd->state = FrameEnd | TO_STATE_TX(skb->len);
+	tx_fd->data = pci_map_single(ppriv->pdev, skb->data, skb->len,
+				     PCI_DMA_TODEVICE);
+	tx_fd->complete = 0x00000000;
+	tx_fd->jiffies = jiffies;
+	mb();
+
+#ifdef DSCC4_POLLING
+	spin_lock(&dpriv->lock);
+	while (dscc4_tx_poll(dpriv, dev));
+	spin_unlock(&dpriv->lock);
+#endif
+
+	dev->trans_start = jiffies;
+
+	if (debug > 2)
+		dscc4_tx_print(dev, dpriv, "Xmit");
+	/* To be cleaned(unsigned int)/optimized. Later, ok ? */
+	if (!((++dpriv->tx_current - dpriv->tx_dirty)%TX_RING_SIZE))
+		netif_stop_queue(dev);
+
+	if (dscc4_tx_quiescent(dpriv, dev))
+		dscc4_do_tx(dpriv, dev);
+
+	return 0;
+}
+
+static int dscc4_close(struct net_device *dev)
+{
+	struct dscc4_dev_priv *dpriv = dscc4_priv(dev);
+
+	del_timer_sync(&dpriv->timer);
+	netif_stop_queue(dev);
+
+	scc_patchl(PowerUp | Vis, 0, dpriv, dev, CCR0);
+	scc_patchl(0x00050000, 0, dpriv, dev, CCR2);
+	scc_writel(0xffffffff, dpriv, dev, IMR);
+
+	dpriv->flags |= FakeReset;
+
+	hdlc_close(dev);
+
+	return 0;
+}
+
+static inline int dscc4_check_clock_ability(int port)
+{
+	int ret = 0;
+
+#ifdef CONFIG_DSCC4_PCISYNC
+	if (port >= 2)
+		ret = -1;
+#endif
+	return ret;
+}
+
+/*
+ * DS1 p.137: "There are a total of 13 different clocking modes..."
+ *                                  ^^
+ * Design choices:
+ * - by default, assume a clock is provided on pin RxClk/TxClk (clock mode 0a).
+ *   Clock mode 3b _should_ work but the testing seems to make this point
+ *   dubious (DIY testing requires setting CCR0 at 0x00000033).
+ *   This is supposed to provide least surprise "DTE like" behavior.
+ * - if line rate is specified, clocks are assumed to be locally generated.
+ *   A quartz must be available (on pin XTAL1). Modes 6b/7b are used. Choosing
+ *   between these it automagically done according on the required frequency
+ *   scaling. Of course some rounding may take place.
+ * - no high speed mode (40Mb/s). May be trivial to do but I don't have an
+ *   appropriate external clocking device for testing.
+ * - no time-slot/clock mode 5: shameless lazyness.
+ *
+ * The clock signals wiring can be (is ?) manufacturer dependant. Good luck.
+ *
+ * BIG FAT WARNING: if the device isn't provided enough clocking signal, it
+ * won't pass the init sequence. For example, straight back-to-back DTE without
+ * external clock will fail when dscc4_open() (<- 'ifconfig hdlcx xxx') is
+ * called.
+ *
+ * Typos lurk in datasheet (missing divier in clock mode 7a figure 51 p.153
+ * DS0 for example)
+ *
+ * Clock mode related bits of CCR0:
+ *     +------------ TOE: output TxClk (0b/2b/3a/3b/6b/7a/7b only)
+ *     | +---------- SSEL: sub-mode select 0 -> a, 1 -> b
+ *     | | +-------- High Speed: say 0
+ *     | | | +-+-+-- Clock Mode: 0..7
+ *     | | | | | |
+ * -+-+-+-+-+-+-+-+
+ * x|x|5|4|3|2|1|0| lower bits
+ *
+ * Division factor of BRR: k = (N+1)x2^M (total divider = 16xk in mode 6b)
+ *            +-+-+-+------------------ M (0..15)
+ *            | | | |     +-+-+-+-+-+-- N (0..63)
+ *    0 0 0 0 | | | | 0 0 | | | | | |
+ * ...-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ *    f|e|d|c|b|a|9|8|7|6|5|4|3|2|1|0| lower bits
+ *
+ */
+static int dscc4_set_clock(struct net_device *dev, u32 *bps, u32 *state)
+{
+	struct dscc4_dev_priv *dpriv = dscc4_priv(dev);
+	int ret = -1;
+	u32 brr;
+
+	*state &= ~Ccr0ClockMask;
+	if (*bps) { /* Clock generated - required for DCE */
+		u32 n = 0, m = 0, divider;
+		int xtal;
+
+		xtal = dpriv->pci_priv->xtal_hz;
+		if (!xtal)
+			goto done;
+		if (dscc4_check_clock_ability(dpriv->dev_id) < 0)
+			goto done;
+		divider = xtal / *bps;
+		if (divider > BRR_DIVIDER_MAX) {
+			divider >>= 4;
+			*state |= 0x00000036; /* Clock mode 6b (BRG/16) */
+		} else
+			*state |= 0x00000037; /* Clock mode 7b (BRG) */
+		if (divider >> 22) {
+			n = 63;
+			m = 15;
+		} else if (divider) {
+			/* Extraction of the 6 highest weighted bits */
+			m = 0;
+			while (0xffffffc0 & divider) {
+				m++;
+				divider >>= 1;
+			}
+			n = divider;
+		}
+		brr = (m << 8) | n;
+		divider = n << m;
+		if (!(*state & 0x00000001)) /* ?b mode mask => clock mode 6b */
+			divider <<= 4;
+		*bps = xtal / divider;
+	} else {
+		/*
+		 * External clock - DTE
+		 * "state" already reflects Clock mode 0a (CCR0 = 0xzzzzzz00).
+		 * Nothing more to be done
+		 */
+		brr = 0;
+	}
+	scc_writel(brr, dpriv, dev, BRR);
+	ret = 0;
+done:
+	return ret;
+}
+
+static int dscc4_ioctl(struct net_device *dev, struct ifreq *ifr, int cmd)
+{
+	sync_serial_settings __user *line = ifr->ifr_settings.ifs_ifsu.sync;
+	struct dscc4_dev_priv *dpriv = dscc4_priv(dev);
+	const size_t size = sizeof(dpriv->settings);
+	int ret = 0;
+
+        if (dev->flags & IFF_UP)
+                return -EBUSY;
+
+	if (cmd != SIOCWANDEV)
+		return -EOPNOTSUPP;
+
+	switch(ifr->ifr_settings.type) {
+	case IF_GET_IFACE:
+		ifr->ifr_settings.type = IF_IFACE_SYNC_SERIAL;
+		if (ifr->ifr_settings.size < size) {
+			ifr->ifr_settings.size = size; /* data size wanted */
+			return -ENOBUFS;
+		}
+		if (copy_to_user(line, &dpriv->settings, size))
+			return -EFAULT;
+		break;
+
+	case IF_IFACE_SYNC_SERIAL:
+		if (!capable(CAP_NET_ADMIN))
+			return -EPERM;
+
+		if (dpriv->flags & FakeReset) {
+			printk(KERN_INFO "%s: please reset the device"
+			       " before this command\n", dev->name);
+			return -EPERM;
+		}
+		if (copy_from_user(&dpriv->settings, line, size))
+			return -EFAULT;
+		ret = dscc4_set_iface(dpriv, dev);
+		break;
+
+	default:
+		ret = hdlc_ioctl(dev, ifr, cmd);
+		break;
+	}
+
+	return ret;
+}
+
+static int dscc4_match(struct thingie *p, int value)
+{
+	int i;
+
+	for (i = 0; p[i].define != -1; i++) {
+		if (value == p[i].define)
+			break;
+	}
+	if (p[i].define == -1)
+		return -1;
+	else
+		return i;
+}
+
+static int dscc4_clock_setting(struct dscc4_dev_priv *dpriv,
+			       struct net_device *dev)
+{
+	sync_serial_settings *settings = &dpriv->settings;
+	int ret = -EOPNOTSUPP;
+	u32 bps, state;
+
+	bps = settings->clock_rate;
+	state = scc_readl(dpriv, CCR0);
+	if (dscc4_set_clock(dev, &bps, &state) < 0)
+		goto done;
+	if (bps) { /* DCE */
+		printk(KERN_DEBUG "%s: generated RxClk (DCE)\n", dev->name);
+		if (settings->clock_rate != bps) {
+			printk(KERN_DEBUG "%s: clock adjusted (%08d -> %08d)\n",
+				dev->name, settings->clock_rate, bps);
+			settings->clock_rate = bps;
+		}
+	} else { /* DTE */
+		state |= PowerUp | Vis;
+		printk(KERN_DEBUG "%s: external RxClk (DTE)\n", dev->name);
+	}
+	scc_writel(state, dpriv, dev, CCR0);
+	ret = 0;
+done:
+	return ret;
+}
+
+static int dscc4_encoding_setting(struct dscc4_dev_priv *dpriv,
+				  struct net_device *dev)
+{
+	struct thingie encoding[] = {
+		{ ENCODING_NRZ,		0x00000000 },
+		{ ENCODING_NRZI,	0x00200000 },
+		{ ENCODING_FM_MARK,	0x00400000 },
+		{ ENCODING_FM_SPACE,	0x00500000 },
+		{ ENCODING_MANCHESTER,	0x00600000 },
+		{ -1,			0}
+	};
+	int i, ret = 0;
+
+	i = dscc4_match(encoding, dpriv->encoding);
+	if (i >= 0)
+		scc_patchl(EncodingMask, encoding[i].bits, dpriv, dev, CCR0);
+	else
+		ret = -EOPNOTSUPP;
+	return ret;
+}
+
+static int dscc4_loopback_setting(struct dscc4_dev_priv *dpriv,
+				  struct net_device *dev)
+{
+	sync_serial_settings *settings = &dpriv->settings;
+	u32 state;
+
+	state = scc_readl(dpriv, CCR1);
+	if (settings->loopback) {
+		printk(KERN_DEBUG "%s: loopback\n", dev->name);
+		state |= 0x00000100;
+	} else {
+		printk(KERN_DEBUG "%s: normal\n", dev->name);
+		state &= ~0x00000100;
+	}
+	scc_writel(state, dpriv, dev, CCR1);
+	return 0;
+}
+
+static int dscc4_crc_setting(struct dscc4_dev_priv *dpriv,
+			     struct net_device *dev)
+{
+	struct thingie crc[] = {
+		{ PARITY_CRC16_PR0_CCITT,	0x00000010 },
+		{ PARITY_CRC16_PR1_CCITT,	0x00000000 },
+		{ PARITY_CRC32_PR0_CCITT,	0x00000011 },
+		{ PARITY_CRC32_PR1_CCITT,	0x00000001 }
+	};
+	int i, ret = 0;
+
+	i = dscc4_match(crc, dpriv->parity);
+	if (i >= 0)
+		scc_patchl(CrcMask, crc[i].bits, dpriv, dev, CCR1);
+	else
+		ret = -EOPNOTSUPP;
+	return ret;
+}
+
+static int dscc4_set_iface(struct dscc4_dev_priv *dpriv, struct net_device *dev)
+{
+	struct {
+		int (*action)(struct dscc4_dev_priv *, struct net_device *);
+	} *p, do_setting[] = {
+		{ dscc4_encoding_setting },
+		{ dscc4_clock_setting },
+		{ dscc4_loopback_setting },
+		{ dscc4_crc_setting },
+		{ NULL }
+	};
+	int ret = 0;
+
+	for (p = do_setting; p->action; p++) {
+		if ((ret = p->action(dpriv, dev)) < 0)
+			break;
+	}
+	return ret;
+}
+
+static irqreturn_t dscc4_irq(int irq, void *token, struct pt_regs *ptregs)
+{
+	struct dscc4_dev_priv *root = token;
+	struct dscc4_pci_priv *priv;
+	struct net_device *dev;
+	void __iomem *ioaddr;
+	u32 state;
+	unsigned long flags;
+	int i, handled = 1;
+
+	priv = root->pci_priv;
+	dev = dscc4_to_dev(root);
+
+	spin_lock_irqsave(&priv->lock, flags);
+
+	ioaddr = root->base_addr;
+
+	state = readl(ioaddr + GSTAR);
+	if (!state) {
+		handled = 0;
+		goto out;
+	}
+	if (debug > 3)
+		printk(KERN_DEBUG "%s: GSTAR = 0x%08x\n", DRV_NAME, state);
+	writel(state, ioaddr + GSTAR);
+
+	if (state & Arf) {
+		printk(KERN_ERR "%s: failure (Arf). Harass the maintener\n",
+		       dev->name);
+		goto out;
+	}
+	state &= ~ArAck;
+	if (state & Cfg) {
+		if (debug > 0)
+			printk(KERN_DEBUG "%s: CfgIV\n", DRV_NAME);
+		if (priv->iqcfg[priv->cfg_cur++%IRQ_RING_SIZE] & Arf)
+			printk(KERN_ERR "%s: %s failed\n", dev->name, "CFG");
+		if (!(state &= ~Cfg))
+			goto out;
+	}
+	if (state & RxEvt) {
+		i = dev_per_card - 1;
+		do {
+			dscc4_rx_irq(priv, root + i);
+		} while (--i >= 0);
+		state &= ~RxEvt;
+	}
+	if (state & TxEvt) {
+		i = dev_per_card - 1;
+		do {
+			dscc4_tx_irq(priv, root + i);
+		} while (--i >= 0);
+		state &= ~TxEvt;
+	}
+out:
+	spin_unlock_irqrestore(&priv->lock, flags);
+	return IRQ_RETVAL(handled);
+}
+
+static void dscc4_tx_irq(struct dscc4_pci_priv *ppriv,
+				struct dscc4_dev_priv *dpriv)
+{
+	struct net_device *dev = dscc4_to_dev(dpriv);
+	u32 state;
+	int cur, loop = 0;
+
+try:
+	cur = dpriv->iqtx_current%IRQ_RING_SIZE;
+	state = dpriv->iqtx[cur];
+	if (!state) {
+		if (debug > 4)
+			printk(KERN_DEBUG "%s: Tx ISR = 0x%08x\n", dev->name,
+			       state);
+		if ((debug > 1) && (loop > 1))
+			printk(KERN_DEBUG "%s: Tx irq loop=%d\n", dev->name, loop);
+		if (loop && netif_queue_stopped(dev))
+			if ((dpriv->tx_current - dpriv->tx_dirty)%TX_RING_SIZE)
+				netif_wake_queue(dev);
+
+		if (netif_running(dev) && dscc4_tx_quiescent(dpriv, dev) &&
+		    !dscc4_tx_done(dpriv))
+				dscc4_do_tx(dpriv, dev);
+		return;
+	}
+	loop++;
+	dpriv->iqtx[cur] = 0;
+	dpriv->iqtx_current++;
+
+	if (state_check(state, dpriv, dev, "Tx") < 0)
+		return;
+
+	if (state & SccEvt) {
+		if (state & Alls) {
+			struct net_device_stats *stats = hdlc_stats(dev);
+			struct sk_buff *skb;
+			struct TxFD *tx_fd;
+
+			if (debug > 2)
+				dscc4_tx_print(dev, dpriv, "Alls");
+			/*
+			 * DataComplete can't be trusted for Tx completion.
+			 * Cf errata DS5 p.8
+			 */
+			cur = dpriv->tx_dirty%TX_RING_SIZE;
+			tx_fd = dpriv->tx_fd + cur;
+			skb = dpriv->tx_skbuff[cur];
+			if (skb) {
+				pci_unmap_single(ppriv->pdev, tx_fd->data,
+						 skb->len, PCI_DMA_TODEVICE);
+				if (tx_fd->state & FrameEnd) {
+					stats->tx_packets++;
+					stats->tx_bytes += skb->len;
+				}
+				dev_kfree_skb_irq(skb);
+				dpriv->tx_skbuff[cur] = NULL;
+				++dpriv->tx_dirty;
+			} else {
+				if (debug > 1)
+					printk(KERN_ERR "%s Tx: NULL skb %d\n",
+						dev->name, cur);
+			}
+			/*
+			 * If the driver ends sending crap on the wire, it
+			 * will be way easier to diagnose than the (not so)
+			 * random freeze induced by null sized tx frames.
+			 */
+			tx_fd->data = tx_fd->next;
+			tx_fd->state = FrameEnd | TO_STATE_TX(2*DUMMY_SKB_SIZE);
+			tx_fd->complete = 0x00000000;
+			tx_fd->jiffies = 0;
+
+			if (!(state &= ~Alls))
+				goto try;
+		}
+		/*
+		 * Transmit Data Underrun
+		 */
+		if (state & Xdu) {
+			printk(KERN_ERR "%s: XDU. Ask maintainer\n", DRV_NAME);
+			dpriv->flags = NeedIDT;
+			/* Tx reset */
+			writel(MTFi | Rdt,
+			       dpriv->base_addr + 0x0c*dpriv->dev_id + CH0CFG);
+			writel(Action, dpriv->base_addr + GCMDR);
+			return;
+		}
+		if (state & Cts) {
+			printk(KERN_INFO "%s: CTS transition\n", dev->name);
+			if (!(state &= ~Cts)) /* DEBUG */
+				goto try;
+		}
+		if (state & Xmr) {
+			/* Frame needs to be sent again - FIXME */
+			printk(KERN_ERR "%s: Xmr. Ask maintainer\n", DRV_NAME);
+			if (!(state &= ~Xmr)) /* DEBUG */
+				goto try;
+		}
+		if (state & Xpr) {
+			void __iomem *scc_addr;
+			unsigned long ring;
+			int i;
+
+			/*
+			 * - the busy condition happens (sometimes);
+			 * - it doesn't seem to make the handler unreliable.
+			 */
+			for (i = 1; i; i <<= 1) {
+				if (!(scc_readl_star(dpriv, dev) & SccBusy))
+					break;
+			}
+			if (!i)
+				printk(KERN_INFO "%s busy in irq\n", dev->name);
+
+			scc_addr = dpriv->base_addr + 0x0c*dpriv->dev_id;
+			/* Keep this order: IDT before IDR */
+			if (dpriv->flags & NeedIDT) {
+				if (debug > 2)
+					dscc4_tx_print(dev, dpriv, "Xpr");
+				ring = dpriv->tx_fd_dma +
+				       (dpriv->tx_dirty%TX_RING_SIZE)*
+				       sizeof(struct TxFD);
+				writel(ring, scc_addr + CH0BTDA);
+				dscc4_do_tx(dpriv, dev);
+				writel(MTFi | Idt, scc_addr + CH0CFG);
+				if (dscc4_do_action(dev, "IDT") < 0)
+					goto err_xpr;
+				dpriv->flags &= ~NeedIDT;
+			}
+			if (dpriv->flags & NeedIDR) {
+				ring = dpriv->rx_fd_dma +
+				       (dpriv->rx_current%RX_RING_SIZE)*
+				       sizeof(struct RxFD);
+				writel(ring, scc_addr + CH0BRDA);
+				dscc4_rx_update(dpriv, dev);
+				writel(MTFi | Idr, scc_addr + CH0CFG);
+				if (dscc4_do_action(dev, "IDR") < 0)
+					goto err_xpr;
+				dpriv->flags &= ~NeedIDR;
+				smp_wmb();
+				/* Activate receiver and misc */
+				scc_writel(0x08050008, dpriv, dev, CCR2);
+			}
+		err_xpr:
+			if (!(state &= ~Xpr))
+				goto try;
+		}
+		if (state & Cd) {
+			if (debug > 0)
+				printk(KERN_INFO "%s: CD transition\n", dev->name);
+			if (!(state &= ~Cd)) /* DEBUG */
+				goto try;
+		}
+	} else { /* ! SccEvt */
+		if (state & Hi) {
+#ifdef DSCC4_POLLING
+			while (!dscc4_tx_poll(dpriv, dev));
+#endif
+			printk(KERN_INFO "%s: Tx Hi\n", dev->name);
+			state &= ~Hi;
+		}
+		if (state & Err) {
+			printk(KERN_INFO "%s: Tx ERR\n", dev->name);
+			hdlc_stats(dev)->tx_errors++;
+			state &= ~Err;
+		}
+	}
+	goto try;
+}
+
+static void dscc4_rx_irq(struct dscc4_pci_priv *priv,
+				    struct dscc4_dev_priv *dpriv)
+{
+	struct net_device *dev = dscc4_to_dev(dpriv);
+	u32 state;
+	int cur;
+
+try:
+	cur = dpriv->iqrx_current%IRQ_RING_SIZE;
+	state = dpriv->iqrx[cur];
+	if (!state)
+		return;
+	dpriv->iqrx[cur] = 0;
+	dpriv->iqrx_current++;
+
+	if (state_check(state, dpriv, dev, "Rx") < 0)
+		return;
+
+	if (!(state & SccEvt)){
+		struct RxFD *rx_fd;
+
+		if (debug > 4)
+			printk(KERN_DEBUG "%s: Rx ISR = 0x%08x\n", dev->name,
+			       state);
+		state &= 0x00ffffff;
+		if (state & Err) { /* Hold or reset */
+			printk(KERN_DEBUG "%s: Rx ERR\n", dev->name);
+			cur = dpriv->rx_current%RX_RING_SIZE;
+			rx_fd = dpriv->rx_fd + cur;
+			/*
+			 * Presume we're not facing a DMAC receiver reset.
+			 * As We use the rx size-filtering feature of the
+			 * DSCC4, the beginning of a new frame is waiting in
+			 * the rx fifo. I bet a Receive Data Overflow will
+			 * happen most of time but let's try and avoid it.
+			 * Btw (as for RDO) if one experiences ERR whereas
+			 * the system looks rather idle, there may be a
+			 * problem with latency. In this case, increasing
+			 * RX_RING_SIZE may help.
+			 */
+			//while (dpriv->rx_needs_refill) {
+				while (!(rx_fd->state1 & Hold)) {
+					rx_fd++;
+					cur++;
+					if (!(cur = cur%RX_RING_SIZE))
+						rx_fd = dpriv->rx_fd;
+				}
+				//dpriv->rx_needs_refill--;
+				try_get_rx_skb(dpriv, dev);
+				if (!rx_fd->data)
+					goto try;
+				rx_fd->state1 &= ~Hold;
+				rx_fd->state2 = 0x00000000;
+				rx_fd->end = 0xbabeface;
+			//}
+			goto try;
+		}
+		if (state & Fi) {
+			dscc4_rx_skb(dpriv, dev);
+			goto try;
+		}
+		if (state & Hi ) { /* HI bit */
+			printk(KERN_INFO "%s: Rx Hi\n", dev->name);
+			state &= ~Hi;
+			goto try;
+		}
+	} else { /* SccEvt */
+		if (debug > 1) {
+			//FIXME: verifier la presence de tous les evenements
+		static struct {
+			u32 mask;
+			const char *irq_name;
+		} evts[] = {
+			{ 0x00008000, "TIN"},
+			{ 0x00000020, "RSC"},
+			{ 0x00000010, "PCE"},
+			{ 0x00000008, "PLLA"},
+			{ 0, NULL}
+		}, *evt;
+
+		for (evt = evts; evt->irq_name; evt++) {
+			if (state & evt->mask) {
+					printk(KERN_DEBUG "%s: %s\n",
+						dev->name, evt->irq_name);
+				if (!(state &= ~evt->mask))
+					goto try;
+			}
+		}
+		} else {
+			if (!(state &= ~0x0000c03c))
+				goto try;
+		}
+		if (state & Cts) {
+			printk(KERN_INFO "%s: CTS transition\n", dev->name);
+			if (!(state &= ~Cts)) /* DEBUG */
+				goto try;
+		}
+		/*
+		 * Receive Data Overflow (FIXME: fscked)
+		 */
+		if (state & Rdo) {
+			struct RxFD *rx_fd;
+			void __iomem *scc_addr;
+			int cur;
+
+			//if (debug)
+			//	dscc4_rx_dump(dpriv);
+			scc_addr = dpriv->base_addr + 0x0c*dpriv->dev_id;
+
+			scc_patchl(RxActivate, 0, dpriv, dev, CCR2);
+			/*
+			 * This has no effect. Why ?
+			 * ORed with TxSccRes, one sees the CFG ack (for
+			 * the TX part only).
+			 */
+			scc_writel(RxSccRes, dpriv, dev, CMDR);
+			dpriv->flags |= RdoSet;
+
+			/*
+			 * Let's try and save something in the received data.
+			 * rx_current must be incremented at least once to
+			 * avoid HOLD in the BRDA-to-be-pointed desc.
+			 */
+			do {
+				cur = dpriv->rx_current++%RX_RING_SIZE;
+				rx_fd = dpriv->rx_fd + cur;
+				if (!(rx_fd->state2 & DataComplete))
+					break;
+				if (rx_fd->state2 & FrameAborted) {
+					hdlc_stats(dev)->rx_over_errors++;
+					rx_fd->state1 |= Hold;
+					rx_fd->state2 = 0x00000000;
+					rx_fd->end = 0xbabeface;
+				} else
+					dscc4_rx_skb(dpriv, dev);
+			} while (1);
+
+			if (debug > 0) {
+				if (dpriv->flags & RdoSet)
+					printk(KERN_DEBUG
+					       "%s: no RDO in Rx data\n", DRV_NAME);
+			}
+#ifdef DSCC4_RDO_EXPERIMENTAL_RECOVERY
+			/*
+			 * FIXME: must the reset be this violent ?
+			 */
+#warning "FIXME: CH0BRDA"
+			writel(dpriv->rx_fd_dma +
+			       (dpriv->rx_current%RX_RING_SIZE)*
+			       sizeof(struct RxFD), scc_addr + CH0BRDA);
+			writel(MTFi|Rdr|Idr, scc_addr + CH0CFG);
+			if (dscc4_do_action(dev, "RDR") < 0) {
+				printk(KERN_ERR "%s: RDO recovery failed(%s)\n",
+				       dev->name, "RDR");
+				goto rdo_end;
+			}
+			writel(MTFi|Idr, scc_addr + CH0CFG);
+			if (dscc4_do_action(dev, "IDR") < 0) {
+				printk(KERN_ERR "%s: RDO recovery failed(%s)\n",
+				       dev->name, "IDR");
+				goto rdo_end;
+			}
+		rdo_end:
+#endif
+			scc_patchl(0, RxActivate, dpriv, dev, CCR2);
+			goto try;
+		}
+		if (state & Cd) {
+			printk(KERN_INFO "%s: CD transition\n", dev->name);
+			if (!(state &= ~Cd)) /* DEBUG */
+				goto try;
+		}
+		if (state & Flex) {
+			printk(KERN_DEBUG "%s: Flex. Ttttt...\n", DRV_NAME);
+			if (!(state &= ~Flex))
+				goto try;
+		}
+	}
+}
+
+/*
+ * I had expected the following to work for the first descriptor
+ * (tx_fd->state = 0xc0000000)
+ * - Hold=1 (don't try and branch to the next descripto);
+ * - No=0 (I want an empty data section, i.e. size=0);
+ * - Fe=1 (required by No=0 or we got an Err irq and must reset).
+ * It failed and locked solid. Thus the introduction of a dummy skb.
+ * Problem is acknowledged in errata sheet DS5. Joy :o/
+ */
+struct sk_buff *dscc4_init_dummy_skb(struct dscc4_dev_priv *dpriv)
+{
+	struct sk_buff *skb;
+
+	skb = dev_alloc_skb(DUMMY_SKB_SIZE);
+	if (skb) {
+		int last = dpriv->tx_dirty%TX_RING_SIZE;
+		struct TxFD *tx_fd = dpriv->tx_fd + last;
+
+		skb->len = DUMMY_SKB_SIZE;
+		memcpy(skb->data, version, strlen(version)%DUMMY_SKB_SIZE);
+		tx_fd->state = FrameEnd | TO_STATE_TX(DUMMY_SKB_SIZE);
+		tx_fd->data = pci_map_single(dpriv->pci_priv->pdev, skb->data,
+					     DUMMY_SKB_SIZE, PCI_DMA_TODEVICE);
+		dpriv->tx_skbuff[last] = skb;
+	}
+	return skb;
+}
+
+static int dscc4_init_ring(struct net_device *dev)
+{
+	struct dscc4_dev_priv *dpriv = dscc4_priv(dev);
+	struct pci_dev *pdev = dpriv->pci_priv->pdev;
+	struct TxFD *tx_fd;
+	struct RxFD *rx_fd;
+	void *ring;
+	int i;
+
+	ring = pci_alloc_consistent(pdev, RX_TOTAL_SIZE, &dpriv->rx_fd_dma);
+	if (!ring)
+		goto err_out;
+	dpriv->rx_fd = rx_fd = (struct RxFD *) ring;
+
+	ring = pci_alloc_consistent(pdev, TX_TOTAL_SIZE, &dpriv->tx_fd_dma);
+	if (!ring)
+		goto err_free_dma_rx;
+	dpriv->tx_fd = tx_fd = (struct TxFD *) ring;
+
+	memset(dpriv->tx_skbuff, 0, sizeof(struct sk_buff *)*TX_RING_SIZE);
+	dpriv->tx_dirty = 0xffffffff;
+	i = dpriv->tx_current = 0;
+	do {
+		tx_fd->state = FrameEnd | TO_STATE_TX(2*DUMMY_SKB_SIZE);
+		tx_fd->complete = 0x00000000;
+	        /* FIXME: NULL should be ok - to be tried */
+	        tx_fd->data = dpriv->tx_fd_dma;
+		(tx_fd++)->next = (u32)(dpriv->tx_fd_dma +
+					(++i%TX_RING_SIZE)*sizeof(*tx_fd));
+	} while (i < TX_RING_SIZE);
+
+	if (dscc4_init_dummy_skb(dpriv) < 0)
+		goto err_free_dma_tx;
+
+	memset(dpriv->rx_skbuff, 0, sizeof(struct sk_buff *)*RX_RING_SIZE);
+	i = dpriv->rx_dirty = dpriv->rx_current = 0;
+	do {
+		/* size set by the host. Multiple of 4 bytes please */
+	        rx_fd->state1 = HiDesc;
+	        rx_fd->state2 = 0x00000000;
+	        rx_fd->end = 0xbabeface;
+	        rx_fd->state1 |= TO_STATE_RX(HDLC_MAX_MRU);
+		// FIXME: return value verifiee mais traitement suspect
+		if (try_get_rx_skb(dpriv, dev) >= 0)
+			dpriv->rx_dirty++;
+		(rx_fd++)->next = (u32)(dpriv->rx_fd_dma +
+					(++i%RX_RING_SIZE)*sizeof(*rx_fd));
+	} while (i < RX_RING_SIZE);
+
+	return 0;
+
+err_free_dma_tx:
+	pci_free_consistent(pdev, TX_TOTAL_SIZE, ring, dpriv->tx_fd_dma);
+err_free_dma_rx:
+	pci_free_consistent(pdev, RX_TOTAL_SIZE, rx_fd, dpriv->rx_fd_dma);
+err_out:
+	return -ENOMEM;
+}
+
+static void __devexit dscc4_remove_one(struct pci_dev *pdev)
+{
+	struct dscc4_pci_priv *ppriv;
+	struct dscc4_dev_priv *root;
+	void __iomem *ioaddr;
+	int i;
+
+	ppriv = pci_get_drvdata(pdev);
+	root = ppriv->root;
+
+	ioaddr = root->base_addr;
+
+	dscc4_pci_reset(pdev, ioaddr);
+
+	free_irq(pdev->irq, root);
+	pci_free_consistent(pdev, IRQ_RING_SIZE*sizeof(u32), ppriv->iqcfg,
+			    ppriv->iqcfg_dma);
+	for (i = 0; i < dev_per_card; i++) {
+		struct dscc4_dev_priv *dpriv = root + i;
+
+		dscc4_release_ring(dpriv);
+		pci_free_consistent(pdev, IRQ_RING_SIZE*sizeof(u32),
+				    dpriv->iqrx, dpriv->iqrx_dma);
+		pci_free_consistent(pdev, IRQ_RING_SIZE*sizeof(u32),
+				    dpriv->iqtx, dpriv->iqtx_dma);
+	}
+
+	dscc4_free1(pdev);
+
+	iounmap(ioaddr);
+
+	pci_release_region(pdev, 1);
+	pci_release_region(pdev, 0);
+
+	pci_disable_device(pdev);
+}
+
+static int dscc4_hdlc_attach(struct net_device *dev, unsigned short encoding,
+	unsigned short parity)
+{
+	struct dscc4_dev_priv *dpriv = dscc4_priv(dev);
+
+	if (encoding != ENCODING_NRZ &&
+	    encoding != ENCODING_NRZI &&
+	    encoding != ENCODING_FM_MARK &&
+	    encoding != ENCODING_FM_SPACE &&
+	    encoding != ENCODING_MANCHESTER)
+		return -EINVAL;
+
+	if (parity != PARITY_NONE &&
+	    parity != PARITY_CRC16_PR0_CCITT &&
+	    parity != PARITY_CRC16_PR1_CCITT &&
+	    parity != PARITY_CRC32_PR0_CCITT &&
+	    parity != PARITY_CRC32_PR1_CCITT)
+		return -EINVAL;
+
+        dpriv->encoding = encoding;
+        dpriv->parity = parity;
+	return 0;
+}
+
+#ifndef MODULE
+static int __init dscc4_setup(char *str)
+{
+	int *args[] = { &debug, &quartz, NULL }, **p = args;
+
+	while (*p && (get_option(&str, *p) == 2))
+		p++;
+	return 1;
+}
+
+__setup("dscc4.setup=", dscc4_setup);
+#endif
+
+static struct pci_device_id dscc4_pci_tbl[] = {
+	{ PCI_VENDOR_ID_SIEMENS, PCI_DEVICE_ID_SIEMENS_DSCC4,
+	        PCI_ANY_ID, PCI_ANY_ID, },
+	{ 0,}
+};
+MODULE_DEVICE_TABLE(pci, dscc4_pci_tbl);
+
+static struct pci_driver dscc4_driver = {
+	.name		= DRV_NAME,
+	.id_table	= dscc4_pci_tbl,
+	.probe		= dscc4_init_one,
+	.remove		= __devexit_p(dscc4_remove_one),
+};
+
+static int __init dscc4_init_module(void)
+{
+	return pci_module_init(&dscc4_driver);
+}
+
+static void __exit dscc4_cleanup_module(void)
+{
+	pci_unregister_driver(&dscc4_driver);
+}
+
+module_init(dscc4_init_module);
+module_exit(dscc4_cleanup_module);
diff --git a/drivers/net/wan/farsync.c b/drivers/net/wan/farsync.c
new file mode 100644
index 0000000..7575b79
--- /dev/null
+++ b/drivers/net/wan/farsync.c
@@ -0,0 +1,2712 @@
+/*
+ *      FarSync WAN driver for Linux (2.6.x kernel version)
+ *
+ *      Actually sync driver for X.21, V.35 and V.24 on FarSync T-series cards
+ *
+ *      Copyright (C) 2001-2004 FarSite Communications Ltd.
+ *      www.farsite.co.uk
+ *
+ *      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.
+ *
+ *      Author:      R.J.Dunlop    <bob.dunlop@farsite.co.uk>
+ *      Maintainer:  Kevin Curtis  <kevin.curtis@farsite.co.uk>
+ */
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/version.h>
+#include <linux/pci.h>
+#include <linux/ioport.h>
+#include <linux/init.h>
+#include <linux/if.h>
+#include <linux/hdlc.h>
+#include <asm/io.h>
+#include <asm/uaccess.h>
+
+#include "farsync.h"
+
+/*
+ *      Module info
+ */
+MODULE_AUTHOR("R.J.Dunlop <bob.dunlop@farsite.co.uk>");
+MODULE_DESCRIPTION("FarSync T-Series WAN driver. FarSite Communications Ltd.");
+MODULE_LICENSE("GPL");
+
+/*      Driver configuration and global parameters
+ *      ==========================================
+ */
+
+/*      Number of ports (per card) and cards supported
+ */
+#define FST_MAX_PORTS           4
+#define FST_MAX_CARDS           32
+
+/*      Default parameters for the link
+ */
+#define FST_TX_QUEUE_LEN        100	/* At 8Mbps a longer queue length is
+					 * useful, the syncppp module forces
+					 * this down assuming a slower line I
+					 * guess.
+					 */
+#define FST_TXQ_DEPTH           16	/* This one is for the buffering
+					 * of frames on the way down to the card
+					 * so that we can keep the card busy
+					 * and maximise throughput
+					 */
+#define FST_HIGH_WATER_MARK     12	/* Point at which we flow control
+					 * network layer */
+#define FST_LOW_WATER_MARK      8	/* Point at which we remove flow
+					 * control from network layer */
+#define FST_MAX_MTU             8000	/* Huge but possible */
+#define FST_DEF_MTU             1500	/* Common sane value */
+
+#define FST_TX_TIMEOUT          (2*HZ)
+
+#ifdef ARPHRD_RAWHDLC
+#define ARPHRD_MYTYPE   ARPHRD_RAWHDLC	/* Raw frames */
+#else
+#define ARPHRD_MYTYPE   ARPHRD_HDLC	/* Cisco-HDLC (keepalives etc) */
+#endif
+
+/*
+ * Modules parameters and associated varaibles
+ */
+int fst_txq_low = FST_LOW_WATER_MARK;
+int fst_txq_high = FST_HIGH_WATER_MARK;
+int fst_max_reads = 7;
+int fst_excluded_cards = 0;
+int fst_excluded_list[FST_MAX_CARDS];
+
+module_param(fst_txq_low, int, 0);
+module_param(fst_txq_high, int, 0);
+module_param(fst_max_reads, int, 0);
+module_param(fst_excluded_cards, int, 0);
+module_param_array(fst_excluded_list, int, NULL, 0);
+
+/*      Card shared memory layout
+ *      =========================
+ */
+#pragma pack(1)
+
+/*      This information is derived in part from the FarSite FarSync Smc.h
+ *      file. Unfortunately various name clashes and the non-portability of the
+ *      bit field declarations in that file have meant that I have chosen to
+ *      recreate the information here.
+ *
+ *      The SMC (Shared Memory Configuration) has a version number that is
+ *      incremented every time there is a significant change. This number can
+ *      be used to check that we have not got out of step with the firmware
+ *      contained in the .CDE files.
+ */
+#define SMC_VERSION 24
+
+#define FST_MEMSIZE 0x100000	/* Size of card memory (1Mb) */
+
+#define SMC_BASE 0x00002000L	/* Base offset of the shared memory window main
+				 * configuration structure */
+#define BFM_BASE 0x00010000L	/* Base offset of the shared memory window DMA
+				 * buffers */
+
+#define LEN_TX_BUFFER 8192	/* Size of packet buffers */
+#define LEN_RX_BUFFER 8192
+
+#define LEN_SMALL_TX_BUFFER 256	/* Size of obsolete buffs used for DOS diags */
+#define LEN_SMALL_RX_BUFFER 256
+
+#define NUM_TX_BUFFER 2		/* Must be power of 2. Fixed by firmware */
+#define NUM_RX_BUFFER 8
+
+/* Interrupt retry time in milliseconds */
+#define INT_RETRY_TIME 2
+
+/*      The Am186CH/CC processors support a SmartDMA mode using circular pools
+ *      of buffer descriptors. The structure is almost identical to that used
+ *      in the LANCE Ethernet controllers. Details available as PDF from the
+ *      AMD web site: http://www.amd.com/products/epd/processors/\
+ *                    2.16bitcont/3.am186cxfa/a21914/21914.pdf
+ */
+struct txdesc {			/* Transmit descriptor */
+	volatile u16 ladr;	/* Low order address of packet. This is a
+				 * linear address in the Am186 memory space
+				 */
+	volatile u8 hadr;	/* High order address. Low 4 bits only, high 4
+				 * bits must be zero
+				 */
+	volatile u8 bits;	/* Status and config */
+	volatile u16 bcnt;	/* 2s complement of packet size in low 15 bits.
+				 * Transmit terminal count interrupt enable in
+				 * top bit.
+				 */
+	u16 unused;		/* Not used in Tx */
+};
+
+struct rxdesc {			/* Receive descriptor */
+	volatile u16 ladr;	/* Low order address of packet */
+	volatile u8 hadr;	/* High order address */
+	volatile u8 bits;	/* Status and config */
+	volatile u16 bcnt;	/* 2s complement of buffer size in low 15 bits.
+				 * Receive terminal count interrupt enable in
+				 * top bit.
+				 */
+	volatile u16 mcnt;	/* Message byte count (15 bits) */
+};
+
+/* Convert a length into the 15 bit 2's complement */
+/* #define cnv_bcnt(len)   (( ~(len) + 1 ) & 0x7FFF ) */
+/* Since we need to set the high bit to enable the completion interrupt this
+ * can be made a lot simpler
+ */
+#define cnv_bcnt(len)   (-(len))
+
+/* Status and config bits for the above */
+#define DMA_OWN         0x80	/* SmartDMA owns the descriptor */
+#define TX_STP          0x02	/* Tx: start of packet */
+#define TX_ENP          0x01	/* Tx: end of packet */
+#define RX_ERR          0x40	/* Rx: error (OR of next 4 bits) */
+#define RX_FRAM         0x20	/* Rx: framing error */
+#define RX_OFLO         0x10	/* Rx: overflow error */
+#define RX_CRC          0x08	/* Rx: CRC error */
+#define RX_HBUF         0x04	/* Rx: buffer error */
+#define RX_STP          0x02	/* Rx: start of packet */
+#define RX_ENP          0x01	/* Rx: end of packet */
+
+/* Interrupts from the card are caused by various events which are presented
+ * in a circular buffer as several events may be processed on one physical int
+ */
+#define MAX_CIRBUFF     32
+
+struct cirbuff {
+	u8 rdindex;		/* read, then increment and wrap */
+	u8 wrindex;		/* write, then increment and wrap */
+	u8 evntbuff[MAX_CIRBUFF];
+};
+
+/* Interrupt event codes.
+ * Where appropriate the two low order bits indicate the port number
+ */
+#define CTLA_CHG        0x18	/* Control signal changed */
+#define CTLB_CHG        0x19
+#define CTLC_CHG        0x1A
+#define CTLD_CHG        0x1B
+
+#define INIT_CPLT       0x20	/* Initialisation complete */
+#define INIT_FAIL       0x21	/* Initialisation failed */
+
+#define ABTA_SENT       0x24	/* Abort sent */
+#define ABTB_SENT       0x25
+#define ABTC_SENT       0x26
+#define ABTD_SENT       0x27
+
+#define TXA_UNDF        0x28	/* Transmission underflow */
+#define TXB_UNDF        0x29
+#define TXC_UNDF        0x2A
+#define TXD_UNDF        0x2B
+
+#define F56_INT         0x2C
+#define M32_INT         0x2D
+
+#define TE1_ALMA        0x30
+
+/* Port physical configuration. See farsync.h for field values */
+struct port_cfg {
+	u16 lineInterface;	/* Physical interface type */
+	u8 x25op;		/* Unused at present */
+	u8 internalClock;	/* 1 => internal clock, 0 => external */
+	u8 transparentMode;	/* 1 => on, 0 => off */
+	u8 invertClock;		/* 0 => normal, 1 => inverted */
+	u8 padBytes[6];		/* Padding */
+	u32 lineSpeed;		/* Speed in bps */
+};
+
+/* TE1 port physical configuration */
+struct su_config {
+	u32 dataRate;
+	u8 clocking;
+	u8 framing;
+	u8 structure;
+	u8 interface;
+	u8 coding;
+	u8 lineBuildOut;
+	u8 equalizer;
+	u8 transparentMode;
+	u8 loopMode;
+	u8 range;
+	u8 txBufferMode;
+	u8 rxBufferMode;
+	u8 startingSlot;
+	u8 losThreshold;
+	u8 enableIdleCode;
+	u8 idleCode;
+	u8 spare[44];
+};
+
+/* TE1 Status */
+struct su_status {
+	u32 receiveBufferDelay;
+	u32 framingErrorCount;
+	u32 codeViolationCount;
+	u32 crcErrorCount;
+	u32 lineAttenuation;
+	u8 portStarted;
+	u8 lossOfSignal;
+	u8 receiveRemoteAlarm;
+	u8 alarmIndicationSignal;
+	u8 spare[40];
+};
+
+/* Finally sling all the above together into the shared memory structure.
+ * Sorry it's a hodge podge of arrays, structures and unused bits, it's been
+ * evolving under NT for some time so I guess we're stuck with it.
+ * The structure starts at offset SMC_BASE.
+ * See farsync.h for some field values.
+ */
+struct fst_shared {
+	/* DMA descriptor rings */
+	struct rxdesc rxDescrRing[FST_MAX_PORTS][NUM_RX_BUFFER];
+	struct txdesc txDescrRing[FST_MAX_PORTS][NUM_TX_BUFFER];
+
+	/* Obsolete small buffers */
+	u8 smallRxBuffer[FST_MAX_PORTS][NUM_RX_BUFFER][LEN_SMALL_RX_BUFFER];
+	u8 smallTxBuffer[FST_MAX_PORTS][NUM_TX_BUFFER][LEN_SMALL_TX_BUFFER];
+
+	u8 taskStatus;		/* 0x00 => initialising, 0x01 => running,
+				 * 0xFF => halted
+				 */
+
+	u8 interruptHandshake;	/* Set to 0x01 by adapter to signal interrupt,
+				 * set to 0xEE by host to acknowledge interrupt
+				 */
+
+	u16 smcVersion;		/* Must match SMC_VERSION */
+
+	u32 smcFirmwareVersion;	/* 0xIIVVRRBB where II = product ID, VV = major
+				 * version, RR = revision and BB = build
+				 */
+
+	u16 txa_done;		/* Obsolete completion flags */
+	u16 rxa_done;
+	u16 txb_done;
+	u16 rxb_done;
+	u16 txc_done;
+	u16 rxc_done;
+	u16 txd_done;
+	u16 rxd_done;
+
+	u16 mailbox[4];		/* Diagnostics mailbox. Not used */
+
+	struct cirbuff interruptEvent;	/* interrupt causes */
+
+	u32 v24IpSts[FST_MAX_PORTS];	/* V.24 control input status */
+	u32 v24OpSts[FST_MAX_PORTS];	/* V.24 control output status */
+
+	struct port_cfg portConfig[FST_MAX_PORTS];
+
+	u16 clockStatus[FST_MAX_PORTS];	/* lsb: 0=> present, 1=> absent */
+
+	u16 cableStatus;	/* lsb: 0=> present, 1=> absent */
+
+	u16 txDescrIndex[FST_MAX_PORTS];	/* transmit descriptor ring index */
+	u16 rxDescrIndex[FST_MAX_PORTS];	/* receive descriptor ring index */
+
+	u16 portMailbox[FST_MAX_PORTS][2];	/* command, modifier */
+	u16 cardMailbox[4];	/* Not used */
+
+	/* Number of times the card thinks the host has
+	 * missed an interrupt by not acknowledging
+	 * within 2mS (I guess NT has problems)
+	 */
+	u32 interruptRetryCount;
+
+	/* Driver private data used as an ID. We'll not
+	 * use this as I'd rather keep such things
+	 * in main memory rather than on the PCI bus
+	 */
+	u32 portHandle[FST_MAX_PORTS];
+
+	/* Count of Tx underflows for stats */
+	u32 transmitBufferUnderflow[FST_MAX_PORTS];
+
+	/* Debounced V.24 control input status */
+	u32 v24DebouncedSts[FST_MAX_PORTS];
+
+	/* Adapter debounce timers. Don't touch */
+	u32 ctsTimer[FST_MAX_PORTS];
+	u32 ctsTimerRun[FST_MAX_PORTS];
+	u32 dcdTimer[FST_MAX_PORTS];
+	u32 dcdTimerRun[FST_MAX_PORTS];
+
+	u32 numberOfPorts;	/* Number of ports detected at startup */
+
+	u16 _reserved[64];
+
+	u16 cardMode;		/* Bit-mask to enable features:
+				 * Bit 0: 1 enables LED identify mode
+				 */
+
+	u16 portScheduleOffset;
+
+	struct su_config suConfig;	/* TE1 Bits */
+	struct su_status suStatus;
+
+	u32 endOfSmcSignature;	/* endOfSmcSignature MUST be the last member of
+				 * the structure and marks the end of shared
+				 * memory. Adapter code initializes it as
+				 * END_SIG.
+				 */
+};
+
+/* endOfSmcSignature value */
+#define END_SIG                 0x12345678
+
+/* Mailbox values. (portMailbox) */
+#define NOP             0	/* No operation */
+#define ACK             1	/* Positive acknowledgement to PC driver */
+#define NAK             2	/* Negative acknowledgement to PC driver */
+#define STARTPORT       3	/* Start an HDLC port */
+#define STOPPORT        4	/* Stop an HDLC port */
+#define ABORTTX         5	/* Abort the transmitter for a port */
+#define SETV24O         6	/* Set V24 outputs */
+
+/* PLX Chip Register Offsets */
+#define CNTRL_9052      0x50	/* Control Register */
+#define CNTRL_9054      0x6c	/* Control Register */
+
+#define INTCSR_9052     0x4c	/* Interrupt control/status register */
+#define INTCSR_9054     0x68	/* Interrupt control/status register */
+
+/* 9054 DMA Registers */
+/*
+ * Note that we will be using DMA Channel 0 for copying rx data
+ * and Channel 1 for copying tx data
+ */
+#define DMAMODE0        0x80
+#define DMAPADR0        0x84
+#define DMALADR0        0x88
+#define DMASIZ0         0x8c
+#define DMADPR0         0x90
+#define DMAMODE1        0x94
+#define DMAPADR1        0x98
+#define DMALADR1        0x9c
+#define DMASIZ1         0xa0
+#define DMADPR1         0xa4
+#define DMACSR0         0xa8
+#define DMACSR1         0xa9
+#define DMAARB          0xac
+#define DMATHR          0xb0
+#define DMADAC0         0xb4
+#define DMADAC1         0xb8
+#define DMAMARBR        0xac
+
+#define FST_MIN_DMA_LEN 64
+#define FST_RX_DMA_INT  0x01
+#define FST_TX_DMA_INT  0x02
+#define FST_CARD_INT    0x04
+
+/* Larger buffers are positioned in memory at offset BFM_BASE */
+struct buf_window {
+	u8 txBuffer[FST_MAX_PORTS][NUM_TX_BUFFER][LEN_TX_BUFFER];
+	u8 rxBuffer[FST_MAX_PORTS][NUM_RX_BUFFER][LEN_RX_BUFFER];
+};
+
+/* Calculate offset of a buffer object within the shared memory window */
+#define BUF_OFFSET(X)   (BFM_BASE + offsetof(struct buf_window, X))
+
+#pragma pack()
+
+/*      Device driver private information
+ *      =================================
+ */
+/*      Per port (line or channel) information
+ */
+struct fst_port_info {
+        struct net_device *dev; /* Device struct - must be first */
+	struct fst_card_info *card;	/* Card we're associated with */
+	int index;		/* Port index on the card */
+	int hwif;		/* Line hardware (lineInterface copy) */
+	int run;		/* Port is running */
+	int mode;		/* Normal or FarSync raw */
+	int rxpos;		/* Next Rx buffer to use */
+	int txpos;		/* Next Tx buffer to use */
+	int txipos;		/* Next Tx buffer to check for free */
+	int start;		/* Indication of start/stop to network */
+	/*
+	 * A sixteen entry transmit queue
+	 */
+	int txqs;		/* index to get next buffer to tx */
+	int txqe;		/* index to queue next packet */
+	struct sk_buff *txq[FST_TXQ_DEPTH];	/* The queue */
+	int rxqdepth;
+};
+
+/*      Per card information
+ */
+struct fst_card_info {
+	char __iomem *mem;	/* Card memory mapped to kernel space */
+	char __iomem *ctlmem;	/* Control memory for PCI cards */
+	unsigned int phys_mem;	/* Physical memory window address */
+	unsigned int phys_ctlmem;	/* Physical control memory address */
+	unsigned int irq;	/* Interrupt request line number */
+	unsigned int nports;	/* Number of serial ports */
+	unsigned int type;	/* Type index of card */
+	unsigned int state;	/* State of card */
+	spinlock_t card_lock;	/* Lock for SMP access */
+	unsigned short pci_conf;	/* PCI card config in I/O space */
+	/* Per port info */
+	struct fst_port_info ports[FST_MAX_PORTS];
+	struct pci_dev *device;	/* Information about the pci device */
+	int card_no;		/* Inst of the card on the system */
+	int family;		/* TxP or TxU */
+	int dmarx_in_progress;
+	int dmatx_in_progress;
+	unsigned long int_count;
+	unsigned long int_time_ave;
+	void *rx_dma_handle_host;
+	dma_addr_t rx_dma_handle_card;
+	void *tx_dma_handle_host;
+	dma_addr_t tx_dma_handle_card;
+	struct sk_buff *dma_skb_rx;
+	struct fst_port_info *dma_port_rx;
+	struct fst_port_info *dma_port_tx;
+	int dma_len_rx;
+	int dma_len_tx;
+	int dma_txpos;
+	int dma_rxpos;
+};
+
+/* Convert an HDLC device pointer into a port info pointer and similar */
+#define dev_to_port(D)  (dev_to_hdlc(D)->priv)
+#define port_to_dev(P)  ((P)->dev)
+
+
+/*
+ *      Shared memory window access macros
+ *
+ *      We have a nice memory based structure above, which could be directly
+ *      mapped on i386 but might not work on other architectures unless we use
+ *      the readb,w,l and writeb,w,l macros. Unfortunately these macros take
+ *      physical offsets so we have to convert. The only saving grace is that
+ *      this should all collapse back to a simple indirection eventually.
+ */
+#define WIN_OFFSET(X)   ((long)&(((struct fst_shared *)SMC_BASE)->X))
+
+#define FST_RDB(C,E)    readb ((C)->mem + WIN_OFFSET(E))
+#define FST_RDW(C,E)    readw ((C)->mem + WIN_OFFSET(E))
+#define FST_RDL(C,E)    readl ((C)->mem + WIN_OFFSET(E))
+
+#define FST_WRB(C,E,B)  writeb ((B), (C)->mem + WIN_OFFSET(E))
+#define FST_WRW(C,E,W)  writew ((W), (C)->mem + WIN_OFFSET(E))
+#define FST_WRL(C,E,L)  writel ((L), (C)->mem + WIN_OFFSET(E))
+
+/*
+ *      Debug support
+ */
+#if FST_DEBUG
+
+static int fst_debug_mask = { FST_DEBUG };
+
+/* Most common debug activity is to print something if the corresponding bit
+ * is set in the debug mask. Note: this uses a non-ANSI extension in GCC to
+ * support variable numbers of macro parameters. The inverted if prevents us
+ * eating someone else's else clause.
+ */
+#define dbg(F,fmt,A...) if ( ! ( fst_debug_mask & (F))) \
+                                ; \
+                        else \
+                                printk ( KERN_DEBUG FST_NAME ": " fmt, ## A )
+
+#else
+#define dbg(X...)		/* NOP */
+#endif
+
+/*      Printing short cuts
+ */
+#define printk_err(fmt,A...)    printk ( KERN_ERR     FST_NAME ": " fmt, ## A )
+#define printk_warn(fmt,A...)   printk ( KERN_WARNING FST_NAME ": " fmt, ## A )
+#define printk_info(fmt,A...)   printk ( KERN_INFO    FST_NAME ": " fmt, ## A )
+
+/*
+ *      PCI ID lookup table
+ */
+static struct pci_device_id fst_pci_dev_id[] __devinitdata = {
+	{PCI_VENDOR_ID_FARSITE, PCI_DEVICE_ID_FARSITE_T2P, PCI_ANY_ID, 
+	 PCI_ANY_ID, 0, 0, FST_TYPE_T2P},
+
+	{PCI_VENDOR_ID_FARSITE, PCI_DEVICE_ID_FARSITE_T4P, PCI_ANY_ID, 
+	 PCI_ANY_ID, 0, 0, FST_TYPE_T4P},
+
+	{PCI_VENDOR_ID_FARSITE, PCI_DEVICE_ID_FARSITE_T1U, PCI_ANY_ID, 
+	 PCI_ANY_ID, 0, 0, FST_TYPE_T1U},
+
+	{PCI_VENDOR_ID_FARSITE, PCI_DEVICE_ID_FARSITE_T2U, PCI_ANY_ID, 
+	 PCI_ANY_ID, 0, 0, FST_TYPE_T2U},
+
+	{PCI_VENDOR_ID_FARSITE, PCI_DEVICE_ID_FARSITE_T4U, PCI_ANY_ID, 
+	 PCI_ANY_ID, 0, 0, FST_TYPE_T4U},
+
+	{PCI_VENDOR_ID_FARSITE, PCI_DEVICE_ID_FARSITE_TE1, PCI_ANY_ID, 
+	 PCI_ANY_ID, 0, 0, FST_TYPE_TE1},
+
+	{PCI_VENDOR_ID_FARSITE, PCI_DEVICE_ID_FARSITE_TE1C, PCI_ANY_ID, 
+	 PCI_ANY_ID, 0, 0, FST_TYPE_TE1},
+	{0,}			/* End */
+};
+
+MODULE_DEVICE_TABLE(pci, fst_pci_dev_id);
+
+/*
+ *      Device Driver Work Queues
+ *
+ *      So that we don't spend too much time processing events in the 
+ *      Interrupt Service routine, we will declare a work queue per Card 
+ *      and make the ISR schedule a task in the queue for later execution.
+ *      In the 2.4 Kernel we used to use the immediate queue for BH's
+ *      Now that they are gone, tasklets seem to be much better than work 
+ *      queues.
+ */
+
+static void do_bottom_half_tx(struct fst_card_info *card);
+static void do_bottom_half_rx(struct fst_card_info *card);
+static void fst_process_tx_work_q(unsigned long work_q);
+static void fst_process_int_work_q(unsigned long work_q);
+
+DECLARE_TASKLET(fst_tx_task, fst_process_tx_work_q, 0);
+DECLARE_TASKLET(fst_int_task, fst_process_int_work_q, 0);
+
+struct fst_card_info *fst_card_array[FST_MAX_CARDS];
+spinlock_t fst_work_q_lock;
+u64 fst_work_txq;
+u64 fst_work_intq;
+
+static void
+fst_q_work_item(u64 * queue, int card_index)
+{
+	unsigned long flags;
+	u64 mask;
+
+	/*
+	 * Grab the queue exclusively
+	 */
+	spin_lock_irqsave(&fst_work_q_lock, flags);
+
+	/*
+	 * Making an entry in the queue is simply a matter of setting
+	 * a bit for the card indicating that there is work to do in the
+	 * bottom half for the card.  Note the limitation of 64 cards.
+	 * That ought to be enough
+	 */
+	mask = 1 << card_index;
+	*queue |= mask;
+	spin_unlock_irqrestore(&fst_work_q_lock, flags);
+}
+
+static void
+fst_process_tx_work_q(unsigned long /*void **/work_q)
+{
+	unsigned long flags;
+	u64 work_txq;
+	int i;
+
+	/*
+	 * Grab the queue exclusively
+	 */
+	dbg(DBG_TX, "fst_process_tx_work_q\n");
+	spin_lock_irqsave(&fst_work_q_lock, flags);
+	work_txq = fst_work_txq;
+	fst_work_txq = 0;
+	spin_unlock_irqrestore(&fst_work_q_lock, flags);
+
+	/*
+	 * Call the bottom half for each card with work waiting
+	 */
+	for (i = 0; i < FST_MAX_CARDS; i++) {
+		if (work_txq & 0x01) {
+			if (fst_card_array[i] != NULL) {
+				dbg(DBG_TX, "Calling tx bh for card %d\n", i);
+				do_bottom_half_tx(fst_card_array[i]);
+			}
+		}
+		work_txq = work_txq >> 1;
+	}
+}
+
+static void
+fst_process_int_work_q(unsigned long /*void **/work_q)
+{
+	unsigned long flags;
+	u64 work_intq;
+	int i;
+
+	/*
+	 * Grab the queue exclusively
+	 */
+	dbg(DBG_INTR, "fst_process_int_work_q\n");
+	spin_lock_irqsave(&fst_work_q_lock, flags);
+	work_intq = fst_work_intq;
+	fst_work_intq = 0;
+	spin_unlock_irqrestore(&fst_work_q_lock, flags);
+
+	/*
+	 * Call the bottom half for each card with work waiting
+	 */
+	for (i = 0; i < FST_MAX_CARDS; i++) {
+		if (work_intq & 0x01) {
+			if (fst_card_array[i] != NULL) {
+				dbg(DBG_INTR,
+				    "Calling rx & tx bh for card %d\n", i);
+				do_bottom_half_rx(fst_card_array[i]);
+				do_bottom_half_tx(fst_card_array[i]);
+			}
+		}
+		work_intq = work_intq >> 1;
+	}
+}
+
+/*      Card control functions
+ *      ======================
+ */
+/*      Place the processor in reset state
+ *
+ * Used to be a simple write to card control space but a glitch in the latest
+ * AMD Am186CH processor means that we now have to do it by asserting and de-
+ * asserting the PLX chip PCI Adapter Software Reset. Bit 30 in CNTRL register
+ * at offset 9052_CNTRL.  Note the updates for the TXU.
+ */
+static inline void
+fst_cpureset(struct fst_card_info *card)
+{
+	unsigned char interrupt_line_register;
+	unsigned long j = jiffies + 1;
+	unsigned int regval;
+
+	if (card->family == FST_FAMILY_TXU) {
+		if (pci_read_config_byte
+		    (card->device, PCI_INTERRUPT_LINE, &interrupt_line_register)) {
+			dbg(DBG_ASS,
+			    "Error in reading interrupt line register\n");
+		}
+		/*
+		 * Assert PLX software reset and Am186 hardware reset
+		 * and then deassert the PLX software reset but 186 still in reset
+		 */
+		outw(0x440f, card->pci_conf + CNTRL_9054 + 2);
+		outw(0x040f, card->pci_conf + CNTRL_9054 + 2);
+		/*
+		 * We are delaying here to allow the 9054 to reset itself
+		 */
+		j = jiffies + 1;
+		while (jiffies < j)
+			/* Do nothing */ ;
+		outw(0x240f, card->pci_conf + CNTRL_9054 + 2);
+		/*
+		 * We are delaying here to allow the 9054 to reload its eeprom
+		 */
+		j = jiffies + 1;
+		while (jiffies < j)
+			/* Do nothing */ ;
+		outw(0x040f, card->pci_conf + CNTRL_9054 + 2);
+
+		if (pci_write_config_byte
+		    (card->device, PCI_INTERRUPT_LINE, interrupt_line_register)) {
+			dbg(DBG_ASS,
+			    "Error in writing interrupt line register\n");
+		}
+
+	} else {
+		regval = inl(card->pci_conf + CNTRL_9052);
+
+		outl(regval | 0x40000000, card->pci_conf + CNTRL_9052);
+		outl(regval & ~0x40000000, card->pci_conf + CNTRL_9052);
+	}
+}
+
+/*      Release the processor from reset
+ */
+static inline void
+fst_cpurelease(struct fst_card_info *card)
+{
+	if (card->family == FST_FAMILY_TXU) {
+		/*
+		 * Force posted writes to complete
+		 */
+		(void) readb(card->mem);
+
+		/*
+		 * Release LRESET DO = 1
+		 * Then release Local Hold, DO = 1
+		 */
+		outw(0x040e, card->pci_conf + CNTRL_9054 + 2);
+		outw(0x040f, card->pci_conf + CNTRL_9054 + 2);
+	} else {
+		(void) readb(card->ctlmem);
+	}
+}
+
+/*      Clear the cards interrupt flag
+ */
+static inline void
+fst_clear_intr(struct fst_card_info *card)
+{
+	if (card->family == FST_FAMILY_TXU) {
+		(void) readb(card->ctlmem);
+	} else {
+		/* Poke the appropriate PLX chip register (same as enabling interrupts)
+		 */
+		outw(0x0543, card->pci_conf + INTCSR_9052);
+	}
+}
+
+/*      Enable card interrupts
+ */
+static inline void
+fst_enable_intr(struct fst_card_info *card)
+{
+	if (card->family == FST_FAMILY_TXU) {
+		outl(0x0f0c0900, card->pci_conf + INTCSR_9054);
+	} else {
+		outw(0x0543, card->pci_conf + INTCSR_9052);
+	}
+}
+
+/*      Disable card interrupts
+ */
+static inline void
+fst_disable_intr(struct fst_card_info *card)
+{
+	if (card->family == FST_FAMILY_TXU) {
+		outl(0x00000000, card->pci_conf + INTCSR_9054);
+	} else {
+		outw(0x0000, card->pci_conf + INTCSR_9052);
+	}
+}
+
+/*      Process the result of trying to pass a received frame up the stack
+ */
+static void
+fst_process_rx_status(int rx_status, char *name)
+{
+	switch (rx_status) {
+	case NET_RX_SUCCESS:
+		{
+			/*
+			 * Nothing to do here
+			 */
+			break;
+		}
+
+	case NET_RX_CN_LOW:
+		{
+			dbg(DBG_ASS, "%s: Receive Low Congestion\n", name);
+			break;
+		}
+
+	case NET_RX_CN_MOD:
+		{
+			dbg(DBG_ASS, "%s: Receive Moderate Congestion\n", name);
+			break;
+		}
+
+	case NET_RX_CN_HIGH:
+		{
+			dbg(DBG_ASS, "%s: Receive High Congestion\n", name);
+			break;
+		}
+
+	case NET_RX_DROP:
+		{
+			dbg(DBG_ASS, "%s: Received packet dropped\n", name);
+			break;
+		}
+	}
+}
+
+/*      Initilaise DMA for PLX 9054
+ */
+static inline void
+fst_init_dma(struct fst_card_info *card)
+{
+	/*
+	 * This is only required for the PLX 9054
+	 */
+	if (card->family == FST_FAMILY_TXU) {
+	        pci_set_master(card->device);
+		outl(0x00020441, card->pci_conf + DMAMODE0);
+		outl(0x00020441, card->pci_conf + DMAMODE1);
+		outl(0x0, card->pci_conf + DMATHR);
+	}
+}
+
+/*      Tx dma complete interrupt
+ */
+static void
+fst_tx_dma_complete(struct fst_card_info *card, struct fst_port_info *port,
+		    int len, int txpos)
+{
+	struct net_device *dev = port_to_dev(port);
+	struct net_device_stats *stats = hdlc_stats(dev);
+
+	/*
+	 * Everything is now set, just tell the card to go
+	 */
+	dbg(DBG_TX, "fst_tx_dma_complete\n");
+	FST_WRB(card, txDescrRing[port->index][txpos].bits,
+		DMA_OWN | TX_STP | TX_ENP);
+	stats->tx_packets++;
+	stats->tx_bytes += len;
+	dev->trans_start = jiffies;
+}
+
+/*
+ * Mark it for our own raw sockets interface
+ */
+static unsigned short farsync_type_trans(struct sk_buff *skb,
+					 struct net_device *dev)
+{
+	skb->dev = dev;
+	skb->mac.raw = skb->data;
+	skb->pkt_type = PACKET_HOST;
+	return htons(ETH_P_CUST);
+}
+
+/*      Rx dma complete interrupt
+ */
+static void
+fst_rx_dma_complete(struct fst_card_info *card, struct fst_port_info *port,
+		    int len, struct sk_buff *skb, int rxp)
+{
+	struct net_device *dev = port_to_dev(port);
+	struct net_device_stats *stats = hdlc_stats(dev);
+	int pi;
+	int rx_status;
+
+	dbg(DBG_TX, "fst_rx_dma_complete\n");
+	pi = port->index;
+	memcpy(skb_put(skb, len), card->rx_dma_handle_host, len);
+
+	/* Reset buffer descriptor */
+	FST_WRB(card, rxDescrRing[pi][rxp].bits, DMA_OWN);
+
+	/* Update stats */
+	stats->rx_packets++;
+	stats->rx_bytes += len;
+
+	/* Push upstream */
+	dbg(DBG_RX, "Pushing the frame up the stack\n");
+	if (port->mode == FST_RAW)
+		skb->protocol = farsync_type_trans(skb, dev);
+	else
+		skb->protocol = hdlc_type_trans(skb, dev);
+	rx_status = netif_rx(skb);
+	fst_process_rx_status(rx_status, port_to_dev(port)->name);
+	if (rx_status == NET_RX_DROP)
+		stats->rx_dropped++;
+	dev->last_rx = jiffies;
+}
+
+/*
+ *      Receive a frame through the DMA
+ */
+static inline void
+fst_rx_dma(struct fst_card_info *card, unsigned char *skb,
+	   unsigned char *mem, int len)
+{
+	/*
+	 * This routine will setup the DMA and start it
+	 */
+
+	dbg(DBG_RX, "In fst_rx_dma %p %p %d\n", skb, mem, len);
+	if (card->dmarx_in_progress) {
+		dbg(DBG_ASS, "In fst_rx_dma while dma in progress\n");
+	}
+
+	outl((unsigned long) skb, card->pci_conf + DMAPADR0);	/* Copy to here */
+	outl((unsigned long) mem, card->pci_conf + DMALADR0);	/* from here */
+	outl(len, card->pci_conf + DMASIZ0);	/* for this length */
+	outl(0x00000000c, card->pci_conf + DMADPR0);	/* In this direction */
+
+	/*
+	 * We use the dmarx_in_progress flag to flag the channel as busy
+	 */
+	card->dmarx_in_progress = 1;
+	outb(0x03, card->pci_conf + DMACSR0);	/* Start the transfer */
+}
+
+/*
+ *      Send a frame through the DMA
+ */
+static inline void
+fst_tx_dma(struct fst_card_info *card, unsigned char *skb,
+	   unsigned char *mem, int len)
+{
+	/*
+	 * This routine will setup the DMA and start it.
+	 */
+
+	dbg(DBG_TX, "In fst_tx_dma %p %p %d\n", skb, mem, len);
+	if (card->dmatx_in_progress) {
+		dbg(DBG_ASS, "In fst_tx_dma while dma in progress\n");
+	}
+
+	outl((unsigned long) skb, card->pci_conf + DMAPADR1);	/* Copy from here */
+	outl((unsigned long) mem, card->pci_conf + DMALADR1);	/* to here */
+	outl(len, card->pci_conf + DMASIZ1);	/* for this length */
+	outl(0x000000004, card->pci_conf + DMADPR1);	/* In this direction */
+
+	/*
+	 * We use the dmatx_in_progress to flag the channel as busy
+	 */
+	card->dmatx_in_progress = 1;
+	outb(0x03, card->pci_conf + DMACSR1);	/* Start the transfer */
+}
+
+/*      Issue a Mailbox command for a port.
+ *      Note we issue them on a fire and forget basis, not expecting to see an
+ *      error and not waiting for completion.
+ */
+static void
+fst_issue_cmd(struct fst_port_info *port, unsigned short cmd)
+{
+	struct fst_card_info *card;
+	unsigned short mbval;
+	unsigned long flags;
+	int safety;
+
+	card = port->card;
+	spin_lock_irqsave(&card->card_lock, flags);
+	mbval = FST_RDW(card, portMailbox[port->index][0]);
+
+	safety = 0;
+	/* Wait for any previous command to complete */
+	while (mbval > NAK) {
+		spin_unlock_irqrestore(&card->card_lock, flags);
+		schedule_timeout(1);
+		spin_lock_irqsave(&card->card_lock, flags);
+
+		if (++safety > 2000) {
+			printk_err("Mailbox safety timeout\n");
+			break;
+		}
+
+		mbval = FST_RDW(card, portMailbox[port->index][0]);
+	}
+	if (safety > 0) {
+		dbg(DBG_CMD, "Mailbox clear after %d jiffies\n", safety);
+	}
+	if (mbval == NAK) {
+		dbg(DBG_CMD, "issue_cmd: previous command was NAK'd\n");
+	}
+
+	FST_WRW(card, portMailbox[port->index][0], cmd);
+
+	if (cmd == ABORTTX || cmd == STARTPORT) {
+		port->txpos = 0;
+		port->txipos = 0;
+		port->start = 0;
+	}
+
+	spin_unlock_irqrestore(&card->card_lock, flags);
+}
+
+/*      Port output signals control
+ */
+static inline void
+fst_op_raise(struct fst_port_info *port, unsigned int outputs)
+{
+	outputs |= FST_RDL(port->card, v24OpSts[port->index]);
+	FST_WRL(port->card, v24OpSts[port->index], outputs);
+
+	if (port->run)
+		fst_issue_cmd(port, SETV24O);
+}
+
+static inline void
+fst_op_lower(struct fst_port_info *port, unsigned int outputs)
+{
+	outputs = ~outputs & FST_RDL(port->card, v24OpSts[port->index]);
+	FST_WRL(port->card, v24OpSts[port->index], outputs);
+
+	if (port->run)
+		fst_issue_cmd(port, SETV24O);
+}
+
+/*
+ *      Setup port Rx buffers
+ */
+static void
+fst_rx_config(struct fst_port_info *port)
+{
+	int i;
+	int pi;
+	unsigned int offset;
+	unsigned long flags;
+	struct fst_card_info *card;
+
+	pi = port->index;
+	card = port->card;
+	spin_lock_irqsave(&card->card_lock, flags);
+	for (i = 0; i < NUM_RX_BUFFER; i++) {
+		offset = BUF_OFFSET(rxBuffer[pi][i][0]);
+
+		FST_WRW(card, rxDescrRing[pi][i].ladr, (u16) offset);
+		FST_WRB(card, rxDescrRing[pi][i].hadr, (u8) (offset >> 16));
+		FST_WRW(card, rxDescrRing[pi][i].bcnt, cnv_bcnt(LEN_RX_BUFFER));
+		FST_WRW(card, rxDescrRing[pi][i].mcnt, LEN_RX_BUFFER);
+		FST_WRB(card, rxDescrRing[pi][i].bits, DMA_OWN);
+	}
+	port->rxpos = 0;
+	spin_unlock_irqrestore(&card->card_lock, flags);
+}
+
+/*
+ *      Setup port Tx buffers
+ */
+static void
+fst_tx_config(struct fst_port_info *port)
+{
+	int i;
+	int pi;
+	unsigned int offset;
+	unsigned long flags;
+	struct fst_card_info *card;
+
+	pi = port->index;
+	card = port->card;
+	spin_lock_irqsave(&card->card_lock, flags);
+	for (i = 0; i < NUM_TX_BUFFER; i++) {
+		offset = BUF_OFFSET(txBuffer[pi][i][0]);
+
+		FST_WRW(card, txDescrRing[pi][i].ladr, (u16) offset);
+		FST_WRB(card, txDescrRing[pi][i].hadr, (u8) (offset >> 16));
+		FST_WRW(card, txDescrRing[pi][i].bcnt, 0);
+		FST_WRB(card, txDescrRing[pi][i].bits, 0);
+	}
+	port->txpos = 0;
+	port->txipos = 0;
+	port->start = 0;
+	spin_unlock_irqrestore(&card->card_lock, flags);
+}
+
+/*      TE1 Alarm change interrupt event
+ */
+static void
+fst_intr_te1_alarm(struct fst_card_info *card, struct fst_port_info *port)
+{
+	u8 los;
+	u8 rra;
+	u8 ais;
+
+	los = FST_RDB(card, suStatus.lossOfSignal);
+	rra = FST_RDB(card, suStatus.receiveRemoteAlarm);
+	ais = FST_RDB(card, suStatus.alarmIndicationSignal);
+
+	if (los) {
+		/*
+		 * Lost the link
+		 */
+		if (netif_carrier_ok(port_to_dev(port))) {
+			dbg(DBG_INTR, "Net carrier off\n");
+			netif_carrier_off(port_to_dev(port));
+		}
+	} else {
+		/*
+		 * Link available
+		 */
+		if (!netif_carrier_ok(port_to_dev(port))) {
+			dbg(DBG_INTR, "Net carrier on\n");
+			netif_carrier_on(port_to_dev(port));
+		}
+	}
+
+	if (los)
+		dbg(DBG_INTR, "Assert LOS Alarm\n");
+	else
+		dbg(DBG_INTR, "De-assert LOS Alarm\n");
+	if (rra)
+		dbg(DBG_INTR, "Assert RRA Alarm\n");
+	else
+		dbg(DBG_INTR, "De-assert RRA Alarm\n");
+
+	if (ais)
+		dbg(DBG_INTR, "Assert AIS Alarm\n");
+	else
+		dbg(DBG_INTR, "De-assert AIS Alarm\n");
+}
+
+/*      Control signal change interrupt event
+ */
+static void
+fst_intr_ctlchg(struct fst_card_info *card, struct fst_port_info *port)
+{
+	int signals;
+
+	signals = FST_RDL(card, v24DebouncedSts[port->index]);
+
+	if (signals & (((port->hwif == X21) || (port->hwif == X21D))
+		       ? IPSTS_INDICATE : IPSTS_DCD)) {
+		if (!netif_carrier_ok(port_to_dev(port))) {
+			dbg(DBG_INTR, "DCD active\n");
+			netif_carrier_on(port_to_dev(port));
+		}
+	} else {
+		if (netif_carrier_ok(port_to_dev(port))) {
+			dbg(DBG_INTR, "DCD lost\n");
+			netif_carrier_off(port_to_dev(port));
+		}
+	}
+}
+
+/*      Log Rx Errors
+ */
+static void
+fst_log_rx_error(struct fst_card_info *card, struct fst_port_info *port,
+		 unsigned char dmabits, int rxp, unsigned short len)
+{
+	struct net_device *dev = port_to_dev(port);
+	struct net_device_stats *stats = hdlc_stats(dev);
+
+	/* 
+	 * Increment the appropriate error counter
+	 */
+	stats->rx_errors++;
+	if (dmabits & RX_OFLO) {
+		stats->rx_fifo_errors++;
+		dbg(DBG_ASS, "Rx fifo error on card %d port %d buffer %d\n",
+		    card->card_no, port->index, rxp);
+	}
+	if (dmabits & RX_CRC) {
+		stats->rx_crc_errors++;
+		dbg(DBG_ASS, "Rx crc error on card %d port %d\n",
+		    card->card_no, port->index);
+	}
+	if (dmabits & RX_FRAM) {
+		stats->rx_frame_errors++;
+		dbg(DBG_ASS, "Rx frame error on card %d port %d\n",
+		    card->card_no, port->index);
+	}
+	if (dmabits == (RX_STP | RX_ENP)) {
+		stats->rx_length_errors++;
+		dbg(DBG_ASS, "Rx length error (%d) on card %d port %d\n",
+		    len, card->card_no, port->index);
+	}
+}
+
+/*      Rx Error Recovery
+ */
+static void
+fst_recover_rx_error(struct fst_card_info *card, struct fst_port_info *port,
+		     unsigned char dmabits, int rxp, unsigned short len)
+{
+	int i;
+	int pi;
+
+	pi = port->index;
+	/* 
+	 * Discard buffer descriptors until we see the start of the
+	 * next frame.  Note that for long frames this could be in
+	 * a subsequent interrupt. 
+	 */
+	i = 0;
+	while ((dmabits & (DMA_OWN | RX_STP)) == 0) {
+		FST_WRB(card, rxDescrRing[pi][rxp].bits, DMA_OWN);
+		rxp = (rxp+1) % NUM_RX_BUFFER;
+		if (++i > NUM_RX_BUFFER) {
+			dbg(DBG_ASS, "intr_rx: Discarding more bufs"
+			    " than we have\n");
+			break;
+		}
+		dmabits = FST_RDB(card, rxDescrRing[pi][rxp].bits);
+		dbg(DBG_ASS, "DMA Bits of next buffer was %x\n", dmabits);
+	}
+	dbg(DBG_ASS, "There were %d subsequent buffers in error\n", i);
+
+	/* Discard the terminal buffer */
+	if (!(dmabits & DMA_OWN)) {
+		FST_WRB(card, rxDescrRing[pi][rxp].bits, DMA_OWN);
+		rxp = (rxp+1) % NUM_RX_BUFFER;
+	}
+	port->rxpos = rxp;
+	return;
+
+}
+
+/*      Rx complete interrupt
+ */
+static void
+fst_intr_rx(struct fst_card_info *card, struct fst_port_info *port)
+{
+	unsigned char dmabits;
+	int pi;
+	int rxp;
+	int rx_status;
+	unsigned short len;
+	struct sk_buff *skb;
+	struct net_device *dev = port_to_dev(port);
+	struct net_device_stats *stats = hdlc_stats(dev);
+
+	/* Check we have a buffer to process */
+	pi = port->index;
+	rxp = port->rxpos;
+	dmabits = FST_RDB(card, rxDescrRing[pi][rxp].bits);
+	if (dmabits & DMA_OWN) {
+		dbg(DBG_RX | DBG_INTR, "intr_rx: No buffer port %d pos %d\n",
+		    pi, rxp);
+		return;
+	}
+	if (card->dmarx_in_progress) {
+		return;
+	}
+
+	/* Get buffer length */
+	len = FST_RDW(card, rxDescrRing[pi][rxp].mcnt);
+	/* Discard the CRC */
+	len -= 2;
+	if (len == 0) {
+		/*
+		 * This seems to happen on the TE1 interface sometimes
+		 * so throw the frame away and log the event.
+		 */
+		printk_err("Frame received with 0 length. Card %d Port %d\n",
+			   card->card_no, port->index);
+		/* Return descriptor to card */
+		FST_WRB(card, rxDescrRing[pi][rxp].bits, DMA_OWN);
+
+		rxp = (rxp+1) % NUM_RX_BUFFER;
+		port->rxpos = rxp;
+		return;
+	}
+
+	/* Check buffer length and for other errors. We insist on one packet
+	 * in one buffer. This simplifies things greatly and since we've
+	 * allocated 8K it shouldn't be a real world limitation
+	 */
+	dbg(DBG_RX, "intr_rx: %d,%d: flags %x len %d\n", pi, rxp, dmabits, len);
+	if (dmabits != (RX_STP | RX_ENP) || len > LEN_RX_BUFFER - 2) {
+		fst_log_rx_error(card, port, dmabits, rxp, len);
+		fst_recover_rx_error(card, port, dmabits, rxp, len);
+		return;
+	}
+
+	/* Allocate SKB */
+	if ((skb = dev_alloc_skb(len)) == NULL) {
+		dbg(DBG_RX, "intr_rx: can't allocate buffer\n");
+
+		stats->rx_dropped++;
+
+		/* Return descriptor to card */
+		FST_WRB(card, rxDescrRing[pi][rxp].bits, DMA_OWN);
+
+		rxp = (rxp+1) % NUM_RX_BUFFER;
+		port->rxpos = rxp;
+		return;
+	}
+
+	/*
+	 * We know the length we need to receive, len.
+	 * It's not worth using the DMA for reads of less than
+	 * FST_MIN_DMA_LEN
+	 */
+
+	if ((len < FST_MIN_DMA_LEN) || (card->family == FST_FAMILY_TXP)) {
+		memcpy_fromio(skb_put(skb, len),
+			      card->mem + BUF_OFFSET(rxBuffer[pi][rxp][0]),
+			      len);
+
+		/* Reset buffer descriptor */
+		FST_WRB(card, rxDescrRing[pi][rxp].bits, DMA_OWN);
+
+		/* Update stats */
+		stats->rx_packets++;
+		stats->rx_bytes += len;
+
+		/* Push upstream */
+		dbg(DBG_RX, "Pushing frame up the stack\n");
+		if (port->mode == FST_RAW)
+			skb->protocol = farsync_type_trans(skb, dev);
+		else
+			skb->protocol = hdlc_type_trans(skb, dev);
+		rx_status = netif_rx(skb);
+		fst_process_rx_status(rx_status, port_to_dev(port)->name);
+		if (rx_status == NET_RX_DROP) {
+			stats->rx_dropped++;
+		}
+		dev->last_rx = jiffies;
+	} else {
+		card->dma_skb_rx = skb;
+		card->dma_port_rx = port;
+		card->dma_len_rx = len;
+		card->dma_rxpos = rxp;
+		fst_rx_dma(card, (char *) card->rx_dma_handle_card,
+			   (char *) BUF_OFFSET(rxBuffer[pi][rxp][0]), len);
+	}
+	if (rxp != port->rxpos) {
+		dbg(DBG_ASS, "About to increment rxpos by more than 1\n");
+		dbg(DBG_ASS, "rxp = %d rxpos = %d\n", rxp, port->rxpos);
+	}
+	rxp = (rxp+1) % NUM_RX_BUFFER;
+	port->rxpos = rxp;
+}
+
+/*
+ *      The bottom halfs to the ISR
+ *
+ */
+
+static void
+do_bottom_half_tx(struct fst_card_info *card)
+{
+	struct fst_port_info *port;
+	int pi;
+	int txq_length;
+	struct sk_buff *skb;
+	unsigned long flags;
+	struct net_device *dev;
+	struct net_device_stats *stats;
+
+	/*
+	 *  Find a free buffer for the transmit
+	 *  Step through each port on this card
+	 */
+
+	dbg(DBG_TX, "do_bottom_half_tx\n");
+	for (pi = 0, port = card->ports; pi < card->nports; pi++, port++) {
+		if (!port->run)
+			continue;
+
+                dev = port_to_dev(port);
+                stats = hdlc_stats(dev);
+		while (!
+		       (FST_RDB(card, txDescrRing[pi][port->txpos].bits) &
+			DMA_OWN)
+                       && !(card->dmatx_in_progress)) {
+			/*
+			 * There doesn't seem to be a txdone event per-se
+			 * We seem to have to deduce it, by checking the DMA_OWN
+			 * bit on the next buffer we think we can use
+			 */
+			spin_lock_irqsave(&card->card_lock, flags);
+			if ((txq_length = port->txqe - port->txqs) < 0) {
+				/*
+				 * This is the case where one has wrapped and the
+				 * maths gives us a negative number
+				 */
+				txq_length = txq_length + FST_TXQ_DEPTH;
+			}
+			spin_unlock_irqrestore(&card->card_lock, flags);
+			if (txq_length > 0) {
+				/*
+				 * There is something to send
+				 */
+				spin_lock_irqsave(&card->card_lock, flags);
+				skb = port->txq[port->txqs];
+				port->txqs++;
+				if (port->txqs == FST_TXQ_DEPTH) {
+					port->txqs = 0;
+				}
+				spin_unlock_irqrestore(&card->card_lock, flags);
+				/*
+				 * copy the data and set the required indicators on the
+				 * card.
+				 */
+				FST_WRW(card, txDescrRing[pi][port->txpos].bcnt,
+					cnv_bcnt(skb->len));
+				if ((skb->len < FST_MIN_DMA_LEN)
+				    || (card->family == FST_FAMILY_TXP)) {
+					/* Enqueue the packet with normal io */
+					memcpy_toio(card->mem +
+						    BUF_OFFSET(txBuffer[pi]
+							       [port->
+								txpos][0]),
+						    skb->data, skb->len);
+					FST_WRB(card,
+						txDescrRing[pi][port->txpos].
+						bits,
+						DMA_OWN | TX_STP | TX_ENP);
+					stats->tx_packets++;
+					stats->tx_bytes += skb->len;
+					dev->trans_start = jiffies;
+				} else {
+					/* Or do it through dma */
+					memcpy(card->tx_dma_handle_host,
+					       skb->data, skb->len);
+					card->dma_port_tx = port;
+					card->dma_len_tx = skb->len;
+					card->dma_txpos = port->txpos;
+					fst_tx_dma(card,
+						   (char *) card->
+						   tx_dma_handle_card,
+						   (char *)
+						   BUF_OFFSET(txBuffer[pi]
+							      [port->txpos][0]),
+						   skb->len);
+				}
+				if (++port->txpos >= NUM_TX_BUFFER)
+					port->txpos = 0;
+				/*
+				 * If we have flow control on, can we now release it?
+				 */
+				if (port->start) {
+					if (txq_length < fst_txq_low) {
+						netif_wake_queue(port_to_dev
+								 (port));
+						port->start = 0;
+					}
+				}
+				dev_kfree_skb(skb);
+			} else {
+				/*
+				 * Nothing to send so break out of the while loop
+				 */
+				break;
+			}
+		}
+	}
+}
+
+static void
+do_bottom_half_rx(struct fst_card_info *card)
+{
+	struct fst_port_info *port;
+	int pi;
+	int rx_count = 0;
+
+	/* Check for rx completions on all ports on this card */
+	dbg(DBG_RX, "do_bottom_half_rx\n");
+	for (pi = 0, port = card->ports; pi < card->nports; pi++, port++) {
+		if (!port->run)
+			continue;
+
+		while (!(FST_RDB(card, rxDescrRing[pi][port->rxpos].bits)
+			 & DMA_OWN) && !(card->dmarx_in_progress)) {
+			if (rx_count > fst_max_reads) {
+				/*
+				 * Don't spend forever in receive processing
+				 * Schedule another event
+				 */
+				fst_q_work_item(&fst_work_intq, card->card_no);
+				tasklet_schedule(&fst_int_task);
+				break;	/* Leave the loop */
+			}
+			fst_intr_rx(card, port);
+			rx_count++;
+		}
+	}
+}
+
+/*
+ *      The interrupt service routine
+ *      Dev_id is our fst_card_info pointer
+ */
+irqreturn_t
+fst_intr(int irq, void *dev_id, struct pt_regs *regs)
+{
+	struct fst_card_info *card;
+	struct fst_port_info *port;
+	int rdidx;		/* Event buffer indices */
+	int wridx;
+	int event;		/* Actual event for processing */
+	unsigned int dma_intcsr = 0;
+	unsigned int do_card_interrupt;
+	unsigned int int_retry_count;
+
+	if ((card = dev_id) == NULL) {
+		dbg(DBG_INTR, "intr: spurious %d\n", irq);
+		return IRQ_NONE;
+	}
+
+	/*
+	 * Check to see if the interrupt was for this card
+	 * return if not
+	 * Note that the call to clear the interrupt is important
+	 */
+	dbg(DBG_INTR, "intr: %d %p\n", irq, card);
+	if (card->state != FST_RUNNING) {
+		printk_err
+		    ("Interrupt received for card %d in a non running state (%d)\n",
+		     card->card_no, card->state);
+
+		/* 
+		 * It is possible to really be running, i.e. we have re-loaded
+		 * a running card
+		 * Clear and reprime the interrupt source 
+		 */
+		fst_clear_intr(card);
+		return IRQ_HANDLED;
+	}
+
+	/* Clear and reprime the interrupt source */
+	fst_clear_intr(card);
+
+	/*
+	 * Is the interrupt for this card (handshake == 1)
+	 */
+	do_card_interrupt = 0;
+	if (FST_RDB(card, interruptHandshake) == 1) {
+		do_card_interrupt += FST_CARD_INT;
+		/* Set the software acknowledge */
+		FST_WRB(card, interruptHandshake, 0xEE);
+	}
+	if (card->family == FST_FAMILY_TXU) {
+		/*
+		 * Is it a DMA Interrupt
+		 */
+		dma_intcsr = inl(card->pci_conf + INTCSR_9054);
+		if (dma_intcsr & 0x00200000) {
+			/*
+			 * DMA Channel 0 (Rx transfer complete)
+			 */
+			dbg(DBG_RX, "DMA Rx xfer complete\n");
+			outb(0x8, card->pci_conf + DMACSR0);
+			fst_rx_dma_complete(card, card->dma_port_rx,
+					    card->dma_len_rx, card->dma_skb_rx,
+					    card->dma_rxpos);
+			card->dmarx_in_progress = 0;
+			do_card_interrupt += FST_RX_DMA_INT;
+		}
+		if (dma_intcsr & 0x00400000) {
+			/*
+			 * DMA Channel 1 (Tx transfer complete)
+			 */
+			dbg(DBG_TX, "DMA Tx xfer complete\n");
+			outb(0x8, card->pci_conf + DMACSR1);
+			fst_tx_dma_complete(card, card->dma_port_tx,
+					    card->dma_len_tx, card->dma_txpos);
+			card->dmatx_in_progress = 0;
+			do_card_interrupt += FST_TX_DMA_INT;
+		}
+	}
+
+	/*
+	 * Have we been missing Interrupts
+	 */
+	int_retry_count = FST_RDL(card, interruptRetryCount);
+	if (int_retry_count) {
+		dbg(DBG_ASS, "Card %d int_retry_count is  %d\n",
+		    card->card_no, int_retry_count);
+		FST_WRL(card, interruptRetryCount, 0);
+	}
+
+	if (!do_card_interrupt) {
+		return IRQ_HANDLED;
+	}
+
+	/* Scehdule the bottom half of the ISR */
+	fst_q_work_item(&fst_work_intq, card->card_no);
+	tasklet_schedule(&fst_int_task);
+
+	/* Drain the event queue */
+	rdidx = FST_RDB(card, interruptEvent.rdindex) & 0x1f;
+	wridx = FST_RDB(card, interruptEvent.wrindex) & 0x1f;
+	while (rdidx != wridx) {
+		event = FST_RDB(card, interruptEvent.evntbuff[rdidx]);
+		port = &card->ports[event & 0x03];
+
+		dbg(DBG_INTR, "Processing Interrupt event: %x\n", event);
+
+		switch (event) {
+		case TE1_ALMA:
+			dbg(DBG_INTR, "TE1 Alarm intr\n");
+			if (port->run)
+				fst_intr_te1_alarm(card, port);
+			break;
+
+		case CTLA_CHG:
+		case CTLB_CHG:
+		case CTLC_CHG:
+		case CTLD_CHG:
+			if (port->run)
+				fst_intr_ctlchg(card, port);
+			break;
+
+		case ABTA_SENT:
+		case ABTB_SENT:
+		case ABTC_SENT:
+		case ABTD_SENT:
+			dbg(DBG_TX, "Abort complete port %d\n", port->index);
+			break;
+
+		case TXA_UNDF:
+		case TXB_UNDF:
+		case TXC_UNDF:
+		case TXD_UNDF:
+			/* Difficult to see how we'd get this given that we
+			 * always load up the entire packet for DMA.
+			 */
+			dbg(DBG_TX, "Tx underflow port %d\n", port->index);
+                        hdlc_stats(port_to_dev(port))->tx_errors++;
+                        hdlc_stats(port_to_dev(port))->tx_fifo_errors++;
+			dbg(DBG_ASS, "Tx underflow on card %d port %d\n",
+			    card->card_no, port->index);
+			break;
+
+		case INIT_CPLT:
+			dbg(DBG_INIT, "Card init OK intr\n");
+			break;
+
+		case INIT_FAIL:
+			dbg(DBG_INIT, "Card init FAILED intr\n");
+			card->state = FST_IFAILED;
+			break;
+
+		default:
+			printk_err("intr: unknown card event %d. ignored\n",
+				   event);
+			break;
+		}
+
+		/* Bump and wrap the index */
+		if (++rdidx >= MAX_CIRBUFF)
+			rdidx = 0;
+	}
+	FST_WRB(card, interruptEvent.rdindex, rdidx);
+        return IRQ_HANDLED;
+}
+
+/*      Check that the shared memory configuration is one that we can handle
+ *      and that some basic parameters are correct
+ */
+static void
+check_started_ok(struct fst_card_info *card)
+{
+	int i;
+
+	/* Check structure version and end marker */
+	if (FST_RDW(card, smcVersion) != SMC_VERSION) {
+		printk_err("Bad shared memory version %d expected %d\n",
+			   FST_RDW(card, smcVersion), SMC_VERSION);
+		card->state = FST_BADVERSION;
+		return;
+	}
+	if (FST_RDL(card, endOfSmcSignature) != END_SIG) {
+		printk_err("Missing shared memory signature\n");
+		card->state = FST_BADVERSION;
+		return;
+	}
+	/* Firmware status flag, 0x00 = initialising, 0x01 = OK, 0xFF = fail */
+	if ((i = FST_RDB(card, taskStatus)) == 0x01) {
+		card->state = FST_RUNNING;
+	} else if (i == 0xFF) {
+		printk_err("Firmware initialisation failed. Card halted\n");
+		card->state = FST_HALTED;
+		return;
+	} else if (i != 0x00) {
+		printk_err("Unknown firmware status 0x%x\n", i);
+		card->state = FST_HALTED;
+		return;
+	}
+
+	/* Finally check the number of ports reported by firmware against the
+	 * number we assumed at card detection. Should never happen with
+	 * existing firmware etc so we just report it for the moment.
+	 */
+	if (FST_RDL(card, numberOfPorts) != card->nports) {
+		printk_warn("Port count mismatch on card %d."
+			    " Firmware thinks %d we say %d\n", card->card_no,
+			    FST_RDL(card, numberOfPorts), card->nports);
+	}
+}
+
+static int
+set_conf_from_info(struct fst_card_info *card, struct fst_port_info *port,
+		   struct fstioc_info *info)
+{
+	int err;
+	unsigned char my_framing;
+
+	/* Set things according to the user set valid flags 
+	 * Several of the old options have been invalidated/replaced by the 
+	 * generic hdlc package.
+	 */
+	err = 0;
+	if (info->valid & FSTVAL_PROTO) {
+		if (info->proto == FST_RAW)
+			port->mode = FST_RAW;
+		else
+			port->mode = FST_GEN_HDLC;
+	}
+
+	if (info->valid & FSTVAL_CABLE)
+		err = -EINVAL;
+
+	if (info->valid & FSTVAL_SPEED)
+		err = -EINVAL;
+
+	if (info->valid & FSTVAL_PHASE)
+		FST_WRB(card, portConfig[port->index].invertClock,
+			info->invertClock);
+	if (info->valid & FSTVAL_MODE)
+		FST_WRW(card, cardMode, info->cardMode);
+	if (info->valid & FSTVAL_TE1) {
+		FST_WRL(card, suConfig.dataRate, info->lineSpeed);
+		FST_WRB(card, suConfig.clocking, info->clockSource);
+		my_framing = FRAMING_E1;
+		if (info->framing == E1)
+			my_framing = FRAMING_E1;
+		if (info->framing == T1)
+			my_framing = FRAMING_T1;
+		if (info->framing == J1)
+			my_framing = FRAMING_J1;
+		FST_WRB(card, suConfig.framing, my_framing);
+		FST_WRB(card, suConfig.structure, info->structure);
+		FST_WRB(card, suConfig.interface, info->interface);
+		FST_WRB(card, suConfig.coding, info->coding);
+		FST_WRB(card, suConfig.lineBuildOut, info->lineBuildOut);
+		FST_WRB(card, suConfig.equalizer, info->equalizer);
+		FST_WRB(card, suConfig.transparentMode, info->transparentMode);
+		FST_WRB(card, suConfig.loopMode, info->loopMode);
+		FST_WRB(card, suConfig.range, info->range);
+		FST_WRB(card, suConfig.txBufferMode, info->txBufferMode);
+		FST_WRB(card, suConfig.rxBufferMode, info->rxBufferMode);
+		FST_WRB(card, suConfig.startingSlot, info->startingSlot);
+		FST_WRB(card, suConfig.losThreshold, info->losThreshold);
+		if (info->idleCode)
+			FST_WRB(card, suConfig.enableIdleCode, 1);
+		else
+			FST_WRB(card, suConfig.enableIdleCode, 0);
+		FST_WRB(card, suConfig.idleCode, info->idleCode);
+#if FST_DEBUG
+		if (info->valid & FSTVAL_TE1) {
+			printk("Setting TE1 data\n");
+			printk("Line Speed = %d\n", info->lineSpeed);
+			printk("Start slot = %d\n", info->startingSlot);
+			printk("Clock source = %d\n", info->clockSource);
+			printk("Framing = %d\n", my_framing);
+			printk("Structure = %d\n", info->structure);
+			printk("interface = %d\n", info->interface);
+			printk("Coding = %d\n", info->coding);
+			printk("Line build out = %d\n", info->lineBuildOut);
+			printk("Equaliser = %d\n", info->equalizer);
+			printk("Transparent mode = %d\n",
+			       info->transparentMode);
+			printk("Loop mode = %d\n", info->loopMode);
+			printk("Range = %d\n", info->range);
+			printk("Tx Buffer mode = %d\n", info->txBufferMode);
+			printk("Rx Buffer mode = %d\n", info->rxBufferMode);
+			printk("LOS Threshold = %d\n", info->losThreshold);
+			printk("Idle Code = %d\n", info->idleCode);
+		}
+#endif
+	}
+#if FST_DEBUG
+	if (info->valid & FSTVAL_DEBUG) {
+		fst_debug_mask = info->debug;
+	}
+#endif
+
+	return err;
+}
+
+static void
+gather_conf_info(struct fst_card_info *card, struct fst_port_info *port,
+		 struct fstioc_info *info)
+{
+	int i;
+
+	memset(info, 0, sizeof (struct fstioc_info));
+
+	i = port->index;
+	info->kernelVersion = LINUX_VERSION_CODE;
+	info->nports = card->nports;
+	info->type = card->type;
+	info->state = card->state;
+	info->proto = FST_GEN_HDLC;
+	info->index = i;
+#if FST_DEBUG
+	info->debug = fst_debug_mask;
+#endif
+
+	/* Only mark information as valid if card is running.
+	 * Copy the data anyway in case it is useful for diagnostics
+	 */
+	info->valid = ((card->state == FST_RUNNING) ? FSTVAL_ALL : FSTVAL_CARD)
+#if FST_DEBUG
+	    | FSTVAL_DEBUG
+#endif
+	    ;
+
+	info->lineInterface = FST_RDW(card, portConfig[i].lineInterface);
+	info->internalClock = FST_RDB(card, portConfig[i].internalClock);
+	info->lineSpeed = FST_RDL(card, portConfig[i].lineSpeed);
+	info->invertClock = FST_RDB(card, portConfig[i].invertClock);
+	info->v24IpSts = FST_RDL(card, v24IpSts[i]);
+	info->v24OpSts = FST_RDL(card, v24OpSts[i]);
+	info->clockStatus = FST_RDW(card, clockStatus[i]);
+	info->cableStatus = FST_RDW(card, cableStatus);
+	info->cardMode = FST_RDW(card, cardMode);
+	info->smcFirmwareVersion = FST_RDL(card, smcFirmwareVersion);
+
+	/*
+	 * The T2U can report cable presence for both A or B
+	 * in bits 0 and 1 of cableStatus.  See which port we are and 
+	 * do the mapping.
+	 */
+	if (card->family == FST_FAMILY_TXU) {
+		if (port->index == 0) {
+			/*
+			 * Port A
+			 */
+			info->cableStatus = info->cableStatus & 1;
+		} else {
+			/*
+			 * Port B
+			 */
+			info->cableStatus = info->cableStatus >> 1;
+			info->cableStatus = info->cableStatus & 1;
+		}
+	}
+	/*
+	 * Some additional bits if we are TE1
+	 */
+	if (card->type == FST_TYPE_TE1) {
+		info->lineSpeed = FST_RDL(card, suConfig.dataRate);
+		info->clockSource = FST_RDB(card, suConfig.clocking);
+		info->framing = FST_RDB(card, suConfig.framing);
+		info->structure = FST_RDB(card, suConfig.structure);
+		info->interface = FST_RDB(card, suConfig.interface);
+		info->coding = FST_RDB(card, suConfig.coding);
+		info->lineBuildOut = FST_RDB(card, suConfig.lineBuildOut);
+		info->equalizer = FST_RDB(card, suConfig.equalizer);
+		info->loopMode = FST_RDB(card, suConfig.loopMode);
+		info->range = FST_RDB(card, suConfig.range);
+		info->txBufferMode = FST_RDB(card, suConfig.txBufferMode);
+		info->rxBufferMode = FST_RDB(card, suConfig.rxBufferMode);
+		info->startingSlot = FST_RDB(card, suConfig.startingSlot);
+		info->losThreshold = FST_RDB(card, suConfig.losThreshold);
+		if (FST_RDB(card, suConfig.enableIdleCode))
+			info->idleCode = FST_RDB(card, suConfig.idleCode);
+		else
+			info->idleCode = 0;
+		info->receiveBufferDelay =
+		    FST_RDL(card, suStatus.receiveBufferDelay);
+		info->framingErrorCount =
+		    FST_RDL(card, suStatus.framingErrorCount);
+		info->codeViolationCount =
+		    FST_RDL(card, suStatus.codeViolationCount);
+		info->crcErrorCount = FST_RDL(card, suStatus.crcErrorCount);
+		info->lineAttenuation = FST_RDL(card, suStatus.lineAttenuation);
+		info->lossOfSignal = FST_RDB(card, suStatus.lossOfSignal);
+		info->receiveRemoteAlarm =
+		    FST_RDB(card, suStatus.receiveRemoteAlarm);
+		info->alarmIndicationSignal =
+		    FST_RDB(card, suStatus.alarmIndicationSignal);
+	}
+}
+
+static int
+fst_set_iface(struct fst_card_info *card, struct fst_port_info *port,
+	      struct ifreq *ifr)
+{
+	sync_serial_settings sync;
+	int i;
+
+	if (ifr->ifr_settings.size != sizeof (sync)) {
+		return -ENOMEM;
+	}
+
+	if (copy_from_user
+	    (&sync, ifr->ifr_settings.ifs_ifsu.sync, sizeof (sync))) {
+		return -EFAULT;
+	}
+
+	if (sync.loopback)
+		return -EINVAL;
+
+	i = port->index;
+
+	switch (ifr->ifr_settings.type) {
+	case IF_IFACE_V35:
+		FST_WRW(card, portConfig[i].lineInterface, V35);
+		port->hwif = V35;
+		break;
+
+	case IF_IFACE_V24:
+		FST_WRW(card, portConfig[i].lineInterface, V24);
+		port->hwif = V24;
+		break;
+
+	case IF_IFACE_X21:
+		FST_WRW(card, portConfig[i].lineInterface, X21);
+		port->hwif = X21;
+		break;
+
+	case IF_IFACE_X21D:
+		FST_WRW(card, portConfig[i].lineInterface, X21D);
+		port->hwif = X21D;
+		break;
+
+	case IF_IFACE_T1:
+		FST_WRW(card, portConfig[i].lineInterface, T1);
+		port->hwif = T1;
+		break;
+
+	case IF_IFACE_E1:
+		FST_WRW(card, portConfig[i].lineInterface, E1);
+		port->hwif = E1;
+		break;
+
+	case IF_IFACE_SYNC_SERIAL:
+		break;
+
+	default:
+		return -EINVAL;
+	}
+
+	switch (sync.clock_type) {
+	case CLOCK_EXT:
+		FST_WRB(card, portConfig[i].internalClock, EXTCLK);
+		break;
+
+	case CLOCK_INT:
+		FST_WRB(card, portConfig[i].internalClock, INTCLK);
+		break;
+
+	default:
+		return -EINVAL;
+	}
+	FST_WRL(card, portConfig[i].lineSpeed, sync.clock_rate);
+	return 0;
+}
+
+static int
+fst_get_iface(struct fst_card_info *card, struct fst_port_info *port,
+	      struct ifreq *ifr)
+{
+	sync_serial_settings sync;
+	int i;
+
+	/* First check what line type is set, we'll default to reporting X.21
+	 * if nothing is set as IF_IFACE_SYNC_SERIAL implies it can't be
+	 * changed
+	 */
+	switch (port->hwif) {
+	case E1:
+		ifr->ifr_settings.type = IF_IFACE_E1;
+		break;
+	case T1:
+		ifr->ifr_settings.type = IF_IFACE_T1;
+		break;
+	case V35:
+		ifr->ifr_settings.type = IF_IFACE_V35;
+		break;
+	case V24:
+		ifr->ifr_settings.type = IF_IFACE_V24;
+		break;
+	case X21D:
+		ifr->ifr_settings.type = IF_IFACE_X21D;
+		break;
+	case X21:
+	default:
+		ifr->ifr_settings.type = IF_IFACE_X21;
+		break;
+	}
+	if (ifr->ifr_settings.size == 0) {
+		return 0;	/* only type requested */
+	}
+	if (ifr->ifr_settings.size < sizeof (sync)) {
+		return -ENOMEM;
+	}
+
+	i = port->index;
+	sync.clock_rate = FST_RDL(card, portConfig[i].lineSpeed);
+	/* Lucky card and linux use same encoding here */
+	sync.clock_type = FST_RDB(card, portConfig[i].internalClock) ==
+	    INTCLK ? CLOCK_INT : CLOCK_EXT;
+	sync.loopback = 0;
+
+	if (copy_to_user(ifr->ifr_settings.ifs_ifsu.sync, &sync, sizeof (sync))) {
+		return -EFAULT;
+	}
+
+	ifr->ifr_settings.size = sizeof (sync);
+	return 0;
+}
+
+static int
+fst_ioctl(struct net_device *dev, struct ifreq *ifr, int cmd)
+{
+	struct fst_card_info *card;
+	struct fst_port_info *port;
+	struct fstioc_write wrthdr;
+	struct fstioc_info info;
+	unsigned long flags;
+
+	dbg(DBG_IOCTL, "ioctl: %x, %p\n", cmd, ifr->ifr_data);
+
+	port = dev_to_port(dev);
+	card = port->card;
+
+	if (!capable(CAP_NET_ADMIN))
+		return -EPERM;
+
+	switch (cmd) {
+	case FSTCPURESET:
+		fst_cpureset(card);
+		card->state = FST_RESET;
+		return 0;
+
+	case FSTCPURELEASE:
+		fst_cpurelease(card);
+		card->state = FST_STARTING;
+		return 0;
+
+	case FSTWRITE:		/* Code write (download) */
+
+		/* First copy in the header with the length and offset of data
+		 * to write
+		 */
+		if (ifr->ifr_data == NULL) {
+			return -EINVAL;
+		}
+		if (copy_from_user(&wrthdr, ifr->ifr_data,
+				   sizeof (struct fstioc_write))) {
+			return -EFAULT;
+		}
+
+		/* Sanity check the parameters. We don't support partial writes
+		 * when going over the top
+		 */
+		if (wrthdr.size > FST_MEMSIZE || wrthdr.offset > FST_MEMSIZE
+		    || wrthdr.size + wrthdr.offset > FST_MEMSIZE) {
+			return -ENXIO;
+		}
+
+		/* Now copy the data to the card.
+		 * This will probably break on some architectures.
+		 * I'll fix it when I have something to test on.
+		 */
+		if (copy_from_user(card->mem + wrthdr.offset,
+				   ifr->ifr_data + sizeof (struct fstioc_write),
+				   wrthdr.size)) {
+			return -EFAULT;
+		}
+
+		/* Writes to the memory of a card in the reset state constitute
+		 * a download
+		 */
+		if (card->state == FST_RESET) {
+			card->state = FST_DOWNLOAD;
+		}
+		return 0;
+
+	case FSTGETCONF:
+
+		/* If card has just been started check the shared memory config
+		 * version and marker
+		 */
+		if (card->state == FST_STARTING) {
+			check_started_ok(card);
+
+			/* If everything checked out enable card interrupts */
+			if (card->state == FST_RUNNING) {
+				spin_lock_irqsave(&card->card_lock, flags);
+				fst_enable_intr(card);
+				FST_WRB(card, interruptHandshake, 0xEE);
+				spin_unlock_irqrestore(&card->card_lock, flags);
+			}
+		}
+
+		if (ifr->ifr_data == NULL) {
+			return -EINVAL;
+		}
+
+		gather_conf_info(card, port, &info);
+
+		if (copy_to_user(ifr->ifr_data, &info, sizeof (info))) {
+			return -EFAULT;
+		}
+		return 0;
+
+	case FSTSETCONF:
+
+		/*
+		 * Most of the settings have been moved to the generic ioctls
+		 * this just covers debug and board ident now
+		 */
+
+		if (card->state != FST_RUNNING) {
+			printk_err
+			    ("Attempt to configure card %d in non-running state (%d)\n",
+			     card->card_no, card->state);
+			return -EIO;
+		}
+		if (copy_from_user(&info, ifr->ifr_data, sizeof (info))) {
+			return -EFAULT;
+		}
+
+		return set_conf_from_info(card, port, &info);
+
+	case SIOCWANDEV:
+		switch (ifr->ifr_settings.type) {
+		case IF_GET_IFACE:
+			return fst_get_iface(card, port, ifr);
+
+		case IF_IFACE_SYNC_SERIAL:
+		case IF_IFACE_V35:
+		case IF_IFACE_V24:
+		case IF_IFACE_X21:
+		case IF_IFACE_X21D:
+		case IF_IFACE_T1:
+		case IF_IFACE_E1:
+			return fst_set_iface(card, port, ifr);
+
+		case IF_PROTO_RAW:
+			port->mode = FST_RAW;
+			return 0;
+
+		case IF_GET_PROTO:
+			if (port->mode == FST_RAW) {
+				ifr->ifr_settings.type = IF_PROTO_RAW;
+				return 0;
+			}
+			return hdlc_ioctl(dev, ifr, cmd);
+
+		default:
+			port->mode = FST_GEN_HDLC;
+			dbg(DBG_IOCTL, "Passing this type to hdlc %x\n",
+			    ifr->ifr_settings.type);
+			return hdlc_ioctl(dev, ifr, cmd);
+		}
+
+	default:
+		/* Not one of ours. Pass through to HDLC package */
+		return hdlc_ioctl(dev, ifr, cmd);
+	}
+}
+
+static void
+fst_openport(struct fst_port_info *port)
+{
+	int signals;
+	int txq_length;
+
+	/* Only init things if card is actually running. This allows open to
+	 * succeed for downloads etc.
+	 */
+	if (port->card->state == FST_RUNNING) {
+		if (port->run) {
+			dbg(DBG_OPEN, "open: found port already running\n");
+
+			fst_issue_cmd(port, STOPPORT);
+			port->run = 0;
+		}
+
+		fst_rx_config(port);
+		fst_tx_config(port);
+		fst_op_raise(port, OPSTS_RTS | OPSTS_DTR);
+
+		fst_issue_cmd(port, STARTPORT);
+		port->run = 1;
+
+		signals = FST_RDL(port->card, v24DebouncedSts[port->index]);
+		if (signals & (((port->hwif == X21) || (port->hwif == X21D))
+			       ? IPSTS_INDICATE : IPSTS_DCD))
+			netif_carrier_on(port_to_dev(port));
+		else
+			netif_carrier_off(port_to_dev(port));
+
+		txq_length = port->txqe - port->txqs;
+		port->txqe = 0;
+		port->txqs = 0;
+	}
+
+}
+
+static void
+fst_closeport(struct fst_port_info *port)
+{
+	if (port->card->state == FST_RUNNING) {
+		if (port->run) {
+			port->run = 0;
+			fst_op_lower(port, OPSTS_RTS | OPSTS_DTR);
+
+			fst_issue_cmd(port, STOPPORT);
+		} else {
+			dbg(DBG_OPEN, "close: port not running\n");
+		}
+	}
+}
+
+static int
+fst_open(struct net_device *dev)
+{
+	int err;
+	struct fst_port_info *port;
+
+	port = dev_to_port(dev);
+	if (!try_module_get(THIS_MODULE))
+          return -EBUSY;
+
+	if (port->mode != FST_RAW) {
+		err = hdlc_open(dev);
+		if (err)
+			return err;
+	}
+
+	fst_openport(port);
+	netif_wake_queue(dev);
+	return 0;
+}
+
+static int
+fst_close(struct net_device *dev)
+{
+	struct fst_port_info *port;
+	struct fst_card_info *card;
+	unsigned char tx_dma_done;
+	unsigned char rx_dma_done;
+
+	port = dev_to_port(dev);
+	card = port->card;
+
+	tx_dma_done = inb(card->pci_conf + DMACSR1);
+	rx_dma_done = inb(card->pci_conf + DMACSR0);
+	dbg(DBG_OPEN,
+	    "Port Close: tx_dma_in_progress = %d (%x) rx_dma_in_progress = %d (%x)\n",
+	    card->dmatx_in_progress, tx_dma_done, card->dmarx_in_progress,
+	    rx_dma_done);
+
+	netif_stop_queue(dev);
+	fst_closeport(dev_to_port(dev));
+	if (port->mode != FST_RAW) {
+		hdlc_close(dev);
+	}
+	module_put(THIS_MODULE);
+	return 0;
+}
+
+static int
+fst_attach(struct net_device *dev, unsigned short encoding, unsigned short parity)
+{
+	/*
+	 * Setting currently fixed in FarSync card so we check and forget
+	 */
+	if (encoding != ENCODING_NRZ || parity != PARITY_CRC16_PR1_CCITT)
+		return -EINVAL;
+	return 0;
+}
+
+static void
+fst_tx_timeout(struct net_device *dev)
+{
+	struct fst_port_info *port;
+	struct fst_card_info *card;
+	struct net_device_stats *stats = hdlc_stats(dev);
+
+	port = dev_to_port(dev);
+	card = port->card;
+	stats->tx_errors++;
+	stats->tx_aborted_errors++;
+	dbg(DBG_ASS, "Tx timeout card %d port %d\n",
+	    card->card_no, port->index);
+	fst_issue_cmd(port, ABORTTX);
+
+	dev->trans_start = jiffies;
+	netif_wake_queue(dev);
+	port->start = 0;
+}
+
+static int
+fst_start_xmit(struct sk_buff *skb, struct net_device *dev)
+{
+	struct fst_card_info *card;
+	struct fst_port_info *port;
+	struct net_device_stats *stats = hdlc_stats(dev);
+	unsigned long flags;
+	int txq_length;
+
+	port = dev_to_port(dev);
+	card = port->card;
+	dbg(DBG_TX, "fst_start_xmit: length = %d\n", skb->len);
+
+	/* Drop packet with error if we don't have carrier */
+	if (!netif_carrier_ok(dev)) {
+		dev_kfree_skb(skb);
+		stats->tx_errors++;
+		stats->tx_carrier_errors++;
+		dbg(DBG_ASS,
+		    "Tried to transmit but no carrier on card %d port %d\n",
+		    card->card_no, port->index);
+		return 0;
+	}
+
+	/* Drop it if it's too big! MTU failure ? */
+	if (skb->len > LEN_TX_BUFFER) {
+		dbg(DBG_ASS, "Packet too large %d vs %d\n", skb->len,
+		    LEN_TX_BUFFER);
+		dev_kfree_skb(skb);
+		stats->tx_errors++;
+		return 0;
+	}
+
+	/*
+	 * We are always going to queue the packet
+	 * so that the bottom half is the only place we tx from
+	 * Check there is room in the port txq
+	 */
+	spin_lock_irqsave(&card->card_lock, flags);
+	if ((txq_length = port->txqe - port->txqs) < 0) {
+		/*
+		 * This is the case where the next free has wrapped but the
+		 * last used hasn't
+		 */
+		txq_length = txq_length + FST_TXQ_DEPTH;
+	}
+	spin_unlock_irqrestore(&card->card_lock, flags);
+	if (txq_length > fst_txq_high) {
+		/*
+		 * We have got enough buffers in the pipeline.  Ask the network
+		 * layer to stop sending frames down
+		 */
+		netif_stop_queue(dev);
+		port->start = 1;	/* I'm using this to signal stop sent up */
+	}
+
+	if (txq_length == FST_TXQ_DEPTH - 1) {
+		/*
+		 * This shouldn't have happened but such is life
+		 */
+		dev_kfree_skb(skb);
+		stats->tx_errors++;
+		dbg(DBG_ASS, "Tx queue overflow card %d port %d\n",
+		    card->card_no, port->index);
+		return 0;
+	}
+
+	/*
+	 * queue the buffer
+	 */
+	spin_lock_irqsave(&card->card_lock, flags);
+	port->txq[port->txqe] = skb;
+	port->txqe++;
+	if (port->txqe == FST_TXQ_DEPTH)
+		port->txqe = 0;
+	spin_unlock_irqrestore(&card->card_lock, flags);
+
+	/* Scehdule the bottom half which now does transmit processing */
+	fst_q_work_item(&fst_work_txq, card->card_no);
+	tasklet_schedule(&fst_tx_task);
+
+	return 0;
+}
+
+/*
+ *      Card setup having checked hardware resources.
+ *      Should be pretty bizarre if we get an error here (kernel memory
+ *      exhaustion is one possibility). If we do see a problem we report it
+ *      via a printk and leave the corresponding interface and all that follow
+ *      disabled.
+ */
+static char *type_strings[] __devinitdata = {
+	"no hardware",		/* Should never be seen */
+	"FarSync T2P",
+	"FarSync T4P",
+	"FarSync T1U",
+	"FarSync T2U",
+	"FarSync T4U",
+	"FarSync TE1"
+};
+
+static void __devinit
+fst_init_card(struct fst_card_info *card)
+{
+	int i;
+	int err;
+
+	/* We're working on a number of ports based on the card ID. If the
+	 * firmware detects something different later (should never happen)
+	 * we'll have to revise it in some way then.
+	 */
+	for (i = 0; i < card->nports; i++) {
+                err = register_hdlc_device(card->ports[i].dev);
+                if (err < 0) {
+			int j;
+                        printk_err ("Cannot register HDLC device for port %d"
+                                    " (errno %d)\n", i, -err );
+			for (j = i; j < card->nports; j++) {
+				free_netdev(card->ports[j].dev);
+				card->ports[j].dev = NULL;
+			}
+                        card->nports = i;
+                        break;
+                }
+	}
+
+	printk_info("%s-%s: %s IRQ%d, %d ports\n",
+	       port_to_dev(&card->ports[0])->name,
+	       port_to_dev(&card->ports[card->nports - 1])->name,
+	       type_strings[card->type], card->irq, card->nports);
+}
+
+/*
+ *      Initialise card when detected.
+ *      Returns 0 to indicate success, or errno otherwise.
+ */
+static int __devinit
+fst_add_one(struct pci_dev *pdev, const struct pci_device_id *ent)
+{
+	static int firsttime_done = 0;
+	static int no_of_cards_added = 0;
+	struct fst_card_info *card;
+	int err = 0;
+	int i;
+
+	if (!firsttime_done) {
+		printk_info("FarSync WAN driver " FST_USER_VERSION
+		       " (c) 2001-2004 FarSite Communications Ltd.\n");
+		firsttime_done = 1;
+		dbg(DBG_ASS, "The value of debug mask is %x\n", fst_debug_mask);
+	}
+
+	/*
+	 * We are going to be clever and allow certain cards not to be
+	 * configured.  An exclude list can be provided in /etc/modules.conf
+	 */
+	if (fst_excluded_cards != 0) {
+		/*
+		 * There are cards to exclude
+		 *
+		 */
+		for (i = 0; i < fst_excluded_cards; i++) {
+			if ((pdev->devfn) >> 3 == fst_excluded_list[i]) {
+				printk_info("FarSync PCI device %d not assigned\n",
+				       (pdev->devfn) >> 3);
+				return -EBUSY;
+			}
+		}
+	}
+
+	/* Allocate driver private data */
+	card = kmalloc(sizeof (struct fst_card_info), GFP_KERNEL);
+	if (card == NULL) {
+		printk_err("FarSync card found but insufficient memory for"
+			   " driver storage\n");
+		return -ENOMEM;
+	}
+	memset(card, 0, sizeof (struct fst_card_info));
+
+	/* Try to enable the device */
+	if ((err = pci_enable_device(pdev)) != 0) {
+		printk_err("Failed to enable card. Err %d\n", -err);
+		kfree(card);
+		return err;
+	}
+
+	if ((err = pci_request_regions(pdev, "FarSync")) !=0) {
+	        printk_err("Failed to allocate regions. Err %d\n", -err);
+		pci_disable_device(pdev);
+		kfree(card);
+	        return err;
+	}
+
+	/* Get virtual addresses of memory regions */
+	card->pci_conf = pci_resource_start(pdev, 1);
+	card->phys_mem = pci_resource_start(pdev, 2);
+	card->phys_ctlmem = pci_resource_start(pdev, 3);
+	if ((card->mem = ioremap(card->phys_mem, FST_MEMSIZE)) == NULL) {
+		printk_err("Physical memory remap failed\n");
+		pci_release_regions(pdev);
+		pci_disable_device(pdev);
+		kfree(card);
+		return -ENODEV;
+	}
+	if ((card->ctlmem = ioremap(card->phys_ctlmem, 0x10)) == NULL) {
+		printk_err("Control memory remap failed\n");
+		pci_release_regions(pdev);
+		pci_disable_device(pdev);
+		kfree(card);
+		return -ENODEV;
+	}
+	dbg(DBG_PCI, "kernel mem %p, ctlmem %p\n", card->mem, card->ctlmem);
+
+	/* Register the interrupt handler */
+	if (request_irq(pdev->irq, fst_intr, SA_SHIRQ, FST_DEV_NAME, card)) {
+		printk_err("Unable to register interrupt %d\n", card->irq);
+		pci_release_regions(pdev);
+		pci_disable_device(pdev);
+		iounmap(card->ctlmem);
+		iounmap(card->mem);
+		kfree(card);
+		return -ENODEV;
+	}
+
+	/* Record info we need */
+	card->irq = pdev->irq;
+	card->type = ent->driver_data;
+	card->family = ((ent->driver_data == FST_TYPE_T2P) ||
+			(ent->driver_data == FST_TYPE_T4P))
+	    ? FST_FAMILY_TXP : FST_FAMILY_TXU;
+	if ((ent->driver_data == FST_TYPE_T1U) ||
+	    (ent->driver_data == FST_TYPE_TE1))
+		card->nports = 1;
+	else
+		card->nports = ((ent->driver_data == FST_TYPE_T2P) ||
+				(ent->driver_data == FST_TYPE_T2U)) ? 2 : 4;
+
+	card->state = FST_UNINIT;
+        spin_lock_init ( &card->card_lock );
+
+        for ( i = 0 ; i < card->nports ; i++ ) {
+		struct net_device *dev = alloc_hdlcdev(&card->ports[i]);
+		hdlc_device *hdlc;
+		if (!dev) {
+			while (i--)
+				free_netdev(card->ports[i].dev);
+			printk_err ("FarSync: out of memory\n");
+                        free_irq(card->irq, card);
+                        pci_release_regions(pdev);
+                        pci_disable_device(pdev);
+                        iounmap(card->ctlmem);
+                        iounmap(card->mem);
+                        kfree(card);
+                        return -ENODEV;
+		}
+		card->ports[i].dev    = dev;
+                card->ports[i].card   = card;
+                card->ports[i].index  = i;
+                card->ports[i].run    = 0;
+
+		hdlc = dev_to_hdlc(dev);
+
+                /* Fill in the net device info */
+		/* Since this is a PCI setup this is purely
+		 * informational. Give them the buffer addresses
+		 * and basic card I/O.
+		 */
+                dev->mem_start   = card->phys_mem
+                                 + BUF_OFFSET ( txBuffer[i][0][0]);
+                dev->mem_end     = card->phys_mem
+                                 + BUF_OFFSET ( txBuffer[i][NUM_TX_BUFFER][0]);
+                dev->base_addr   = card->pci_conf;
+                dev->irq         = card->irq;
+
+                dev->tx_queue_len          = FST_TX_QUEUE_LEN;
+                dev->open                  = fst_open;
+                dev->stop                  = fst_close;
+                dev->do_ioctl              = fst_ioctl;
+                dev->watchdog_timeo        = FST_TX_TIMEOUT;
+                dev->tx_timeout            = fst_tx_timeout;
+                hdlc->attach = fst_attach;
+                hdlc->xmit   = fst_start_xmit;
+	}
+
+	card->device = pdev;
+
+	dbg(DBG_PCI, "type %d nports %d irq %d\n", card->type,
+	    card->nports, card->irq);
+	dbg(DBG_PCI, "conf %04x mem %08x ctlmem %08x\n",
+	    card->pci_conf, card->phys_mem, card->phys_ctlmem);
+
+	/* Reset the card's processor */
+	fst_cpureset(card);
+	card->state = FST_RESET;
+
+	/* Initialise DMA (if required) */
+	fst_init_dma(card);
+
+	/* Record driver data for later use */
+	pci_set_drvdata(pdev, card);
+
+	/* Remainder of card setup */
+	fst_card_array[no_of_cards_added] = card;
+	card->card_no = no_of_cards_added++;	/* Record instance and bump it */
+	fst_init_card(card);
+	if (card->family == FST_FAMILY_TXU) {
+		/*
+		 * Allocate a dma buffer for transmit and receives
+		 */
+		card->rx_dma_handle_host =
+		    pci_alloc_consistent(card->device, FST_MAX_MTU,
+					 &card->rx_dma_handle_card);
+		if (card->rx_dma_handle_host == NULL) {
+			printk_err("Could not allocate rx dma buffer\n");
+			fst_disable_intr(card);
+			pci_release_regions(pdev);
+			pci_disable_device(pdev);
+			iounmap(card->ctlmem);
+			iounmap(card->mem);
+			kfree(card);
+			return -ENOMEM;
+		}
+		card->tx_dma_handle_host =
+		    pci_alloc_consistent(card->device, FST_MAX_MTU,
+					 &card->tx_dma_handle_card);
+		if (card->tx_dma_handle_host == NULL) {
+			printk_err("Could not allocate tx dma buffer\n");
+			fst_disable_intr(card);
+			pci_release_regions(pdev);
+			pci_disable_device(pdev);
+			iounmap(card->ctlmem);
+			iounmap(card->mem);
+			kfree(card);
+			return -ENOMEM;
+		}
+	}
+	return 0;		/* Success */
+}
+
+/*
+ *      Cleanup and close down a card
+ */
+static void __devexit
+fst_remove_one(struct pci_dev *pdev)
+{
+	struct fst_card_info *card;
+	int i;
+
+	card = pci_get_drvdata(pdev);
+
+	for (i = 0; i < card->nports; i++) {
+		struct net_device *dev = port_to_dev(&card->ports[i]);
+		unregister_hdlc_device(dev);
+	}
+
+	fst_disable_intr(card);
+	free_irq(card->irq, card);
+
+	iounmap(card->ctlmem);
+	iounmap(card->mem);
+	pci_release_regions(pdev);
+	if (card->family == FST_FAMILY_TXU) {
+		/*
+		 * Free dma buffers
+		 */
+		pci_free_consistent(card->device, FST_MAX_MTU,
+				    card->rx_dma_handle_host,
+				    card->rx_dma_handle_card);
+		pci_free_consistent(card->device, FST_MAX_MTU,
+				    card->tx_dma_handle_host,
+				    card->tx_dma_handle_card);
+	}
+	fst_card_array[card->card_no] = NULL;
+}
+
+static struct pci_driver fst_driver = {
+        .name		= FST_NAME,
+        .id_table	= fst_pci_dev_id,
+        .probe		= fst_add_one,
+        .remove	= __devexit_p(fst_remove_one),
+        .suspend	= NULL,
+        .resume	= NULL,
+};
+
+static int __init
+fst_init(void)
+{
+	int i;
+
+	for (i = 0; i < FST_MAX_CARDS; i++)
+		fst_card_array[i] = NULL;
+	spin_lock_init(&fst_work_q_lock);
+	return pci_module_init(&fst_driver);
+}
+
+static void __exit
+fst_cleanup_module(void)
+{
+	printk_info("FarSync WAN driver unloading\n");
+	pci_unregister_driver(&fst_driver);
+}
+
+module_init(fst_init);
+module_exit(fst_cleanup_module);
diff --git a/drivers/net/wan/farsync.h b/drivers/net/wan/farsync.h
new file mode 100644
index 0000000..d871daf
--- /dev/null
+++ b/drivers/net/wan/farsync.h
@@ -0,0 +1,357 @@
+/*
+ *      FarSync X21 driver for Linux
+ *
+ *      Actually sync driver for X.21, V.35 and V.24 on FarSync T-series cards
+ *
+ *      Copyright (C) 2001 FarSite Communications Ltd.
+ *      www.farsite.co.uk
+ *
+ *      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.
+ *
+ *      Author: R.J.Dunlop      <bob.dunlop@farsite.co.uk>
+ *
+ *      For the most part this file only contains structures and information
+ *      that is visible to applications outside the driver. Shared memory
+ *      layout etc is internal to the driver and described within farsync.c.
+ *      Overlap exists in that the values used for some fields within the
+ *      ioctl interface extend into the cards firmware interface so values in
+ *      this file may not be changed arbitrarily.
+ */
+
+/*      What's in a name
+ *
+ *      The project name for this driver is Oscar. The driver is intended to be
+ *      used with the FarSite T-Series cards (T2P & T4P) running in the high
+ *      speed frame shifter mode. This is sometimes referred to as X.21 mode
+ *      which is a complete misnomer as the card continues to support V.24 and
+ *      V.35 as well as X.21.
+ *
+ *      A short common prefix is useful for routines within the driver to avoid
+ *      conflict with other similar drivers and I chosen to use "fst_" for this
+ *      purpose (FarSite T-series).
+ *
+ *      Finally the device driver needs a short network interface name. Since
+ *      "hdlc" is already in use I've chosen the even less informative "sync"
+ *      for the present.
+ */
+#define FST_NAME                "fst"           /* In debug/info etc */
+#define FST_NDEV_NAME           "sync"          /* For net interface */
+#define FST_DEV_NAME            "farsync"       /* For misc interfaces */
+
+
+/*      User version number
+ *
+ *      This version number is incremented with each official release of the
+ *      package and is a simplified number for normal user reference.
+ *      Individual files are tracked by the version control system and may
+ *      have individual versions (or IDs) that move much faster than the
+ *      the release version as individual updates are tracked.
+ */
+#define FST_USER_VERSION        "1.04"
+
+
+/*      Ioctl call command values
+ *
+ *      The first three private ioctls are used by the sync-PPP module,
+ *      allowing a little room for expansion we start our numbering at 10.
+ */
+#define FSTWRITE        (SIOCDEVPRIVATE+10)
+#define FSTCPURESET     (SIOCDEVPRIVATE+11)
+#define FSTCPURELEASE   (SIOCDEVPRIVATE+12)
+#define FSTGETCONF      (SIOCDEVPRIVATE+13)
+#define FSTSETCONF      (SIOCDEVPRIVATE+14)
+
+
+/*      FSTWRITE
+ *
+ *      Used to write a block of data (firmware etc) before the card is running
+ */
+struct fstioc_write {
+        unsigned int  size;
+        unsigned int  offset;
+        unsigned char data[0];
+};
+
+
+/*      FSTCPURESET and FSTCPURELEASE
+ *
+ *      These take no additional data.
+ *      FSTCPURESET forces the cards CPU into a reset state and holds it there.
+ *      FSTCPURELEASE releases the CPU from this reset state allowing it to run,
+ *      the reset vector should be setup before this ioctl is run.
+ */
+
+/*      FSTGETCONF and FSTSETCONF
+ *
+ *      Get and set a card/ports configuration.
+ *      In order to allow selective setting of items and for the kernel to
+ *      indicate a partial status response the first field "valid" is a bitmask
+ *      indicating which other fields in the structure are valid.
+ *      Many of the field names in this structure match those used in the
+ *      firmware shared memory configuration interface and come originally from
+ *      the NT header file Smc.h
+ *
+ *      When used with FSTGETCONF this structure should be zeroed before use.
+ *      This is to allow for possible future expansion when some of the fields
+ *      might be used to indicate a different (expanded) structure.
+ */
+struct fstioc_info {
+        unsigned int   valid;           /* Bits of structure that are valid */
+        unsigned int   nports;          /* Number of serial ports */
+        unsigned int   type;            /* Type index of card */
+        unsigned int   state;           /* State of card */
+        unsigned int   index;           /* Index of port ioctl was issued on */
+        unsigned int   smcFirmwareVersion;
+        unsigned long  kernelVersion;   /* What Kernel version we are working with */
+        unsigned short lineInterface;   /* Physical interface type */
+        unsigned char  proto;           /* Line protocol */
+        unsigned char  internalClock;   /* 1 => internal clock, 0 => external */
+        unsigned int   lineSpeed;       /* Speed in bps */
+        unsigned int   v24IpSts;        /* V.24 control input status */
+        unsigned int   v24OpSts;        /* V.24 control output status */
+        unsigned short clockStatus;     /* lsb: 0=> present, 1=> absent */
+        unsigned short cableStatus;     /* lsb: 0=> present, 1=> absent */
+        unsigned short cardMode;        /* lsb: LED id mode */
+        unsigned short debug;           /* Debug flags */
+        unsigned char  transparentMode; /* Not used always 0 */
+        unsigned char  invertClock;     /* Invert clock feature for syncing */
+        unsigned char  startingSlot;    /* Time slot to use for start of tx */
+        unsigned char  clockSource;     /* External or internal */
+        unsigned char  framing;         /* E1, T1 or J1 */
+        unsigned char  structure;       /* unframed, double, crc4, f4, f12, */
+                                        /* f24 f72 */
+        unsigned char  interface;       /* rj48c or bnc */
+        unsigned char  coding;          /* hdb3 b8zs */
+        unsigned char  lineBuildOut;    /* 0, -7.5, -15, -22 */
+        unsigned char  equalizer;       /* short or lon haul settings */
+        unsigned char  loopMode;        /* various loopbacks */
+        unsigned char  range;           /* cable lengths */
+        unsigned char  txBufferMode;    /* tx elastic buffer depth */
+        unsigned char  rxBufferMode;    /* rx elastic buffer depth */
+        unsigned char  losThreshold;    /* Attenuation on LOS signal */
+        unsigned char  idleCode;        /* Value to send as idle timeslot */
+        unsigned int   receiveBufferDelay; /* delay thro rx buffer timeslots */
+        unsigned int   framingErrorCount; /* framing errors */
+        unsigned int   codeViolationCount; /* code violations */
+        unsigned int   crcErrorCount;   /* CRC errors */
+        int            lineAttenuation; /* in dB*/
+        unsigned short lossOfSignal;
+        unsigned short receiveRemoteAlarm;
+        unsigned short alarmIndicationSignal;
+};
+
+/* "valid" bitmask */
+#define FSTVAL_NONE     0x00000000      /* Nothing valid (firmware not running).
+                                         * Slight misnomer. In fact nports,
+                                         * type, state and index will be set
+                                         * based on hardware detected.
+                                         */
+#define FSTVAL_OMODEM   0x0000001F      /* First 5 bits correspond to the
+                                         * output status bits defined for
+                                         * v24OpSts
+                                         */
+#define FSTVAL_SPEED    0x00000020      /* internalClock, lineSpeed, clockStatus
+                                         */
+#define FSTVAL_CABLE    0x00000040      /* lineInterface, cableStatus */
+#define FSTVAL_IMODEM   0x00000080      /* v24IpSts */
+#define FSTVAL_CARD     0x00000100      /* nports, type, state, index,
+                                         * smcFirmwareVersion
+                                         */
+#define FSTVAL_PROTO    0x00000200      /* proto */
+#define FSTVAL_MODE     0x00000400      /* cardMode */
+#define FSTVAL_PHASE    0x00000800      /* Clock phase */
+#define FSTVAL_TE1      0x00001000      /* T1E1 Configuration */
+#define FSTVAL_DEBUG    0x80000000      /* debug */
+#define FSTVAL_ALL      0x00001FFF      /* Note: does not include DEBUG flag */
+
+/* "type" */
+#define FST_TYPE_NONE   0               /* Probably should never happen */
+#define FST_TYPE_T2P    1               /* T2P X21 2 port card */
+#define FST_TYPE_T4P    2               /* T4P X21 4 port card */
+#define FST_TYPE_T1U    3               /* T1U X21 1 port card */
+#define FST_TYPE_T2U    4               /* T2U X21 2 port card */
+#define FST_TYPE_T4U    5               /* T4U X21 4 port card */
+#define FST_TYPE_TE1    6               /* T1E1 X21 1 port card */
+
+/* "family" */
+#define FST_FAMILY_TXP  0               /* T2P or T4P */
+#define FST_FAMILY_TXU  1               /* T1U or T2U or T4U */
+
+/* "state" */
+#define FST_UNINIT      0               /* Raw uninitialised state following
+                                         * system startup */
+#define FST_RESET       1               /* Processor held in reset state */
+#define FST_DOWNLOAD    2               /* Card being downloaded */
+#define FST_STARTING    3               /* Released following download */
+#define FST_RUNNING     4               /* Processor running */
+#define FST_BADVERSION  5               /* Bad shared memory version detected */
+#define FST_HALTED      6               /* Processor flagged a halt */
+#define FST_IFAILED     7               /* Firmware issued initialisation failed
+                                         * interrupt
+                                         */
+/* "lineInterface" */
+#define V24             1
+#define X21             2
+#define V35             3
+#define X21D            4
+#define T1              5
+#define E1              6
+#define J1              7
+
+/* "proto" */
+#define FST_HDLC        1               /* Cisco compatible HDLC */
+#define FST_PPP         2               /* Sync PPP */
+#define FST_MONITOR     3               /* Monitor only (raw packet reception) */
+#define FST_RAW         4               /* Two way raw packets */
+#define FST_GEN_HDLC    5               /* Using "Generic HDLC" module */
+
+/* "internalClock" */
+#define INTCLK          1
+#define EXTCLK          0
+
+/* "v24IpSts" bitmask */
+#define IPSTS_CTS       0x00000001      /* Clear To Send (Indicate for X.21) */
+#define IPSTS_INDICATE  IPSTS_CTS
+#define IPSTS_DSR       0x00000002      /* Data Set Ready (T2P Port A) */
+#define IPSTS_DCD       0x00000004      /* Data Carrier Detect */
+#define IPSTS_RI        0x00000008      /* Ring Indicator (T2P Port A) */
+#define IPSTS_TMI       0x00000010      /* Test Mode Indicator (Not Supported)*/
+
+/* "v24OpSts" bitmask */
+#define OPSTS_RTS       0x00000001      /* Request To Send (Control for X.21) */
+#define OPSTS_CONTROL   OPSTS_RTS
+#define OPSTS_DTR       0x00000002      /* Data Terminal Ready */
+#define OPSTS_DSRS      0x00000004      /* Data Signalling Rate Select (Not
+                                         * Supported) */
+#define OPSTS_SS        0x00000008      /* Select Standby (Not Supported) */
+#define OPSTS_LL        0x00000010      /* Maintenance Test (Not Supported) */
+
+/* "cardMode" bitmask */
+#define CARD_MODE_IDENTIFY      0x0001
+
+/* 
+ * Constants for T1/E1 configuration
+ */
+
+/*
+ * Clock source
+ */
+#define CLOCKING_SLAVE       0
+#define CLOCKING_MASTER      1
+
+/*
+ * Framing
+ */
+#define FRAMING_E1           0
+#define FRAMING_J1           1
+#define FRAMING_T1           2
+
+/*
+ * Structure
+ */
+#define STRUCTURE_UNFRAMED   0
+#define STRUCTURE_E1_DOUBLE  1
+#define STRUCTURE_E1_CRC4    2
+#define STRUCTURE_E1_CRC4M   3
+#define STRUCTURE_T1_4       4
+#define STRUCTURE_T1_12      5
+#define STRUCTURE_T1_24      6
+#define STRUCTURE_T1_72      7
+
+/*
+ * Interface
+ */
+#define INTERFACE_RJ48C      0
+#define INTERFACE_BNC        1
+
+/*
+ * Coding
+ */
+
+#define CODING_HDB3          0
+#define CODING_NRZ           1
+#define CODING_CMI           2
+#define CODING_CMI_HDB3      3
+#define CODING_CMI_B8ZS      4
+#define CODING_AMI           5
+#define CODING_AMI_ZCS       6
+#define CODING_B8ZS          7
+
+/*
+ * Line Build Out
+ */
+#define LBO_0dB              0
+#define LBO_7dB5             1
+#define LBO_15dB             2
+#define LBO_22dB5            3
+
+/*
+ * Range for long haul t1 > 655ft
+ */
+#define RANGE_0_133_FT       0
+#define RANGE_0_40_M         RANGE_0_133_FT
+#define RANGE_133_266_FT     1
+#define RANGE_40_81_M        RANGE_133_266_FT
+#define RANGE_266_399_FT     2
+#define RANGE_81_122_M       RANGE_266_399_FT
+#define RANGE_399_533_FT     3
+#define RANGE_122_162_M       RANGE_399_533_FT
+#define RANGE_533_655_FT     4
+#define RANGE_162_200_M      RANGE_533_655_FT
+/*
+ * Receive Equaliser
+ */
+#define EQUALIZER_SHORT      0
+#define EQUALIZER_LONG       1
+
+/*
+ * Loop modes
+ */
+#define LOOP_NONE            0
+#define LOOP_LOCAL           1
+#define LOOP_PAYLOAD_EXC_TS0 2
+#define LOOP_PAYLOAD_INC_TS0 3
+#define LOOP_REMOTE          4
+
+/*
+ * Buffer modes
+ */
+#define BUFFER_2_FRAME       0
+#define BUFFER_1_FRAME       1
+#define BUFFER_96_BIT        2
+#define BUFFER_NONE          3
+
+/*      Debug support
+ *
+ *      These should only be enabled for development kernels, production code
+ *      should define FST_DEBUG=0 in order to exclude the code.
+ *      Setting FST_DEBUG=1 will include all the debug code but in a disabled
+ *      state, use the FSTSETCONF ioctl to enable specific debug actions, or
+ *      FST_DEBUG can be set to prime the debug selection.
+ */
+#define FST_DEBUG       0x0000
+#if FST_DEBUG
+
+extern int fst_debug_mask;              /* Bit mask of actions to debug, bits
+                                         * listed below. Note: Bit 0 is used
+                                         * to trigger the inclusion of this
+                                         * code, without enabling any actions.
+                                         */
+#define DBG_INIT        0x0002          /* Card detection and initialisation */
+#define DBG_OPEN        0x0004          /* Open and close sequences */
+#define DBG_PCI         0x0008          /* PCI config operations */
+#define DBG_IOCTL       0x0010          /* Ioctls and other config */
+#define DBG_INTR        0x0020          /* Interrupt routines (be careful) */
+#define DBG_TX          0x0040          /* Packet transmission */
+#define DBG_RX          0x0080          /* Packet reception */
+#define DBG_CMD         0x0100          /* Port command issuing */
+
+#define DBG_ASS         0xFFFF          /* Assert like statements. Code that
+                                         * should never be reached, if you see
+                                         * one of these then I've been an ass
+                                         */
+#endif  /* FST_DEBUG */
+
diff --git a/drivers/net/wan/hd64570.h b/drivers/net/wan/hd64570.h
new file mode 100644
index 0000000..3839662
--- /dev/null
+++ b/drivers/net/wan/hd64570.h
@@ -0,0 +1,241 @@
+#ifndef __HD64570_H
+#define __HD64570_H
+
+/* SCA HD64570 register definitions - all addresses for mode 0 (8086 MPU)
+   and 1 (64180 MPU). For modes 2 and 3, XOR the address with 0x01.
+
+   Source: HD64570 SCA User's Manual
+*/
+
+
+
+/* SCA Control Registers */
+#define LPR    0x00		/* Low Power */
+
+/* Wait controller registers */
+#define PABR0  0x02		/* Physical Address Boundary 0 */
+#define PABR1  0x03		/* Physical Address Boundary 1 */
+#define WCRL   0x04		/* Wait Control L */
+#define WCRM   0x05		/* Wait Control M */
+#define WCRH   0x06		/* Wait Control H */
+
+#define PCR    0x08		/* DMA Priority Control */
+#define DMER   0x09		/* DMA Master Enable */
+
+
+/* Interrupt registers */
+#define ISR0   0x10		/* Interrupt Status 0  */
+#define ISR1   0x11		/* Interrupt Status 1  */
+#define ISR2   0x12		/* Interrupt Status 2  */
+
+#define IER0   0x14		/* Interrupt Enable 0  */
+#define IER1   0x15		/* Interrupt Enable 1  */
+#define IER2   0x16		/* Interrupt Enable 2  */
+
+#define ITCR   0x18		/* Interrupt Control */
+#define IVR    0x1A		/* Interrupt Vector */
+#define IMVR   0x1C		/* Interrupt Modified Vector */
+
+
+
+/* MSCI channel (port) 0 registers - offset 0x20
+   MSCI channel (port) 1 registers - offset 0x40 */
+
+#define MSCI0_OFFSET 0x20
+#define MSCI1_OFFSET 0x40
+
+#define TRBL   0x00		/* TX/RX buffer L */ 
+#define TRBH   0x01		/* TX/RX buffer H */ 
+#define ST0    0x02		/* Status 0 */
+#define ST1    0x03		/* Status 1 */
+#define ST2    0x04		/* Status 2 */
+#define ST3    0x05		/* Status 3 */
+#define FST    0x06		/* Frame Status  */
+#define IE0    0x08		/* Interrupt Enable 0 */
+#define IE1    0x09		/* Interrupt Enable 1 */
+#define IE2    0x0A		/* Interrupt Enable 2 */
+#define FIE    0x0B		/* Frame Interrupt Enable  */
+#define CMD    0x0C		/* Command */
+#define MD0    0x0E		/* Mode 0 */
+#define MD1    0x0F		/* Mode 1 */
+#define MD2    0x10		/* Mode 2 */
+#define CTL    0x11		/* Control */
+#define SA0    0x12		/* Sync/Address 0 */
+#define SA1    0x13		/* Sync/Address 1 */
+#define IDL    0x14		/* Idle Pattern */
+#define TMC    0x15		/* Time Constant */
+#define RXS    0x16		/* RX Clock Source */
+#define TXS    0x17		/* TX Clock Source */
+#define TRC0   0x18		/* TX Ready Control 0 */ 
+#define TRC1   0x19		/* TX Ready Control 1 */ 
+#define RRC    0x1A		/* RX Ready Control */ 
+#define CST0   0x1C		/* Current Status 0 */
+#define CST1   0x1D		/* Current Status 1 */
+
+
+/* Timer channel 0 (port 0 RX) registers - offset 0x60
+   Timer channel 1 (port 0 TX) registers - offset 0x68
+   Timer channel 2 (port 1 RX) registers - offset 0x70
+   Timer channel 3 (port 1 TX) registers - offset 0x78
+*/
+
+#define TIMER0RX_OFFSET 0x60
+#define TIMER0TX_OFFSET 0x68
+#define TIMER1RX_OFFSET 0x70
+#define TIMER1TX_OFFSET 0x78
+
+#define TCNTL  0x00		/* Up-counter L */
+#define TCNTH  0x01		/* Up-counter H */
+#define TCONRL 0x02		/* Constant L */
+#define TCONRH 0x03		/* Constant H */
+#define TCSR   0x04		/* Control/Status */
+#define TEPR   0x05		/* Expand Prescale */
+
+
+
+/* DMA channel 0 (port 0 RX) registers - offset 0x80
+   DMA channel 1 (port 0 TX) registers - offset 0xA0
+   DMA channel 2 (port 1 RX) registers - offset 0xC0
+   DMA channel 3 (port 1 TX) registers - offset 0xE0
+*/
+
+#define DMAC0RX_OFFSET 0x80
+#define DMAC0TX_OFFSET 0xA0
+#define DMAC1RX_OFFSET 0xC0
+#define DMAC1TX_OFFSET 0xE0
+
+#define BARL   0x00		/* Buffer Address L (chained block) */
+#define BARH   0x01		/* Buffer Address H (chained block) */
+#define BARB   0x02		/* Buffer Address B (chained block) */
+
+#define DARL   0x00		/* RX Destination Addr L (single block) */
+#define DARH   0x01		/* RX Destination Addr H (single block) */
+#define DARB   0x02		/* RX Destination Addr B (single block) */
+
+#define SARL   0x04		/* TX Source Address L (single block) */
+#define SARH   0x05		/* TX Source Address H (single block) */
+#define SARB   0x06		/* TX Source Address B (single block) */
+
+#define CPB    0x06		/* Chain Pointer Base (chained block) */
+
+#define CDAL   0x08		/* Current Descriptor Addr L (chained block) */
+#define CDAH   0x09		/* Current Descriptor Addr H (chained block) */
+#define EDAL   0x0A		/* Error Descriptor Addr L (chained block) */
+#define EDAH   0x0B		/* Error Descriptor Addr H (chained block) */
+#define BFLL   0x0C		/* RX Receive Buffer Length L (chained block)*/
+#define BFLH   0x0D		/* RX Receive Buffer Length H (chained block)*/
+#define BCRL   0x0E		/* Byte Count L */
+#define BCRH   0x0F		/* Byte Count H */
+#define DSR    0x10		/* DMA Status */
+#define DSR_RX(node) (DSR + (node ? DMAC1RX_OFFSET : DMAC0RX_OFFSET))
+#define DSR_TX(node) (DSR + (node ? DMAC1TX_OFFSET : DMAC0TX_OFFSET))
+#define DMR    0x11		/* DMA Mode */
+#define DMR_RX(node) (DMR + (node ? DMAC1RX_OFFSET : DMAC0RX_OFFSET))
+#define DMR_TX(node) (DMR + (node ? DMAC1TX_OFFSET : DMAC0TX_OFFSET))
+#define FCT    0x13		/* Frame End Interrupt Counter */
+#define FCT_RX(node) (FCT + (node ? DMAC1RX_OFFSET : DMAC0RX_OFFSET))
+#define FCT_TX(node) (FCT + (node ? DMAC1TX_OFFSET : DMAC0TX_OFFSET))
+#define DIR    0x14		/* DMA Interrupt Enable */
+#define DIR_RX(node) (DIR + (node ? DMAC1RX_OFFSET : DMAC0RX_OFFSET))
+#define DIR_TX(node) (DIR + (node ? DMAC1TX_OFFSET : DMAC0TX_OFFSET))
+#define DCR    0x15		/* DMA Command  */
+#define DCR_RX(node) (DCR + (node ? DMAC1RX_OFFSET : DMAC0RX_OFFSET))
+#define DCR_TX(node) (DCR + (node ? DMAC1TX_OFFSET : DMAC0TX_OFFSET))
+
+
+
+
+/* Descriptor Structure */
+
+typedef struct {
+	u16 cp;			/* Chain Pointer */
+	u32 bp;			/* Buffer Pointer (24 bits) */
+	u16 len;		/* Data Length */
+	u8 stat;		/* Status */
+	u8 unused;		/* pads to 2-byte boundary */
+}__attribute__ ((packed)) pkt_desc;
+
+
+/* Packet Descriptor Status bits */
+
+#define ST_TX_EOM     0x80	/* End of frame */
+#define ST_TX_EOT     0x01	/* End of transmition */
+
+#define ST_RX_EOM     0x80	/* End of frame */
+#define ST_RX_SHORT   0x40	/* Short frame */
+#define ST_RX_ABORT   0x20	/* Abort */
+#define ST_RX_RESBIT  0x10	/* Residual bit */
+#define ST_RX_OVERRUN 0x08	/* Overrun */
+#define ST_RX_CRC     0x04	/* CRC */
+
+#define ST_ERROR_MASK 0x7C
+
+#define DIR_EOTE      0x80      /* Transfer completed */
+#define DIR_EOME      0x40      /* Frame Transfer Completed (chained-block) */
+#define DIR_BOFE      0x20      /* Buffer Overflow/Underflow (chained-block)*/
+#define DIR_COFE      0x10      /* Counter Overflow (chained-block) */
+
+
+#define DSR_EOT       0x80      /* Transfer completed */
+#define DSR_EOM       0x40      /* Frame Transfer Completed (chained-block) */
+#define DSR_BOF       0x20      /* Buffer Overflow/Underflow (chained-block)*/
+#define DSR_COF       0x10      /* Counter Overflow (chained-block) */
+#define DSR_DE        0x02	/* DMA Enable */
+#define DSR_DWE       0x01      /* DMA Write Disable */
+
+/* DMA Master Enable Register (DMER) bits */
+#define DMER_DME      0x80	/* DMA Master Enable */
+
+
+#define CMD_RESET     0x21	/* Reset Channel */
+#define CMD_TX_ENABLE 0x02	/* Start transmitter */
+#define CMD_RX_ENABLE 0x12	/* Start receiver */
+
+#define MD0_HDLC      0x80	/* Bit-sync HDLC mode */
+#define MD0_CRC_ENA   0x04	/* Enable CRC code calculation */
+#define MD0_CRC_CCITT 0x02	/* CCITT CRC instead of CRC-16 */
+#define MD0_CRC_PR1   0x01	/* Initial all-ones instead of all-zeros */
+
+#define MD0_CRC_NONE  0x00
+#define MD0_CRC_16_0  0x04
+#define MD0_CRC_16    0x05
+#define MD0_CRC_ITU_0 0x06
+#define MD0_CRC_ITU   0x07
+
+#define MD2_NRZ	      0x00
+#define MD2_NRZI      0x20
+#define MD2_MANCHESTER 0x80
+#define MD2_FM_MARK   0xA0
+#define MD2_FM_SPACE  0xC0
+#define MD2_LOOPBACK  0x03      /* Local data Loopback */
+
+#define CTL_NORTS     0x01
+#define CTL_IDLE      0x10	/* Transmit an idle pattern */
+#define CTL_UDRNC     0x20	/* Idle after CRC or FCS+flag transmition */
+
+#define ST0_TXRDY     0x02	/* TX ready */
+#define ST0_RXRDY     0x01	/* RX ready */
+
+#define ST1_UDRN      0x80	/* MSCI TX underrun */
+#define ST1_CDCD      0x04	/* DCD level changed */
+
+#define ST3_CTS       0x08	/* modem input - /CTS */
+#define ST3_DCD       0x04	/* modem input - /DCD */
+
+#define IE0_TXINT     0x80	/* TX INT MSCI interrupt enable */
+#define IE0_RXINTA    0x40	/* RX INT A MSCI interrupt enable */
+#define IE1_UDRN      0x80	/* TX underrun MSCI interrupt enable */
+#define IE1_CDCD      0x04	/* DCD level changed */
+
+#define DCR_ABORT     0x01	/* Software abort command */
+#define DCR_CLEAR_EOF 0x02	/* Clear EOF interrupt */
+
+/* TX and RX Clock Source - RXS and TXS */
+#define CLK_BRG_MASK  0x0F
+#define CLK_LINE_RX   0x00	/* TX/RX clock line input */
+#define CLK_LINE_TX   0x00	/* TX/RX line input */
+#define CLK_BRG_RX    0x40	/* internal baud rate generator */
+#define CLK_BRG_TX    0x40	/* internal baud rate generator */
+#define CLK_RXCLK_TX  0x60	/* TX clock from RX clock */
+
+#endif
diff --git a/drivers/net/wan/hd64572.h b/drivers/net/wan/hd64572.h
new file mode 100644
index 0000000..96567c2
--- /dev/null
+++ b/drivers/net/wan/hd64572.h
@@ -0,0 +1,527 @@
+/*
+ * hd64572.h	Description of the Hitachi HD64572 (SCA-II), valid for 
+ * 		CPU modes 0 & 2.
+ *
+ * Author:	Ivan Passos <ivan@cyclades.com>
+ *
+ * Copyright:   (c) 2000-2001 Cyclades Corp.
+ *
+ *	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.
+ *
+ * $Log: hd64572.h,v $
+ * Revision 3.1  2001/06/15 12:41:10  regina
+ * upping major version number
+ *
+ * Revision 1.1.1.1  2001/06/13 20:24:49  daniela
+ * PC300 initial CVS version (3.4.0-pre1)
+ *
+ * Revision 1.0 2000/01/25 ivan
+ * Initial version.
+ *
+ */
+
+#ifndef __HD64572_H
+#define __HD64572_H
+
+/* Illegal Access Register */
+#define	ILAR	0x00
+
+/* Wait Controller Registers */
+#define PABR0L	0x20	/* Physical Addr Boundary Register 0 L */
+#define PABR0H	0x21	/* Physical Addr Boundary Register 0 H */
+#define PABR1L	0x22	/* Physical Addr Boundary Register 1 L */
+#define PABR1H	0x23	/* Physical Addr Boundary Register 1 H */
+#define WCRL	0x24	/* Wait Control Register L */
+#define WCRM	0x25	/* Wait Control Register M */
+#define WCRH	0x26	/* Wait Control Register H */
+
+/* Interrupt Registers */
+#define IVR	0x60	/* Interrupt Vector Register */
+#define IMVR	0x64	/* Interrupt Modified Vector Register */
+#define ITCR	0x68	/* Interrupt Control Register */
+#define ISR0	0x6c	/* Interrupt Status Register 0 */
+#define ISR1	0x70	/* Interrupt Status Register 1 */
+#define IER0	0x74	/* Interrupt Enable Register 0 */
+#define IER1	0x78	/* Interrupt Enable Register 1 */
+
+/* Register Access Macros (chan is 0 or 1 in _any_ case) */
+#define	M_REG(reg, chan)	(reg + 0x80*chan)		/* MSCI */
+#define	DRX_REG(reg, chan)	(reg + 0x40*chan)		/* DMA Rx */
+#define	DTX_REG(reg, chan)	(reg + 0x20*(2*chan + 1))	/* DMA Tx */
+#define	TRX_REG(reg, chan)	(reg + 0x20*chan)		/* Timer Rx */
+#define	TTX_REG(reg, chan)	(reg + 0x10*(2*chan + 1))	/* Timer Tx */
+#define	ST_REG(reg, chan)	(reg + 0x80*chan)		/* Status Cnt */
+#define IR0_DRX(val, chan)	((val)<<(8*(chan)))		/* Int DMA Rx */
+#define IR0_DTX(val, chan)	((val)<<(4*(2*chan + 1)))	/* Int DMA Tx */
+#define IR0_M(val, chan)	((val)<<(8*(chan)))		/* Int MSCI */
+
+/* MSCI Channel Registers */
+#define MSCI0_OFFSET 0x00
+#define MSCI1_OFFSET 0x80
+
+#define MD0	0x138	/* Mode reg 0 */
+#define MD1	0x139	/* Mode reg 1 */
+#define MD2	0x13a	/* Mode reg 2 */
+#define MD3	0x13b	/* Mode reg 3 */
+#define CTL	0x130	/* Control reg */
+#define RXS	0x13c	/* RX clock source */
+#define TXS	0x13d	/* TX clock source */
+#define EXS	0x13e	/* External clock input selection */
+#define TMCT	0x144	/* Time constant (Tx) */
+#define TMCR	0x145	/* Time constant (Rx) */
+#define CMD	0x128	/* Command reg */
+#define ST0	0x118	/* Status reg 0 */
+#define ST1	0x119	/* Status reg 1 */
+#define ST2	0x11a	/* Status reg 2 */
+#define ST3	0x11b	/* Status reg 3 */
+#define ST4	0x11c	/* Status reg 4 */
+#define FST	0x11d	/* frame Status reg  */
+#define IE0	0x120	/* Interrupt enable reg 0 */
+#define IE1	0x121	/* Interrupt enable reg 1 */
+#define IE2	0x122	/* Interrupt enable reg 2 */
+#define IE4	0x124	/* Interrupt enable reg 4 */
+#define FIE	0x125	/* Frame Interrupt enable reg  */
+#define SA0	0x140	/* Syn Address reg 0 */
+#define SA1	0x141	/* Syn Address reg 1 */
+#define IDL	0x142	/* Idle register */
+#define TRBL	0x100	/* TX/RX buffer reg L */ 
+#define TRBK	0x101	/* TX/RX buffer reg K */ 
+#define TRBJ	0x102	/* TX/RX buffer reg J */ 
+#define TRBH	0x103	/* TX/RX buffer reg H */ 
+#define TRC0	0x148	/* TX Ready control reg 0 */ 
+#define TRC1	0x149	/* TX Ready control reg 1 */ 
+#define RRC	0x14a	/* RX Ready control reg */ 
+#define CST0	0x108	/* Current Status Register 0 */ 
+#define CST1	0x109	/* Current Status Register 1 */ 
+#define CST2	0x10a	/* Current Status Register 2 */ 
+#define CST3	0x10b	/* Current Status Register 3 */ 
+#define GPO	0x131	/* General Purpose Output Pin Ctl Reg */
+#define TFS	0x14b	/* Tx Start Threshold Ctl Reg */
+#define TFN	0x143	/* Inter-transmit-frame Time Fill Ctl Reg */
+#define TBN	0x110	/* Tx Buffer Number Reg */
+#define RBN	0x111	/* Rx Buffer Number Reg */
+#define TNR0	0x150	/* Tx DMA Request Ctl Reg 0 */
+#define TNR1	0x151	/* Tx DMA Request Ctl Reg 1 */
+#define TCR	0x152	/* Tx DMA Critical Request Reg */
+#define RNR	0x154	/* Rx DMA Request Ctl Reg */
+#define RCR	0x156	/* Rx DMA Critical Request Reg */
+
+/* Timer Registers */
+#define TIMER0RX_OFFSET 0x00
+#define TIMER0TX_OFFSET 0x10
+#define TIMER1RX_OFFSET 0x20
+#define TIMER1TX_OFFSET 0x30
+
+#define TCNTL	0x200	/* Timer Upcounter L */
+#define TCNTH	0x201	/* Timer Upcounter H */
+#define TCONRL	0x204	/* Timer Constant Register L */
+#define TCONRH	0x205	/* Timer Constant Register H */
+#define TCSR	0x206	/* Timer Control/Status Register */
+#define TEPR	0x207	/* Timer Expand Prescale Register */
+
+/* DMA registers */
+#define PCR		0x40		/* DMA priority control reg */
+#define DRR		0x44		/* DMA reset reg */
+#define DMER		0x07		/* DMA Master Enable reg */
+#define BTCR		0x08		/* Burst Tx Ctl Reg */
+#define BOLR		0x0c		/* Back-off Length Reg */
+#define DSR_RX(chan)	(0x48 + 2*chan)	/* DMA Status Reg (Rx) */
+#define DSR_TX(chan)	(0x49 + 2*chan)	/* DMA Status Reg (Tx) */
+#define DIR_RX(chan)	(0x4c + 2*chan)	/* DMA Interrupt Enable Reg (Rx) */
+#define DIR_TX(chan)	(0x4d + 2*chan)	/* DMA Interrupt Enable Reg (Tx) */
+#define FCT_RX(chan)	(0x50 + 2*chan)	/* Frame End Interrupt Counter (Rx) */
+#define FCT_TX(chan)	(0x51 + 2*chan)	/* Frame End Interrupt Counter (Tx) */
+#define DMR_RX(chan)	(0x54 + 2*chan)	/* DMA Mode Reg (Rx) */
+#define DMR_TX(chan)	(0x55 + 2*chan)	/* DMA Mode Reg (Tx) */
+#define DCR_RX(chan)	(0x58 + 2*chan)	/* DMA Command Reg (Rx) */
+#define DCR_TX(chan)	(0x59 + 2*chan)	/* DMA Command Reg (Tx) */
+
+/* DMA Channel Registers */
+#define DMAC0RX_OFFSET 0x00
+#define DMAC0TX_OFFSET 0x20
+#define DMAC1RX_OFFSET 0x40
+#define DMAC1TX_OFFSET 0x60
+
+#define DARL	0x80	/* Dest Addr Register L (single-block, RX only) */
+#define DARH	0x81	/* Dest Addr Register H (single-block, RX only) */
+#define DARB	0x82	/* Dest Addr Register B (single-block, RX only) */
+#define DARBH	0x83	/* Dest Addr Register BH (single-block, RX only) */
+#define SARL	0x80	/* Source Addr Register L (single-block, TX only) */
+#define SARH	0x81	/* Source Addr Register H (single-block, TX only) */
+#define SARB	0x82	/* Source Addr Register B (single-block, TX only) */
+#define DARBH	0x83	/* Source Addr Register BH (single-block, TX only) */
+#define BARL	0x80	/* Buffer Addr Register L (chained-block) */
+#define BARH	0x81	/* Buffer Addr Register H (chained-block) */
+#define BARB	0x82	/* Buffer Addr Register B (chained-block) */
+#define BARBH	0x83	/* Buffer Addr Register BH (chained-block) */
+#define CDAL	0x84	/* Current Descriptor Addr Register L */
+#define CDAH	0x85	/* Current Descriptor Addr Register H */
+#define CDAB	0x86	/* Current Descriptor Addr Register B */
+#define CDABH	0x87	/* Current Descriptor Addr Register BH */
+#define EDAL	0x88	/* Error Descriptor Addr Register L */
+#define EDAH	0x89	/* Error Descriptor Addr Register H */
+#define EDAB	0x8a	/* Error Descriptor Addr Register B */
+#define EDABH	0x8b	/* Error Descriptor Addr Register BH */
+#define BFLL	0x90	/* RX Buffer Length L (only RX) */
+#define BFLH	0x91	/* RX Buffer Length H (only RX) */
+#define BCRL	0x8c	/* Byte Count Register L */
+#define BCRH	0x8d	/* Byte Count Register H */
+
+/* Block Descriptor Structure */
+typedef struct {
+	unsigned long	next;		/* pointer to next block descriptor */
+	unsigned long	ptbuf;		/* buffer pointer */
+	unsigned short	len;		/* data length */
+	unsigned char	status;		/* status */
+	unsigned char	filler[5];	/* alignment filler (16 bytes) */ 
+} pcsca_bd_t;
+
+/* Block Descriptor Structure */
+typedef struct {
+	u32 cp;			/* pointer to next block descriptor */
+	u32 bp;			/* buffer pointer */
+	u16 len;		/* data length */
+	u8 stat;		/* status */
+	u8 unused;		/* pads to 4-byte boundary */
+}pkt_desc;
+
+
+/*
+	Descriptor Status definitions:
+
+	Bit	Transmission	Reception
+
+	7	EOM		EOM
+	6	-		Short Frame
+	5	-		Abort
+	4	-		Residual bit
+	3	Underrun	Overrun	
+	2	-		CRC
+	1	Ownership	Ownership
+	0	EOT		-
+*/
+#define DST_EOT		0x01	/* End of transmit command */
+#define DST_OSB		0x02	/* Ownership bit */
+#define DST_CRC		0x04	/* CRC Error */
+#define DST_OVR		0x08	/* Overrun */
+#define DST_UDR		0x08	/* Underrun */
+#define DST_RBIT	0x10	/* Residual bit */
+#define DST_ABT		0x20	/* Abort */
+#define DST_SHRT	0x40	/* Short Frame  */
+#define DST_EOM		0x80	/* End of Message  */
+
+/* Packet Descriptor Status bits */
+
+#define ST_TX_EOM     0x80	/* End of frame */
+#define ST_TX_UNDRRUN 0x08
+#define ST_TX_OWNRSHP 0x02
+#define ST_TX_EOT     0x01	/* End of transmition */
+
+#define ST_RX_EOM     0x80	/* End of frame */
+#define ST_RX_SHORT   0x40	/* Short frame */
+#define ST_RX_ABORT   0x20	/* Abort */
+#define ST_RX_RESBIT  0x10	/* Residual bit */
+#define ST_RX_OVERRUN 0x08	/* Overrun */
+#define ST_RX_CRC     0x04	/* CRC */
+#define ST_RX_OWNRSHP 0x02
+
+#define ST_ERROR_MASK 0x7C
+
+/* Status Counter Registers */
+#define CMCR	0x158	/* Counter Master Ctl Reg */
+#define TECNTL	0x160	/* Tx EOM Counter L */
+#define TECNTM	0x161	/* Tx EOM Counter M */
+#define TECNTH	0x162	/* Tx EOM Counter H */
+#define TECCR	0x163	/* Tx EOM Counter Ctl Reg */
+#define URCNTL	0x164	/* Underrun Counter L */
+#define URCNTH	0x165	/* Underrun Counter H */
+#define URCCR	0x167	/* Underrun Counter Ctl Reg */
+#define RECNTL	0x168	/* Rx EOM Counter L */
+#define RECNTM	0x169	/* Rx EOM Counter M */
+#define RECNTH	0x16a	/* Rx EOM Counter H */
+#define RECCR	0x16b	/* Rx EOM Counter Ctl Reg */
+#define ORCNTL	0x16c	/* Overrun Counter L */
+#define ORCNTH	0x16d	/* Overrun Counter H */
+#define ORCCR	0x16f	/* Overrun Counter Ctl Reg */
+#define CECNTL	0x170	/* CRC Counter L */
+#define CECNTH	0x171	/* CRC Counter H */
+#define CECCR	0x173	/* CRC Counter Ctl Reg */
+#define ABCNTL	0x174	/* Abort frame Counter L */
+#define ABCNTH	0x175	/* Abort frame Counter H */
+#define ABCCR	0x177	/* Abort frame Counter Ctl Reg */
+#define SHCNTL	0x178	/* Short frame Counter L */
+#define SHCNTH	0x179	/* Short frame Counter H */
+#define SHCCR	0x17b	/* Short frame Counter Ctl Reg */
+#define RSCNTL	0x17c	/* Residual bit Counter L */
+#define RSCNTH	0x17d	/* Residual bit Counter H */
+#define RSCCR	0x17f	/* Residual bit Counter Ctl Reg */
+
+/* Register Programming Constants */
+
+#define IR0_DMIC	0x00000001
+#define IR0_DMIB	0x00000002
+#define IR0_DMIA	0x00000004
+#define IR0_EFT		0x00000008
+#define IR0_DMAREQ	0x00010000
+#define IR0_TXINT	0x00020000
+#define IR0_RXINTB	0x00040000
+#define IR0_RXINTA	0x00080000
+#define IR0_TXRDY	0x00100000
+#define IR0_RXRDY	0x00200000
+
+#define MD0_CRC16_0	0x00
+#define MD0_CRC16_1	0x01
+#define MD0_CRC32	0x02
+#define MD0_CRC_CCITT	0x03
+#define MD0_CRCC0	0x04
+#define MD0_CRCC1	0x08
+#define MD0_AUTO_ENA	0x10
+#define MD0_ASYNC	0x00
+#define MD0_BY_MSYNC	0x20
+#define MD0_BY_BISYNC	0x40
+#define MD0_BY_EXT	0x60
+#define MD0_BIT_SYNC	0x80
+#define MD0_TRANSP	0xc0
+
+#define MD0_HDLC        0x80	/* Bit-sync HDLC mode */
+
+#define MD0_CRC_NONE	0x00
+#define MD0_CRC_16_0	0x04
+#define MD0_CRC_16	0x05
+#define MD0_CRC_ITU32	0x06
+#define MD0_CRC_ITU	0x07
+
+#define MD1_NOADDR	0x00
+#define MD1_SADDR1	0x40
+#define MD1_SADDR2	0x80
+#define MD1_DADDR	0xc0
+
+#define MD2_NRZI_IEEE	0x40
+#define MD2_MANCHESTER	0x80
+#define MD2_FM_MARK	0xA0
+#define MD2_FM_SPACE	0xC0
+#define MD2_LOOPBACK	0x03	/* Local data Loopback */
+
+#define MD2_F_DUPLEX	0x00
+#define MD2_AUTO_ECHO	0x01
+#define MD2_LOOP_HI_Z	0x02
+#define MD2_LOOP_MIR	0x03
+#define MD2_ADPLL_X8	0x00
+#define MD2_ADPLL_X16	0x08
+#define MD2_ADPLL_X32	0x10
+#define MD2_NRZ		0x00
+#define MD2_NRZI	0x20
+#define MD2_NRZ_IEEE	0x40
+#define MD2_MANCH	0x00
+#define MD2_FM1		0x20
+#define MD2_FM0		0x40
+#define MD2_FM		0x80
+
+#define CTL_RTS		0x01
+#define CTL_DTR		0x02
+#define CTL_SYN		0x04
+#define CTL_IDLC	0x10
+#define CTL_UDRNC	0x20
+#define CTL_URSKP	0x40
+#define CTL_URCT	0x80
+
+#define CTL_NORTS	0x01
+#define CTL_NODTR	0x02
+#define CTL_IDLE	0x10
+
+#define	RXS_BR0		0x01
+#define	RXS_BR1		0x02
+#define	RXS_BR2		0x04
+#define	RXS_BR3		0x08
+#define	RXS_ECLK	0x00
+#define	RXS_ECLK_NS	0x20
+#define	RXS_IBRG	0x40
+#define	RXS_PLL1	0x50
+#define	RXS_PLL2	0x60
+#define	RXS_PLL3	0x70
+#define	RXS_DRTXC	0x80
+
+#define	TXS_BR0		0x01
+#define	TXS_BR1		0x02
+#define	TXS_BR2		0x04
+#define	TXS_BR3		0x08
+#define	TXS_ECLK	0x00
+#define	TXS_IBRG	0x40
+#define	TXS_RCLK	0x60
+#define	TXS_DTRXC	0x80
+
+#define	EXS_RES0	0x01
+#define	EXS_RES1	0x02
+#define	EXS_RES2	0x04
+#define	EXS_TES0	0x10
+#define	EXS_TES1	0x20
+#define	EXS_TES2	0x40
+
+#define CLK_BRG_MASK	0x0F
+#define CLK_PIN_OUT	0x80
+#define CLK_LINE    	0x00	/* clock line input */
+#define CLK_BRG     	0x40	/* internal baud rate generator */
+#define CLK_TX_RXCLK	0x60	/* TX clock from RX clock */
+
+#define CMD_RX_RST	0x11
+#define CMD_RX_ENA	0x12
+#define CMD_RX_DIS	0x13
+#define CMD_RX_CRC_INIT	0x14
+#define CMD_RX_MSG_REJ	0x15
+#define CMD_RX_MP_SRCH	0x16
+#define CMD_RX_CRC_EXC	0x17
+#define CMD_RX_CRC_FRC	0x18
+#define CMD_TX_RST	0x01
+#define CMD_TX_ENA	0x02
+#define CMD_TX_DISA	0x03
+#define CMD_TX_CRC_INIT	0x04
+#define CMD_TX_CRC_EXC	0x05
+#define CMD_TX_EOM	0x06
+#define CMD_TX_ABORT	0x07
+#define CMD_TX_MP_ON	0x08
+#define CMD_TX_BUF_CLR	0x09
+#define CMD_TX_DISB	0x0b
+#define CMD_CH_RST	0x21
+#define CMD_SRCH_MODE	0x31
+#define CMD_NOP		0x00
+
+#define CMD_RESET	0x21
+#define CMD_TX_ENABLE	0x02
+#define CMD_RX_ENABLE	0x12
+
+#define ST0_RXRDY	0x01
+#define ST0_TXRDY	0x02
+#define ST0_RXINTB	0x20
+#define ST0_RXINTA	0x40
+#define ST0_TXINT	0x80
+
+#define ST1_IDLE	0x01
+#define ST1_ABORT	0x02
+#define ST1_CDCD	0x04
+#define ST1_CCTS	0x08
+#define ST1_SYN_FLAG	0x10
+#define ST1_CLMD	0x20
+#define ST1_TXIDLE	0x40
+#define ST1_UDRN	0x80
+
+#define ST2_CRCE	0x04
+#define ST2_ONRN	0x08
+#define ST2_RBIT	0x10
+#define ST2_ABORT	0x20
+#define ST2_SHORT	0x40
+#define ST2_EOM		0x80
+
+#define ST3_RX_ENA	0x01
+#define ST3_TX_ENA	0x02
+#define ST3_DCD		0x04
+#define ST3_CTS		0x08
+#define ST3_SRCH_MODE	0x10
+#define ST3_SLOOP	0x20
+#define ST3_GPI		0x80
+
+#define ST4_RDNR	0x01
+#define ST4_RDCR	0x02
+#define ST4_TDNR	0x04
+#define ST4_TDCR	0x08
+#define ST4_OCLM	0x20
+#define ST4_CFT		0x40
+#define ST4_CGPI	0x80
+
+#define FST_CRCEF	0x04
+#define FST_OVRNF	0x08
+#define FST_RBIF	0x10
+#define FST_ABTF	0x20
+#define FST_SHRTF	0x40
+#define FST_EOMF	0x80
+
+#define IE0_RXRDY	0x01
+#define IE0_TXRDY	0x02
+#define IE0_RXINTB	0x20
+#define IE0_RXINTA	0x40
+#define IE0_TXINT	0x80
+#define IE0_UDRN	0x00008000 /* TX underrun MSCI interrupt enable */
+#define IE0_CDCD	0x00000400 /* CD level change interrupt enable */
+
+#define IE1_IDLD	0x01
+#define IE1_ABTD	0x02
+#define IE1_CDCD	0x04
+#define IE1_CCTS	0x08
+#define IE1_SYNCD	0x10
+#define IE1_CLMD	0x20
+#define IE1_IDL		0x40
+#define IE1_UDRN	0x80
+
+#define IE2_CRCE	0x04
+#define IE2_OVRN	0x08
+#define IE2_RBIT	0x10
+#define IE2_ABT		0x20
+#define IE2_SHRT	0x40
+#define IE2_EOM		0x80
+
+#define IE4_RDNR	0x01
+#define IE4_RDCR	0x02
+#define IE4_TDNR	0x04
+#define IE4_TDCR	0x08
+#define IE4_OCLM	0x20
+#define IE4_CFT		0x40
+#define IE4_CGPI	0x80
+
+#define FIE_CRCEF	0x04
+#define FIE_OVRNF	0x08
+#define FIE_RBIF	0x10
+#define FIE_ABTF	0x20
+#define FIE_SHRTF	0x40
+#define FIE_EOMF	0x80
+
+#define DSR_DWE		0x01
+#define DSR_DE		0x02
+#define DSR_REF		0x04
+#define DSR_UDRF	0x04
+#define DSR_COA		0x08
+#define DSR_COF		0x10
+#define DSR_BOF		0x20
+#define DSR_EOM		0x40
+#define DSR_EOT		0x80
+
+#define DIR_REF		0x04
+#define DIR_UDRF	0x04
+#define DIR_COA		0x08
+#define DIR_COF		0x10
+#define DIR_BOF		0x20
+#define DIR_EOM		0x40
+#define DIR_EOT		0x80
+
+#define DIR_REFE	0x04
+#define DIR_UDRFE	0x04
+#define DIR_COAE	0x08
+#define DIR_COFE	0x10
+#define DIR_BOFE	0x20
+#define DIR_EOME	0x40
+#define DIR_EOTE	0x80
+
+#define DMR_CNTE	0x02
+#define DMR_NF		0x04
+#define DMR_SEOME	0x08
+#define DMR_TMOD	0x10
+
+#define DMER_DME        0x80	/* DMA Master Enable */
+
+#define DCR_SW_ABT	0x01
+#define DCR_FCT_CLR	0x02
+
+#define DCR_ABORT	0x01
+#define DCR_CLEAR_EOF	0x02
+
+#define PCR_COTE	0x80
+#define PCR_PR0		0x01
+#define PCR_PR1		0x02
+#define PCR_PR2		0x04
+#define PCR_CCC		0x08
+#define PCR_BRC		0x10
+#define PCR_OSB		0x40
+#define PCR_BURST	0x80
+
+#endif /* (__HD64572_H) */
diff --git a/drivers/net/wan/hd6457x.c b/drivers/net/wan/hd6457x.c
new file mode 100644
index 0000000..d374332
--- /dev/null
+++ b/drivers/net/wan/hd6457x.c
@@ -0,0 +1,853 @@
+/*
+ * Hitachi SCA HD64570 and HD64572 common driver for Linux
+ *
+ * Copyright (C) 1998-2003 Krzysztof Halasa <khc@pm.waw.pl>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of version 2 of the GNU General Public License
+ * as published by the Free Software Foundation.
+ *
+ * Sources of information:
+ *    Hitachi HD64570 SCA User's Manual
+ *    Hitachi HD64572 SCA-II User's Manual
+ *
+ * We use the following SCA memory map:
+ *
+ * Packet buffer descriptor rings - starting from winbase or win0base:
+ * rx_ring_buffers * sizeof(pkt_desc) = logical channel #0 RX ring
+ * tx_ring_buffers * sizeof(pkt_desc) = logical channel #0 TX ring
+ * rx_ring_buffers * sizeof(pkt_desc) = logical channel #1 RX ring (if used)
+ * tx_ring_buffers * sizeof(pkt_desc) = logical channel #1 TX ring (if used)
+ *
+ * Packet data buffers - starting from winbase + buff_offset:
+ * rx_ring_buffers * HDLC_MAX_MRU     = logical channel #0 RX buffers
+ * tx_ring_buffers * HDLC_MAX_MRU     = logical channel #0 TX buffers
+ * rx_ring_buffers * HDLC_MAX_MRU     = logical channel #0 RX buffers (if used)
+ * tx_ring_buffers * HDLC_MAX_MRU     = logical channel #0 TX buffers (if used)
+ */
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include <linux/jiffies.h>
+#include <linux/types.h>
+#include <linux/fcntl.h>
+#include <linux/interrupt.h>
+#include <linux/in.h>
+#include <linux/string.h>
+#include <linux/errno.h>
+#include <linux/init.h>
+#include <linux/ioport.h>
+#include <linux/bitops.h>
+
+#include <asm/system.h>
+#include <asm/uaccess.h>
+#include <asm/io.h>
+
+#include <linux/netdevice.h>
+#include <linux/skbuff.h>
+
+#include <linux/hdlc.h>
+
+#if (!defined (__HD64570_H) && !defined (__HD64572_H)) || \
+    (defined (__HD64570_H) && defined (__HD64572_H))
+#error Either hd64570.h or hd64572.h must be included
+#endif
+
+#define get_msci(port)	  (phy_node(port) ?   MSCI1_OFFSET :   MSCI0_OFFSET)
+#define get_dmac_rx(port) (phy_node(port) ? DMAC1RX_OFFSET : DMAC0RX_OFFSET)
+#define get_dmac_tx(port) (phy_node(port) ? DMAC1TX_OFFSET : DMAC0TX_OFFSET)
+
+#define SCA_INTR_MSCI(node)    (node ? 0x10 : 0x01)
+#define SCA_INTR_DMAC_RX(node) (node ? 0x20 : 0x02)
+#define SCA_INTR_DMAC_TX(node) (node ? 0x40 : 0x04)
+
+#ifdef __HD64570_H /* HD64570 */
+#define sca_outa(value, reg, card)	sca_outw(value, reg, card)
+#define sca_ina(reg, card)		sca_inw(reg, card)
+#define writea(value, ptr)		writew(value, ptr)
+
+#else /* HD64572 */
+#define sca_outa(value, reg, card)	sca_outl(value, reg, card)
+#define sca_ina(reg, card)		sca_inl(reg, card)
+#define writea(value, ptr)		writel(value, ptr)
+#endif
+
+static inline struct net_device *port_to_dev(port_t *port)
+{
+	return port->dev;
+}
+
+static inline int sca_intr_status(card_t *card)
+{
+	u8 result = 0;
+
+#ifdef __HD64570_H /* HD64570 */
+	u8 isr0 = sca_in(ISR0, card);
+	u8 isr1 = sca_in(ISR1, card);
+
+	if (isr1 & 0x03) result |= SCA_INTR_DMAC_RX(0);
+	if (isr1 & 0x0C) result |= SCA_INTR_DMAC_TX(0);
+	if (isr1 & 0x30) result |= SCA_INTR_DMAC_RX(1);
+	if (isr1 & 0xC0) result |= SCA_INTR_DMAC_TX(1);
+	if (isr0 & 0x0F) result |= SCA_INTR_MSCI(0);
+	if (isr0 & 0xF0) result |= SCA_INTR_MSCI(1);
+
+#else /* HD64572 */
+	u32 isr0 = sca_inl(ISR0, card);
+
+	if (isr0 & 0x0000000F) result |= SCA_INTR_DMAC_RX(0);
+	if (isr0 & 0x000000F0) result |= SCA_INTR_DMAC_TX(0);
+	if (isr0 & 0x00000F00) result |= SCA_INTR_DMAC_RX(1);
+	if (isr0 & 0x0000F000) result |= SCA_INTR_DMAC_TX(1);
+	if (isr0 & 0x003E0000) result |= SCA_INTR_MSCI(0);
+	if (isr0 & 0x3E000000) result |= SCA_INTR_MSCI(1);
+
+#endif /* HD64570 vs HD64572 */
+
+	if (!(result & SCA_INTR_DMAC_TX(0)))
+		if (sca_in(DSR_TX(0), card) & DSR_EOM)
+			result |= SCA_INTR_DMAC_TX(0);
+	if (!(result & SCA_INTR_DMAC_TX(1)))
+		if (sca_in(DSR_TX(1), card) & DSR_EOM)
+			result |= SCA_INTR_DMAC_TX(1);
+
+	return result;
+}
+
+static inline port_t* dev_to_port(struct net_device *dev)
+{
+	return dev_to_hdlc(dev)->priv;
+}
+
+static inline u16 next_desc(port_t *port, u16 desc, int transmit)
+{
+	return (desc + 1) % (transmit ? port_to_card(port)->tx_ring_buffers
+			     : port_to_card(port)->rx_ring_buffers);
+}
+
+
+
+static inline u16 desc_abs_number(port_t *port, u16 desc, int transmit)
+{
+	u16 rx_buffs = port_to_card(port)->rx_ring_buffers;
+	u16 tx_buffs = port_to_card(port)->tx_ring_buffers;
+
+	desc %= (transmit ? tx_buffs : rx_buffs); // called with "X + 1" etc.
+	return log_node(port) * (rx_buffs + tx_buffs) +
+		transmit * rx_buffs + desc;
+}
+
+
+
+static inline u16 desc_offset(port_t *port, u16 desc, int transmit)
+{
+	/* Descriptor offset always fits in 16 bytes */
+	return desc_abs_number(port, desc, transmit) * sizeof(pkt_desc);
+}
+
+
+
+static inline pkt_desc __iomem *desc_address(port_t *port, u16 desc, int transmit)
+{
+#ifdef PAGE0_ALWAYS_MAPPED
+	return (pkt_desc __iomem *)(win0base(port_to_card(port))
+			   + desc_offset(port, desc, transmit));
+#else
+	return (pkt_desc __iomem *)(winbase(port_to_card(port))
+			   + desc_offset(port, desc, transmit));
+#endif
+}
+
+
+
+static inline u32 buffer_offset(port_t *port, u16 desc, int transmit)
+{
+	return port_to_card(port)->buff_offset +
+		desc_abs_number(port, desc, transmit) * (u32)HDLC_MAX_MRU;
+}
+
+
+
+static void sca_init_sync_port(port_t *port)
+{
+	card_t *card = port_to_card(port);
+	int transmit, i;
+
+	port->rxin = 0;
+	port->txin = 0;
+	port->txlast = 0;
+
+#if !defined(PAGE0_ALWAYS_MAPPED) && !defined(ALL_PAGES_ALWAYS_MAPPED)
+	openwin(card, 0);
+#endif
+
+	for (transmit = 0; transmit < 2; transmit++) {
+		u16 dmac = transmit ? get_dmac_tx(port) : get_dmac_rx(port);
+		u16 buffs = transmit ? card->tx_ring_buffers
+			: card->rx_ring_buffers;
+
+		for (i = 0; i < buffs; i++) {
+			pkt_desc __iomem *desc = desc_address(port, i, transmit);
+			u16 chain_off = desc_offset(port, i + 1, transmit);
+			u32 buff_off = buffer_offset(port, i, transmit);
+
+			writea(chain_off, &desc->cp);
+			writel(buff_off, &desc->bp);
+			writew(0, &desc->len);
+			writeb(0, &desc->stat);
+		}
+
+		/* DMA disable - to halt state */
+		sca_out(0, transmit ? DSR_TX(phy_node(port)) :
+			DSR_RX(phy_node(port)), card);
+		/* software ABORT - to initial state */
+		sca_out(DCR_ABORT, transmit ? DCR_TX(phy_node(port)) :
+			DCR_RX(phy_node(port)), card);
+
+#ifdef __HD64570_H
+		sca_out(0, dmac + CPB, card); /* pointer base */
+#endif
+		/* current desc addr */
+		sca_outa(desc_offset(port, 0, transmit), dmac + CDAL, card);
+		if (!transmit)
+			sca_outa(desc_offset(port, buffs - 1, transmit),
+				 dmac + EDAL, card);
+		else
+			sca_outa(desc_offset(port, 0, transmit), dmac + EDAL,
+				 card);
+
+		/* clear frame end interrupt counter */
+		sca_out(DCR_CLEAR_EOF, transmit ? DCR_TX(phy_node(port)) :
+			DCR_RX(phy_node(port)), card);
+
+		if (!transmit) { /* Receive */
+			/* set buffer length */
+			sca_outw(HDLC_MAX_MRU, dmac + BFLL, card);
+			/* Chain mode, Multi-frame */
+			sca_out(0x14, DMR_RX(phy_node(port)), card);
+			sca_out(DIR_EOME | DIR_BOFE, DIR_RX(phy_node(port)),
+				card);
+			/* DMA enable */
+			sca_out(DSR_DE, DSR_RX(phy_node(port)), card);
+		} else {	/* Transmit */
+			/* Chain mode, Multi-frame */
+			sca_out(0x14, DMR_TX(phy_node(port)), card);
+			/* enable underflow interrupts */
+			sca_out(DIR_BOFE, DIR_TX(phy_node(port)), card);
+		}
+	}
+
+	hdlc_set_carrier(!(sca_in(get_msci(port) + ST3, card) & ST3_DCD),
+			 port_to_dev(port));
+}
+
+
+
+#ifdef NEED_SCA_MSCI_INTR
+/* MSCI interrupt service */
+static inline void sca_msci_intr(port_t *port)
+{
+	u16 msci = get_msci(port);
+	card_t* card = port_to_card(port);
+	u8 stat = sca_in(msci + ST1, card); /* read MSCI ST1 status */
+
+	/* Reset MSCI TX underrun and CDCD status bit */
+	sca_out(stat & (ST1_UDRN | ST1_CDCD), msci + ST1, card);
+
+	if (stat & ST1_UDRN) {
+		struct net_device_stats *stats = hdlc_stats(port_to_dev(port));
+		stats->tx_errors++; /* TX Underrun error detected */
+		stats->tx_fifo_errors++;
+	}
+
+	if (stat & ST1_CDCD)
+		hdlc_set_carrier(!(sca_in(msci + ST3, card) & ST3_DCD),
+				 port_to_dev(port));
+}
+#endif
+
+
+
+static inline void sca_rx(card_t *card, port_t *port, pkt_desc __iomem *desc, u16 rxin)
+{
+	struct net_device *dev = port_to_dev(port);
+	struct net_device_stats *stats = hdlc_stats(dev);
+	struct sk_buff *skb;
+	u16 len;
+	u32 buff;
+#ifndef ALL_PAGES_ALWAYS_MAPPED
+	u32 maxlen;
+	u8 page;
+#endif
+
+	len = readw(&desc->len);
+	skb = dev_alloc_skb(len);
+	if (!skb) {
+		stats->rx_dropped++;
+		return;
+	}
+
+	buff = buffer_offset(port, rxin, 0);
+#ifndef ALL_PAGES_ALWAYS_MAPPED
+	page = buff / winsize(card);
+	buff = buff % winsize(card);
+	maxlen = winsize(card) - buff;
+
+	openwin(card, page);
+
+	if (len > maxlen) {
+		memcpy_fromio(skb->data, winbase(card) + buff, maxlen);
+		openwin(card, page + 1);
+		memcpy_fromio(skb->data + maxlen, winbase(card), len - maxlen);
+	} else
+#endif
+	memcpy_fromio(skb->data, winbase(card) + buff, len);
+
+#if !defined(PAGE0_ALWAYS_MAPPED) && !defined(ALL_PAGES_ALWAYS_MAPPED)
+	/* select pkt_desc table page back */
+	openwin(card, 0);
+#endif
+	skb_put(skb, len);
+#ifdef DEBUG_PKT
+	printk(KERN_DEBUG "%s RX(%i):", dev->name, skb->len);
+	debug_frame(skb);
+#endif
+	stats->rx_packets++;
+	stats->rx_bytes += skb->len;
+	dev->last_rx = jiffies;
+	skb->protocol = hdlc_type_trans(skb, dev);
+	netif_rx(skb);
+}
+
+
+
+/* Receive DMA interrupt service */
+static inline void sca_rx_intr(port_t *port)
+{
+	u16 dmac = get_dmac_rx(port);
+	card_t *card = port_to_card(port);
+	u8 stat = sca_in(DSR_RX(phy_node(port)), card); /* read DMA Status */
+	struct net_device_stats *stats = hdlc_stats(port_to_dev(port));
+
+	/* Reset DSR status bits */
+	sca_out((stat & (DSR_EOT | DSR_EOM | DSR_BOF | DSR_COF)) | DSR_DWE,
+		DSR_RX(phy_node(port)), card);
+
+	if (stat & DSR_BOF)
+		stats->rx_over_errors++; /* Dropped one or more frames */
+
+	while (1) {
+		u32 desc_off = desc_offset(port, port->rxin, 0);
+		pkt_desc __iomem *desc;
+		u32 cda = sca_ina(dmac + CDAL, card);
+
+		if ((cda >= desc_off) && (cda < desc_off + sizeof(pkt_desc)))
+			break;	/* No frame received */
+
+		desc = desc_address(port, port->rxin, 0);
+		stat = readb(&desc->stat);
+		if (!(stat & ST_RX_EOM))
+			port->rxpart = 1; /* partial frame received */
+		else if ((stat & ST_ERROR_MASK) || port->rxpart) {
+			stats->rx_errors++;
+			if (stat & ST_RX_OVERRUN) stats->rx_fifo_errors++;
+			else if ((stat & (ST_RX_SHORT | ST_RX_ABORT |
+					  ST_RX_RESBIT)) || port->rxpart)
+				stats->rx_frame_errors++;
+			else if (stat & ST_RX_CRC) stats->rx_crc_errors++;
+			if (stat & ST_RX_EOM)
+				port->rxpart = 0; /* received last fragment */
+		} else
+			sca_rx(card, port, desc, port->rxin);
+
+		/* Set new error descriptor address */
+		sca_outa(desc_off, dmac + EDAL, card);
+		port->rxin = next_desc(port, port->rxin, 0);
+	}
+
+	/* make sure RX DMA is enabled */
+	sca_out(DSR_DE, DSR_RX(phy_node(port)), card);
+}
+
+
+
+/* Transmit DMA interrupt service */
+static inline void sca_tx_intr(port_t *port)
+{
+	struct net_device *dev = port_to_dev(port);
+	struct net_device_stats *stats = hdlc_stats(dev);
+	u16 dmac = get_dmac_tx(port);
+	card_t* card = port_to_card(port);
+	u8 stat;
+
+	spin_lock(&port->lock);
+
+	stat = sca_in(DSR_TX(phy_node(port)), card); /* read DMA Status */
+
+	/* Reset DSR status bits */
+	sca_out((stat & (DSR_EOT | DSR_EOM | DSR_BOF | DSR_COF)) | DSR_DWE,
+		DSR_TX(phy_node(port)), card);
+
+	while (1) {
+		pkt_desc __iomem *desc;
+
+		u32 desc_off = desc_offset(port, port->txlast, 1);
+		u32 cda = sca_ina(dmac + CDAL, card);
+		if ((cda >= desc_off) && (cda < desc_off + sizeof(pkt_desc)))
+			break;	/* Transmitter is/will_be sending this frame */
+
+		desc = desc_address(port, port->txlast, 1);
+		stats->tx_packets++;
+		stats->tx_bytes += readw(&desc->len);
+		writeb(0, &desc->stat);	/* Free descriptor */
+		port->txlast = next_desc(port, port->txlast, 1);
+	}
+
+	netif_wake_queue(dev);
+	spin_unlock(&port->lock);
+}
+
+
+
+static irqreturn_t sca_intr(int irq, void* dev_id, struct pt_regs *regs)
+{
+	card_t *card = dev_id;
+	int i;
+	u8 stat;
+	int handled = 0;
+
+#ifndef ALL_PAGES_ALWAYS_MAPPED
+	u8 page = sca_get_page(card);
+#endif
+
+	while((stat = sca_intr_status(card)) != 0) {
+		handled = 1;
+		for (i = 0; i < 2; i++) {
+			port_t *port = get_port(card, i);
+			if (port) {
+				if (stat & SCA_INTR_MSCI(i))
+					sca_msci_intr(port);
+
+				if (stat & SCA_INTR_DMAC_RX(i))
+					sca_rx_intr(port);
+
+				if (stat & SCA_INTR_DMAC_TX(i))
+					sca_tx_intr(port);
+			}
+		}
+	}
+
+#ifndef ALL_PAGES_ALWAYS_MAPPED
+	openwin(card, page);		/* Restore original page */
+#endif
+	return IRQ_RETVAL(handled);
+}
+
+
+
+static void sca_set_port(port_t *port)
+{
+	card_t* card = port_to_card(port);
+	u16 msci = get_msci(port);
+	u8 md2 = sca_in(msci + MD2, card);
+	unsigned int tmc, br = 10, brv = 1024;
+
+
+	if (port->settings.clock_rate > 0) {
+		/* Try lower br for better accuracy*/
+		do {
+			br--;
+			brv >>= 1; /* brv = 2^9 = 512 max in specs */
+
+			/* Baud Rate = CLOCK_BASE / TMC / 2^BR */
+			tmc = CLOCK_BASE / brv / port->settings.clock_rate;
+		}while (br > 1 && tmc <= 128);
+
+		if (tmc < 1) {
+			tmc = 1;
+			br = 0;	/* For baud=CLOCK_BASE we use tmc=1 br=0 */
+			brv = 1;
+		} else if (tmc > 255)
+			tmc = 256; /* tmc=0 means 256 - low baud rates */
+
+		port->settings.clock_rate = CLOCK_BASE / brv / tmc;
+	} else {
+		br = 9; /* Minimum clock rate */
+		tmc = 256;	/* 8bit = 0 */
+		port->settings.clock_rate = CLOCK_BASE / (256 * 512);
+	}
+
+	port->rxs = (port->rxs & ~CLK_BRG_MASK) | br;
+	port->txs = (port->txs & ~CLK_BRG_MASK) | br;
+	port->tmc = tmc;
+
+	/* baud divisor - time constant*/
+#ifdef __HD64570_H
+	sca_out(port->tmc, msci + TMC, card);
+#else
+	sca_out(port->tmc, msci + TMCR, card);
+	sca_out(port->tmc, msci + TMCT, card);
+#endif
+
+	/* Set BRG bits */
+	sca_out(port->rxs, msci + RXS, card);
+	sca_out(port->txs, msci + TXS, card);
+
+	if (port->settings.loopback)
+		md2 |= MD2_LOOPBACK;
+	else
+		md2 &= ~MD2_LOOPBACK;
+
+	sca_out(md2, msci + MD2, card);
+
+}
+
+
+
+static void sca_open(struct net_device *dev)
+{
+	port_t *port = dev_to_port(dev);
+	card_t* card = port_to_card(port);
+	u16 msci = get_msci(port);
+	u8 md0, md2;
+
+	switch(port->encoding) {
+	case ENCODING_NRZ:	md2 = MD2_NRZ;		break;
+	case ENCODING_NRZI:	md2 = MD2_NRZI;		break;
+	case ENCODING_FM_MARK:	md2 = MD2_FM_MARK;	break;
+	case ENCODING_FM_SPACE:	md2 = MD2_FM_SPACE;	break;
+	default:		md2 = MD2_MANCHESTER;
+	}
+
+	if (port->settings.loopback)
+		md2 |= MD2_LOOPBACK;
+
+	switch(port->parity) {
+	case PARITY_CRC16_PR0:	     md0 = MD0_HDLC | MD0_CRC_16_0;  break;
+	case PARITY_CRC16_PR1:	     md0 = MD0_HDLC | MD0_CRC_16;    break;
+#ifdef __HD64570_H
+	case PARITY_CRC16_PR0_CCITT: md0 = MD0_HDLC | MD0_CRC_ITU_0; break;
+#else
+	case PARITY_CRC32_PR1_CCITT: md0 = MD0_HDLC | MD0_CRC_ITU32; break;
+#endif
+	case PARITY_CRC16_PR1_CCITT: md0 = MD0_HDLC | MD0_CRC_ITU;   break;
+	default:		     md0 = MD0_HDLC | MD0_CRC_NONE;
+	}
+
+	sca_out(CMD_RESET, msci + CMD, card);
+	sca_out(md0, msci + MD0, card);
+	sca_out(0x00, msci + MD1, card); /* no address field check */
+	sca_out(md2, msci + MD2, card);
+	sca_out(0x7E, msci + IDL, card); /* flag character 0x7E */
+#ifdef __HD64570_H
+	sca_out(CTL_IDLE, msci + CTL, card);
+#else
+	/* Skip the rest of underrun frame */
+	sca_out(CTL_IDLE | CTL_URCT | CTL_URSKP, msci + CTL, card);
+#endif
+
+#ifdef __HD64570_H
+	/* Allow at least 8 bytes before requesting RX DMA operation */
+	/* TX with higher priority and possibly with shorter transfers */
+	sca_out(0x07, msci + RRC, card); /* +1=RXRDY/DMA activation condition*/
+	sca_out(0x10, msci + TRC0, card); /* = TXRDY/DMA activation condition*/
+	sca_out(0x14, msci + TRC1, card); /* +1=TXRDY/DMA deactiv condition */
+#else
+	sca_out(0x0F, msci + RNR, card); /* +1=RX DMA activation condition */
+	sca_out(0x3C, msci + TFS, card); /* +1 = TX start */
+	sca_out(0x38, msci + TCR, card); /* =Critical TX DMA activ condition */
+	sca_out(0x38, msci + TNR0, card); /* =TX DMA activation condition */
+	sca_out(0x3F, msci + TNR1, card); /* +1=TX DMA deactivation condition*/
+#endif
+
+/* We're using the following interrupts:
+   - TXINT (DMAC completed all transmisions, underrun or DCD change)
+   - all DMA interrupts
+*/
+
+	hdlc_set_carrier(!(sca_in(msci + ST3, card) & ST3_DCD), dev);
+
+#ifdef __HD64570_H
+	/* MSCI TX INT and RX INT A IRQ enable */
+	sca_out(IE0_TXINT | IE0_RXINTA, msci + IE0, card);
+	sca_out(IE1_UDRN | IE1_CDCD, msci + IE1, card);
+	sca_out(sca_in(IER0, card) | (phy_node(port) ? 0xC0 : 0x0C),
+		IER0, card); /* TXINT and RXINT */
+	/* enable DMA IRQ */
+	sca_out(sca_in(IER1, card) | (phy_node(port) ? 0xF0 : 0x0F),
+		IER1, card);
+#else
+	/* MSCI TXINT and RXINTA interrupt enable */
+	sca_outl(IE0_TXINT | IE0_RXINTA | IE0_UDRN | IE0_CDCD, msci + IE0,
+		 card);
+	/* DMA & MSCI IRQ enable */
+	sca_outl(sca_inl(IER0, card) |
+		 (phy_node(port) ? 0x0A006600 : 0x000A0066), IER0, card);
+#endif
+
+#ifdef __HD64570_H
+	sca_out(port->tmc, msci + TMC, card); /* Restore registers */
+#else
+	sca_out(port->tmc, msci + TMCR, card);
+	sca_out(port->tmc, msci + TMCT, card);
+#endif
+	sca_out(port->rxs, msci + RXS, card);
+	sca_out(port->txs, msci + TXS, card);
+	sca_out(CMD_TX_ENABLE, msci + CMD, card);
+	sca_out(CMD_RX_ENABLE, msci + CMD, card);
+
+	netif_start_queue(dev);
+}
+
+
+
+static void sca_close(struct net_device *dev)
+{
+	port_t *port = dev_to_port(dev);
+	card_t* card = port_to_card(port);
+
+	/* reset channel */
+	sca_out(CMD_RESET, get_msci(port) + CMD, port_to_card(port));
+#ifdef __HD64570_H
+	/* disable MSCI interrupts */
+	sca_out(sca_in(IER0, card) & (phy_node(port) ? 0x0F : 0xF0),
+		IER0, card);
+	/* disable DMA interrupts */
+	sca_out(sca_in(IER1, card) & (phy_node(port) ? 0x0F : 0xF0),
+		IER1, card);
+#else
+	/* disable DMA & MSCI IRQ */
+	sca_outl(sca_inl(IER0, card) &
+		 (phy_node(port) ? 0x00FF00FF : 0xFF00FF00), IER0, card);
+#endif
+	netif_stop_queue(dev);
+}
+
+
+
+static int sca_attach(struct net_device *dev, unsigned short encoding,
+		      unsigned short parity)
+{
+	if (encoding != ENCODING_NRZ &&
+	    encoding != ENCODING_NRZI &&
+	    encoding != ENCODING_FM_MARK &&
+	    encoding != ENCODING_FM_SPACE &&
+	    encoding != ENCODING_MANCHESTER)
+		return -EINVAL;
+
+	if (parity != PARITY_NONE &&
+	    parity != PARITY_CRC16_PR0 &&
+	    parity != PARITY_CRC16_PR1 &&
+#ifdef __HD64570_H
+	    parity != PARITY_CRC16_PR0_CCITT &&
+#else
+	    parity != PARITY_CRC32_PR1_CCITT &&
+#endif
+	    parity != PARITY_CRC16_PR1_CCITT)
+		return -EINVAL;
+
+	dev_to_port(dev)->encoding = encoding;
+	dev_to_port(dev)->parity = parity;
+	return 0;
+}
+
+
+
+#ifdef DEBUG_RINGS
+static void sca_dump_rings(struct net_device *dev)
+{
+	port_t *port = dev_to_port(dev);
+	card_t *card = port_to_card(port);
+	u16 cnt;
+#if !defined(PAGE0_ALWAYS_MAPPED) && !defined(ALL_PAGES_ALWAYS_MAPPED)
+	u8 page;
+#endif
+
+#if !defined(PAGE0_ALWAYS_MAPPED) && !defined(ALL_PAGES_ALWAYS_MAPPED)
+	page = sca_get_page(card);
+	openwin(card, 0);
+#endif
+
+	printk(KERN_DEBUG "RX ring: CDA=%u EDA=%u DSR=%02X in=%u %sactive",
+	       sca_ina(get_dmac_rx(port) + CDAL, card),
+	       sca_ina(get_dmac_rx(port) + EDAL, card),
+	       sca_in(DSR_RX(phy_node(port)), card), port->rxin,
+	       sca_in(DSR_RX(phy_node(port)), card) & DSR_DE?"":"in");
+	for (cnt = 0; cnt < port_to_card(port)->rx_ring_buffers; cnt++)
+		printk(" %02X", readb(&(desc_address(port, cnt, 0)->stat)));
+
+	printk("\n" KERN_DEBUG "TX ring: CDA=%u EDA=%u DSR=%02X in=%u "
+	       "last=%u %sactive",
+	       sca_ina(get_dmac_tx(port) + CDAL, card),
+	       sca_ina(get_dmac_tx(port) + EDAL, card),
+	       sca_in(DSR_TX(phy_node(port)), card), port->txin, port->txlast,
+	       sca_in(DSR_TX(phy_node(port)), card) & DSR_DE ? "" : "in");
+
+	for (cnt = 0; cnt < port_to_card(port)->tx_ring_buffers; cnt++)
+		printk(" %02X", readb(&(desc_address(port, cnt, 1)->stat)));
+	printk("\n");
+
+	printk(KERN_DEBUG "MSCI: MD: %02x %02x %02x, "
+	       "ST: %02x %02x %02x %02x"
+#ifdef __HD64572_H
+	       " %02x"
+#endif
+	       ", FST: %02x CST: %02x %02x\n",
+	       sca_in(get_msci(port) + MD0, card),
+	       sca_in(get_msci(port) + MD1, card),
+	       sca_in(get_msci(port) + MD2, card),
+	       sca_in(get_msci(port) + ST0, card),
+	       sca_in(get_msci(port) + ST1, card),
+	       sca_in(get_msci(port) + ST2, card),
+	       sca_in(get_msci(port) + ST3, card),
+#ifdef __HD64572_H
+	       sca_in(get_msci(port) + ST4, card),
+#endif
+	       sca_in(get_msci(port) + FST, card),
+	       sca_in(get_msci(port) + CST0, card),
+	       sca_in(get_msci(port) + CST1, card));
+
+#ifdef __HD64572_H
+	printk(KERN_DEBUG "ILAR: %02x ISR: %08x %08x\n", sca_in(ILAR, card),
+	       sca_inl(ISR0, card), sca_inl(ISR1, card));
+#else
+	printk(KERN_DEBUG "ISR: %02x %02x %02x\n", sca_in(ISR0, card),
+	       sca_in(ISR1, card), sca_in(ISR2, card));
+#endif
+
+#if !defined(PAGE0_ALWAYS_MAPPED) && !defined(ALL_PAGES_ALWAYS_MAPPED)
+	openwin(card, page); /* Restore original page */
+#endif
+}
+#endif /* DEBUG_RINGS */
+
+
+
+static int sca_xmit(struct sk_buff *skb, struct net_device *dev)
+{
+	port_t *port = dev_to_port(dev);
+	card_t *card = port_to_card(port);
+	pkt_desc __iomem *desc;
+	u32 buff, len;
+#ifndef ALL_PAGES_ALWAYS_MAPPED
+	u8 page;
+	u32 maxlen;
+#endif
+
+	spin_lock_irq(&port->lock);
+
+	desc = desc_address(port, port->txin + 1, 1);
+	if (readb(&desc->stat)) { /* allow 1 packet gap */
+		/* should never happen - previous xmit should stop queue */
+#ifdef DEBUG_PKT
+		printk(KERN_DEBUG "%s: transmitter buffer full\n", dev->name);
+#endif
+		netif_stop_queue(dev);
+		spin_unlock_irq(&port->lock);
+		return 1;	/* request packet to be queued */
+	}
+
+#ifdef DEBUG_PKT
+	printk(KERN_DEBUG "%s TX(%i):", dev->name, skb->len);
+	debug_frame(skb);
+#endif
+
+	desc = desc_address(port, port->txin, 1);
+	buff = buffer_offset(port, port->txin, 1);
+	len = skb->len;
+#ifndef ALL_PAGES_ALWAYS_MAPPED
+	page = buff / winsize(card);
+	buff = buff % winsize(card);
+	maxlen = winsize(card) - buff;
+
+	openwin(card, page);
+	if (len > maxlen) {
+		memcpy_toio(winbase(card) + buff, skb->data, maxlen);
+		openwin(card, page + 1);
+		memcpy_toio(winbase(card), skb->data + maxlen, len - maxlen);
+	}
+	else
+#endif
+		memcpy_toio(winbase(card) + buff, skb->data, len);
+
+#if !defined(PAGE0_ALWAYS_MAPPED) && !defined(ALL_PAGES_ALWAYS_MAPPED)
+	openwin(card, 0);	/* select pkt_desc table page back */
+#endif
+	writew(len, &desc->len);
+	writeb(ST_TX_EOM, &desc->stat);
+	dev->trans_start = jiffies;
+
+	port->txin = next_desc(port, port->txin, 1);
+	sca_outa(desc_offset(port, port->txin, 1),
+		 get_dmac_tx(port) + EDAL, card);
+
+	sca_out(DSR_DE, DSR_TX(phy_node(port)), card); /* Enable TX DMA */
+
+	desc = desc_address(port, port->txin + 1, 1);
+	if (readb(&desc->stat)) /* allow 1 packet gap */
+		netif_stop_queue(dev);
+
+	spin_unlock_irq(&port->lock);
+
+	dev_kfree_skb(skb);
+	return 0;
+}
+
+
+
+#ifdef NEED_DETECT_RAM
+static u32 __devinit sca_detect_ram(card_t *card, u8 __iomem *rambase, u32 ramsize)
+{
+	/* Round RAM size to 32 bits, fill from end to start */
+	u32 i = ramsize &= ~3;
+
+#ifndef ALL_PAGES_ALWAYS_MAPPED
+	u32 size = winsize(card);
+
+	openwin(card, (i - 4) / size); /* select last window */
+#endif
+	do {
+		i -= 4;
+#ifndef ALL_PAGES_ALWAYS_MAPPED
+		if ((i + 4) % size == 0)
+			openwin(card, i / size);
+		writel(i ^ 0x12345678, rambase + i % size);
+#else
+		writel(i ^ 0x12345678, rambase + i);
+#endif
+	}while (i > 0);
+
+	for (i = 0; i < ramsize ; i += 4) {
+#ifndef ALL_PAGES_ALWAYS_MAPPED
+		if (i % size == 0)
+			openwin(card, i / size);
+
+		if (readl(rambase + i % size) != (i ^ 0x12345678))
+			break;
+#else
+		if (readl(rambase + i) != (i ^ 0x12345678))
+			break;
+#endif
+	}
+
+	return i;
+}
+#endif /* NEED_DETECT_RAM */
+
+
+
+static void __devinit sca_init(card_t *card, int wait_states)
+{
+	sca_out(wait_states, WCRL, card); /* Wait Control */
+	sca_out(wait_states, WCRM, card);
+	sca_out(wait_states, WCRH, card);
+
+	sca_out(0, DMER, card);	/* DMA Master disable */
+	sca_out(0x03, PCR, card); /* DMA priority */
+	sca_out(0, DSR_RX(0), card); /* DMA disable - to halt state */
+	sca_out(0, DSR_TX(0), card);
+	sca_out(0, DSR_RX(1), card);
+	sca_out(0, DSR_TX(1), card);
+	sca_out(DMER_DME, DMER, card); /* DMA Master enable */
+}
diff --git a/drivers/net/wan/hdlc_cisco.c b/drivers/net/wan/hdlc_cisco.c
new file mode 100644
index 0000000..c1b6896
--- /dev/null
+++ b/drivers/net/wan/hdlc_cisco.c
@@ -0,0 +1,330 @@
+/*
+ * Generic HDLC support routines for Linux
+ * Cisco HDLC support
+ *
+ * Copyright (C) 2000 - 2003 Krzysztof Halasa <khc@pm.waw.pl>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of version 2 of the GNU General Public License
+ * as published by the Free Software Foundation.
+ */
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include <linux/poll.h>
+#include <linux/errno.h>
+#include <linux/if_arp.h>
+#include <linux/init.h>
+#include <linux/skbuff.h>
+#include <linux/pkt_sched.h>
+#include <linux/inetdevice.h>
+#include <linux/lapb.h>
+#include <linux/rtnetlink.h>
+#include <linux/hdlc.h>
+
+#undef DEBUG_HARD_HEADER
+
+#define CISCO_MULTICAST		0x8F	/* Cisco multicast address */
+#define CISCO_UNICAST		0x0F	/* Cisco unicast address */
+#define CISCO_KEEPALIVE		0x8035	/* Cisco keepalive protocol */
+#define CISCO_SYS_INFO		0x2000	/* Cisco interface/system info */
+#define CISCO_ADDR_REQ		0	/* Cisco address request */
+#define CISCO_ADDR_REPLY	1	/* Cisco address reply */
+#define CISCO_KEEPALIVE_REQ	2	/* Cisco keepalive request */
+
+
+static int cisco_hard_header(struct sk_buff *skb, struct net_device *dev,
+			     u16 type, void *daddr, void *saddr,
+			     unsigned int len)
+{
+	hdlc_header *data;
+#ifdef DEBUG_HARD_HEADER
+	printk(KERN_DEBUG "%s: cisco_hard_header called\n", dev->name);
+#endif
+
+	skb_push(skb, sizeof(hdlc_header));
+	data = (hdlc_header*)skb->data;
+	if (type == CISCO_KEEPALIVE)
+		data->address = CISCO_MULTICAST;
+	else
+		data->address = CISCO_UNICAST;
+	data->control = 0;
+	data->protocol = htons(type);
+
+	return sizeof(hdlc_header);
+}
+
+
+
+static void cisco_keepalive_send(struct net_device *dev, u32 type,
+				 u32 par1, u32 par2)
+{
+	struct sk_buff *skb;
+	cisco_packet *data;
+
+	skb = dev_alloc_skb(sizeof(hdlc_header) + sizeof(cisco_packet));
+	if (!skb) {
+		printk(KERN_WARNING
+		       "%s: Memory squeeze on cisco_keepalive_send()\n",
+		       dev->name);
+		return;
+	}
+	skb_reserve(skb, 4);
+	cisco_hard_header(skb, dev, CISCO_KEEPALIVE, NULL, NULL, 0);
+	data = (cisco_packet*)skb->tail;
+
+	data->type = htonl(type);
+	data->par1 = htonl(par1);
+	data->par2 = htonl(par2);
+	data->rel = 0xFFFF;
+	/* we will need do_div here if 1000 % HZ != 0 */
+	data->time = htonl((jiffies - INITIAL_JIFFIES) * (1000 / HZ));
+
+	skb_put(skb, sizeof(cisco_packet));
+	skb->priority = TC_PRIO_CONTROL;
+	skb->dev = dev;
+	skb->nh.raw = skb->data;
+
+	dev_queue_xmit(skb);
+}
+
+
+
+static unsigned short cisco_type_trans(struct sk_buff *skb,
+				       struct net_device *dev)
+{
+	hdlc_header *data = (hdlc_header*)skb->data;
+
+	if (skb->len < sizeof(hdlc_header))
+		return __constant_htons(ETH_P_HDLC);
+
+	if (data->address != CISCO_MULTICAST &&
+	    data->address != CISCO_UNICAST)
+		return __constant_htons(ETH_P_HDLC);
+
+	switch(data->protocol) {
+	case __constant_htons(ETH_P_IP):
+	case __constant_htons(ETH_P_IPX):
+	case __constant_htons(ETH_P_IPV6):
+		skb_pull(skb, sizeof(hdlc_header));
+		return data->protocol;
+	default:
+		return __constant_htons(ETH_P_HDLC);
+	}
+}
+
+
+static int cisco_rx(struct sk_buff *skb)
+{
+	struct net_device *dev = skb->dev;
+	hdlc_device *hdlc = dev_to_hdlc(dev);
+	hdlc_header *data = (hdlc_header*)skb->data;
+	cisco_packet *cisco_data;
+	struct in_device *in_dev;
+	u32 addr, mask;
+
+	if (skb->len < sizeof(hdlc_header))
+		goto rx_error;
+
+	if (data->address != CISCO_MULTICAST &&
+	    data->address != CISCO_UNICAST)
+		goto rx_error;
+
+	switch(ntohs(data->protocol)) {
+	case CISCO_SYS_INFO:
+		/* Packet is not needed, drop it. */
+		dev_kfree_skb_any(skb);
+		return NET_RX_SUCCESS;
+
+	case CISCO_KEEPALIVE:
+		if (skb->len != sizeof(hdlc_header) + CISCO_PACKET_LEN &&
+		    skb->len != sizeof(hdlc_header) + CISCO_BIG_PACKET_LEN) {
+			printk(KERN_INFO "%s: Invalid length of Cisco "
+			       "control packet (%d bytes)\n",
+			       dev->name, skb->len);
+			goto rx_error;
+		}
+
+		cisco_data = (cisco_packet*)(skb->data + sizeof(hdlc_header));
+
+		switch(ntohl (cisco_data->type)) {
+		case CISCO_ADDR_REQ: /* Stolen from syncppp.c :-) */
+			in_dev = dev->ip_ptr;
+			addr = 0;
+			mask = ~0; /* is the mask correct? */
+
+			if (in_dev != NULL) {
+				struct in_ifaddr **ifap = &in_dev->ifa_list;
+
+				while (*ifap != NULL) {
+					if (strcmp(dev->name,
+						   (*ifap)->ifa_label) == 0) {
+						addr = (*ifap)->ifa_local;
+						mask = (*ifap)->ifa_mask;
+						break;
+					}
+					ifap = &(*ifap)->ifa_next;
+				}
+
+				cisco_keepalive_send(dev, CISCO_ADDR_REPLY,
+						     addr, mask);
+			}
+			dev_kfree_skb_any(skb);
+			return NET_RX_SUCCESS;
+
+		case CISCO_ADDR_REPLY:
+			printk(KERN_INFO "%s: Unexpected Cisco IP address "
+			       "reply\n", dev->name);
+			goto rx_error;
+
+		case CISCO_KEEPALIVE_REQ:
+			hdlc->state.cisco.rxseq = ntohl(cisco_data->par1);
+			if (hdlc->state.cisco.request_sent &&
+			    ntohl(cisco_data->par2)==hdlc->state.cisco.txseq) {
+				hdlc->state.cisco.last_poll = jiffies;
+				if (!hdlc->state.cisco.up) {
+					u32 sec, min, hrs, days;
+					sec = ntohl(cisco_data->time) / 1000;
+					min = sec / 60; sec -= min * 60;
+					hrs = min / 60; min -= hrs * 60;
+					days = hrs / 24; hrs -= days * 24;
+					printk(KERN_INFO "%s: Link up (peer "
+					       "uptime %ud%uh%um%us)\n",
+					       dev->name, days, hrs,
+					       min, sec);
+					netif_carrier_on(dev);
+					hdlc->state.cisco.up = 1;
+				}
+			}
+
+			dev_kfree_skb_any(skb);
+			return NET_RX_SUCCESS;
+		} /* switch(keepalive type) */
+	} /* switch(protocol) */
+
+	printk(KERN_INFO "%s: Unsupported protocol %x\n", dev->name,
+	       data->protocol);
+	dev_kfree_skb_any(skb);
+	return NET_RX_DROP;
+
+ rx_error:
+	hdlc->stats.rx_errors++; /* Mark error */
+	dev_kfree_skb_any(skb);
+	return NET_RX_DROP;
+}
+
+
+
+static void cisco_timer(unsigned long arg)
+{
+	struct net_device *dev = (struct net_device *)arg;
+	hdlc_device *hdlc = dev_to_hdlc(dev);
+
+	if (hdlc->state.cisco.up &&
+	    time_after(jiffies, hdlc->state.cisco.last_poll +
+		       hdlc->state.cisco.settings.timeout * HZ)) {
+		hdlc->state.cisco.up = 0;
+		printk(KERN_INFO "%s: Link down\n", dev->name);
+		netif_carrier_off(dev);
+	}
+
+	cisco_keepalive_send(dev, CISCO_KEEPALIVE_REQ,
+			     ++hdlc->state.cisco.txseq,
+			     hdlc->state.cisco.rxseq);
+	hdlc->state.cisco.request_sent = 1;
+	hdlc->state.cisco.timer.expires = jiffies +
+		hdlc->state.cisco.settings.interval * HZ;
+	hdlc->state.cisco.timer.function = cisco_timer;
+	hdlc->state.cisco.timer.data = arg;
+	add_timer(&hdlc->state.cisco.timer);
+}
+
+
+
+static void cisco_start(struct net_device *dev)
+{
+	hdlc_device *hdlc = dev_to_hdlc(dev);
+	hdlc->state.cisco.up = 0;
+	hdlc->state.cisco.request_sent = 0;
+	hdlc->state.cisco.txseq = hdlc->state.cisco.rxseq = 0;
+
+	init_timer(&hdlc->state.cisco.timer);
+	hdlc->state.cisco.timer.expires = jiffies + HZ; /*First poll after 1s*/
+	hdlc->state.cisco.timer.function = cisco_timer;
+	hdlc->state.cisco.timer.data = (unsigned long)dev;
+	add_timer(&hdlc->state.cisco.timer);
+}
+
+
+
+static void cisco_stop(struct net_device *dev)
+{
+	hdlc_device *hdlc = dev_to_hdlc(dev);
+	del_timer_sync(&hdlc->state.cisco.timer);
+	if (netif_carrier_ok(dev))
+		netif_carrier_off(dev);
+	hdlc->state.cisco.up = 0;
+	hdlc->state.cisco.request_sent = 0;
+}
+
+
+
+int hdlc_cisco_ioctl(struct net_device *dev, struct ifreq *ifr)
+{
+	cisco_proto __user *cisco_s = ifr->ifr_settings.ifs_ifsu.cisco;
+	const size_t size = sizeof(cisco_proto);
+	cisco_proto new_settings;
+	hdlc_device *hdlc = dev_to_hdlc(dev);
+	int result;
+
+	switch (ifr->ifr_settings.type) {
+	case IF_GET_PROTO:
+		ifr->ifr_settings.type = IF_PROTO_CISCO;
+		if (ifr->ifr_settings.size < size) {
+			ifr->ifr_settings.size = size; /* data size wanted */
+			return -ENOBUFS;
+		}
+		if (copy_to_user(cisco_s, &hdlc->state.cisco.settings, size))
+			return -EFAULT;
+		return 0;
+
+	case IF_PROTO_CISCO:
+		if(!capable(CAP_NET_ADMIN))
+			return -EPERM;
+
+		if(dev->flags & IFF_UP)
+			return -EBUSY;
+
+		if (copy_from_user(&new_settings, cisco_s, size))
+			return -EFAULT;
+
+		if (new_settings.interval < 1 ||
+		    new_settings.timeout < 2)
+			return -EINVAL;
+
+		result=hdlc->attach(dev, ENCODING_NRZ,PARITY_CRC16_PR1_CCITT);
+
+		if (result)
+			return result;
+
+		hdlc_proto_detach(hdlc);
+		memcpy(&hdlc->state.cisco.settings, &new_settings, size);
+		memset(&hdlc->proto, 0, sizeof(hdlc->proto));
+
+		hdlc->proto.start = cisco_start;
+		hdlc->proto.stop = cisco_stop;
+		hdlc->proto.netif_rx = cisco_rx;
+		hdlc->proto.type_trans = cisco_type_trans;
+		hdlc->proto.id = IF_PROTO_CISCO;
+		dev->hard_start_xmit = hdlc->xmit;
+		dev->hard_header = cisco_hard_header;
+		dev->hard_header_cache = NULL;
+		dev->type = ARPHRD_CISCO;
+		dev->flags = IFF_POINTOPOINT | IFF_NOARP;
+		dev->addr_len = 0;
+		return 0;
+	}
+
+	return -EINVAL;
+}
diff --git a/drivers/net/wan/hdlc_fr.c b/drivers/net/wan/hdlc_fr.c
new file mode 100644
index 0000000..7f450b5
--- /dev/null
+++ b/drivers/net/wan/hdlc_fr.c
@@ -0,0 +1,1237 @@
+/*
+ * Generic HDLC support routines for Linux
+ * Frame Relay support
+ *
+ * Copyright (C) 1999 - 2003 Krzysztof Halasa <khc@pm.waw.pl>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of version 2 of the GNU General Public License
+ * as published by the Free Software Foundation.
+ *
+
+            Theory of PVC state
+
+ DCE mode:
+
+ (exist,new) -> 0,0 when "PVC create" or if "link unreliable"
+         0,x -> 1,1 if "link reliable" when sending FULL STATUS
+         1,1 -> 1,0 if received FULL STATUS ACK
+
+ (active)    -> 0 when "ifconfig PVC down" or "link unreliable" or "PVC create"
+             -> 1 when "PVC up" and (exist,new) = 1,0
+
+ DTE mode:
+ (exist,new,active) = FULL STATUS if "link reliable"
+		    = 0, 0, 0 if "link unreliable"
+ No LMI:
+ active = open and "link reliable"
+ exist = new = not used
+
+*/
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include <linux/poll.h>
+#include <linux/errno.h>
+#include <linux/if_arp.h>
+#include <linux/init.h>
+#include <linux/skbuff.h>
+#include <linux/pkt_sched.h>
+#include <linux/random.h>
+#include <linux/inetdevice.h>
+#include <linux/lapb.h>
+#include <linux/rtnetlink.h>
+#include <linux/etherdevice.h>
+#include <linux/hdlc.h>
+
+#undef DEBUG_PKT
+#undef DEBUG_ECN
+#undef DEBUG_LINK
+
+#define MAXLEN_LMISTAT  20	/* max size of status enquiry frame */
+
+#define PVC_STATE_NEW	 0x01
+#define PVC_STATE_ACTIVE 0x02
+#define PVC_STATE_FECN	 0x08 /* FECN condition */
+#define PVC_STATE_BECN	 0x10 /* BECN condition */
+
+
+#define FR_UI		 0x03
+#define FR_PAD		 0x00
+
+#define NLPID_IP	 0xCC
+#define NLPID_IPV6	 0x8E
+#define NLPID_SNAP	 0x80
+#define NLPID_PAD	 0x00
+#define NLPID_Q933	 0x08
+
+
+#define LMI_DLCI                   0 /* LMI DLCI */
+#define LMI_PROTO               0x08
+#define LMI_CALLREF             0x00 /* Call Reference */
+#define LMI_ANSI_LOCKSHIFT      0x95 /* ANSI lockshift */
+#define LMI_REPTYPE                1 /* report type */
+#define LMI_CCITT_REPTYPE       0x51
+#define LMI_ALIVE                  3 /* keep alive */
+#define LMI_CCITT_ALIVE         0x53
+#define LMI_PVCSTAT                7 /* pvc status */
+#define LMI_CCITT_PVCSTAT       0x57
+#define LMI_FULLREP                0 /* full report  */
+#define LMI_INTEGRITY              1 /* link integrity report */
+#define LMI_SINGLE                 2 /* single pvc report */
+#define LMI_STATUS_ENQUIRY      0x75
+#define LMI_STATUS              0x7D /* reply */
+
+#define LMI_REPT_LEN               1 /* report type element length */
+#define LMI_INTEG_LEN              2 /* link integrity element length */
+
+#define LMI_LENGTH                13 /* standard LMI frame length */
+#define LMI_ANSI_LENGTH           14
+
+
+typedef struct {
+#if defined(__LITTLE_ENDIAN_BITFIELD)
+	unsigned ea1:	1;
+	unsigned cr:	1;
+	unsigned dlcih:	6;
+  
+	unsigned ea2:	1;
+	unsigned de:	1;
+	unsigned becn:	1;
+	unsigned fecn:	1;
+	unsigned dlcil:	4;
+#else
+	unsigned dlcih:	6;
+	unsigned cr:	1;
+	unsigned ea1:	1;
+
+	unsigned dlcil:	4;
+	unsigned fecn:	1;
+	unsigned becn:	1;
+	unsigned de:	1;
+	unsigned ea2:	1;
+#endif
+}__attribute__ ((packed)) fr_hdr;
+
+
+static inline u16 q922_to_dlci(u8 *hdr)
+{
+	return ((hdr[0] & 0xFC) << 2) | ((hdr[1] & 0xF0) >> 4);
+}
+
+
+
+static inline void dlci_to_q922(u8 *hdr, u16 dlci)
+{
+	hdr[0] = (dlci >> 2) & 0xFC;
+	hdr[1] = ((dlci << 4) & 0xF0) | 0x01;
+}
+
+
+
+static inline pvc_device* find_pvc(hdlc_device *hdlc, u16 dlci)
+{
+	pvc_device *pvc = hdlc->state.fr.first_pvc;
+
+	while (pvc) {
+		if (pvc->dlci == dlci)
+			return pvc;
+		if (pvc->dlci > dlci)
+			return NULL; /* the listed is sorted */
+		pvc = pvc->next;
+	}
+
+	return NULL;
+}
+
+
+static inline pvc_device* add_pvc(struct net_device *dev, u16 dlci)
+{
+	hdlc_device *hdlc = dev_to_hdlc(dev);
+	pvc_device *pvc, **pvc_p = &hdlc->state.fr.first_pvc;
+
+	while (*pvc_p) {
+		if ((*pvc_p)->dlci == dlci)
+			return *pvc_p;
+		if ((*pvc_p)->dlci > dlci)
+			break;	/* the list is sorted */
+		pvc_p = &(*pvc_p)->next;
+	}
+
+	pvc = kmalloc(sizeof(pvc_device), GFP_ATOMIC);
+	if (!pvc)
+		return NULL;
+
+	memset(pvc, 0, sizeof(pvc_device));
+	pvc->dlci = dlci;
+	pvc->master = dev;
+	pvc->next = *pvc_p;	/* Put it in the chain */
+	*pvc_p = pvc;
+	return pvc;
+}
+
+
+static inline int pvc_is_used(pvc_device *pvc)
+{
+	return pvc->main != NULL || pvc->ether != NULL;
+}
+
+
+static inline void pvc_carrier(int on, pvc_device *pvc)
+{
+	if (on) {
+		if (pvc->main)
+			if (!netif_carrier_ok(pvc->main))
+				netif_carrier_on(pvc->main);
+		if (pvc->ether)
+			if (!netif_carrier_ok(pvc->ether))
+				netif_carrier_on(pvc->ether);
+	} else {
+		if (pvc->main)
+			if (netif_carrier_ok(pvc->main))
+				netif_carrier_off(pvc->main);
+		if (pvc->ether)
+			if (netif_carrier_ok(pvc->ether))
+				netif_carrier_off(pvc->ether);
+	}
+}
+
+
+static inline void delete_unused_pvcs(hdlc_device *hdlc)
+{
+	pvc_device **pvc_p = &hdlc->state.fr.first_pvc;
+
+	while (*pvc_p) {
+		if (!pvc_is_used(*pvc_p)) {
+			pvc_device *pvc = *pvc_p;
+			*pvc_p = pvc->next;
+			kfree(pvc);
+			continue;
+		}
+		pvc_p = &(*pvc_p)->next;
+	}
+}
+
+
+static inline struct net_device** get_dev_p(pvc_device *pvc, int type)
+{
+	if (type == ARPHRD_ETHER)
+		return &pvc->ether;
+	else
+		return &pvc->main;
+}
+
+
+static inline u16 status_to_dlci(u8 *status, int *active, int *new)
+{
+	*new = (status[2] & 0x08) ? 1 : 0;
+	*active = (status[2] & 0x02) ? 1 : 0;
+
+	return ((status[0] & 0x3F) << 4) | ((status[1] & 0x78) >> 3);
+}
+
+
+static inline void dlci_to_status(u16 dlci, u8 *status, int active, int new)
+{
+	status[0] = (dlci >> 4) & 0x3F;
+	status[1] = ((dlci << 3) & 0x78) | 0x80;
+	status[2] = 0x80;
+
+	if (new)
+		status[2] |= 0x08;
+	else if (active)
+		status[2] |= 0x02;
+}
+
+
+
+static int fr_hard_header(struct sk_buff **skb_p, u16 dlci)
+{
+	u16 head_len;
+	struct sk_buff *skb = *skb_p;
+
+	switch (skb->protocol) {
+	case __constant_ntohs(ETH_P_IP):
+		head_len = 4;
+		skb_push(skb, head_len);
+		skb->data[3] = NLPID_IP;
+		break;
+
+	case __constant_ntohs(ETH_P_IPV6):
+		head_len = 4;
+		skb_push(skb, head_len);
+		skb->data[3] = NLPID_IPV6;
+		break;
+
+	case __constant_ntohs(LMI_PROTO):
+		head_len = 4;
+		skb_push(skb, head_len);
+		skb->data[3] = LMI_PROTO;
+		break;
+
+	case __constant_ntohs(ETH_P_802_3):
+		head_len = 10;
+		if (skb_headroom(skb) < head_len) {
+			struct sk_buff *skb2 = skb_realloc_headroom(skb,
+								    head_len);
+			if (!skb2)
+				return -ENOBUFS;
+			dev_kfree_skb(skb);
+			skb = *skb_p = skb2;
+		}
+		skb_push(skb, head_len);
+		skb->data[3] = FR_PAD;
+		skb->data[4] = NLPID_SNAP;
+		skb->data[5] = FR_PAD;
+		skb->data[6] = 0x80;
+		skb->data[7] = 0xC2;
+		skb->data[8] = 0x00;
+		skb->data[9] = 0x07; /* bridged Ethernet frame w/out FCS */
+		break;
+
+	default:
+		head_len = 10;
+		skb_push(skb, head_len);
+		skb->data[3] = FR_PAD;
+		skb->data[4] = NLPID_SNAP;
+		skb->data[5] = FR_PAD;
+		skb->data[6] = FR_PAD;
+		skb->data[7] = FR_PAD;
+		*(u16*)(skb->data + 8) = skb->protocol;
+	}
+
+	dlci_to_q922(skb->data, dlci);
+	skb->data[2] = FR_UI;
+	return 0;
+}
+
+
+
+static int pvc_open(struct net_device *dev)
+{
+	pvc_device *pvc = dev_to_pvc(dev);
+
+	if ((pvc->master->flags & IFF_UP) == 0)
+		return -EIO;  /* Master must be UP in order to activate PVC */
+
+	if (pvc->open_count++ == 0) {
+		hdlc_device *hdlc = dev_to_hdlc(pvc->master);
+		if (hdlc->state.fr.settings.lmi == LMI_NONE)
+			pvc->state.active = hdlc->carrier;
+
+		pvc_carrier(pvc->state.active, pvc);
+		hdlc->state.fr.dce_changed = 1;
+	}
+	return 0;
+}
+
+
+
+static int pvc_close(struct net_device *dev)
+{
+	pvc_device *pvc = dev_to_pvc(dev);
+
+	if (--pvc->open_count == 0) {
+		hdlc_device *hdlc = dev_to_hdlc(pvc->master);
+		if (hdlc->state.fr.settings.lmi == LMI_NONE)
+			pvc->state.active = 0;
+
+		if (hdlc->state.fr.settings.dce) {
+			hdlc->state.fr.dce_changed = 1;
+			pvc->state.active = 0;
+		}
+	}
+	return 0;
+}
+
+
+
+int pvc_ioctl(struct net_device *dev, struct ifreq *ifr, int cmd)
+{
+	pvc_device *pvc = dev_to_pvc(dev);
+	fr_proto_pvc_info info;
+
+	if (ifr->ifr_settings.type == IF_GET_PROTO) {
+		if (dev->type == ARPHRD_ETHER)
+			ifr->ifr_settings.type = IF_PROTO_FR_ETH_PVC;
+		else
+			ifr->ifr_settings.type = IF_PROTO_FR_PVC;
+
+		if (ifr->ifr_settings.size < sizeof(info)) {
+			/* data size wanted */
+			ifr->ifr_settings.size = sizeof(info);
+			return -ENOBUFS;
+		}
+
+		info.dlci = pvc->dlci;
+		memcpy(info.master, pvc->master->name, IFNAMSIZ);
+		if (copy_to_user(ifr->ifr_settings.ifs_ifsu.fr_pvc_info,
+				 &info, sizeof(info)))
+			return -EFAULT;
+		return 0;
+	}
+
+	return -EINVAL;
+}
+
+
+static inline struct net_device_stats *pvc_get_stats(struct net_device *dev)
+{
+	return netdev_priv(dev);
+}
+
+
+
+static int pvc_xmit(struct sk_buff *skb, struct net_device *dev)
+{
+	pvc_device *pvc = dev_to_pvc(dev);
+	struct net_device_stats *stats = pvc_get_stats(dev);
+
+	if (pvc->state.active) {
+		if (dev->type == ARPHRD_ETHER) {
+			int pad = ETH_ZLEN - skb->len;
+			if (pad > 0) { /* Pad the frame with zeros */
+				int len = skb->len;
+				if (skb_tailroom(skb) < pad)
+					if (pskb_expand_head(skb, 0, pad,
+							     GFP_ATOMIC)) {
+						stats->tx_dropped++;
+						dev_kfree_skb(skb);
+						return 0;
+					}
+				skb_put(skb, pad);
+				memset(skb->data + len, 0, pad);
+			}
+			skb->protocol = __constant_htons(ETH_P_802_3);
+		}
+		if (!fr_hard_header(&skb, pvc->dlci)) {
+			stats->tx_bytes += skb->len;
+			stats->tx_packets++;
+			if (pvc->state.fecn) /* TX Congestion counter */
+				stats->tx_compressed++;
+			skb->dev = pvc->master;
+			dev_queue_xmit(skb);
+			return 0;
+		}
+	}
+
+	stats->tx_dropped++;
+	dev_kfree_skb(skb);
+	return 0;
+}
+
+
+
+static int pvc_change_mtu(struct net_device *dev, int new_mtu)
+{
+	if ((new_mtu < 68) || (new_mtu > HDLC_MAX_MTU))
+		return -EINVAL;
+	dev->mtu = new_mtu;
+	return 0;
+}
+
+
+
+static inline void fr_log_dlci_active(pvc_device *pvc)
+{
+	printk(KERN_INFO "%s: DLCI %d [%s%s%s]%s %s\n",
+	       pvc->master->name,
+	       pvc->dlci,
+	       pvc->main ? pvc->main->name : "",
+	       pvc->main && pvc->ether ? " " : "",
+	       pvc->ether ? pvc->ether->name : "",
+	       pvc->state.new ? " new" : "",
+	       !pvc->state.exist ? "deleted" :
+	       pvc->state.active ? "active" : "inactive");
+}
+
+
+
+static inline u8 fr_lmi_nextseq(u8 x)
+{
+	x++;
+	return x ? x : 1;
+}
+
+
+
+static void fr_lmi_send(struct net_device *dev, int fullrep)
+{
+	hdlc_device *hdlc = dev_to_hdlc(dev);
+	struct sk_buff *skb;
+	pvc_device *pvc = hdlc->state.fr.first_pvc;
+	int len = (hdlc->state.fr.settings.lmi == LMI_ANSI) ? LMI_ANSI_LENGTH
+		: LMI_LENGTH;
+	int stat_len = 3;
+	u8 *data;
+	int i = 0;
+
+	if (hdlc->state.fr.settings.dce && fullrep) {
+		len += hdlc->state.fr.dce_pvc_count * (2 + stat_len);
+		if (len > HDLC_MAX_MRU) {
+			printk(KERN_WARNING "%s: Too many PVCs while sending "
+			       "LMI full report\n", dev->name);
+			return;
+		}
+	}
+
+	skb = dev_alloc_skb(len);
+	if (!skb) {
+		printk(KERN_WARNING "%s: Memory squeeze on fr_lmi_send()\n",
+		       dev->name);
+		return;
+	}
+	memset(skb->data, 0, len);
+	skb_reserve(skb, 4);
+	skb->protocol = __constant_htons(LMI_PROTO);
+	fr_hard_header(&skb, LMI_DLCI);
+	data = skb->tail;
+	data[i++] = LMI_CALLREF;
+	data[i++] = hdlc->state.fr.settings.dce
+		? LMI_STATUS : LMI_STATUS_ENQUIRY;
+	if (hdlc->state.fr.settings.lmi == LMI_ANSI)
+		data[i++] = LMI_ANSI_LOCKSHIFT;
+	data[i++] = (hdlc->state.fr.settings.lmi == LMI_CCITT)
+		? LMI_CCITT_REPTYPE : LMI_REPTYPE;
+	data[i++] = LMI_REPT_LEN;
+	data[i++] = fullrep ? LMI_FULLREP : LMI_INTEGRITY;
+
+	data[i++] = (hdlc->state.fr.settings.lmi == LMI_CCITT)
+		? LMI_CCITT_ALIVE : LMI_ALIVE;
+	data[i++] = LMI_INTEG_LEN;
+	data[i++] = hdlc->state.fr.txseq =fr_lmi_nextseq(hdlc->state.fr.txseq);
+	data[i++] = hdlc->state.fr.rxseq;
+
+	if (hdlc->state.fr.settings.dce && fullrep) {
+		while (pvc) {
+			data[i++] = (hdlc->state.fr.settings.lmi == LMI_CCITT)
+				? LMI_CCITT_PVCSTAT : LMI_PVCSTAT;
+			data[i++] = stat_len;
+
+			/* LMI start/restart */
+			if (hdlc->state.fr.reliable && !pvc->state.exist) {
+				pvc->state.exist = pvc->state.new = 1;
+				fr_log_dlci_active(pvc);
+			}
+
+			/* ifconfig PVC up */
+			if (pvc->open_count && !pvc->state.active &&
+			    pvc->state.exist && !pvc->state.new) {
+				pvc_carrier(1, pvc);
+				pvc->state.active = 1;
+				fr_log_dlci_active(pvc);
+			}
+
+			dlci_to_status(pvc->dlci, data + i,
+				       pvc->state.active, pvc->state.new);
+			i += stat_len;
+			pvc = pvc->next;
+		}
+	}
+
+	skb_put(skb, i);
+	skb->priority = TC_PRIO_CONTROL;
+	skb->dev = dev;
+	skb->nh.raw = skb->data;
+
+	dev_queue_xmit(skb);
+}
+
+
+
+static void fr_set_link_state(int reliable, struct net_device *dev)
+{
+	hdlc_device *hdlc = dev_to_hdlc(dev);
+	pvc_device *pvc = hdlc->state.fr.first_pvc;
+
+	hdlc->state.fr.reliable = reliable;
+	if (reliable) {
+		if (!netif_carrier_ok(dev))
+			netif_carrier_on(dev);
+
+		hdlc->state.fr.n391cnt = 0; /* Request full status */
+		hdlc->state.fr.dce_changed = 1;
+
+		if (hdlc->state.fr.settings.lmi == LMI_NONE) {
+			while (pvc) {	/* Activate all PVCs */
+				pvc_carrier(1, pvc);
+				pvc->state.exist = pvc->state.active = 1;
+				pvc->state.new = 0;
+				pvc = pvc->next;
+			}
+		}
+	} else {
+		if (netif_carrier_ok(dev))
+			netif_carrier_off(dev);
+
+		while (pvc) {		/* Deactivate all PVCs */
+			pvc_carrier(0, pvc);
+			pvc->state.exist = pvc->state.active = 0;
+			pvc->state.new = 0;
+			pvc = pvc->next;
+		}
+	}
+}
+
+
+
+static void fr_timer(unsigned long arg)
+{
+	struct net_device *dev = (struct net_device *)arg;
+	hdlc_device *hdlc = dev_to_hdlc(dev);
+	int i, cnt = 0, reliable;
+	u32 list;
+
+	if (hdlc->state.fr.settings.dce)
+		reliable = hdlc->state.fr.request &&
+			time_before(jiffies, hdlc->state.fr.last_poll +
+				    hdlc->state.fr.settings.t392 * HZ);
+	else {
+		hdlc->state.fr.last_errors <<= 1; /* Shift the list */
+		if (hdlc->state.fr.request) {
+			if (hdlc->state.fr.reliable)
+				printk(KERN_INFO "%s: No LMI status reply "
+				       "received\n", dev->name);
+			hdlc->state.fr.last_errors |= 1;
+		}
+
+		list = hdlc->state.fr.last_errors;
+		for (i = 0; i < hdlc->state.fr.settings.n393; i++, list >>= 1)
+			cnt += (list & 1);	/* errors count */
+
+		reliable = (cnt < hdlc->state.fr.settings.n392);
+	}
+
+	if (hdlc->state.fr.reliable != reliable) {
+		printk(KERN_INFO "%s: Link %sreliable\n", dev->name,
+		       reliable ? "" : "un");
+		fr_set_link_state(reliable, dev);
+	}
+
+	if (hdlc->state.fr.settings.dce)
+		hdlc->state.fr.timer.expires = jiffies +
+			hdlc->state.fr.settings.t392 * HZ;
+	else {
+		if (hdlc->state.fr.n391cnt)
+			hdlc->state.fr.n391cnt--;
+
+		fr_lmi_send(dev, hdlc->state.fr.n391cnt == 0);
+
+		hdlc->state.fr.last_poll = jiffies;
+		hdlc->state.fr.request = 1;
+		hdlc->state.fr.timer.expires = jiffies +
+			hdlc->state.fr.settings.t391 * HZ;
+	}
+
+	hdlc->state.fr.timer.function = fr_timer;
+	hdlc->state.fr.timer.data = arg;
+	add_timer(&hdlc->state.fr.timer);
+}
+
+
+
+static int fr_lmi_recv(struct net_device *dev, struct sk_buff *skb)
+{
+	hdlc_device *hdlc = dev_to_hdlc(dev);
+	int stat_len;
+	pvc_device *pvc;
+	int reptype = -1, error, no_ram;
+	u8 rxseq, txseq;
+	int i;
+
+	if (skb->len < ((hdlc->state.fr.settings.lmi == LMI_ANSI)
+			? LMI_ANSI_LENGTH : LMI_LENGTH)) {
+		printk(KERN_INFO "%s: Short LMI frame\n", dev->name);
+		return 1;
+	}
+
+	if (skb->data[5] != (!hdlc->state.fr.settings.dce ?
+			     LMI_STATUS : LMI_STATUS_ENQUIRY)) {
+		printk(KERN_INFO "%s: LMI msgtype=%x, Not LMI status %s\n",
+		       dev->name, skb->data[2],
+		       hdlc->state.fr.settings.dce ? "enquiry" : "reply");
+		return 1;
+	}
+
+	i = (hdlc->state.fr.settings.lmi == LMI_ANSI) ? 7 : 6;
+
+	if (skb->data[i] !=
+	    ((hdlc->state.fr.settings.lmi == LMI_CCITT)
+	     ? LMI_CCITT_REPTYPE : LMI_REPTYPE)) {
+		printk(KERN_INFO "%s: Not a report type=%x\n",
+		       dev->name, skb->data[i]);
+		return 1;
+	}
+	i++;
+
+	i++;				/* Skip length field */
+
+	reptype = skb->data[i++];
+
+	if (skb->data[i]!=
+	    ((hdlc->state.fr.settings.lmi == LMI_CCITT)
+	     ? LMI_CCITT_ALIVE : LMI_ALIVE)) {
+		printk(KERN_INFO "%s: Unsupported status element=%x\n",
+		       dev->name, skb->data[i]);
+		return 1;
+	}
+	i++;
+
+	i++;			/* Skip length field */
+
+	hdlc->state.fr.rxseq = skb->data[i++]; /* TX sequence from peer */
+	rxseq = skb->data[i++];	/* Should confirm our sequence */
+
+	txseq = hdlc->state.fr.txseq;
+
+	if (hdlc->state.fr.settings.dce) {
+		if (reptype != LMI_FULLREP && reptype != LMI_INTEGRITY) {
+			printk(KERN_INFO "%s: Unsupported report type=%x\n",
+			       dev->name, reptype);
+			return 1;
+		}
+		hdlc->state.fr.last_poll = jiffies;
+	}
+
+	error = 0;
+	if (!hdlc->state.fr.reliable)
+		error = 1;
+
+	if (rxseq == 0 || rxseq != txseq) {
+		hdlc->state.fr.n391cnt = 0; /* Ask for full report next time */
+		error = 1;
+	}
+
+	if (hdlc->state.fr.settings.dce) {
+		if (hdlc->state.fr.fullrep_sent && !error) {
+/* Stop sending full report - the last one has been confirmed by DTE */
+			hdlc->state.fr.fullrep_sent = 0;
+			pvc = hdlc->state.fr.first_pvc;
+			while (pvc) {
+				if (pvc->state.new) {
+					pvc->state.new = 0;
+
+/* Tell DTE that new PVC is now active */
+					hdlc->state.fr.dce_changed = 1;
+				}
+				pvc = pvc->next;
+			}
+		}
+
+		if (hdlc->state.fr.dce_changed) {
+			reptype = LMI_FULLREP;
+			hdlc->state.fr.fullrep_sent = 1;
+			hdlc->state.fr.dce_changed = 0;
+		}
+
+		fr_lmi_send(dev, reptype == LMI_FULLREP ? 1 : 0);
+		return 0;
+	}
+
+	/* DTE */
+
+	hdlc->state.fr.request = 0; /* got response, no request pending */
+
+	if (error)
+		return 0;
+
+	if (reptype != LMI_FULLREP)
+		return 0;
+
+	stat_len = 3;
+	pvc = hdlc->state.fr.first_pvc;
+
+	while (pvc) {
+		pvc->state.deleted = 1;
+		pvc = pvc->next;
+	}
+
+	no_ram = 0;
+	while (skb->len >= i + 2 + stat_len) {
+		u16 dlci;
+		unsigned int active, new;
+
+		if (skb->data[i] != ((hdlc->state.fr.settings.lmi == LMI_CCITT)
+				     ? LMI_CCITT_PVCSTAT : LMI_PVCSTAT)) {
+			printk(KERN_WARNING "%s: Invalid PVCSTAT ID: %x\n",
+			       dev->name, skb->data[i]);
+			return 1;
+		}
+		i++;
+
+		if (skb->data[i] != stat_len) {
+			printk(KERN_WARNING "%s: Invalid PVCSTAT length: %x\n",
+			       dev->name, skb->data[i]);
+			return 1;
+		}
+		i++;
+
+		dlci = status_to_dlci(skb->data + i, &active, &new);
+
+		pvc = add_pvc(dev, dlci);
+
+		if (!pvc && !no_ram) {
+			printk(KERN_WARNING
+			       "%s: Memory squeeze on fr_lmi_recv()\n",
+			       dev->name);
+			no_ram = 1;
+		}
+
+		if (pvc) {
+			pvc->state.exist = 1;
+			pvc->state.deleted = 0;
+			if (active != pvc->state.active ||
+			    new != pvc->state.new ||
+			    !pvc->state.exist) {
+				pvc->state.new = new;
+				pvc->state.active = active;
+				pvc_carrier(active, pvc);
+				fr_log_dlci_active(pvc);
+			}
+		}
+
+		i += stat_len;
+	}
+
+	pvc = hdlc->state.fr.first_pvc;
+
+	while (pvc) {
+		if (pvc->state.deleted && pvc->state.exist) {
+			pvc_carrier(0, pvc);
+			pvc->state.active = pvc->state.new = 0;
+			pvc->state.exist = 0;
+			fr_log_dlci_active(pvc);
+		}
+		pvc = pvc->next;
+	}
+
+	/* Next full report after N391 polls */
+	hdlc->state.fr.n391cnt = hdlc->state.fr.settings.n391;
+
+	return 0;
+}
+
+
+
+static int fr_rx(struct sk_buff *skb)
+{
+	struct net_device *ndev = skb->dev;
+	hdlc_device *hdlc = dev_to_hdlc(ndev);
+	fr_hdr *fh = (fr_hdr*)skb->data;
+	u8 *data = skb->data;
+	u16 dlci;
+	pvc_device *pvc;
+	struct net_device *dev = NULL;
+
+	if (skb->len <= 4 || fh->ea1 || data[2] != FR_UI)
+		goto rx_error;
+
+	dlci = q922_to_dlci(skb->data);
+
+	if (dlci == LMI_DLCI) {
+		if (hdlc->state.fr.settings.lmi == LMI_NONE)
+			goto rx_error; /* LMI packet with no LMI? */
+
+		if (data[3] == LMI_PROTO) {
+			if (fr_lmi_recv(ndev, skb))
+				goto rx_error;
+			else {
+				dev_kfree_skb_any(skb);
+				return NET_RX_SUCCESS;
+			}
+		}
+
+		printk(KERN_INFO "%s: Received non-LMI frame with LMI DLCI\n",
+		       ndev->name);
+		goto rx_error;
+	}
+
+	pvc = find_pvc(hdlc, dlci);
+	if (!pvc) {
+#ifdef DEBUG_PKT
+		printk(KERN_INFO "%s: No PVC for received frame's DLCI %d\n",
+		       ndev->name, dlci);
+#endif
+		dev_kfree_skb_any(skb);
+		return NET_RX_DROP;
+	}
+
+	if (pvc->state.fecn != fh->fecn) {
+#ifdef DEBUG_ECN
+		printk(KERN_DEBUG "%s: DLCI %d FECN O%s\n", ndev->name,
+		       dlci, fh->fecn ? "N" : "FF");
+#endif
+		pvc->state.fecn ^= 1;
+	}
+
+	if (pvc->state.becn != fh->becn) {
+#ifdef DEBUG_ECN
+		printk(KERN_DEBUG "%s: DLCI %d BECN O%s\n", ndev->name,
+		       dlci, fh->becn ? "N" : "FF");
+#endif
+		pvc->state.becn ^= 1;
+	}
+
+
+	if ((skb = skb_share_check(skb, GFP_ATOMIC)) == NULL) {
+		hdlc->stats.rx_dropped++;
+		return NET_RX_DROP;
+	}
+
+	if (data[3] == NLPID_IP) {
+		skb_pull(skb, 4); /* Remove 4-byte header (hdr, UI, NLPID) */
+		dev = pvc->main;
+		skb->protocol = htons(ETH_P_IP);
+
+	} else if (data[3] == NLPID_IPV6) {
+		skb_pull(skb, 4); /* Remove 4-byte header (hdr, UI, NLPID) */
+		dev = pvc->main;
+		skb->protocol = htons(ETH_P_IPV6);
+
+	} else if (skb->len > 10 && data[3] == FR_PAD &&
+		   data[4] == NLPID_SNAP && data[5] == FR_PAD) {
+		u16 oui = ntohs(*(u16*)(data + 6));
+		u16 pid = ntohs(*(u16*)(data + 8));
+		skb_pull(skb, 10);
+
+		switch ((((u32)oui) << 16) | pid) {
+		case ETH_P_ARP: /* routed frame with SNAP */
+		case ETH_P_IPX:
+		case ETH_P_IP:	/* a long variant */
+		case ETH_P_IPV6:
+			dev = pvc->main;
+			skb->protocol = htons(pid);
+			break;
+
+		case 0x80C20007: /* bridged Ethernet frame */
+			if ((dev = pvc->ether) != NULL)
+				skb->protocol = eth_type_trans(skb, dev);
+			break;
+
+		default:
+			printk(KERN_INFO "%s: Unsupported protocol, OUI=%x "
+			       "PID=%x\n", ndev->name, oui, pid);
+			dev_kfree_skb_any(skb);
+			return NET_RX_DROP;
+		}
+	} else {
+		printk(KERN_INFO "%s: Unsupported protocol, NLPID=%x "
+		       "length = %i\n", ndev->name, data[3], skb->len);
+		dev_kfree_skb_any(skb);
+		return NET_RX_DROP;
+	}
+
+	if (dev) {
+		struct net_device_stats *stats = pvc_get_stats(dev);
+		stats->rx_packets++; /* PVC traffic */
+		stats->rx_bytes += skb->len;
+		if (pvc->state.becn)
+			stats->rx_compressed++;
+		skb->dev = dev;
+		netif_rx(skb);
+		return NET_RX_SUCCESS;
+	} else {
+		dev_kfree_skb_any(skb);
+		return NET_RX_DROP;
+	}
+
+ rx_error:
+	hdlc->stats.rx_errors++; /* Mark error */
+	dev_kfree_skb_any(skb);
+	return NET_RX_DROP;
+}
+
+
+
+static void fr_start(struct net_device *dev)
+{
+	hdlc_device *hdlc = dev_to_hdlc(dev);
+#ifdef DEBUG_LINK
+	printk(KERN_DEBUG "fr_start\n");
+#endif
+	if (hdlc->state.fr.settings.lmi != LMI_NONE) {
+		hdlc->state.fr.reliable = 0;
+		hdlc->state.fr.dce_changed = 1;
+		hdlc->state.fr.request = 0;
+		hdlc->state.fr.fullrep_sent = 0;
+		hdlc->state.fr.last_errors = 0xFFFFFFFF;
+		hdlc->state.fr.n391cnt = 0;
+		hdlc->state.fr.txseq = hdlc->state.fr.rxseq = 0;
+
+		init_timer(&hdlc->state.fr.timer);
+		/* First poll after 1 s */
+		hdlc->state.fr.timer.expires = jiffies + HZ;
+		hdlc->state.fr.timer.function = fr_timer;
+		hdlc->state.fr.timer.data = (unsigned long)dev;
+		add_timer(&hdlc->state.fr.timer);
+	} else
+		fr_set_link_state(1, dev);
+}
+
+
+
+static void fr_stop(struct net_device *dev)
+{
+	hdlc_device *hdlc = dev_to_hdlc(dev);
+#ifdef DEBUG_LINK
+	printk(KERN_DEBUG "fr_stop\n");
+#endif
+	if (hdlc->state.fr.settings.lmi != LMI_NONE)
+		del_timer_sync(&hdlc->state.fr.timer);
+	fr_set_link_state(0, dev);
+}
+
+
+
+static void fr_close(struct net_device *dev)
+{
+	hdlc_device *hdlc = dev_to_hdlc(dev);
+	pvc_device *pvc = hdlc->state.fr.first_pvc;
+
+	while (pvc) {		/* Shutdown all PVCs for this FRAD */
+		if (pvc->main)
+			dev_close(pvc->main);
+		if (pvc->ether)
+			dev_close(pvc->ether);
+		pvc = pvc->next;
+	}
+}
+
+static void dlci_setup(struct net_device *dev)
+{
+	dev->type = ARPHRD_DLCI;
+	dev->flags = IFF_POINTOPOINT;
+	dev->hard_header_len = 10;
+	dev->addr_len = 2;
+}
+
+static int fr_add_pvc(struct net_device *master, unsigned int dlci, int type)
+{
+	hdlc_device *hdlc = dev_to_hdlc(master);
+	pvc_device *pvc = NULL;
+	struct net_device *dev;
+	int result, used;
+	char * prefix = "pvc%d";
+
+	if (type == ARPHRD_ETHER)
+		prefix = "pvceth%d";
+
+	if ((pvc = add_pvc(master, dlci)) == NULL) {
+		printk(KERN_WARNING "%s: Memory squeeze on fr_add_pvc()\n",
+		       master->name);
+		return -ENOBUFS;
+	}
+
+	if (*get_dev_p(pvc, type))
+		return -EEXIST;
+
+	used = pvc_is_used(pvc);
+
+	if (type == ARPHRD_ETHER)
+		dev = alloc_netdev(sizeof(struct net_device_stats),
+				   "pvceth%d", ether_setup);
+	else
+		dev = alloc_netdev(sizeof(struct net_device_stats),
+				   "pvc%d", dlci_setup);
+
+	if (!dev) {
+		printk(KERN_WARNING "%s: Memory squeeze on fr_pvc()\n",
+		       master->name);
+		delete_unused_pvcs(hdlc);
+		return -ENOBUFS;
+	}
+
+	if (type == ARPHRD_ETHER) {
+		memcpy(dev->dev_addr, "\x00\x01", 2);
+                get_random_bytes(dev->dev_addr + 2, ETH_ALEN - 2);
+	} else {
+		*(u16*)dev->dev_addr = htons(dlci);
+		dlci_to_q922(dev->broadcast, dlci);
+	}
+	dev->hard_start_xmit = pvc_xmit;
+	dev->get_stats = pvc_get_stats;
+	dev->open = pvc_open;
+	dev->stop = pvc_close;
+	dev->do_ioctl = pvc_ioctl;
+	dev->change_mtu = pvc_change_mtu;
+	dev->mtu = HDLC_MAX_MTU;
+	dev->tx_queue_len = 0;
+	dev->priv = pvc;
+
+	result = dev_alloc_name(dev, dev->name);
+	if (result < 0) {
+		free_netdev(dev);
+		delete_unused_pvcs(hdlc);
+		return result;
+	}
+
+	if (register_netdevice(dev) != 0) {
+		free_netdev(dev);
+		delete_unused_pvcs(hdlc);
+		return -EIO;
+	}
+
+	dev->destructor = free_netdev;
+	*get_dev_p(pvc, type) = dev;
+	if (!used) {
+		hdlc->state.fr.dce_changed = 1;
+		hdlc->state.fr.dce_pvc_count++;
+	}
+	return 0;
+}
+
+
+
+static int fr_del_pvc(hdlc_device *hdlc, unsigned int dlci, int type)
+{
+	pvc_device *pvc;
+	struct net_device *dev;
+
+	if ((pvc = find_pvc(hdlc, dlci)) == NULL)
+		return -ENOENT;
+
+	if ((dev = *get_dev_p(pvc, type)) == NULL)
+		return -ENOENT;
+
+	if (dev->flags & IFF_UP)
+		return -EBUSY;		/* PVC in use */
+
+	unregister_netdevice(dev); /* the destructor will free_netdev(dev) */
+	*get_dev_p(pvc, type) = NULL;
+
+	if (!pvc_is_used(pvc)) {
+		hdlc->state.fr.dce_pvc_count--;
+		hdlc->state.fr.dce_changed = 1;
+	}
+	delete_unused_pvcs(hdlc);
+	return 0;
+}
+
+
+
+static void fr_destroy(hdlc_device *hdlc)
+{
+	pvc_device *pvc;
+
+	pvc = hdlc->state.fr.first_pvc;
+	hdlc->state.fr.first_pvc = NULL; /* All PVCs destroyed */
+	hdlc->state.fr.dce_pvc_count = 0;
+	hdlc->state.fr.dce_changed = 1;
+
+	while (pvc) {
+		pvc_device *next = pvc->next;
+		/* destructors will free_netdev() main and ether */
+		if (pvc->main)
+			unregister_netdevice(pvc->main);
+
+		if (pvc->ether)
+			unregister_netdevice(pvc->ether);
+
+		kfree(pvc);
+		pvc = next;
+	}
+}
+
+
+
+int hdlc_fr_ioctl(struct net_device *dev, struct ifreq *ifr)
+{
+	fr_proto __user *fr_s = ifr->ifr_settings.ifs_ifsu.fr;
+	const size_t size = sizeof(fr_proto);
+	fr_proto new_settings;
+	hdlc_device *hdlc = dev_to_hdlc(dev);
+	fr_proto_pvc pvc;
+	int result;
+
+	switch (ifr->ifr_settings.type) {
+	case IF_GET_PROTO:
+		ifr->ifr_settings.type = IF_PROTO_FR;
+		if (ifr->ifr_settings.size < size) {
+			ifr->ifr_settings.size = size; /* data size wanted */
+			return -ENOBUFS;
+		}
+		if (copy_to_user(fr_s, &hdlc->state.fr.settings, size))
+			return -EFAULT;
+		return 0;
+
+	case IF_PROTO_FR:
+		if(!capable(CAP_NET_ADMIN))
+			return -EPERM;
+
+		if(dev->flags & IFF_UP)
+			return -EBUSY;
+
+		if (copy_from_user(&new_settings, fr_s, size))
+			return -EFAULT;
+
+		if (new_settings.lmi == LMI_DEFAULT)
+			new_settings.lmi = LMI_ANSI;
+
+		if ((new_settings.lmi != LMI_NONE &&
+		     new_settings.lmi != LMI_ANSI &&
+		     new_settings.lmi != LMI_CCITT) ||
+		    new_settings.t391 < 1 ||
+		    new_settings.t392 < 2 ||
+		    new_settings.n391 < 1 ||
+		    new_settings.n392 < 1 ||
+		    new_settings.n393 < new_settings.n392 ||
+		    new_settings.n393 > 32 ||
+		    (new_settings.dce != 0 &&
+		     new_settings.dce != 1))
+			return -EINVAL;
+
+		result=hdlc->attach(dev, ENCODING_NRZ,PARITY_CRC16_PR1_CCITT);
+		if (result)
+			return result;
+
+		if (hdlc->proto.id != IF_PROTO_FR) {
+			hdlc_proto_detach(hdlc);
+			hdlc->state.fr.first_pvc = NULL;
+			hdlc->state.fr.dce_pvc_count = 0;
+		}
+		memcpy(&hdlc->state.fr.settings, &new_settings, size);
+		memset(&hdlc->proto, 0, sizeof(hdlc->proto));
+
+		hdlc->proto.close = fr_close;
+		hdlc->proto.start = fr_start;
+		hdlc->proto.stop = fr_stop;
+		hdlc->proto.detach = fr_destroy;
+		hdlc->proto.netif_rx = fr_rx;
+		hdlc->proto.id = IF_PROTO_FR;
+		dev->hard_start_xmit = hdlc->xmit;
+		dev->hard_header = NULL;
+		dev->type = ARPHRD_FRAD;
+		dev->flags = IFF_POINTOPOINT | IFF_NOARP;
+		dev->addr_len = 0;
+		return 0;
+
+	case IF_PROTO_FR_ADD_PVC:
+	case IF_PROTO_FR_DEL_PVC:
+	case IF_PROTO_FR_ADD_ETH_PVC:
+	case IF_PROTO_FR_DEL_ETH_PVC:
+		if(!capable(CAP_NET_ADMIN))
+			return -EPERM;
+
+		if (copy_from_user(&pvc, ifr->ifr_settings.ifs_ifsu.fr_pvc,
+				   sizeof(fr_proto_pvc)))
+			return -EFAULT;
+
+		if (pvc.dlci <= 0 || pvc.dlci >= 1024)
+			return -EINVAL;	/* Only 10 bits, DLCI 0 reserved */
+
+		if (ifr->ifr_settings.type == IF_PROTO_FR_ADD_ETH_PVC ||
+		    ifr->ifr_settings.type == IF_PROTO_FR_DEL_ETH_PVC)
+			result = ARPHRD_ETHER; /* bridged Ethernet device */
+		else
+			result = ARPHRD_DLCI;
+
+		if (ifr->ifr_settings.type == IF_PROTO_FR_ADD_PVC ||
+		    ifr->ifr_settings.type == IF_PROTO_FR_ADD_ETH_PVC)
+			return fr_add_pvc(dev, pvc.dlci, result);
+		else
+			return fr_del_pvc(hdlc, pvc.dlci, result);
+	}
+
+	return -EINVAL;
+}
diff --git a/drivers/net/wan/hdlc_generic.c b/drivers/net/wan/hdlc_generic.c
new file mode 100644
index 0000000..6ed064c
--- /dev/null
+++ b/drivers/net/wan/hdlc_generic.c
@@ -0,0 +1,343 @@
+/*
+ * Generic HDLC support routines for Linux
+ *
+ * Copyright (C) 1999 - 2003 Krzysztof Halasa <khc@pm.waw.pl>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of version 2 of the GNU General Public License
+ * as published by the Free Software Foundation.
+ *
+ * Currently supported:
+ *	* raw IP-in-HDLC
+ *	* Cisco HDLC
+ *	* Frame Relay with ANSI or CCITT LMI (both user and network side)
+ *	* PPP
+ *	* X.25
+ *
+ * Use sethdlc utility to set line parameters, protocol and PVCs
+ *
+ * How does it work:
+ * - proto.open(), close(), start(), stop() calls are serialized.
+ *   The order is: open, [ start, stop ... ] close ...
+ * - proto.start() and stop() are called with spin_lock_irq held.
+ */
+
+#include <linux/config.h>
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include <linux/poll.h>
+#include <linux/errno.h>
+#include <linux/if_arp.h>
+#include <linux/init.h>
+#include <linux/skbuff.h>
+#include <linux/pkt_sched.h>
+#include <linux/inetdevice.h>
+#include <linux/lapb.h>
+#include <linux/rtnetlink.h>
+#include <linux/hdlc.h>
+
+
+static const char* version = "HDLC support module revision 1.17";
+
+#undef DEBUG_LINK
+
+
+static int hdlc_change_mtu(struct net_device *dev, int new_mtu)
+{
+	if ((new_mtu < 68) || (new_mtu > HDLC_MAX_MTU))
+		return -EINVAL;
+	dev->mtu = new_mtu;
+	return 0;
+}
+
+
+
+static struct net_device_stats *hdlc_get_stats(struct net_device *dev)
+{
+	return hdlc_stats(dev);
+}
+
+
+
+static int hdlc_rcv(struct sk_buff *skb, struct net_device *dev,
+		    struct packet_type *p)
+{
+	hdlc_device *hdlc = dev_to_hdlc(dev);
+	if (hdlc->proto.netif_rx)
+		return hdlc->proto.netif_rx(skb);
+
+	hdlc->stats.rx_dropped++; /* Shouldn't happen */
+	dev_kfree_skb(skb);
+	return NET_RX_DROP;
+}
+
+
+
+static void __hdlc_set_carrier_on(struct net_device *dev)
+{
+	hdlc_device *hdlc = dev_to_hdlc(dev);
+	if (hdlc->proto.start)
+		return hdlc->proto.start(dev);
+#ifdef DEBUG_LINK
+	if (netif_carrier_ok(dev))
+		printk(KERN_ERR "hdlc_set_carrier_on(): already on\n");
+#endif
+	netif_carrier_on(dev);
+}
+
+
+
+static void __hdlc_set_carrier_off(struct net_device *dev)
+{
+	hdlc_device *hdlc = dev_to_hdlc(dev);
+	if (hdlc->proto.stop)
+		return hdlc->proto.stop(dev);
+
+#ifdef DEBUG_LINK
+	if (!netif_carrier_ok(dev))
+		printk(KERN_ERR "hdlc_set_carrier_off(): already off\n");
+#endif
+	netif_carrier_off(dev);
+}
+
+
+
+void hdlc_set_carrier(int on, struct net_device *dev)
+{
+	hdlc_device *hdlc = dev_to_hdlc(dev);
+	unsigned long flags;
+	on = on ? 1 : 0;
+
+#ifdef DEBUG_LINK
+	printk(KERN_DEBUG "hdlc_set_carrier %i\n", on);
+#endif
+
+	spin_lock_irqsave(&hdlc->state_lock, flags);
+
+	if (hdlc->carrier == on)
+		goto carrier_exit; /* no change in DCD line level */
+
+#ifdef DEBUG_LINK
+	printk(KERN_INFO "%s: carrier %s\n", dev->name, on ? "ON" : "off");
+#endif
+	hdlc->carrier = on;
+
+	if (!hdlc->open)
+		goto carrier_exit;
+
+	if (hdlc->carrier)
+		__hdlc_set_carrier_on(dev);
+	else
+		__hdlc_set_carrier_off(dev);
+
+carrier_exit:
+	spin_unlock_irqrestore(&hdlc->state_lock, flags);
+}
+
+
+
+/* Must be called by hardware driver when HDLC device is being opened */
+int hdlc_open(struct net_device *dev)
+{
+	hdlc_device *hdlc = dev_to_hdlc(dev);
+#ifdef DEBUG_LINK
+	printk(KERN_DEBUG "hdlc_open() carrier %i open %i\n",
+	       hdlc->carrier, hdlc->open);
+#endif
+
+	if (hdlc->proto.id == -1)
+		return -ENOSYS;	/* no protocol attached */
+
+	if (hdlc->proto.open) {
+		int result = hdlc->proto.open(dev);
+		if (result)
+			return result;
+	}
+
+	spin_lock_irq(&hdlc->state_lock);
+
+	if (hdlc->carrier)
+		__hdlc_set_carrier_on(dev);
+
+	hdlc->open = 1;
+
+	spin_unlock_irq(&hdlc->state_lock);
+	return 0;
+}
+
+
+
+/* Must be called by hardware driver when HDLC device is being closed */
+void hdlc_close(struct net_device *dev)
+{
+	hdlc_device *hdlc = dev_to_hdlc(dev);
+#ifdef DEBUG_LINK
+	printk(KERN_DEBUG "hdlc_close() carrier %i open %i\n",
+	       hdlc->carrier, hdlc->open);
+#endif
+
+	spin_lock_irq(&hdlc->state_lock);
+
+	hdlc->open = 0;
+	if (hdlc->carrier)
+		__hdlc_set_carrier_off(dev);
+
+	spin_unlock_irq(&hdlc->state_lock);
+
+	if (hdlc->proto.close)
+		hdlc->proto.close(dev);
+}
+
+
+
+#ifndef CONFIG_HDLC_RAW
+#define hdlc_raw_ioctl(dev, ifr)	-ENOSYS
+#endif
+
+#ifndef CONFIG_HDLC_RAW_ETH
+#define hdlc_raw_eth_ioctl(dev, ifr)	-ENOSYS
+#endif
+
+#ifndef CONFIG_HDLC_PPP
+#define hdlc_ppp_ioctl(dev, ifr)	-ENOSYS
+#endif
+
+#ifndef CONFIG_HDLC_CISCO
+#define hdlc_cisco_ioctl(dev, ifr)	-ENOSYS
+#endif
+
+#ifndef CONFIG_HDLC_FR
+#define hdlc_fr_ioctl(dev, ifr)		-ENOSYS
+#endif
+
+#ifndef CONFIG_HDLC_X25
+#define hdlc_x25_ioctl(dev, ifr)	-ENOSYS
+#endif
+
+
+int hdlc_ioctl(struct net_device *dev, struct ifreq *ifr, int cmd)
+{
+	hdlc_device *hdlc = dev_to_hdlc(dev);
+	unsigned int proto;
+
+	if (cmd != SIOCWANDEV)
+		return -EINVAL;
+
+	switch(ifr->ifr_settings.type) {
+	case IF_PROTO_HDLC:
+	case IF_PROTO_HDLC_ETH:
+	case IF_PROTO_PPP:
+	case IF_PROTO_CISCO:
+	case IF_PROTO_FR:
+	case IF_PROTO_X25:
+		proto = ifr->ifr_settings.type;
+		break;
+
+	default:
+		proto = hdlc->proto.id;
+	}
+
+	switch(proto) {
+	case IF_PROTO_HDLC:	return hdlc_raw_ioctl(dev, ifr);
+	case IF_PROTO_HDLC_ETH:	return hdlc_raw_eth_ioctl(dev, ifr);
+	case IF_PROTO_PPP:	return hdlc_ppp_ioctl(dev, ifr);
+	case IF_PROTO_CISCO:	return hdlc_cisco_ioctl(dev, ifr);
+	case IF_PROTO_FR:	return hdlc_fr_ioctl(dev, ifr);
+	case IF_PROTO_X25:	return hdlc_x25_ioctl(dev, ifr);
+	default:		return -EINVAL;
+	}
+}
+
+static void hdlc_setup(struct net_device *dev)
+{
+	hdlc_device *hdlc = dev_to_hdlc(dev);
+
+	dev->get_stats = hdlc_get_stats;
+	dev->change_mtu = hdlc_change_mtu;
+	dev->mtu = HDLC_MAX_MTU;
+
+	dev->type = ARPHRD_RAWHDLC;
+	dev->hard_header_len = 16;
+
+	dev->flags = IFF_POINTOPOINT | IFF_NOARP;
+
+	hdlc->proto.id = -1;
+	hdlc->proto.detach = NULL;
+	hdlc->carrier = 1;
+	hdlc->open = 0;
+	spin_lock_init(&hdlc->state_lock);
+}
+
+struct net_device *alloc_hdlcdev(void *priv)
+{
+	struct net_device *dev;
+	dev = alloc_netdev(sizeof(hdlc_device), "hdlc%d", hdlc_setup);
+	if (dev)
+		dev_to_hdlc(dev)->priv = priv;
+	return dev;
+}
+
+int register_hdlc_device(struct net_device *dev)
+{
+	int result = dev_alloc_name(dev, "hdlc%d");
+	if (result < 0)
+		return result;
+
+	result = register_netdev(dev);
+	if (result != 0)
+		return -EIO;
+
+	if (netif_carrier_ok(dev))
+		netif_carrier_off(dev); /* no carrier until DCD goes up */
+
+	return 0;
+}
+
+
+
+void unregister_hdlc_device(struct net_device *dev)
+{
+	rtnl_lock();
+	hdlc_proto_detach(dev_to_hdlc(dev));
+	unregister_netdevice(dev);
+	rtnl_unlock();
+}
+
+
+
+MODULE_AUTHOR("Krzysztof Halasa <khc@pm.waw.pl>");
+MODULE_DESCRIPTION("HDLC support module");
+MODULE_LICENSE("GPL v2");
+
+EXPORT_SYMBOL(hdlc_open);
+EXPORT_SYMBOL(hdlc_close);
+EXPORT_SYMBOL(hdlc_set_carrier);
+EXPORT_SYMBOL(hdlc_ioctl);
+EXPORT_SYMBOL(alloc_hdlcdev);
+EXPORT_SYMBOL(register_hdlc_device);
+EXPORT_SYMBOL(unregister_hdlc_device);
+
+static struct packet_type hdlc_packet_type = {
+	.type = __constant_htons(ETH_P_HDLC),
+	.func = hdlc_rcv,
+};
+
+
+static int __init hdlc_module_init(void)
+{
+	printk(KERN_INFO "%s\n", version);
+        dev_add_pack(&hdlc_packet_type);
+	return 0;
+}
+
+
+
+static void __exit hdlc_module_exit(void)
+{
+	dev_remove_pack(&hdlc_packet_type);
+}
+
+
+module_init(hdlc_module_init);
+module_exit(hdlc_module_exit);
diff --git a/drivers/net/wan/hdlc_ppp.c b/drivers/net/wan/hdlc_ppp.c
new file mode 100644
index 0000000..7cd6195
--- /dev/null
+++ b/drivers/net/wan/hdlc_ppp.c
@@ -0,0 +1,115 @@
+/*
+ * Generic HDLC support routines for Linux
+ * Point-to-point protocol support
+ *
+ * Copyright (C) 1999 - 2003 Krzysztof Halasa <khc@pm.waw.pl>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of version 2 of the GNU General Public License
+ * as published by the Free Software Foundation.
+ */
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include <linux/poll.h>
+#include <linux/errno.h>
+#include <linux/if_arp.h>
+#include <linux/init.h>
+#include <linux/skbuff.h>
+#include <linux/pkt_sched.h>
+#include <linux/inetdevice.h>
+#include <linux/lapb.h>
+#include <linux/rtnetlink.h>
+#include <linux/hdlc.h>
+
+
+static int ppp_open(struct net_device *dev)
+{
+	hdlc_device *hdlc = dev_to_hdlc(dev);
+	void *old_ioctl;
+	int result;
+
+	dev->priv = &hdlc->state.ppp.syncppp_ptr;
+	hdlc->state.ppp.syncppp_ptr = &hdlc->state.ppp.pppdev;
+	hdlc->state.ppp.pppdev.dev = dev;
+
+	old_ioctl = dev->do_ioctl;
+	hdlc->state.ppp.old_change_mtu = dev->change_mtu;
+	sppp_attach(&hdlc->state.ppp.pppdev);
+	/* sppp_attach nukes them. We don't need syncppp's ioctl */
+	dev->do_ioctl = old_ioctl;
+	hdlc->state.ppp.pppdev.sppp.pp_flags &= ~PP_CISCO;
+	dev->type = ARPHRD_PPP;
+	result = sppp_open(dev);
+	if (result) {
+		sppp_detach(dev);
+		return result;
+	}
+
+	return 0;
+}
+
+
+
+static void ppp_close(struct net_device *dev)
+{
+	hdlc_device *hdlc = dev_to_hdlc(dev);
+
+	sppp_close(dev);
+	sppp_detach(dev);
+	dev->rebuild_header = NULL;
+	dev->change_mtu = hdlc->state.ppp.old_change_mtu;
+	dev->mtu = HDLC_MAX_MTU;
+	dev->hard_header_len = 16;
+}
+
+
+
+static unsigned short ppp_type_trans(struct sk_buff *skb,
+				     struct net_device *dev)
+{
+	return __constant_htons(ETH_P_WAN_PPP);
+}
+
+
+
+int hdlc_ppp_ioctl(struct net_device *dev, struct ifreq *ifr)
+{
+	hdlc_device *hdlc = dev_to_hdlc(dev);
+	int result;
+
+	switch (ifr->ifr_settings.type) {
+	case IF_GET_PROTO:
+		ifr->ifr_settings.type = IF_PROTO_PPP;
+		return 0; /* return protocol only, no settable parameters */
+
+	case IF_PROTO_PPP:
+		if(!capable(CAP_NET_ADMIN))
+			return -EPERM;
+
+		if(dev->flags & IFF_UP)
+			return -EBUSY;
+
+		/* no settable parameters */
+
+		result=hdlc->attach(dev, ENCODING_NRZ,PARITY_CRC16_PR1_CCITT);
+		if (result)
+			return result;
+
+		hdlc_proto_detach(hdlc);
+		memset(&hdlc->proto, 0, sizeof(hdlc->proto));
+
+		hdlc->proto.open = ppp_open;
+		hdlc->proto.close = ppp_close;
+		hdlc->proto.type_trans = ppp_type_trans;
+		hdlc->proto.id = IF_PROTO_PPP;
+		dev->hard_start_xmit = hdlc->xmit;
+		dev->hard_header = NULL;
+		dev->type = ARPHRD_PPP;
+		dev->addr_len = 0;
+		return 0;
+	}
+
+	return -EINVAL;
+}
diff --git a/drivers/net/wan/hdlc_raw.c b/drivers/net/wan/hdlc_raw.c
new file mode 100644
index 0000000..c41fb70
--- /dev/null
+++ b/drivers/net/wan/hdlc_raw.c
@@ -0,0 +1,90 @@
+/*
+ * Generic HDLC support routines for Linux
+ * HDLC support
+ *
+ * Copyright (C) 1999 - 2003 Krzysztof Halasa <khc@pm.waw.pl>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of version 2 of the GNU General Public License
+ * as published by the Free Software Foundation.
+ */
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include <linux/poll.h>
+#include <linux/errno.h>
+#include <linux/if_arp.h>
+#include <linux/init.h>
+#include <linux/skbuff.h>
+#include <linux/pkt_sched.h>
+#include <linux/inetdevice.h>
+#include <linux/lapb.h>
+#include <linux/rtnetlink.h>
+#include <linux/hdlc.h>
+
+
+static unsigned short raw_type_trans(struct sk_buff *skb,
+				     struct net_device *dev)
+{
+	return __constant_htons(ETH_P_IP);
+}
+
+
+
+int hdlc_raw_ioctl(struct net_device *dev, struct ifreq *ifr)
+{
+	raw_hdlc_proto __user *raw_s = ifr->ifr_settings.ifs_ifsu.raw_hdlc;
+	const size_t size = sizeof(raw_hdlc_proto);
+	raw_hdlc_proto new_settings;
+	hdlc_device *hdlc = dev_to_hdlc(dev);
+	int result;
+
+	switch (ifr->ifr_settings.type) {
+	case IF_GET_PROTO:
+		ifr->ifr_settings.type = IF_PROTO_HDLC;
+		if (ifr->ifr_settings.size < size) {
+			ifr->ifr_settings.size = size; /* data size wanted */
+			return -ENOBUFS;
+		}
+		if (copy_to_user(raw_s, &hdlc->state.raw_hdlc.settings, size))
+			return -EFAULT;
+		return 0;
+
+	case IF_PROTO_HDLC:
+		if (!capable(CAP_NET_ADMIN))
+			return -EPERM;
+
+		if (dev->flags & IFF_UP)
+			return -EBUSY;
+
+		if (copy_from_user(&new_settings, raw_s, size))
+			return -EFAULT;
+
+		if (new_settings.encoding == ENCODING_DEFAULT)
+			new_settings.encoding = ENCODING_NRZ;
+
+		if (new_settings.parity == PARITY_DEFAULT)
+			new_settings.parity = PARITY_CRC16_PR1_CCITT;
+
+		result = hdlc->attach(dev, new_settings.encoding,
+				      new_settings.parity);
+		if (result)
+			return result;
+
+		hdlc_proto_detach(hdlc);
+		memcpy(&hdlc->state.raw_hdlc.settings, &new_settings, size);
+		memset(&hdlc->proto, 0, sizeof(hdlc->proto));
+
+		hdlc->proto.type_trans = raw_type_trans;
+		hdlc->proto.id = IF_PROTO_HDLC;
+		dev->hard_start_xmit = hdlc->xmit;
+		dev->hard_header = NULL;
+		dev->type = ARPHRD_RAWHDLC;
+		dev->flags = IFF_POINTOPOINT | IFF_NOARP;
+		dev->addr_len = 0;
+		return 0;
+	}
+
+	return -EINVAL;
+}
diff --git a/drivers/net/wan/hdlc_raw_eth.c b/drivers/net/wan/hdlc_raw_eth.c
new file mode 100644
index 0000000..b1285cc
--- /dev/null
+++ b/drivers/net/wan/hdlc_raw_eth.c
@@ -0,0 +1,107 @@
+/*
+ * Generic HDLC support routines for Linux
+ * HDLC Ethernet emulation support
+ *
+ * Copyright (C) 2002-2003 Krzysztof Halasa <khc@pm.waw.pl>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of version 2 of the GNU General Public License
+ * as published by the Free Software Foundation.
+ */
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include <linux/poll.h>
+#include <linux/errno.h>
+#include <linux/if_arp.h>
+#include <linux/init.h>
+#include <linux/skbuff.h>
+#include <linux/pkt_sched.h>
+#include <linux/random.h>
+#include <linux/inetdevice.h>
+#include <linux/lapb.h>
+#include <linux/rtnetlink.h>
+#include <linux/etherdevice.h>
+#include <linux/hdlc.h>
+
+
+static int eth_tx(struct sk_buff *skb, struct net_device *dev)
+{
+	int pad = ETH_ZLEN - skb->len;
+	if (pad > 0) {		/* Pad the frame with zeros */
+		int len = skb->len;
+		if (skb_tailroom(skb) < pad)
+			if (pskb_expand_head(skb, 0, pad, GFP_ATOMIC)) {
+				hdlc_stats(dev)->tx_dropped++;
+				dev_kfree_skb(skb);
+				return 0;
+			}
+		skb_put(skb, pad);
+		memset(skb->data + len, 0, pad);
+	}
+	return dev_to_hdlc(dev)->xmit(skb, dev);
+}
+
+
+int hdlc_raw_eth_ioctl(struct net_device *dev, struct ifreq *ifr)
+{
+	raw_hdlc_proto __user *raw_s = ifr->ifr_settings.ifs_ifsu.raw_hdlc;
+	const size_t size = sizeof(raw_hdlc_proto);
+	raw_hdlc_proto new_settings;
+	hdlc_device *hdlc = dev_to_hdlc(dev);
+	int result;
+	void *old_ch_mtu;
+	int old_qlen;
+
+	switch (ifr->ifr_settings.type) {
+	case IF_GET_PROTO:
+		ifr->ifr_settings.type = IF_PROTO_HDLC_ETH;
+		if (ifr->ifr_settings.size < size) {
+			ifr->ifr_settings.size = size; /* data size wanted */
+			return -ENOBUFS;
+		}
+		if (copy_to_user(raw_s, &hdlc->state.raw_hdlc.settings, size))
+			return -EFAULT;
+		return 0;
+
+	case IF_PROTO_HDLC_ETH:
+		if (!capable(CAP_NET_ADMIN))
+			return -EPERM;
+
+		if (dev->flags & IFF_UP)
+			return -EBUSY;
+
+		if (copy_from_user(&new_settings, raw_s, size))
+			return -EFAULT;
+
+		if (new_settings.encoding == ENCODING_DEFAULT)
+			new_settings.encoding = ENCODING_NRZ;
+
+		if (new_settings.parity == PARITY_DEFAULT)
+			new_settings.parity = PARITY_CRC16_PR1_CCITT;
+
+		result = hdlc->attach(dev, new_settings.encoding,
+				      new_settings.parity);
+		if (result)
+			return result;
+
+		hdlc_proto_detach(hdlc);
+		memcpy(&hdlc->state.raw_hdlc.settings, &new_settings, size);
+		memset(&hdlc->proto, 0, sizeof(hdlc->proto));
+
+		hdlc->proto.type_trans = eth_type_trans;
+		hdlc->proto.id = IF_PROTO_HDLC_ETH;
+		dev->hard_start_xmit = eth_tx;
+		old_ch_mtu = dev->change_mtu;
+		old_qlen = dev->tx_queue_len;
+		ether_setup(dev);
+		dev->change_mtu = old_ch_mtu;
+		dev->tx_queue_len = old_qlen;
+		memcpy(dev->dev_addr, "\x00\x01", 2);
+                get_random_bytes(dev->dev_addr + 2, ETH_ALEN - 2);
+		return 0;
+	}
+
+	return -EINVAL;
+}
diff --git a/drivers/net/wan/hdlc_x25.c b/drivers/net/wan/hdlc_x25.c
new file mode 100644
index 0000000..07e5eef
--- /dev/null
+++ b/drivers/net/wan/hdlc_x25.c
@@ -0,0 +1,219 @@
+/*
+ * Generic HDLC support routines for Linux
+ * X.25 support
+ *
+ * Copyright (C) 1999 - 2003 Krzysztof Halasa <khc@pm.waw.pl>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of version 2 of the GNU General Public License
+ * as published by the Free Software Foundation.
+ */
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include <linux/poll.h>
+#include <linux/errno.h>
+#include <linux/if_arp.h>
+#include <linux/init.h>
+#include <linux/skbuff.h>
+#include <linux/pkt_sched.h>
+#include <linux/inetdevice.h>
+#include <linux/lapb.h>
+#include <linux/rtnetlink.h>
+#include <linux/hdlc.h>
+
+#include <net/x25device.h>
+
+/* These functions are callbacks called by LAPB layer */
+
+static void x25_connect_disconnect(struct net_device *dev, int reason, int code)
+{
+	struct sk_buff *skb;
+	unsigned char *ptr;
+
+	if ((skb = dev_alloc_skb(1)) == NULL) {
+		printk(KERN_ERR "%s: out of memory\n", dev->name);
+		return;
+	}
+
+	ptr = skb_put(skb, 1);
+	*ptr = code;
+
+	skb->protocol = x25_type_trans(skb, dev);
+	netif_rx(skb);
+}
+
+
+
+static void x25_connected(struct net_device *dev, int reason)
+{
+	x25_connect_disconnect(dev, reason, 1);
+}
+
+
+
+static void x25_disconnected(struct net_device *dev, int reason)
+{
+	x25_connect_disconnect(dev, reason, 2);
+}
+
+
+
+static int x25_data_indication(struct net_device *dev, struct sk_buff *skb)
+{
+	unsigned char *ptr;
+
+	skb_push(skb, 1);
+
+	if (skb_cow(skb, 1))
+		return NET_RX_DROP;
+
+	ptr  = skb->data;
+	*ptr = 0;
+
+	skb->protocol = x25_type_trans(skb, dev);
+	return netif_rx(skb);
+}
+
+
+
+static void x25_data_transmit(struct net_device *dev, struct sk_buff *skb)
+{
+	hdlc_device *hdlc = dev_to_hdlc(dev);
+	hdlc->xmit(skb, dev); /* Ignore return value :-( */
+}
+
+
+
+static int x25_xmit(struct sk_buff *skb, struct net_device *dev)
+{
+	int result;
+
+
+	/* X.25 to LAPB */
+	switch (skb->data[0]) {
+	case 0:		/* Data to be transmitted */
+		skb_pull(skb, 1);
+		if ((result = lapb_data_request(dev, skb)) != LAPB_OK)
+			dev_kfree_skb(skb);
+		return 0;
+
+	case 1:
+		if ((result = lapb_connect_request(dev))!= LAPB_OK) {
+			if (result == LAPB_CONNECTED)
+				/* Send connect confirm. msg to level 3 */
+				x25_connected(dev, 0);
+			else
+				printk(KERN_ERR "%s: LAPB connect request "
+				       "failed, error code = %i\n",
+				       dev->name, result);
+		}
+		break;
+
+	case 2:
+		if ((result = lapb_disconnect_request(dev)) != LAPB_OK) {
+			if (result == LAPB_NOTCONNECTED)
+				/* Send disconnect confirm. msg to level 3 */
+				x25_disconnected(dev, 0);
+			else
+				printk(KERN_ERR "%s: LAPB disconnect request "
+				       "failed, error code = %i\n",
+				       dev->name, result);
+		}
+		break;
+
+	default:		/* to be defined */
+		break;
+	}
+
+	dev_kfree_skb(skb);
+	return 0;
+}
+
+
+
+static int x25_open(struct net_device *dev)
+{
+	struct lapb_register_struct cb;
+	int result;
+
+	cb.connect_confirmation = x25_connected;
+	cb.connect_indication = x25_connected;
+	cb.disconnect_confirmation = x25_disconnected;
+	cb.disconnect_indication = x25_disconnected;
+	cb.data_indication = x25_data_indication;
+	cb.data_transmit = x25_data_transmit;
+
+	result = lapb_register(dev, &cb);
+	if (result != LAPB_OK)
+		return result;
+	return 0;
+}
+
+
+
+static void x25_close(struct net_device *dev)
+{
+	lapb_unregister(dev);
+}
+
+
+
+static int x25_rx(struct sk_buff *skb)
+{
+	hdlc_device *hdlc = dev_to_hdlc(skb->dev);
+
+	if ((skb = skb_share_check(skb, GFP_ATOMIC)) == NULL) {
+		hdlc->stats.rx_dropped++;
+		return NET_RX_DROP;
+	}
+
+	if (lapb_data_received(skb->dev, skb) == LAPB_OK)
+		return NET_RX_SUCCESS;
+
+	hdlc->stats.rx_errors++;
+	dev_kfree_skb_any(skb);
+	return NET_RX_DROP;
+}
+
+
+
+int hdlc_x25_ioctl(struct net_device *dev, struct ifreq *ifr)
+{
+	hdlc_device *hdlc = dev_to_hdlc(dev);
+	int result;
+
+	switch (ifr->ifr_settings.type) {
+	case IF_GET_PROTO:
+		ifr->ifr_settings.type = IF_PROTO_X25;
+		return 0; /* return protocol only, no settable parameters */
+
+	case IF_PROTO_X25:
+		if(!capable(CAP_NET_ADMIN))
+			return -EPERM;
+
+		if(dev->flags & IFF_UP)
+			return -EBUSY;
+
+		result=hdlc->attach(dev, ENCODING_NRZ,PARITY_CRC16_PR1_CCITT);
+		if (result)
+			return result;
+
+		hdlc_proto_detach(hdlc);
+		memset(&hdlc->proto, 0, sizeof(hdlc->proto));
+
+		hdlc->proto.open = x25_open;
+		hdlc->proto.close = x25_close;
+		hdlc->proto.netif_rx = x25_rx;
+		hdlc->proto.type_trans = NULL;
+		hdlc->proto.id = IF_PROTO_X25;
+		dev->hard_start_xmit = x25_xmit;
+		dev->hard_header = NULL;
+		dev->type = ARPHRD_X25;
+		dev->addr_len = 0;
+		return 0;
+	}
+
+	return -EINVAL;
+}
diff --git a/drivers/net/wan/hostess_sv11.c b/drivers/net/wan/hostess_sv11.c
new file mode 100644
index 0000000..7db1d1d
--- /dev/null
+++ b/drivers/net/wan/hostess_sv11.c
@@ -0,0 +1,420 @@
+/*
+ *	Comtrol SV11 card driver
+ *
+ *	This is a slightly odd Z85230 synchronous driver. All you need to
+ *	know basically is
+ *
+ *	Its a genuine Z85230
+ *
+ *	It supports DMA using two DMA channels in SYNC mode. The driver doesn't
+ *	use these facilities
+ *	
+ *	The control port is at io+1, the data at io+3 and turning off the DMA
+ *	is done by writing 0 to io+4
+ *
+ *	The hardware does the bus handling to avoid the need for delays between
+ *	touching control registers.
+ *
+ *	Port B isnt wired (why - beats me)
+ */
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/mm.h>
+#include <linux/net.h>
+#include <linux/skbuff.h>
+#include <linux/netdevice.h>
+#include <linux/if_arp.h>
+#include <linux/delay.h>
+#include <linux/ioport.h>
+#include <net/arp.h>
+
+#include <asm/io.h>
+#include <asm/dma.h>
+#include <asm/byteorder.h>
+#include <net/syncppp.h>
+#include "z85230.h"
+
+static int dma;
+
+struct sv11_device
+{
+	void *if_ptr;	/* General purpose pointer (used by SPPP) */
+	struct z8530_dev sync;
+	struct ppp_device netdev;
+};
+
+/*
+ *	Network driver support routines
+ */
+
+/*
+ *	Frame receive. Simple for our card as we do sync ppp and there
+ *	is no funny garbage involved
+ */
+ 
+static void hostess_input(struct z8530_channel *c, struct sk_buff *skb)
+{
+	/* Drop the CRC - it's not a good idea to try and negotiate it ;) */
+	skb_trim(skb, skb->len-2);
+	skb->protocol=__constant_htons(ETH_P_WAN_PPP);
+	skb->mac.raw=skb->data;
+	skb->dev=c->netdevice;
+	/*
+	 *	Send it to the PPP layer. We don't have time to process
+	 *	it right now.
+	 */
+	netif_rx(skb);
+	c->netdevice->last_rx = jiffies;
+}
+ 
+/*
+ *	We've been placed in the UP state
+ */ 
+ 
+static int hostess_open(struct net_device *d)
+{
+	struct sv11_device *sv11=d->priv;
+	int err = -1;
+	
+	/*
+	 *	Link layer up
+	 */
+	switch(dma)
+	{
+		case 0:
+			err=z8530_sync_open(d, &sv11->sync.chanA);
+			break;
+		case 1:
+			err=z8530_sync_dma_open(d, &sv11->sync.chanA);
+			break;
+		case 2:
+			err=z8530_sync_txdma_open(d, &sv11->sync.chanA);
+			break;
+	}
+	
+	if(err)
+		return err;
+	/*
+	 *	Begin PPP
+	 */
+	err=sppp_open(d);
+	if(err)
+	{
+		switch(dma)
+		{
+			case 0:
+				z8530_sync_close(d, &sv11->sync.chanA);
+				break;
+			case 1:
+				z8530_sync_dma_close(d, &sv11->sync.chanA);
+				break;
+			case 2:
+				z8530_sync_txdma_close(d, &sv11->sync.chanA);
+				break;
+		}				
+		return err;
+	}
+	sv11->sync.chanA.rx_function=hostess_input;
+	
+	/*
+	 *	Go go go
+	 */
+
+	netif_start_queue(d);
+	return 0;
+}
+
+static int hostess_close(struct net_device *d)
+{
+	struct sv11_device *sv11=d->priv;
+	/*
+	 *	Discard new frames
+	 */
+	sv11->sync.chanA.rx_function=z8530_null_rx;
+	/*
+	 *	PPP off
+	 */
+	sppp_close(d);
+	/*
+	 *	Link layer down
+	 */
+	netif_stop_queue(d);
+		
+	switch(dma)
+	{
+		case 0:
+			z8530_sync_close(d, &sv11->sync.chanA);
+			break;
+		case 1:
+			z8530_sync_dma_close(d, &sv11->sync.chanA);
+			break;
+		case 2:
+			z8530_sync_txdma_close(d, &sv11->sync.chanA);
+			break;
+	}
+	return 0;
+}
+
+static int hostess_ioctl(struct net_device *d, struct ifreq *ifr, int cmd)
+{
+	/* struct sv11_device *sv11=d->priv;
+	   z8530_ioctl(d,&sv11->sync.chanA,ifr,cmd) */
+	return sppp_do_ioctl(d, ifr,cmd);
+}
+
+static struct net_device_stats *hostess_get_stats(struct net_device *d)
+{
+	struct sv11_device *sv11=d->priv;
+	if(sv11)
+		return z8530_get_stats(&sv11->sync.chanA);
+	else
+		return NULL;
+}
+
+/*
+ *	Passed PPP frames, fire them downwind.
+ */
+ 
+static int hostess_queue_xmit(struct sk_buff *skb, struct net_device *d)
+{
+	struct sv11_device *sv11=d->priv;
+	return z8530_queue_xmit(&sv11->sync.chanA, skb);
+}
+
+static int hostess_neigh_setup(struct neighbour *n)
+{
+	if (n->nud_state == NUD_NONE) {
+		n->ops = &arp_broken_ops;
+		n->output = n->ops->output;
+	}
+	return 0;
+}
+
+static int hostess_neigh_setup_dev(struct net_device *dev, struct neigh_parms *p)
+{
+	if (p->tbl->family == AF_INET) {
+		p->neigh_setup = hostess_neigh_setup;
+		p->ucast_probes = 0;
+		p->mcast_probes = 0;
+	}
+	return 0;
+}
+
+static void sv11_setup(struct net_device *dev)
+{	
+	dev->open = hostess_open;
+	dev->stop = hostess_close;
+	dev->hard_start_xmit = hostess_queue_xmit;
+	dev->get_stats = hostess_get_stats;
+	dev->do_ioctl = hostess_ioctl;
+	dev->neigh_setup = hostess_neigh_setup_dev;
+}
+
+/*
+ *	Description block for a Comtrol Hostess SV11 card
+ */
+ 
+static struct sv11_device *sv11_init(int iobase, int irq)
+{
+	struct z8530_dev *dev;
+	struct sv11_device *sv;
+	
+	/*
+	 *	Get the needed I/O space
+	 */
+	 
+	if(!request_region(iobase, 8, "Comtrol SV11"))
+	{	
+		printk(KERN_WARNING "hostess: I/O 0x%X already in use.\n", iobase);
+		return NULL;
+	}
+	
+	sv=(struct sv11_device *)kmalloc(sizeof(struct sv11_device), GFP_KERNEL);
+	if(!sv)
+		goto fail3;
+			
+	memset(sv, 0, sizeof(*sv));
+	sv->if_ptr=&sv->netdev;
+	
+	sv->netdev.dev = alloc_netdev(0, "hdlc%d", sv11_setup);
+	if(!sv->netdev.dev)
+		goto fail2;
+
+	SET_MODULE_OWNER(sv->netdev.dev);
+
+	dev=&sv->sync;
+	
+	/*
+	 *	Stuff in the I/O addressing
+	 */
+	 
+	dev->active = 0;
+	
+	dev->chanA.ctrlio=iobase+1;
+	dev->chanA.dataio=iobase+3;
+	dev->chanB.ctrlio=-1;
+	dev->chanB.dataio=-1;
+	dev->chanA.irqs=&z8530_nop;
+	dev->chanB.irqs=&z8530_nop;
+	
+	outb(0, iobase+4);		/* DMA off */
+	
+	/* We want a fast IRQ for this device. Actually we'd like an even faster
+	   IRQ ;) - This is one driver RtLinux is made for */
+	   
+	if(request_irq(irq, &z8530_interrupt, SA_INTERRUPT, "Hostess SV11", dev)<0)
+	{
+		printk(KERN_WARNING "hostess: IRQ %d already in use.\n", irq);
+		goto fail1;
+	}
+	
+	dev->irq=irq;
+	dev->chanA.private=sv;
+	dev->chanA.netdevice=sv->netdev.dev;
+	dev->chanA.dev=dev;
+	dev->chanB.dev=dev;
+	
+	if(dma)
+	{
+		/*
+		 *	You can have DMA off or 1 and 3 thats the lot
+		 *	on the Comtrol.
+		 */
+		dev->chanA.txdma=3;
+		dev->chanA.rxdma=1;
+		outb(0x03|0x08, iobase+4);		/* DMA on */
+		if(request_dma(dev->chanA.txdma, "Hostess SV/11 (TX)")!=0)
+			goto fail;
+			
+		if(dma==1)
+		{
+			if(request_dma(dev->chanA.rxdma, "Hostess SV/11 (RX)")!=0)
+				goto dmafail;
+		}
+	}
+
+	/* Kill our private IRQ line the hostess can end up chattering
+	   until the configuration is set */
+	disable_irq(irq);
+		
+	/*
+	 *	Begin normal initialise
+	 */
+	 
+	if(z8530_init(dev)!=0)
+	{
+		printk(KERN_ERR "Z8530 series device not found.\n");
+		enable_irq(irq);
+		goto dmafail2;
+	}
+	z8530_channel_load(&dev->chanB, z8530_dead_port);
+	if(dev->type==Z85C30)
+		z8530_channel_load(&dev->chanA, z8530_hdlc_kilostream);
+	else
+		z8530_channel_load(&dev->chanA, z8530_hdlc_kilostream_85230);
+	
+	enable_irq(irq);
+	
+
+	/*
+	 *	Now we can take the IRQ
+	 */
+	if(dev_alloc_name(dev->chanA.netdevice,"hdlc%d")>=0)
+	{
+		struct net_device *d=dev->chanA.netdevice;
+
+		/* 
+		 *	Initialise the PPP components
+		 */
+		sppp_attach(&sv->netdev);
+		
+		/*
+		 *	Local fields
+		 */	
+		
+		d->base_addr = iobase;
+		d->irq = irq;
+		d->priv = sv;
+		
+		if(register_netdev(d))
+		{
+			printk(KERN_ERR "%s: unable to register device.\n",
+				d->name);
+			sppp_detach(d);
+			goto dmafail2;
+		}
+
+		z8530_describe(dev, "I/O", iobase);
+		dev->active=1;
+		return sv;	
+	}
+dmafail2:
+	if(dma==1)
+		free_dma(dev->chanA.rxdma);
+dmafail:
+	if(dma)
+		free_dma(dev->chanA.txdma);
+fail:
+	free_irq(irq, dev);
+fail1:
+	free_netdev(sv->netdev.dev);
+fail2:
+	kfree(sv);
+fail3:
+	release_region(iobase,8);
+	return NULL;
+}
+
+static void sv11_shutdown(struct sv11_device *dev)
+{
+	sppp_detach(dev->netdev.dev);
+	unregister_netdev(dev->netdev.dev);
+	z8530_shutdown(&dev->sync);
+	free_irq(dev->sync.irq, dev);
+	if(dma)
+	{
+		if(dma==1)
+			free_dma(dev->sync.chanA.rxdma);
+		free_dma(dev->sync.chanA.txdma);
+	}
+	release_region(dev->sync.chanA.ctrlio-1, 8);
+	free_netdev(dev->netdev.dev);
+	kfree(dev);
+}
+
+#ifdef MODULE
+
+static int io=0x200;
+static int irq=9;
+
+module_param(io, int, 0);
+MODULE_PARM_DESC(io, "The I/O base of the Comtrol Hostess SV11 card");
+module_param(dma, int, 0);
+MODULE_PARM_DESC(dma, "Set this to 1 to use DMA1/DMA3 for TX/RX");
+module_param(irq, int, 0);
+MODULE_PARM_DESC(irq, "The interrupt line setting for the Comtrol Hostess SV11 card");
+
+MODULE_AUTHOR("Alan Cox");
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("Modular driver for the Comtrol Hostess SV11");
+
+static struct sv11_device *sv11_unit;
+
+int init_module(void)
+{
+	printk(KERN_INFO "SV-11 Z85230 Synchronous Driver v 0.03.\n");
+	printk(KERN_INFO "(c) Copyright 2001, Red Hat Inc.\n");	
+	if((sv11_unit=sv11_init(io,irq))==NULL)
+		return -ENODEV;
+	return 0;
+}
+
+void cleanup_module(void)
+{
+	if(sv11_unit)
+		sv11_shutdown(sv11_unit);
+}
+
+#endif
+
diff --git a/drivers/net/wan/lapbether.c b/drivers/net/wan/lapbether.c
new file mode 100644
index 0000000..7f2e365
--- /dev/null
+++ b/drivers/net/wan/lapbether.c
@@ -0,0 +1,465 @@
+/*
+ *	"LAPB via ethernet" driver release 001
+ *
+ *	This code REQUIRES 2.1.15 or higher/ NET3.038
+ *
+ *	This module:
+ *		This module 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 is a "pseudo" network driver to allow LAPB over Ethernet.
+ *
+ *	This driver can use any ethernet destination address, and can be 
+ *	limited to accept frames from one dedicated ethernet card only.
+ *
+ *	History
+ *	LAPBETH 001	Jonathan Naylor		Cloned from bpqether.c
+ *	2000-10-29	Henner Eisen	lapb_data_indication() return status.
+ *	2000-11-14	Henner Eisen	dev_hold/put, NETDEV_GOING_DOWN support
+ */
+
+#include <linux/errno.h>
+#include <linux/types.h>
+#include <linux/socket.h>
+#include <linux/in.h>
+#include <linux/kernel.h>
+#include <linux/string.h>
+#include <linux/net.h>
+#include <linux/inet.h>
+#include <linux/netdevice.h>
+#include <linux/if_arp.h>
+#include <linux/skbuff.h>
+#include <net/sock.h>
+#include <asm/system.h>
+#include <asm/uaccess.h>
+#include <linux/mm.h>
+#include <linux/interrupt.h>
+#include <linux/notifier.h>
+#include <linux/stat.h>
+#include <linux/netfilter.h>
+#include <linux/module.h>
+#include <linux/lapb.h>
+#include <linux/init.h>
+
+#include <net/x25device.h>
+
+static char bcast_addr[6] = { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF };
+
+/* If this number is made larger, check that the temporary string buffer
+ * in lapbeth_new_device is large enough to store the probe device name.*/
+#define MAXLAPBDEV 100
+
+struct lapbethdev {
+	struct list_head	node;
+	struct net_device	*ethdev;	/* link to ethernet device */
+	struct net_device	*axdev;		/* lapbeth device (lapb#) */
+	struct net_device_stats stats;		/* some statistics */
+};
+
+static struct list_head lapbeth_devices = LIST_HEAD_INIT(lapbeth_devices);
+
+/* ------------------------------------------------------------------------ */
+
+/*
+ *	Get the LAPB device for the ethernet device
+ */
+static struct lapbethdev *lapbeth_get_x25_dev(struct net_device *dev)
+{
+	struct lapbethdev *lapbeth;
+
+	list_for_each_entry_rcu(lapbeth, &lapbeth_devices, node) {
+		if (lapbeth->ethdev == dev) 
+			return lapbeth;
+	}
+	return NULL;
+}
+
+static __inline__ int dev_is_ethdev(struct net_device *dev)
+{
+	return dev->type == ARPHRD_ETHER && strncmp(dev->name, "dummy", 5);
+}
+
+/* ------------------------------------------------------------------------ */
+
+/*
+ *	Receive a LAPB frame via an ethernet interface.
+ */
+static int lapbeth_rcv(struct sk_buff *skb, struct net_device *dev, struct packet_type *ptype)
+{
+	int len, err;
+	struct lapbethdev *lapbeth;
+
+	if ((skb = skb_share_check(skb, GFP_ATOMIC)) == NULL)
+		return NET_RX_DROP;
+
+	if (!pskb_may_pull(skb, 2))
+		goto drop;
+
+	rcu_read_lock();
+	lapbeth = lapbeth_get_x25_dev(dev);
+	if (!lapbeth)
+		goto drop_unlock;
+	if (!netif_running(lapbeth->axdev))
+		goto drop_unlock;
+
+	lapbeth->stats.rx_packets++;
+
+	len = skb->data[0] + skb->data[1] * 256;
+	lapbeth->stats.rx_bytes += len;
+
+	skb_pull(skb, 2);	/* Remove the length bytes */
+	skb_trim(skb, len);	/* Set the length of the data */
+
+	if ((err = lapb_data_received(lapbeth->axdev, skb)) != LAPB_OK) {
+		printk(KERN_DEBUG "lapbether: lapb_data_received err - %d\n", err);
+		goto drop_unlock;
+	}
+out:
+	rcu_read_unlock();
+	return 0;
+drop_unlock:
+	kfree_skb(skb);
+	goto out;
+drop:
+	kfree_skb(skb);
+	return 0;
+}
+
+static int lapbeth_data_indication(struct net_device *dev, struct sk_buff *skb)
+{
+	unsigned char *ptr;
+
+	skb_push(skb, 1);
+
+	if (skb_cow(skb, 1))
+		return NET_RX_DROP;
+
+	ptr  = skb->data;
+	*ptr = 0x00;
+
+	skb->protocol = x25_type_trans(skb, dev);
+	skb->dev->last_rx = jiffies;
+	return netif_rx(skb);
+}
+
+/*
+ *	Send a LAPB frame via an ethernet interface
+ */
+static int lapbeth_xmit(struct sk_buff *skb, struct net_device *dev)
+{
+	int err = -ENODEV;
+
+	/*
+	 * Just to be *really* sure not to send anything if the interface
+	 * is down, the ethernet device may have gone.
+	 */
+	if (!netif_running(dev)) {
+		goto drop;
+	}
+
+	switch (skb->data[0]) {
+	case 0x00:
+		err = 0;
+		break;
+	case 0x01:
+		if ((err = lapb_connect_request(dev)) != LAPB_OK)
+			printk(KERN_ERR "lapbeth: lapb_connect_request "
+			       "error: %d\n", err);
+		goto drop_ok;
+	case 0x02:
+		if ((err = lapb_disconnect_request(dev)) != LAPB_OK)
+			printk(KERN_ERR "lapbeth: lapb_disconnect_request "
+			       "err: %d\n", err);
+		/* Fall thru */
+	default:
+		goto drop_ok;
+	}
+
+	skb_pull(skb, 1);
+
+	if ((err = lapb_data_request(dev, skb)) != LAPB_OK) {
+		printk(KERN_ERR "lapbeth: lapb_data_request error - %d\n", err);
+		err = -ENOMEM;
+		goto drop;
+	}
+	err = 0;
+out:
+	return err;
+drop_ok:
+	err = 0;
+drop:
+	kfree_skb(skb);
+	goto out;
+}
+
+static void lapbeth_data_transmit(struct net_device *ndev, struct sk_buff *skb)
+{
+	struct lapbethdev *lapbeth = netdev_priv(ndev);
+	unsigned char *ptr;
+	struct net_device *dev;
+	int size = skb->len;
+
+	skb->protocol = htons(ETH_P_X25);
+
+	ptr = skb_push(skb, 2);
+
+	*ptr++ = size % 256;
+	*ptr++ = size / 256;
+
+	lapbeth->stats.tx_packets++;
+	lapbeth->stats.tx_bytes += size;
+
+	skb->dev = dev = lapbeth->ethdev;
+
+	dev->hard_header(skb, dev, ETH_P_DEC, bcast_addr, NULL, 0);
+
+	dev_queue_xmit(skb);
+}
+
+static void lapbeth_connected(struct net_device *dev, int reason)
+{
+	unsigned char *ptr;
+	struct sk_buff *skb = dev_alloc_skb(1);
+
+	if (!skb) {
+		printk(KERN_ERR "lapbeth: out of memory\n");
+		return;
+	}
+
+	ptr  = skb_put(skb, 1);
+	*ptr = 0x01;
+
+	skb->protocol = x25_type_trans(skb, dev);
+	skb->dev->last_rx = jiffies;
+	netif_rx(skb);
+}
+
+static void lapbeth_disconnected(struct net_device *dev, int reason)
+{
+	unsigned char *ptr;
+	struct sk_buff *skb = dev_alloc_skb(1);
+
+	if (!skb) {
+		printk(KERN_ERR "lapbeth: out of memory\n");
+		return;
+	}
+
+	ptr  = skb_put(skb, 1);
+	*ptr = 0x02;
+
+	skb->protocol = x25_type_trans(skb, dev);
+	skb->dev->last_rx = jiffies;
+	netif_rx(skb);
+}
+
+/*
+ *	Statistics
+ */
+static struct net_device_stats *lapbeth_get_stats(struct net_device *dev)
+{
+	struct lapbethdev *lapbeth = netdev_priv(dev);
+	return &lapbeth->stats;
+}
+
+/*
+ *	Set AX.25 callsign
+ */
+static int lapbeth_set_mac_address(struct net_device *dev, void *addr)
+{
+	struct sockaddr *sa = addr;
+	memcpy(dev->dev_addr, sa->sa_data, dev->addr_len);
+	return 0;
+}
+
+
+static struct lapb_register_struct lapbeth_callbacks = {
+	.connect_confirmation    = lapbeth_connected,
+	.connect_indication      = lapbeth_connected,
+	.disconnect_confirmation = lapbeth_disconnected,
+	.disconnect_indication   = lapbeth_disconnected,
+	.data_indication         = lapbeth_data_indication,
+	.data_transmit           = lapbeth_data_transmit,
+
+};
+
+/*
+ * open/close a device
+ */
+static int lapbeth_open(struct net_device *dev)
+{
+	int err;
+
+	if ((err = lapb_register(dev, &lapbeth_callbacks)) != LAPB_OK) {
+		printk(KERN_ERR "lapbeth: lapb_register error - %d\n", err);
+		return -ENODEV;
+	}
+
+	netif_start_queue(dev);
+	return 0;
+}
+
+static int lapbeth_close(struct net_device *dev)
+{
+	int err;
+
+	netif_stop_queue(dev);
+
+	if ((err = lapb_unregister(dev)) != LAPB_OK)
+		printk(KERN_ERR "lapbeth: lapb_unregister error - %d\n", err);
+
+	return 0;
+}
+
+/* ------------------------------------------------------------------------ */
+
+static void lapbeth_setup(struct net_device *dev)
+{
+	dev->hard_start_xmit = lapbeth_xmit;
+	dev->open	     = lapbeth_open;
+	dev->stop	     = lapbeth_close;
+	dev->destructor	     = free_netdev;
+	dev->set_mac_address = lapbeth_set_mac_address;
+	dev->get_stats	     = lapbeth_get_stats;
+	dev->type            = ARPHRD_X25;
+	dev->hard_header_len = 3;
+	dev->mtu             = 1000;
+	dev->addr_len        = 0;
+	SET_MODULE_OWNER(dev);
+}
+
+/*
+ *	Setup a new device.
+ */
+static int lapbeth_new_device(struct net_device *dev)
+{
+	struct net_device *ndev;
+	struct lapbethdev *lapbeth;
+	int rc = -ENOMEM;
+
+	ASSERT_RTNL();
+
+	ndev = alloc_netdev(sizeof(*lapbeth), "lapb%d", 
+			   lapbeth_setup);
+	if (!ndev)
+		goto out;
+
+	lapbeth = netdev_priv(ndev);
+	lapbeth->axdev = ndev;
+
+	dev_hold(dev);
+	lapbeth->ethdev = dev;
+
+	rc = dev_alloc_name(ndev, ndev->name);
+	if (rc < 0) 
+		goto fail;
+
+	rc = -EIO;
+	if (register_netdevice(ndev))
+		goto fail;
+
+	list_add_rcu(&lapbeth->node, &lapbeth_devices);
+	rc = 0;
+out:
+	return rc;
+fail:
+	dev_put(dev);
+	free_netdev(ndev);
+	kfree(lapbeth);
+	goto out;
+}
+
+/*
+ *	Free a lapb network device.
+ */
+static void lapbeth_free_device(struct lapbethdev *lapbeth)
+{
+	dev_put(lapbeth->ethdev);
+	list_del_rcu(&lapbeth->node);
+	unregister_netdevice(lapbeth->axdev);
+}
+
+/*
+ *	Handle device status changes.
+ *
+ * Called from notifier with RTNL held.
+ */
+static int lapbeth_device_event(struct notifier_block *this,
+				unsigned long event, void *ptr)
+{
+	struct lapbethdev *lapbeth;
+	struct net_device *dev = ptr;
+
+	if (!dev_is_ethdev(dev))
+		return NOTIFY_DONE;
+
+	switch (event) {
+	case NETDEV_UP:
+		/* New ethernet device -> new LAPB interface	 */
+		if (lapbeth_get_x25_dev(dev) == NULL)
+			lapbeth_new_device(dev);
+		break;
+	case NETDEV_DOWN:	
+		/* ethernet device closed -> close LAPB interface */
+		lapbeth = lapbeth_get_x25_dev(dev);
+		if (lapbeth) 
+			dev_close(lapbeth->axdev);
+		break;
+	case NETDEV_UNREGISTER:
+		/* ethernet device disappears -> remove LAPB interface */
+		lapbeth = lapbeth_get_x25_dev(dev);
+		if (lapbeth)
+			lapbeth_free_device(lapbeth);
+		break;
+	}
+
+	return NOTIFY_DONE;
+}
+
+/* ------------------------------------------------------------------------ */
+
+static struct packet_type lapbeth_packet_type = {
+	.type = __constant_htons(ETH_P_DEC),
+	.func = lapbeth_rcv,
+};
+
+static struct notifier_block lapbeth_dev_notifier = {
+	.notifier_call = lapbeth_device_event,
+};
+
+static char banner[] __initdata = KERN_INFO "LAPB Ethernet driver version 0.02\n";
+
+static int __init lapbeth_init_driver(void)
+{
+	dev_add_pack(&lapbeth_packet_type);
+
+	register_netdevice_notifier(&lapbeth_dev_notifier);
+
+	printk(banner);
+
+	return 0;
+}
+module_init(lapbeth_init_driver);
+
+static void __exit lapbeth_cleanup_driver(void)
+{
+	struct lapbethdev *lapbeth;
+	struct list_head *entry, *tmp;
+
+	dev_remove_pack(&lapbeth_packet_type);
+	unregister_netdevice_notifier(&lapbeth_dev_notifier);
+
+	rtnl_lock();
+	list_for_each_safe(entry, tmp, &lapbeth_devices) {
+		lapbeth = list_entry(entry, struct lapbethdev, node);
+
+		unregister_netdevice(lapbeth->axdev);
+	}
+	rtnl_unlock();
+}
+module_exit(lapbeth_cleanup_driver);
+
+MODULE_AUTHOR("Jonathan Naylor <g4klx@g4klx.demon.co.uk>");
+MODULE_DESCRIPTION("The unofficial LAPB over Ethernet driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/net/wan/lmc/Makefile b/drivers/net/wan/lmc/Makefile
new file mode 100644
index 0000000..dabdcfe
--- /dev/null
+++ b/drivers/net/wan/lmc/Makefile
@@ -0,0 +1,17 @@
+#
+# Makefile for the Lan Media 21140 based WAN cards
+# Specifically the 1000,1200,5200,5245
+#
+
+obj-$(CONFIG_LANMEDIA) += lmc.o
+
+lmc-objs := lmc_debug.o lmc_media.o lmc_main.o lmc_proto.o
+
+# Like above except every packet gets echoed to KERN_DEBUG
+# in hex
+#
+# DBDEF = \
+# -DDEBUG \
+# -DLMC_PACKET_LOG
+
+EXTRA_CFLAGS += -I. $(DBGDEF)
diff --git a/drivers/net/wan/lmc/lmc.h b/drivers/net/wan/lmc/lmc.h
new file mode 100644
index 0000000..882e58c
--- /dev/null
+++ b/drivers/net/wan/lmc/lmc.h
@@ -0,0 +1,33 @@
+#ifndef _LMC_H_
+#define _LMC_H_
+
+#include "lmc_var.h"
+
+/*
+ * prototypes for everyone
+ */
+int lmc_probe(struct net_device * dev);
+unsigned lmc_mii_readreg(lmc_softc_t * const sc, unsigned
+      			  devaddr, unsigned regno);
+void lmc_mii_writereg(lmc_softc_t * const sc, unsigned devaddr,
+			       unsigned regno, unsigned data);
+void lmc_led_on(lmc_softc_t * const, u_int32_t);
+void lmc_led_off(lmc_softc_t * const, u_int32_t);
+unsigned lmc_mii_readreg(lmc_softc_t * const, unsigned, unsigned);
+void lmc_mii_writereg(lmc_softc_t * const, unsigned, unsigned, unsigned);
+void lmc_gpio_mkinput(lmc_softc_t * const sc, u_int32_t bits);
+void lmc_gpio_mkoutput(lmc_softc_t * const sc, u_int32_t bits);
+
+int lmc_ioctl(struct net_device *dev, struct ifreq *ifr, int cmd);
+
+extern lmc_media_t lmc_ds3_media;
+extern lmc_media_t lmc_ssi_media;
+extern lmc_media_t lmc_t1_media;
+extern lmc_media_t lmc_hssi_media;
+
+#ifdef _DBG_EVENTLOG
+static void lmcEventLog( u_int32_t EventNum, u_int32_t arg2, u_int32_t arg3 );
+#endif
+
+#endif
+
diff --git a/drivers/net/wan/lmc/lmc_debug.c b/drivers/net/wan/lmc/lmc_debug.c
new file mode 100644
index 0000000..9dccd95
--- /dev/null
+++ b/drivers/net/wan/lmc/lmc_debug.c
@@ -0,0 +1,85 @@
+
+#include <linux/types.h>
+#include <linux/netdevice.h>
+#include <linux/interrupt.h>
+
+#include "lmc_debug.h"
+
+/*
+ * Prints out len, max to 80 octets using printk, 20 per line
+ */
+void lmcConsoleLog(char *type, unsigned char *ucData, int iLen)
+{
+#ifdef DEBUG
+#ifdef LMC_PACKET_LOG
+  int iNewLine = 1;
+  char str[80], *pstr;
+  
+  sprintf(str, KERN_DEBUG "lmc: %s: ", type);
+  pstr = str+strlen(str);
+  
+  if(iLen > 240){
+      printk(KERN_DEBUG "lmc: Printing 240 chars... out of: %d\n", iLen);
+    iLen = 240;
+  }
+  else{
+      printk(KERN_DEBUG "lmc: Printing %d chars\n", iLen);
+  }
+
+  while(iLen > 0) 
+    {
+      sprintf(pstr, "%02x ", *ucData);
+      pstr+=3;
+      ucData++;
+      if( !(iNewLine % 20))
+	{
+	  sprintf(pstr, "\n");
+	  printk(str);
+	  sprintf(str, KERN_DEBUG "lmc: %s: ", type);
+	  pstr=str+strlen(str);
+	}
+      iNewLine++;
+      iLen--;
+    }
+  sprintf(pstr, "\n");
+  printk(str);
+#endif
+#endif
+}
+
+#ifdef DEBUG
+u_int32_t lmcEventLogIndex = 0;
+u_int32_t lmcEventLogBuf[LMC_EVENTLOGSIZE * LMC_EVENTLOGARGS];
+#endif
+
+void lmcEventLog (u_int32_t EventNum, u_int32_t arg2, u_int32_t arg3)
+{
+#ifdef DEBUG
+  lmcEventLogBuf[lmcEventLogIndex++] = EventNum;
+  lmcEventLogBuf[lmcEventLogIndex++] = arg2;
+  lmcEventLogBuf[lmcEventLogIndex++] = arg3;
+  lmcEventLogBuf[lmcEventLogIndex++] = jiffies;
+
+  lmcEventLogIndex &= (LMC_EVENTLOGSIZE * LMC_EVENTLOGARGS) - 1;
+#endif
+}
+
+void lmc_trace(struct net_device *dev, char *msg){
+#ifdef LMC_TRACE
+    unsigned long j = jiffies + 3; /* Wait for 50 ms */
+
+    if(in_interrupt()){
+        printk("%s: * %s\n", dev->name, msg);
+//        while(time_before(jiffies, j+10))
+//            ;
+    }
+    else {
+        printk("%s: %s\n", dev->name, msg);
+        while(time_before(jiffies, j))
+            schedule();
+    }
+#endif
+}
+
+
+/* --------------------------- end if_lmc_linux.c ------------------------ */
diff --git a/drivers/net/wan/lmc/lmc_debug.h b/drivers/net/wan/lmc/lmc_debug.h
new file mode 100644
index 0000000..cf35638
--- /dev/null
+++ b/drivers/net/wan/lmc/lmc_debug.h
@@ -0,0 +1,52 @@
+#ifndef _LMC_DEBUG_H_
+#define _LMC_DEBUG_H_
+
+#ifdef DEBUG
+#ifdef LMC_PACKET_LOG
+#define LMC_CONSOLE_LOG(x,y,z) lmcConsoleLog((x), (y), (z))
+#else
+#define LMC_CONSOLE_LOG(x,y,z)
+#endif
+#else
+#define LMC_CONSOLE_LOG(x,y,z)
+#endif
+
+
+
+/* Debug --- Event log definitions --- */
+/* EVENTLOGSIZE*EVENTLOGARGS needs to be a power of 2 */
+#define LMC_EVENTLOGSIZE 1024	/* number of events in eventlog */
+#define LMC_EVENTLOGARGS 4		/* number of args for each event */
+
+/* event indicators */
+#define LMC_EVENT_XMT           1
+#define LMC_EVENT_XMTEND        2
+#define LMC_EVENT_XMTINT        3
+#define LMC_EVENT_RCVINT        4
+#define LMC_EVENT_RCVEND        5
+#define LMC_EVENT_INT           6
+#define LMC_EVENT_XMTINTTMO     7
+#define LMC_EVENT_XMTPRCTMO     8
+#define LMC_EVENT_INTEND        9
+#define LMC_EVENT_RESET1       10
+#define LMC_EVENT_RESET2       11
+#define LMC_EVENT_FORCEDRESET  12
+#define LMC_EVENT_WATCHDOG     13
+#define LMC_EVENT_BADPKTSURGE  14
+#define LMC_EVENT_TBUSY0       15
+#define LMC_EVENT_TBUSY1       16
+
+
+#ifdef DEBUG
+extern u_int32_t lmcEventLogIndex;
+extern u_int32_t lmcEventLogBuf[LMC_EVENTLOGSIZE * LMC_EVENTLOGARGS];
+#define LMC_EVENT_LOG(x, y, z) lmcEventLog((x), (y), (z))
+#else
+#define LMC_EVENT_LOG(x,y,z)
+#endif /* end ifdef _DBG_EVENTLOG */
+
+void lmcConsoleLog(char *type, unsigned char *ucData, int iLen);
+void lmcEventLog (u_int32_t EventNum, u_int32_t arg2, u_int32_t arg3);
+void lmc_trace(struct net_device *dev, char *msg);
+
+#endif
diff --git a/drivers/net/wan/lmc/lmc_ioctl.h b/drivers/net/wan/lmc/lmc_ioctl.h
new file mode 100644
index 0000000..57dd861
--- /dev/null
+++ b/drivers/net/wan/lmc/lmc_ioctl.h
@@ -0,0 +1,257 @@
+#ifndef _LMC_IOCTL_H_
+#define _LMC_IOCTL_H_
+/*	$Id: lmc_ioctl.h,v 1.15 2000/04/06 12:16:43 asj Exp $	*/
+
+ /*
+  * Copyright (c) 1997-2000 LAN Media Corporation (LMC)
+  * All rights reserved.  www.lanmedia.com
+  *
+  * This code is written by:
+  * Andrew Stanley-Jones (asj@cban.com)
+  * Rob Braun (bbraun@vix.com),
+  * Michael Graff (explorer@vix.com) and
+  * Matt Thomas (matt@3am-software.com).
+  *
+  * This software may be used and distributed according to the terms
+  * of the GNU General Public License version 2, incorporated herein by reference.
+  */
+
+#define LMCIOCGINFO             SIOCDEVPRIVATE+3 /* get current state */
+#define LMCIOCSINFO             SIOCDEVPRIVATE+4 /* set state to user values */
+#define LMCIOCGETLMCSTATS       SIOCDEVPRIVATE+5
+#define LMCIOCCLEARLMCSTATS     SIOCDEVPRIVATE+6
+#define LMCIOCDUMPEVENTLOG      SIOCDEVPRIVATE+7
+#define LMCIOCGETXINFO          SIOCDEVPRIVATE+8
+#define LMCIOCSETCIRCUIT        SIOCDEVPRIVATE+9
+#define LMCIOCUNUSEDATM         SIOCDEVPRIVATE+10
+#define LMCIOCRESET             SIOCDEVPRIVATE+11
+#define LMCIOCT1CONTROL         SIOCDEVPRIVATE+12
+#define LMCIOCIFTYPE            SIOCDEVPRIVATE+13
+#define LMCIOCXILINX            SIOCDEVPRIVATE+14
+
+#define LMC_CARDTYPE_UNKNOWN            -1
+#define LMC_CARDTYPE_HSSI               1       /* probed card is a HSSI card */
+#define LMC_CARDTYPE_DS3                2       /* probed card is a DS3 card */
+#define LMC_CARDTYPE_SSI                3       /* probed card is a SSI card */
+#define LMC_CARDTYPE_T1                 4       /* probed card is a T1 card */
+
+#define LMC_CTL_CARDTYPE_LMC5200	0	/* HSSI */
+#define LMC_CTL_CARDTYPE_LMC5245	1	/* DS3 */
+#define LMC_CTL_CARDTYPE_LMC1000	2	/* SSI, V.35 */
+#define LMC_CTL_CARDTYPE_LMC1200        3       /* DS1 */
+
+#define LMC_CTL_OFF			0	/* generic OFF value */
+#define LMC_CTL_ON			1	/* generic ON value */
+
+#define LMC_CTL_CLOCK_SOURCE_EXT	0	/* clock off line */
+#define LMC_CTL_CLOCK_SOURCE_INT	1	/* internal clock */
+
+#define LMC_CTL_CRC_LENGTH_16		16
+#define LMC_CTL_CRC_LENGTH_32		32
+#define LMC_CTL_CRC_BYTESIZE_2          2
+#define LMC_CTL_CRC_BYTESIZE_4          4
+
+
+#define LMC_CTL_CABLE_LENGTH_LT_100FT	0	/* DS3 cable < 100 feet */
+#define LMC_CTL_CABLE_LENGTH_GT_100FT	1	/* DS3 cable >= 100 feet */
+
+#define LMC_CTL_CIRCUIT_TYPE_E1 0
+#define LMC_CTL_CIRCUIT_TYPE_T1 1
+
+/*
+ * IFTYPE defines
+ */
+#define LMC_PPP         1               /* use sppp interface */
+#define LMC_NET         2               /* use direct net interface */
+#define LMC_RAW         3               /* use direct net interface */
+
+/*
+ * These are not in the least IOCTL related, but I want them common.
+ */
+/*
+ * assignments for the GPIO register on the DEC chip (common)
+ */
+#define LMC_GEP_INIT		0x01 /* 0: */
+#define LMC_GEP_RESET		0x02 /* 1: */
+#define LMC_GEP_MODE		0x10 /* 4: */
+#define LMC_GEP_DP		0x20 /* 5: */
+#define LMC_GEP_DATA		0x40 /* 6: serial out */
+#define LMC_GEP_CLK	        0x80 /* 7: serial clock */
+
+/*
+ * HSSI GPIO assignments
+ */
+#define LMC_GEP_HSSI_ST		0x04 /* 2: receive timing sense (deprecated) */
+#define LMC_GEP_HSSI_CLOCK	0x08 /* 3: clock source */
+
+/*
+ * T1 GPIO assignments
+ */
+#define LMC_GEP_SSI_GENERATOR	0x04 /* 2: enable prog freq gen serial i/f */
+#define LMC_GEP_SSI_TXCLOCK	0x08 /* 3: provide clock on TXCLOCK output */
+
+/*
+ * Common MII16 bits
+ */
+#define LMC_MII16_LED0         0x0080
+#define LMC_MII16_LED1         0x0100
+#define LMC_MII16_LED2         0x0200
+#define LMC_MII16_LED3         0x0400  /* Error, and the red one */
+#define LMC_MII16_LED_ALL      0x0780  /* LED bit mask */
+#define LMC_MII16_FIFO_RESET   0x0800
+
+/*
+ * definitions for HSSI
+ */
+#define LMC_MII16_HSSI_TA      0x0001
+#define LMC_MII16_HSSI_CA      0x0002
+#define LMC_MII16_HSSI_LA      0x0004
+#define LMC_MII16_HSSI_LB      0x0008
+#define LMC_MII16_HSSI_LC      0x0010
+#define LMC_MII16_HSSI_TM      0x0020
+#define LMC_MII16_HSSI_CRC     0x0040
+
+/*
+ * assignments for the MII register 16 (DS3)
+ */
+#define LMC_MII16_DS3_ZERO	0x0001
+#define LMC_MII16_DS3_TRLBK	0x0002
+#define LMC_MII16_DS3_LNLBK	0x0004
+#define LMC_MII16_DS3_RAIS	0x0008
+#define LMC_MII16_DS3_TAIS	0x0010
+#define LMC_MII16_DS3_BIST	0x0020
+#define LMC_MII16_DS3_DLOS	0x0040
+#define LMC_MII16_DS3_CRC	0x1000
+#define LMC_MII16_DS3_SCRAM	0x2000
+#define LMC_MII16_DS3_SCRAM_LARS 0x4000
+
+/* Note: 2 pairs of LEDs where swapped by mistake
+ * in Xilinx code for DS3 & DS1 adapters */
+#define LMC_DS3_LED0    0x0100          /* bit 08  yellow */
+#define LMC_DS3_LED1    0x0080          /* bit 07  blue   */
+#define LMC_DS3_LED2    0x0400          /* bit 10  green  */
+#define LMC_DS3_LED3    0x0200          /* bit 09  red    */
+
+/*
+ * framer register 0 and 7 (7 is latched and reset on read)
+ */
+#define LMC_FRAMER_REG0_DLOS            0x80    /* digital loss of service */
+#define LMC_FRAMER_REG0_OOFS            0x40    /* out of frame sync */
+#define LMC_FRAMER_REG0_AIS             0x20    /* alarm indication signal */
+#define LMC_FRAMER_REG0_CIS             0x10    /* channel idle */
+#define LMC_FRAMER_REG0_LOC             0x08    /* loss of clock */
+
+/*
+ * Framer register 9 contains the blue alarm signal
+ */
+#define LMC_FRAMER_REG9_RBLUE          0x02     /* Blue alarm failure */
+
+/*
+ * Framer register 0x10 contains xbit error
+ */
+#define LMC_FRAMER_REG10_XBIT          0x01     /* X bit error alarm failure */
+
+/*
+ * And SSI, LMC1000
+ */
+#define LMC_MII16_SSI_DTR	0x0001	/* DTR output RW */
+#define LMC_MII16_SSI_DSR	0x0002	/* DSR input RO */
+#define LMC_MII16_SSI_RTS	0x0004	/* RTS output RW */
+#define LMC_MII16_SSI_CTS	0x0008	/* CTS input RO */
+#define LMC_MII16_SSI_DCD	0x0010	/* DCD input RO */
+#define LMC_MII16_SSI_RI		0x0020	/* RI input RO */
+#define LMC_MII16_SSI_CRC                0x1000  /* CRC select - RW */
+
+/*
+ * bits 0x0080 through 0x0800 are generic, and described
+ * above with LMC_MII16_LED[0123] _LED_ALL, and _FIFO_RESET
+ */
+#define LMC_MII16_SSI_LL		0x1000	/* LL output RW */
+#define LMC_MII16_SSI_RL		0x2000	/* RL output RW */
+#define LMC_MII16_SSI_TM		0x4000	/* TM input RO */
+#define LMC_MII16_SSI_LOOP	0x8000	/* loopback enable RW */
+
+/*
+ * Some of the MII16 bits are mirrored in the MII17 register as well,
+ * but let's keep thing separate for now, and get only the cable from
+ * the MII17.
+ */
+#define LMC_MII17_SSI_CABLE_MASK	0x0038	/* mask to extract the cable type */
+#define LMC_MII17_SSI_CABLE_SHIFT 3	/* shift to extract the cable type */
+
+/*
+ * And T1, LMC1200
+ */
+#define LMC_MII16_T1_UNUSED1    0x0003
+#define LMC_MII16_T1_XOE                0x0004
+#define LMC_MII16_T1_RST                0x0008  /* T1 chip reset - RW */
+#define LMC_MII16_T1_Z                  0x0010  /* output impedance T1=1, E1=0 output - RW */
+#define LMC_MII16_T1_INTR               0x0020  /* interrupt from 8370 - RO */
+#define LMC_MII16_T1_ONESEC             0x0040  /* one second square wave - ro */
+
+#define LMC_MII16_T1_LED0               0x0100
+#define LMC_MII16_T1_LED1               0x0080
+#define LMC_MII16_T1_LED2               0x0400
+#define LMC_MII16_T1_LED3               0x0200
+#define LMC_MII16_T1_FIFO_RESET 0x0800
+
+#define LMC_MII16_T1_CRC                0x1000  /* CRC select - RW */
+#define LMC_MII16_T1_UNUSED2    0xe000
+
+
+/* 8370 framer registers  */
+
+#define T1FRAMER_ALARM1_STATUS  0x47
+#define T1FRAMER_ALARM2_STATUS  0x48
+#define T1FRAMER_FERR_LSB               0x50
+#define T1FRAMER_FERR_MSB               0x51    /* framing bit error counter */
+#define T1FRAMER_LCV_LSB                0x54
+#define T1FRAMER_LCV_MSB                0x55    /* line code violation counter */
+#define T1FRAMER_AERR                   0x5A
+
+/* mask for the above AERR register */
+#define T1FRAMER_LOF_MASK               (0x0f0) /* receive loss of frame */
+#define T1FRAMER_COFA_MASK              (0x0c0) /* change of frame alignment */
+#define T1FRAMER_SEF_MASK               (0x03)  /* severely errored frame  */
+
+/* 8370 framer register ALM1 (0x47) values
+ * used to determine link status
+ */
+
+#define T1F_SIGFRZ      0x01    /* signaling freeze */
+#define T1F_RLOF        0x02    /* receive loss of frame alignment */
+#define T1F_RLOS        0x04    /* receive loss of signal */
+#define T1F_RALOS       0x08    /* receive analog loss of signal or RCKI loss of clock */
+#define T1F_RAIS        0x10    /* receive alarm indication signal */
+#define T1F_UNUSED      0x20
+#define T1F_RYEL        0x40    /* receive yellow alarm */
+#define T1F_RMYEL       0x80    /* receive multiframe yellow alarm */
+
+#define LMC_T1F_WRITE       0
+#define LMC_T1F_READ        1
+
+typedef struct lmc_st1f_control {
+  int command;
+  int address;
+  int value;
+  char __user *data;
+} lmc_t1f_control;
+
+enum lmc_xilinx_c {
+    lmc_xilinx_reset = 1,
+    lmc_xilinx_load_prom = 2,
+    lmc_xilinx_load = 3
+};
+
+struct lmc_xilinx_control {
+    enum lmc_xilinx_c command;
+    int len;
+    char __user *data;
+};
+
+/* ------------------ end T1 defs ------------------- */
+
+#define LMC_MII_LedMask                 0x0780
+#define LMC_MII_LedBitPos               7
+
+#endif
diff --git a/drivers/net/wan/lmc/lmc_main.c b/drivers/net/wan/lmc/lmc_main.c
new file mode 100644
index 0000000..15e545f
--- /dev/null
+++ b/drivers/net/wan/lmc/lmc_main.c
@@ -0,0 +1,2201 @@
+ /*
+  * Copyright (c) 1997-2000 LAN Media Corporation (LMC)
+  * All rights reserved.  www.lanmedia.com
+  *
+  * This code is written by:
+  * Andrew Stanley-Jones (asj@cban.com)
+  * Rob Braun (bbraun@vix.com),
+  * Michael Graff (explorer@vix.com) and
+  * Matt Thomas (matt@3am-software.com).
+  *
+  * With Help By:
+  * David Boggs
+  * Ron Crane
+  * Alan Cox
+  *
+  * This software may be used and distributed according to the terms
+  * of the GNU General Public License version 2, incorporated herein by reference.
+  *
+  * Driver for the LanMedia LMC5200, LMC5245, LMC1000, LMC1200 cards.
+  *
+  * To control link specific options lmcctl is required.
+  * It can be obtained from ftp.lanmedia.com.
+  *
+  * Linux driver notes:
+  * Linux uses the device struct lmc_private to pass private information
+  * arround.
+  *
+  * The initialization portion of this driver (the lmc_reset() and the
+  * lmc_dec_reset() functions, as well as the led controls and the
+  * lmc_initcsrs() functions.
+  *
+  * The watchdog function runs every second and checks to see if
+  * we still have link, and that the timing source is what we expected
+  * it to be.  If link is lost, the interface is marked down, and
+  * we no longer can transmit.
+  *
+  */
+
+/* $Id: lmc_main.c,v 1.36 2000/04/11 05:25:25 asj Exp $ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/string.h>
+#include <linux/timer.h>
+#include <linux/ptrace.h>
+#include <linux/errno.h>
+#include <linux/ioport.h>
+#include <linux/slab.h>
+#include <linux/interrupt.h>
+#include <linux/pci.h>
+#include <linux/delay.h>
+#include <linux/init.h>
+#include <linux/in.h>
+#include <linux/if_arp.h>
+#include <linux/netdevice.h>
+#include <linux/etherdevice.h>
+#include <linux/skbuff.h>
+#include <linux/inet.h>
+#include <linux/bitops.h>
+
+#include <net/syncppp.h>
+
+#include <asm/processor.h>             /* Processor type for cache alignment. */
+#include <asm/io.h>
+#include <asm/dma.h>
+#include <asm/uaccess.h>
+//#include <asm/spinlock.h>
+
+#define DRIVER_MAJOR_VERSION     1
+#define DRIVER_MINOR_VERSION    34
+#define DRIVER_SUB_VERSION       0
+
+#define DRIVER_VERSION  ((DRIVER_MAJOR_VERSION << 8) + DRIVER_MINOR_VERSION)
+
+#include "lmc.h"
+#include "lmc_var.h"
+#include "lmc_ioctl.h"
+#include "lmc_debug.h"
+#include "lmc_proto.h"
+
+static int lmc_first_load = 0;
+
+static int LMC_PKT_BUF_SZ = 1542;
+
+static struct pci_device_id lmc_pci_tbl[] = {
+	{ PCI_VENDOR_ID_DEC, PCI_DEVICE_ID_DEC_TULIP_FAST,
+	  PCI_VENDOR_ID_LMC, PCI_ANY_ID },
+	{ PCI_VENDOR_ID_DEC, PCI_DEVICE_ID_DEC_TULIP_FAST,
+	  PCI_ANY_ID, PCI_VENDOR_ID_LMC },
+	{ 0 }
+};
+
+MODULE_DEVICE_TABLE(pci, lmc_pci_tbl);
+MODULE_LICENSE("GPL");
+
+
+static int lmc_start_xmit(struct sk_buff *skb, struct net_device *dev);
+static int lmc_start_xmit(struct sk_buff *skb, struct net_device *dev);
+static int lmc_rx (struct net_device *dev);
+static int lmc_open(struct net_device *dev);
+static int lmc_close(struct net_device *dev);
+static struct net_device_stats *lmc_get_stats(struct net_device *dev);
+static irqreturn_t lmc_interrupt(int irq, void *dev_instance, struct pt_regs *regs);
+static void lmc_initcsrs(lmc_softc_t * const sc, lmc_csrptr_t csr_base, size_t csr_size);
+static void lmc_softreset(lmc_softc_t * const);
+static void lmc_running_reset(struct net_device *dev);
+static int lmc_ifdown(struct net_device * const);
+static void lmc_watchdog(unsigned long data);
+static void lmc_reset(lmc_softc_t * const sc);
+static void lmc_dec_reset(lmc_softc_t * const sc);
+static void lmc_driver_timeout(struct net_device *dev);
+
+/*
+ * linux reserves 16 device specific IOCTLs.  We call them
+ * LMCIOC* to control various bits of our world.
+ */
+int lmc_ioctl (struct net_device *dev, struct ifreq *ifr, int cmd) /*fold00*/
+{
+    lmc_softc_t *sc;
+    lmc_ctl_t ctl;
+    int ret;
+    u_int16_t regVal;
+    unsigned long flags;
+
+    struct sppp *sp;
+
+    ret = -EOPNOTSUPP;
+
+    sc = dev->priv;
+
+    lmc_trace(dev, "lmc_ioctl in");
+
+    /*
+     * Most functions mess with the structure
+     * Disable interrupts while we do the polling
+     */
+    spin_lock_irqsave(&sc->lmc_lock, flags);
+
+    switch (cmd) {
+        /*
+         * Return current driver state.  Since we keep this up
+         * To date internally, just copy this out to the user.
+         */
+    case LMCIOCGINFO: /*fold01*/
+        if (copy_to_user(ifr->ifr_data, &sc->ictl, sizeof (lmc_ctl_t)))
+            return -EFAULT;
+        ret = 0;
+        break;
+
+    case LMCIOCSINFO: /*fold01*/
+        sp = &((struct ppp_device *) dev)->sppp;
+        if (!capable(CAP_NET_ADMIN)) {
+            ret = -EPERM;
+            break;
+        }
+
+        if(dev->flags & IFF_UP){
+            ret = -EBUSY;
+            break;
+        }
+
+        if (copy_from_user(&ctl, ifr->ifr_data, sizeof (lmc_ctl_t)))
+            return -EFAULT;
+
+        sc->lmc_media->set_status (sc, &ctl);
+
+        if(ctl.crc_length != sc->ictl.crc_length) {
+            sc->lmc_media->set_crc_length(sc, ctl.crc_length);
+	    if (sc->ictl.crc_length == LMC_CTL_CRC_LENGTH_16)
+		sc->TxDescriptControlInit |=  LMC_TDES_ADD_CRC_DISABLE;
+	    else
+		sc->TxDescriptControlInit &= ~LMC_TDES_ADD_CRC_DISABLE;
+        }
+
+        if (ctl.keepalive_onoff == LMC_CTL_OFF)
+            sp->pp_flags &= ~PP_KEEPALIVE;	/* Turn off */
+        else
+            sp->pp_flags |= PP_KEEPALIVE;	/* Turn on */
+
+        ret = 0;
+        break;
+
+    case LMCIOCIFTYPE: /*fold01*/
+        {
+            u_int16_t	old_type = sc->if_type;
+            u_int16_t	new_type;
+
+	    if (!capable(CAP_NET_ADMIN)) {
+		ret = -EPERM;
+		break;
+	    }
+
+	    if (copy_from_user(&new_type, ifr->ifr_data, sizeof(u_int16_t)))
+                return -EFAULT;
+
+            
+	    if (new_type == old_type)
+	    {
+		ret = 0 ;
+		break;				/* no change */
+            }
+            
+            lmc_proto_close(sc);
+            lmc_proto_detach(sc);
+
+            sc->if_type = new_type;
+//            lmc_proto_init(sc);
+            lmc_proto_attach(sc);
+            lmc_proto_open(sc);
+
+	    ret = 0 ;
+	    break ;
+	}
+
+    case LMCIOCGETXINFO: /*fold01*/
+        sc->lmc_xinfo.Magic0 = 0xBEEFCAFE;
+
+        sc->lmc_xinfo.PciCardType = sc->lmc_cardtype;
+        sc->lmc_xinfo.PciSlotNumber = 0;
+        sc->lmc_xinfo.DriverMajorVersion = DRIVER_MAJOR_VERSION;
+        sc->lmc_xinfo.DriverMinorVersion = DRIVER_MINOR_VERSION;
+        sc->lmc_xinfo.DriverSubVersion = DRIVER_SUB_VERSION;
+        sc->lmc_xinfo.XilinxRevisionNumber =
+            lmc_mii_readreg (sc, 0, 3) & 0xf;
+        sc->lmc_xinfo.MaxFrameSize = LMC_PKT_BUF_SZ;
+        sc->lmc_xinfo.link_status = sc->lmc_media->get_link_status (sc);
+        sc->lmc_xinfo.mii_reg16 = lmc_mii_readreg (sc, 0, 16);
+
+        sc->lmc_xinfo.Magic1 = 0xDEADBEEF;
+
+        if (copy_to_user(ifr->ifr_data, &sc->lmc_xinfo,
+                         sizeof (struct lmc_xinfo)))
+            return -EFAULT;
+        ret = 0;
+
+        break;
+
+    case LMCIOCGETLMCSTATS: /*fold01*/
+        if (sc->lmc_cardtype == LMC_CARDTYPE_T1){
+            lmc_mii_writereg (sc, 0, 17, T1FRAMER_FERR_LSB);
+            sc->stats.framingBitErrorCount +=
+                lmc_mii_readreg (sc, 0, 18) & 0xff;
+            lmc_mii_writereg (sc, 0, 17, T1FRAMER_FERR_MSB);
+            sc->stats.framingBitErrorCount +=
+                (lmc_mii_readreg (sc, 0, 18) & 0xff) << 8;
+            lmc_mii_writereg (sc, 0, 17, T1FRAMER_LCV_LSB);
+            sc->stats.lineCodeViolationCount +=
+                lmc_mii_readreg (sc, 0, 18) & 0xff;
+            lmc_mii_writereg (sc, 0, 17, T1FRAMER_LCV_MSB);
+            sc->stats.lineCodeViolationCount +=
+                (lmc_mii_readreg (sc, 0, 18) & 0xff) << 8;
+            lmc_mii_writereg (sc, 0, 17, T1FRAMER_AERR);
+            regVal = lmc_mii_readreg (sc, 0, 18) & 0xff;
+
+            sc->stats.lossOfFrameCount +=
+                (regVal & T1FRAMER_LOF_MASK) >> 4;
+            sc->stats.changeOfFrameAlignmentCount +=
+                (regVal & T1FRAMER_COFA_MASK) >> 2;
+            sc->stats.severelyErroredFrameCount +=
+                regVal & T1FRAMER_SEF_MASK;
+        }
+
+        if (copy_to_user(ifr->ifr_data, &sc->stats,
+                         sizeof (struct lmc_statistics)))
+            return -EFAULT;
+
+        ret = 0;
+        break;
+
+    case LMCIOCCLEARLMCSTATS: /*fold01*/
+        if (!capable(CAP_NET_ADMIN)){
+            ret = -EPERM;
+            break;
+        }
+
+        memset (&sc->stats, 0, sizeof (struct lmc_statistics));
+        sc->stats.check = STATCHECK;
+        sc->stats.version_size = (DRIVER_VERSION << 16) +
+            sizeof (struct lmc_statistics);
+        sc->stats.lmc_cardtype = sc->lmc_cardtype;
+        ret = 0;
+        break;
+
+    case LMCIOCSETCIRCUIT: /*fold01*/
+        if (!capable(CAP_NET_ADMIN)){
+            ret = -EPERM;
+            break;
+        }
+
+        if(dev->flags & IFF_UP){
+            ret = -EBUSY;
+            break;
+        }
+
+        if (copy_from_user(&ctl, ifr->ifr_data, sizeof (lmc_ctl_t)))
+            return -EFAULT;
+        sc->lmc_media->set_circuit_type(sc, ctl.circuit_type);
+        sc->ictl.circuit_type = ctl.circuit_type;
+        ret = 0;
+
+        break;
+
+    case LMCIOCRESET: /*fold01*/
+        if (!capable(CAP_NET_ADMIN)){
+            ret = -EPERM;
+            break;
+        }
+
+        /* Reset driver and bring back to current state */
+        printk (" REG16 before reset +%04x\n", lmc_mii_readreg (sc, 0, 16));
+        lmc_running_reset (dev);
+        printk (" REG16 after reset +%04x\n", lmc_mii_readreg (sc, 0, 16));
+
+        LMC_EVENT_LOG(LMC_EVENT_FORCEDRESET, LMC_CSR_READ (sc, csr_status), lmc_mii_readreg (sc, 0, 16));
+
+        ret = 0;
+        break;
+
+#ifdef DEBUG
+    case LMCIOCDUMPEVENTLOG:
+        if (copy_to_user(ifr->ifr_data, &lmcEventLogIndex, sizeof (u32)))
+            return -EFAULT;
+        if (copy_to_user(ifr->ifr_data + sizeof (u32), lmcEventLogBuf, sizeof (lmcEventLogBuf)))
+            return -EFAULT;
+
+        ret = 0;
+        break;
+#endif /* end ifdef _DBG_EVENTLOG */
+    case LMCIOCT1CONTROL: /*fold01*/
+        if (sc->lmc_cardtype != LMC_CARDTYPE_T1){
+            ret = -EOPNOTSUPP;
+            break;
+        }
+        break;
+    case LMCIOCXILINX: /*fold01*/
+        {
+            struct lmc_xilinx_control xc; /*fold02*/
+
+            if (!capable(CAP_NET_ADMIN)){
+                ret = -EPERM;
+                break;
+            }
+
+            /*
+             * Stop the xwitter whlie we restart the hardware
+             */
+            netif_stop_queue(dev);
+
+            if (copy_from_user(&xc, ifr->ifr_data, sizeof (struct lmc_xilinx_control)))
+                return -EFAULT;
+            switch(xc.command){
+            case lmc_xilinx_reset: /*fold02*/
+                {
+                    u16 mii;
+                    mii = lmc_mii_readreg (sc, 0, 16);
+
+                    /*
+                     * Make all of them 0 and make input
+                     */
+                    lmc_gpio_mkinput(sc, 0xff);
+
+                    /*
+                     * make the reset output
+                     */
+                    lmc_gpio_mkoutput(sc, LMC_GEP_RESET);
+
+                    /*
+                     * RESET low to force configuration.  This also forces
+                     * the transmitter clock to be internal, but we expect to reset
+                     * that later anyway.
+                     */
+
+                    sc->lmc_gpio &= ~LMC_GEP_RESET;
+                    LMC_CSR_WRITE(sc, csr_gp, sc->lmc_gpio);
+
+
+                    /*
+                     * hold for more than 10 microseconds
+                     */
+                    udelay(50);
+
+                    sc->lmc_gpio |= LMC_GEP_RESET;
+                    LMC_CSR_WRITE(sc, csr_gp, sc->lmc_gpio);
+
+
+                    /*
+                     * stop driving Xilinx-related signals
+                     */
+                    lmc_gpio_mkinput(sc, 0xff);
+
+                    /* Reset the frammer hardware */
+                    sc->lmc_media->set_link_status (sc, 1);
+                    sc->lmc_media->set_status (sc, NULL);
+//                    lmc_softreset(sc);
+
+                    {
+                        int i;
+                        for(i = 0; i < 5; i++){
+                            lmc_led_on(sc, LMC_DS3_LED0);
+                            mdelay(100);
+                            lmc_led_off(sc, LMC_DS3_LED0);
+                            lmc_led_on(sc, LMC_DS3_LED1);
+                            mdelay(100);
+                            lmc_led_off(sc, LMC_DS3_LED1);
+                            lmc_led_on(sc, LMC_DS3_LED3);
+                            mdelay(100);
+                            lmc_led_off(sc, LMC_DS3_LED3);
+                            lmc_led_on(sc, LMC_DS3_LED2);
+                            mdelay(100);
+                            lmc_led_off(sc, LMC_DS3_LED2);
+                        }
+                    }
+                    
+                    
+
+                    ret = 0x0;
+
+                }
+
+                break;
+            case lmc_xilinx_load_prom: /*fold02*/
+                {
+                    u16 mii;
+                    int timeout = 500000;
+                    mii = lmc_mii_readreg (sc, 0, 16);
+
+                    /*
+                     * Make all of them 0 and make input
+                     */
+                    lmc_gpio_mkinput(sc, 0xff);
+
+                    /*
+                     * make the reset output
+                     */
+                    lmc_gpio_mkoutput(sc,  LMC_GEP_DP | LMC_GEP_RESET);
+
+                    /*
+                     * RESET low to force configuration.  This also forces
+                     * the transmitter clock to be internal, but we expect to reset
+                     * that later anyway.
+                     */
+
+                    sc->lmc_gpio &= ~(LMC_GEP_RESET | LMC_GEP_DP);
+                    LMC_CSR_WRITE(sc, csr_gp, sc->lmc_gpio);
+
+
+                    /*
+                     * hold for more than 10 microseconds
+                     */
+                    udelay(50);
+
+                    sc->lmc_gpio |= LMC_GEP_DP | LMC_GEP_RESET;
+                    LMC_CSR_WRITE(sc, csr_gp, sc->lmc_gpio);
+
+                    /*
+                     * busy wait for the chip to reset
+                     */
+                    while( (LMC_CSR_READ(sc, csr_gp) & LMC_GEP_INIT) == 0 &&
+                           (timeout-- > 0))
+                        ;
+
+
+                    /*
+                     * stop driving Xilinx-related signals
+                     */
+                    lmc_gpio_mkinput(sc, 0xff);
+
+                    ret = 0x0;
+                    
+
+                    break;
+
+                }
+
+            case lmc_xilinx_load: /*fold02*/
+                {
+                    char *data;
+                    int pos;
+                    int timeout = 500000;
+
+                    if(xc.data == 0x0){
+                            ret = -EINVAL;
+                            break;
+                    }
+
+                    data = kmalloc(xc.len, GFP_KERNEL);
+                    if(data == 0x0){
+                            printk(KERN_WARNING "%s: Failed to allocate memory for copy\n", dev->name);
+                            ret = -ENOMEM;
+                            break;
+                    }
+                    
+                    if(copy_from_user(data, xc.data, xc.len))
+                    {
+                    	kfree(data);
+                    	ret = -ENOMEM;
+                    	break;
+                    }
+
+                    printk("%s: Starting load of data Len: %d at 0x%p == 0x%p\n", dev->name, xc.len, xc.data, data);
+
+                    lmc_gpio_mkinput(sc, 0xff);
+
+                    /*
+                     * Clear the Xilinx and start prgramming from the DEC
+                     */
+
+                    /*
+                     * Set ouput as:
+                     * Reset: 0 (active)
+                     * DP:    0 (active)
+                     * Mode:  1
+                     *
+                     */
+                    sc->lmc_gpio = 0x00;
+                    sc->lmc_gpio &= ~LMC_GEP_DP;
+                    sc->lmc_gpio &= ~LMC_GEP_RESET;
+                    sc->lmc_gpio |=  LMC_GEP_MODE;
+                    LMC_CSR_WRITE(sc, csr_gp, sc->lmc_gpio);
+
+                    lmc_gpio_mkoutput(sc, LMC_GEP_MODE | LMC_GEP_DP | LMC_GEP_RESET);
+
+                    /*
+                     * Wait at least 10 us 20 to be safe
+                     */
+                    udelay(50);
+
+                    /*
+                     * Clear reset and activate programming lines
+                     * Reset: Input
+                     * DP:    Input
+                     * Clock: Output
+                     * Data:  Output
+                     * Mode:  Output
+                     */
+                    lmc_gpio_mkinput(sc, LMC_GEP_DP | LMC_GEP_RESET);
+
+                    /*
+                     * Set LOAD, DATA, Clock to 1
+                     */
+                    sc->lmc_gpio = 0x00;
+                    sc->lmc_gpio |= LMC_GEP_MODE;
+                    sc->lmc_gpio |= LMC_GEP_DATA;
+                    sc->lmc_gpio |= LMC_GEP_CLK;
+                    LMC_CSR_WRITE(sc, csr_gp, sc->lmc_gpio);
+                    
+                    lmc_gpio_mkoutput(sc, LMC_GEP_DATA | LMC_GEP_CLK | LMC_GEP_MODE );
+
+                    /*
+                     * busy wait for the chip to reset
+                     */
+                    while( (LMC_CSR_READ(sc, csr_gp) & LMC_GEP_INIT) == 0 &&
+                           (timeout-- > 0))
+                        ;
+
+                    printk(KERN_DEBUG "%s: Waited %d for the Xilinx to clear it's memory\n", dev->name, 500000-timeout);
+
+                    for(pos = 0; pos < xc.len; pos++){
+                        switch(data[pos]){
+                        case 0:
+                            sc->lmc_gpio &= ~LMC_GEP_DATA; /* Data is 0 */
+                            break;
+                        case 1:
+                            sc->lmc_gpio |= LMC_GEP_DATA; /* Data is 1 */
+                            break;
+                        default:
+                            printk(KERN_WARNING "%s Bad data in xilinx programming data at %d, got %d wanted 0 or 1\n", dev->name, pos, data[pos]);
+                            sc->lmc_gpio |= LMC_GEP_DATA; /* Assume it's 1 */
+                        }
+                        sc->lmc_gpio &= ~LMC_GEP_CLK; /* Clock to zero */
+                        sc->lmc_gpio |= LMC_GEP_MODE;
+                        LMC_CSR_WRITE(sc, csr_gp, sc->lmc_gpio);
+                        udelay(1);
+                        
+                        sc->lmc_gpio |= LMC_GEP_CLK; /* Put the clack back to one */
+                        sc->lmc_gpio |= LMC_GEP_MODE;
+                        LMC_CSR_WRITE(sc, csr_gp, sc->lmc_gpio);
+                        udelay(1);
+                    }
+                    if((LMC_CSR_READ(sc, csr_gp) & LMC_GEP_INIT) == 0){
+                        printk(KERN_WARNING "%s: Reprogramming FAILED. Needs to be reprogrammed. (corrupted data)\n", dev->name);
+                    }
+                    else if((LMC_CSR_READ(sc, csr_gp) & LMC_GEP_DP) == 0){
+                        printk(KERN_WARNING "%s: Reprogramming FAILED. Needs to be reprogrammed. (done)\n", dev->name);
+                    }
+                    else {
+                        printk(KERN_DEBUG "%s: Done reprogramming Xilinx, %d bits, good luck!\n", dev->name, pos);
+                    }
+
+                    lmc_gpio_mkinput(sc, 0xff);
+                    
+                    sc->lmc_miireg16 |= LMC_MII16_FIFO_RESET;
+                    lmc_mii_writereg(sc, 0, 16, sc->lmc_miireg16);
+
+                    sc->lmc_miireg16 &= ~LMC_MII16_FIFO_RESET;
+                    lmc_mii_writereg(sc, 0, 16, sc->lmc_miireg16);
+
+                    kfree(data);
+                    
+                    ret = 0;
+                    
+                    break;
+                }
+            default: /*fold02*/
+                ret = -EBADE;
+                break;
+            }
+
+            netif_wake_queue(dev);
+            sc->lmc_txfull = 0;
+
+        }
+        break;
+    default: /*fold01*/
+        /* If we don't know what to do, give the protocol a shot. */
+        ret = lmc_proto_ioctl (sc, ifr, cmd);
+        break;
+    }
+
+    spin_unlock_irqrestore(&sc->lmc_lock, flags); /*fold01*/
+
+    lmc_trace(dev, "lmc_ioctl out");
+
+    return ret;
+}
+
+
+/* the watchdog process that cruises around */
+static void lmc_watchdog (unsigned long data) /*fold00*/
+{
+    struct net_device *dev = (struct net_device *) data;
+    lmc_softc_t *sc;
+    int link_status;
+    u_int32_t ticks;
+    unsigned long flags;
+
+    sc = dev->priv;
+
+    lmc_trace(dev, "lmc_watchdog in");
+
+    spin_lock_irqsave(&sc->lmc_lock, flags);
+
+    if(sc->check != 0xBEAFCAFE){
+        printk("LMC: Corrupt net_device stuct, breaking out\n");
+	spin_unlock_irqrestore(&sc->lmc_lock, flags);
+        return;
+    }
+
+
+    /* Make sure the tx jabber and rx watchdog are off,
+     * and the transmit and receive processes are running.
+     */
+
+    LMC_CSR_WRITE (sc, csr_15, 0x00000011);
+    sc->lmc_cmdmode |= TULIP_CMD_TXRUN | TULIP_CMD_RXRUN;
+    LMC_CSR_WRITE (sc, csr_command, sc->lmc_cmdmode);
+
+    if (sc->lmc_ok == 0)
+        goto kick_timer;
+
+    LMC_EVENT_LOG(LMC_EVENT_WATCHDOG, LMC_CSR_READ (sc, csr_status), lmc_mii_readreg (sc, 0, 16));
+
+    /* --- begin time out check -----------------------------------
+     * check for a transmit interrupt timeout
+     * Has the packet xmt vs xmt serviced threshold been exceeded */
+    if (sc->lmc_taint_tx == sc->lastlmc_taint_tx &&
+        sc->stats.tx_packets > sc->lasttx_packets &&
+        sc->tx_TimeoutInd == 0)
+    {
+
+        /* wait for the watchdog to come around again */
+        sc->tx_TimeoutInd = 1;
+    }
+    else if (sc->lmc_taint_tx == sc->lastlmc_taint_tx &&
+             sc->stats.tx_packets > sc->lasttx_packets &&
+             sc->tx_TimeoutInd)
+    {
+
+        LMC_EVENT_LOG(LMC_EVENT_XMTINTTMO, LMC_CSR_READ (sc, csr_status), 0);
+
+        sc->tx_TimeoutDisplay = 1;
+        sc->stats.tx_TimeoutCnt++;
+
+        /* DEC chip is stuck, hit it with a RESET!!!! */
+        lmc_running_reset (dev);
+
+
+        /* look at receive & transmit process state to make sure they are running */
+        LMC_EVENT_LOG(LMC_EVENT_RESET1, LMC_CSR_READ (sc, csr_status), 0);
+
+        /* look at: DSR - 02  for Reg 16
+         *                  CTS - 08
+         *                  DCD - 10
+         *                  RI  - 20
+         * for Reg 17
+         */
+        LMC_EVENT_LOG(LMC_EVENT_RESET2, lmc_mii_readreg (sc, 0, 16), lmc_mii_readreg (sc, 0, 17));
+
+        /* reset the transmit timeout detection flag */
+        sc->tx_TimeoutInd = 0;
+        sc->lastlmc_taint_tx = sc->lmc_taint_tx;
+        sc->lasttx_packets = sc->stats.tx_packets;
+    }
+    else
+    {
+        sc->tx_TimeoutInd = 0;
+        sc->lastlmc_taint_tx = sc->lmc_taint_tx;
+        sc->lasttx_packets = sc->stats.tx_packets;
+    }
+
+    /* --- end time out check ----------------------------------- */
+
+
+    link_status = sc->lmc_media->get_link_status (sc);
+
+    /*
+     * hardware level link lost, but the interface is marked as up.
+     * Mark it as down.
+     */
+    if ((link_status == 0) && (sc->last_link_status != 0)) {
+        printk(KERN_WARNING "%s: hardware/physical link down\n", dev->name);
+        sc->last_link_status = 0;
+        /* lmc_reset (sc); Why reset??? The link can go down ok */
+
+        /* Inform the world that link has been lost */
+        dev->flags &= ~IFF_RUNNING;
+    }
+
+    /*
+     * hardware link is up, but the interface is marked as down.
+     * Bring it back up again.
+     */
+     if (link_status != 0 && sc->last_link_status == 0) {
+         printk(KERN_WARNING "%s: hardware/physical link up\n", dev->name);
+         sc->last_link_status = 1;
+         /* lmc_reset (sc); Again why reset??? */
+
+         /* Inform the world that link protocol is back up. */
+         dev->flags |= IFF_RUNNING;
+
+         /* Now we have to tell the syncppp that we had an outage
+          * and that it should deal.  Calling sppp_reopen here
+          * should do the trick, but we may have to call sppp_close
+          * when the link goes down, and call sppp_open here.
+          * Subject to more testing.
+          * --bbraun
+          */
+
+         lmc_proto_reopen(sc);
+
+     }
+
+    /* Call media specific watchdog functions */
+    sc->lmc_media->watchdog(sc);
+
+    /*
+     * Poke the transmitter to make sure it
+     * never stops, even if we run out of mem
+     */
+    LMC_CSR_WRITE(sc, csr_rxpoll, 0);
+
+    /*
+     * Check for code that failed
+     * and try and fix it as appropriate
+     */
+    if(sc->failed_ring == 1){
+        /*
+         * Failed to setup the recv/xmit rin
+         * Try again
+         */
+        sc->failed_ring = 0;
+        lmc_softreset(sc);
+    }
+    if(sc->failed_recv_alloc == 1){
+        /*
+         * We failed to alloc mem in the
+         * interrupt handler, go through the rings
+         * and rebuild them
+         */
+        sc->failed_recv_alloc = 0;
+        lmc_softreset(sc);
+    }
+
+
+    /*
+     * remember the timer value
+     */
+kick_timer:
+
+    ticks = LMC_CSR_READ (sc, csr_gp_timer);
+    LMC_CSR_WRITE (sc, csr_gp_timer, 0xffffffffUL);
+    sc->ictl.ticks = 0x0000ffff - (ticks & 0x0000ffff);
+
+    /*
+     * restart this timer.
+     */
+    sc->timer.expires = jiffies + (HZ);
+    add_timer (&sc->timer);
+
+    spin_unlock_irqrestore(&sc->lmc_lock, flags);
+
+    lmc_trace(dev, "lmc_watchdog out");
+
+}
+
+static void lmc_setup(struct net_device * const dev) /*fold00*/
+{
+    lmc_trace(dev, "lmc_setup in");
+
+    dev->type = ARPHRD_HDLC;
+    dev->hard_start_xmit = lmc_start_xmit;
+    dev->open = lmc_open;
+    dev->stop = lmc_close;
+    dev->get_stats = lmc_get_stats;
+    dev->do_ioctl = lmc_ioctl;
+    dev->tx_timeout = lmc_driver_timeout;
+    dev->watchdog_timeo = (HZ); /* 1 second */
+    
+    lmc_trace(dev, "lmc_setup out");
+}
+
+
+static int __devinit lmc_init_one(struct pci_dev *pdev,
+				  const struct pci_device_id *ent)
+{
+    struct net_device *dev;
+    lmc_softc_t *sc;
+    u16 subdevice;
+    u_int16_t AdapModelNum;
+    int err = -ENOMEM;
+    static int cards_found;
+#ifndef GCOM
+    /* We name by type not by vendor */
+    static const char lmcname[] = "hdlc%d";
+#else
+    /* 
+     * GCOM uses LMC vendor name so that clients can know which card
+     * to attach to.
+     */
+    static const char lmcname[] = "lmc%d";
+#endif
+
+
+    /*
+     * Allocate our own device structure
+     */
+    dev = alloc_netdev(sizeof(lmc_softc_t), lmcname, lmc_setup);
+    if (!dev) {
+        printk (KERN_ERR "lmc:alloc_netdev for device failed\n");
+	goto out1;
+    }
+ 
+    lmc_trace(dev, "lmc_init_one in");
+
+    err = pci_enable_device(pdev);
+    if (err) {
+	    printk(KERN_ERR "lmc: pci enable failed:%d\n", err);
+	    goto out2;
+    }
+    
+    if (pci_request_regions(pdev, "lmc")) {
+	    printk(KERN_ERR "lmc: pci_request_region failed\n");
+	    err = -EIO;
+	    goto out3;
+    }
+
+    pci_set_drvdata(pdev, dev);
+
+    if(lmc_first_load == 0){
+        printk(KERN_INFO "Lan Media Corporation WAN Driver Version %d.%d.%d\n",
+	       DRIVER_MAJOR_VERSION, DRIVER_MINOR_VERSION,DRIVER_SUB_VERSION);
+        lmc_first_load = 1;
+    }
+    
+    sc = dev->priv;
+    sc->lmc_device = dev;
+    sc->name = dev->name;
+
+    /* Initialize the sppp layer */
+    /* An ioctl can cause a subsequent detach for raw frame interface */
+    sc->if_type = LMC_PPP;
+    sc->check = 0xBEAFCAFE;
+    dev->base_addr = pci_resource_start(pdev, 0);
+    dev->irq = pdev->irq;
+
+    SET_MODULE_OWNER(dev);
+    SET_NETDEV_DEV(dev, &pdev->dev);
+
+    /*
+     * This will get the protocol layer ready and do any 1 time init's
+     * Must have a valid sc and dev structure
+     */
+    lmc_proto_init(sc);
+
+    lmc_proto_attach(sc);
+
+    /*
+     * Why were we changing this???
+     dev->tx_queue_len = 100;
+     */
+
+    /* Init the spin lock so can call it latter */
+
+    spin_lock_init(&sc->lmc_lock);
+    pci_set_master(pdev);
+
+    printk ("%s: detected at %lx, irq %d\n", dev->name,
+	    dev->base_addr, dev->irq);
+
+    if (register_netdev (dev) != 0) {
+        printk (KERN_ERR "%s: register_netdev failed.\n", dev->name);
+	goto out4;
+    }
+
+    sc->lmc_cardtype = LMC_CARDTYPE_UNKNOWN;
+    sc->lmc_timing = LMC_CTL_CLOCK_SOURCE_EXT;
+
+    /*
+     *
+     * Check either the subvendor or the subdevice, some systems reverse
+     * the setting in the bois, seems to be version and arch dependent?
+     * Fix the error, exchange the two values 
+     */
+    if ((subdevice = pdev->subsystem_device) == PCI_VENDOR_ID_LMC)
+	    subdevice = pdev->subsystem_vendor;
+
+    switch (subdevice) {
+    case PCI_DEVICE_ID_LMC_HSSI:
+        printk ("%s: LMC HSSI\n", dev->name);
+        sc->lmc_cardtype = LMC_CARDTYPE_HSSI;
+        sc->lmc_media = &lmc_hssi_media;
+        break;
+    case PCI_DEVICE_ID_LMC_DS3:
+        printk ("%s: LMC DS3\n", dev->name);
+        sc->lmc_cardtype = LMC_CARDTYPE_DS3;
+        sc->lmc_media = &lmc_ds3_media;
+        break;
+    case PCI_DEVICE_ID_LMC_SSI:
+        printk ("%s: LMC SSI\n", dev->name);
+        sc->lmc_cardtype = LMC_CARDTYPE_SSI;
+        sc->lmc_media = &lmc_ssi_media;
+        break;
+    case PCI_DEVICE_ID_LMC_T1:
+        printk ("%s: LMC T1\n", dev->name);
+        sc->lmc_cardtype = LMC_CARDTYPE_T1;
+        sc->lmc_media = &lmc_t1_media;
+        break;
+    default:
+        printk (KERN_WARNING "%s: LMC UNKOWN CARD!\n", dev->name);
+        break;
+    }
+
+    lmc_initcsrs (sc, dev->base_addr, 8);
+
+    lmc_gpio_mkinput (sc, 0xff);
+    sc->lmc_gpio = 0;		/* drive no signals yet */
+
+    sc->lmc_media->defaults (sc);
+
+    sc->lmc_media->set_link_status (sc, LMC_LINK_UP);
+
+    /* verify that the PCI Sub System ID matches the Adapter Model number
+     * from the MII register
+     */
+    AdapModelNum = (lmc_mii_readreg (sc, 0, 3) & 0x3f0) >> 4;
+
+    if ((AdapModelNum == LMC_ADAP_T1
+         && subdevice == PCI_DEVICE_ID_LMC_T1) ||	/* detect LMC1200 */
+        (AdapModelNum == LMC_ADAP_SSI
+         && subdevice == PCI_DEVICE_ID_LMC_SSI) ||	/* detect LMC1000 */
+        (AdapModelNum == LMC_ADAP_DS3
+         && subdevice == PCI_DEVICE_ID_LMC_DS3) ||	/* detect LMC5245 */
+        (AdapModelNum == LMC_ADAP_HSSI
+         && subdevice == PCI_DEVICE_ID_LMC_HSSI))
+    {				/* detect LMC5200 */
+
+    }
+    else {
+        printk ("%s: Model number (%d) miscompare for PCI Subsystem ID = 0x%04x\n",
+                dev->name, AdapModelNum, subdevice);
+//        return (NULL);
+    }
+    /*
+     * reset clock
+     */
+    LMC_CSR_WRITE (sc, csr_gp_timer, 0xFFFFFFFFUL);
+
+    sc->board_idx = cards_found++;
+    sc->stats.check = STATCHECK;
+    sc->stats.version_size = (DRIVER_VERSION << 16) +
+        sizeof (struct lmc_statistics);
+    sc->stats.lmc_cardtype = sc->lmc_cardtype;
+
+    sc->lmc_ok = 0;
+    sc->last_link_status = 0;
+
+    lmc_trace(dev, "lmc_init_one out");
+    return 0;
+
+ out4:
+    lmc_proto_detach(sc);
+ out3:
+    if (pdev) {
+	    pci_release_regions(pdev);
+	    pci_set_drvdata(pdev, NULL);
+    }
+ out2:
+    free_netdev(dev);
+ out1:
+    return err;
+}
+
+/*
+ * Called from pci when removing module.
+ */
+static void __devexit lmc_remove_one (struct pci_dev *pdev)
+{
+    struct net_device *dev = pci_get_drvdata(pdev);
+    
+    if (dev) {
+	    lmc_softc_t *sc = dev->priv;
+	    
+	    printk("%s: removing...\n", dev->name);
+	    lmc_proto_detach(sc);
+	    unregister_netdev(dev);
+	    free_netdev(dev);
+	    pci_release_regions(pdev);
+	    pci_disable_device(pdev);
+	    pci_set_drvdata(pdev, NULL);
+    }
+}
+
+/* After this is called, packets can be sent.
+ * Does not initialize the addresses
+ */
+static int lmc_open (struct net_device *dev) /*fold00*/
+{
+    lmc_softc_t *sc = dev->priv;
+
+    lmc_trace(dev, "lmc_open in");
+
+    lmc_led_on(sc, LMC_DS3_LED0);
+
+    lmc_dec_reset (sc);
+    lmc_reset (sc);
+
+    LMC_EVENT_LOG(LMC_EVENT_RESET1, LMC_CSR_READ (sc, csr_status), 0);
+    LMC_EVENT_LOG(LMC_EVENT_RESET2,
+                  lmc_mii_readreg (sc, 0, 16),
+                  lmc_mii_readreg (sc, 0, 17));
+
+
+    if (sc->lmc_ok){
+        lmc_trace(dev, "lmc_open lmc_ok out");
+        return (0);
+    }
+
+    lmc_softreset (sc);
+
+    /* Since we have to use PCI bus, this should work on x86,alpha,ppc */
+    if (request_irq (dev->irq, &lmc_interrupt, SA_SHIRQ, dev->name, dev)){
+        printk(KERN_WARNING "%s: could not get irq: %d\n", dev->name, dev->irq);
+        lmc_trace(dev, "lmc_open irq failed out");
+        return -EAGAIN;
+    }
+    sc->got_irq = 1;
+
+    /* Assert Terminal Active */
+    sc->lmc_miireg16 |= LMC_MII16_LED_ALL;
+    sc->lmc_media->set_link_status (sc, LMC_LINK_UP);
+
+    /*
+     * reset to last state.
+     */
+    sc->lmc_media->set_status (sc, NULL);
+
+    /* setup default bits to be used in tulip_desc_t transmit descriptor
+     * -baz */
+    sc->TxDescriptControlInit = (
+                                 LMC_TDES_INTERRUPT_ON_COMPLETION
+                                 | LMC_TDES_FIRST_SEGMENT
+                                 | LMC_TDES_LAST_SEGMENT
+                                 | LMC_TDES_SECOND_ADDR_CHAINED
+                                 | LMC_TDES_DISABLE_PADDING
+                                );
+
+    if (sc->ictl.crc_length == LMC_CTL_CRC_LENGTH_16) {
+        /* disable 32 bit CRC generated by ASIC */
+        sc->TxDescriptControlInit |= LMC_TDES_ADD_CRC_DISABLE;
+    }
+    sc->lmc_media->set_crc_length(sc, sc->ictl.crc_length);
+    /* Acknoledge the Terminal Active and light LEDs */
+
+    /* dev->flags |= IFF_UP; */
+
+    lmc_proto_open(sc);
+
+    dev->do_ioctl = lmc_ioctl;
+
+
+    netif_start_queue(dev);
+    
+    sc->stats.tx_tbusy0++ ;
+
+    /*
+     * select what interrupts we want to get
+     */
+    sc->lmc_intrmask = 0;
+    /* Should be using the default interrupt mask defined in the .h file. */
+    sc->lmc_intrmask |= (TULIP_STS_NORMALINTR
+                         | TULIP_STS_RXINTR
+                         | TULIP_STS_TXINTR
+                         | TULIP_STS_ABNRMLINTR
+                         | TULIP_STS_SYSERROR
+                         | TULIP_STS_TXSTOPPED
+                         | TULIP_STS_TXUNDERFLOW
+                         | TULIP_STS_RXSTOPPED
+		         | TULIP_STS_RXNOBUF
+                        );
+    LMC_CSR_WRITE (sc, csr_intr, sc->lmc_intrmask);
+
+    sc->lmc_cmdmode |= TULIP_CMD_TXRUN;
+    sc->lmc_cmdmode |= TULIP_CMD_RXRUN;
+    LMC_CSR_WRITE (sc, csr_command, sc->lmc_cmdmode);
+
+    sc->lmc_ok = 1; /* Run watchdog */
+
+    /*
+     * Set the if up now - pfb
+     */
+
+    sc->last_link_status = 1;
+
+    /*
+     * Setup a timer for the watchdog on probe, and start it running.
+     * Since lmc_ok == 0, it will be a NOP for now.
+     */
+    init_timer (&sc->timer);
+    sc->timer.expires = jiffies + HZ;
+    sc->timer.data = (unsigned long) dev;
+    sc->timer.function = &lmc_watchdog;
+    add_timer (&sc->timer);
+
+    lmc_trace(dev, "lmc_open out");
+
+    return (0);
+}
+
+/* Total reset to compensate for the AdTran DSU doing bad things
+ *  under heavy load
+ */
+
+static void lmc_running_reset (struct net_device *dev) /*fold00*/
+{
+
+    lmc_softc_t *sc = (lmc_softc_t *) dev->priv;
+
+    lmc_trace(dev, "lmc_runnig_reset in");
+
+    /* stop interrupts */
+    /* Clear the interrupt mask */
+    LMC_CSR_WRITE (sc, csr_intr, 0x00000000);
+
+    lmc_dec_reset (sc);
+    lmc_reset (sc);
+    lmc_softreset (sc);
+    /* sc->lmc_miireg16 |= LMC_MII16_LED_ALL; */
+    sc->lmc_media->set_link_status (sc, 1);
+    sc->lmc_media->set_status (sc, NULL);
+
+    //dev->flags |= IFF_RUNNING;
+    
+    netif_wake_queue(dev);
+
+    sc->lmc_txfull = 0;
+    sc->stats.tx_tbusy0++ ;
+
+    sc->lmc_intrmask = TULIP_DEFAULT_INTR_MASK;
+    LMC_CSR_WRITE (sc, csr_intr, sc->lmc_intrmask);
+
+    sc->lmc_cmdmode |= (TULIP_CMD_TXRUN | TULIP_CMD_RXRUN);
+    LMC_CSR_WRITE (sc, csr_command, sc->lmc_cmdmode);
+
+    lmc_trace(dev, "lmc_runnin_reset_out");
+}
+
+
+/* This is what is called when you ifconfig down a device.
+ * This disables the timer for the watchdog and keepalives,
+ * and disables the irq for dev.
+ */
+static int lmc_close (struct net_device *dev) /*fold00*/
+{
+    /* not calling release_region() as we should */
+    lmc_softc_t *sc;
+
+    lmc_trace(dev, "lmc_close in");
+    
+    sc = dev->priv;
+    sc->lmc_ok = 0;
+    sc->lmc_media->set_link_status (sc, 0);
+    del_timer (&sc->timer);
+    lmc_proto_close(sc);
+    lmc_ifdown (dev);
+
+    lmc_trace(dev, "lmc_close out");
+    
+    return 0;
+}
+
+/* Ends the transfer of packets */
+/* When the interface goes down, this is called */
+static int lmc_ifdown (struct net_device *dev) /*fold00*/
+{
+    lmc_softc_t *sc = dev->priv;
+    u32 csr6;
+    int i;
+
+    lmc_trace(dev, "lmc_ifdown in");
+    
+    /* Don't let anything else go on right now */
+    //    dev->start = 0;
+    netif_stop_queue(dev);
+    sc->stats.tx_tbusy1++ ;
+
+    /* stop interrupts */
+    /* Clear the interrupt mask */
+    LMC_CSR_WRITE (sc, csr_intr, 0x00000000);
+
+    /* Stop Tx and Rx on the chip */
+    csr6 = LMC_CSR_READ (sc, csr_command);
+    csr6 &= ~LMC_DEC_ST;		/* Turn off the Transmission bit */
+    csr6 &= ~LMC_DEC_SR;		/* Turn off the Receive bit */
+    LMC_CSR_WRITE (sc, csr_command, csr6);
+
+    dev->flags &= ~IFF_RUNNING;
+
+    sc->stats.rx_missed_errors +=
+        LMC_CSR_READ (sc, csr_missed_frames) & 0xffff;
+
+    /* release the interrupt */
+    if(sc->got_irq == 1){
+        free_irq (dev->irq, dev);
+        sc->got_irq = 0;
+    }
+
+    /* free skbuffs in the Rx queue */
+    for (i = 0; i < LMC_RXDESCS; i++)
+    {
+        struct sk_buff *skb = sc->lmc_rxq[i];
+        sc->lmc_rxq[i] = NULL;
+        sc->lmc_rxring[i].status = 0;
+        sc->lmc_rxring[i].length = 0;
+        sc->lmc_rxring[i].buffer1 = 0xDEADBEEF;
+        if (skb != NULL)
+            dev_kfree_skb(skb);
+        sc->lmc_rxq[i] = NULL;
+    }
+
+    for (i = 0; i < LMC_TXDESCS; i++)
+    {
+        if (sc->lmc_txq[i] != NULL)
+            dev_kfree_skb(sc->lmc_txq[i]);
+        sc->lmc_txq[i] = NULL;
+    }
+
+    lmc_led_off (sc, LMC_MII16_LED_ALL);
+
+    netif_wake_queue(dev);
+    sc->stats.tx_tbusy0++ ;
+
+    lmc_trace(dev, "lmc_ifdown out");
+
+    return 0;
+}
+
+/* Interrupt handling routine.  This will take an incoming packet, or clean
+ * up after a trasmit.
+ */
+static irqreturn_t lmc_interrupt (int irq, void *dev_instance, struct pt_regs *regs) /*fold00*/
+{
+    struct net_device *dev = (struct net_device *) dev_instance;
+    lmc_softc_t *sc;
+    u32 csr;
+    int i;
+    s32 stat;
+    unsigned int badtx;
+    u32 firstcsr;
+    int max_work = LMC_RXDESCS;
+    int handled = 0;
+
+    lmc_trace(dev, "lmc_interrupt in");
+
+    sc = dev->priv;
+    
+    spin_lock(&sc->lmc_lock);
+
+    /*
+     * Read the csr to find what interrupts we have (if any)
+     */
+    csr = LMC_CSR_READ (sc, csr_status);
+
+    /*
+     * Make sure this is our interrupt
+     */
+    if ( ! (csr & sc->lmc_intrmask)) {
+        goto lmc_int_fail_out;
+    }
+
+    firstcsr = csr;
+
+    /* always go through this loop at least once */
+    while (csr & sc->lmc_intrmask) {
+	handled = 1;
+
+        /*
+         * Clear interrupt bits, we handle all case below
+         */
+        LMC_CSR_WRITE (sc, csr_status, csr);
+
+        /*
+         * One of
+         *  - Transmit process timed out CSR5<1>
+         *  - Transmit jabber timeout    CSR5<3>
+         *  - Transmit underflow         CSR5<5>
+         *  - Transmit Receiver buffer unavailable CSR5<7>
+         *  - Receive process stopped    CSR5<8>
+         *  - Receive watchdog timeout   CSR5<9>
+         *  - Early transmit interrupt   CSR5<10>
+         *
+         * Is this really right? Should we do a running reset for jabber?
+         * (being a WAN card and all)
+         */
+        if (csr & TULIP_STS_ABNRMLINTR){
+            lmc_running_reset (dev);
+            break;
+        }
+        
+        if (csr & TULIP_STS_RXINTR){
+            lmc_trace(dev, "rx interrupt");
+            lmc_rx (dev);
+            
+        }
+        if (csr & (TULIP_STS_TXINTR | TULIP_STS_TXNOBUF | TULIP_STS_TXSTOPPED)) {
+
+	    int		n_compl = 0 ;
+            /* reset the transmit timeout detection flag -baz */
+            sc->stats.tx_NoCompleteCnt = 0;
+
+            badtx = sc->lmc_taint_tx;
+            i = badtx % LMC_TXDESCS;
+
+            while ((badtx < sc->lmc_next_tx)) {
+                stat = sc->lmc_txring[i].status;
+
+                LMC_EVENT_LOG (LMC_EVENT_XMTINT, stat,
+						 sc->lmc_txring[i].length);
+                /*
+                 * If bit 31 is 1 the tulip owns it break out of the loop
+                 */
+                if (stat & 0x80000000)
+                    break;
+
+		n_compl++ ;		/* i.e., have an empty slot in ring */
+                /*
+                 * If we have no skbuff or have cleared it
+                 * Already continue to the next buffer
+                 */
+                if (sc->lmc_txq[i] == NULL)
+                    continue;
+
+                /*
+                 * Check the total error summary to look for any errors
+                 */
+                if (stat & 0x8000) {
+                    sc->stats.tx_errors++;
+                    if (stat & 0x4104)
+                        sc->stats.tx_aborted_errors++;
+                    if (stat & 0x0C00)
+                        sc->stats.tx_carrier_errors++;
+                    if (stat & 0x0200)
+                        sc->stats.tx_window_errors++;
+                    if (stat & 0x0002)
+                        sc->stats.tx_fifo_errors++;
+                }
+                else {
+                    
+                    sc->stats.tx_bytes += sc->lmc_txring[i].length & 0x7ff;
+                    
+                    sc->stats.tx_packets++;
+                }
+                
+                //                dev_kfree_skb(sc->lmc_txq[i]);
+                dev_kfree_skb_irq(sc->lmc_txq[i]);
+                sc->lmc_txq[i] = NULL;
+
+                badtx++;
+                i = badtx % LMC_TXDESCS;
+            }
+
+            if (sc->lmc_next_tx - badtx > LMC_TXDESCS)
+            {
+                printk ("%s: out of sync pointer\n", dev->name);
+                badtx += LMC_TXDESCS;
+            }
+            LMC_EVENT_LOG(LMC_EVENT_TBUSY0, n_compl, 0);
+            sc->lmc_txfull = 0;
+            netif_wake_queue(dev);
+            sc->stats.tx_tbusy0++ ;
+
+
+#ifdef DEBUG
+            sc->stats.dirtyTx = badtx;
+            sc->stats.lmc_next_tx = sc->lmc_next_tx;
+            sc->stats.lmc_txfull = sc->lmc_txfull;
+#endif
+            sc->lmc_taint_tx = badtx;
+
+            /*
+             * Why was there a break here???
+             */
+        }			/* end handle transmit interrupt */
+
+        if (csr & TULIP_STS_SYSERROR) {
+            u32 error;
+            printk (KERN_WARNING "%s: system bus error csr: %#8.8x\n", dev->name, csr);
+            error = csr>>23 & 0x7;
+            switch(error){
+            case 0x000:
+                printk(KERN_WARNING "%s: Parity Fault (bad)\n", dev->name);
+                break;
+            case 0x001:
+                printk(KERN_WARNING "%s: Master Abort (naughty)\n", dev->name);
+                break;
+            case 0x010:
+                printk(KERN_WARNING "%s: Target Abort (not so naughty)\n", dev->name);
+                break;
+            default:
+                printk(KERN_WARNING "%s: This bus error code was supposed to be reserved!\n", dev->name);
+            }
+            lmc_dec_reset (sc);
+            lmc_reset (sc);
+            LMC_EVENT_LOG(LMC_EVENT_RESET1, LMC_CSR_READ (sc, csr_status), 0);
+            LMC_EVENT_LOG(LMC_EVENT_RESET2,
+                          lmc_mii_readreg (sc, 0, 16),
+                          lmc_mii_readreg (sc, 0, 17));
+
+        }
+
+        
+        if(max_work-- <= 0)
+            break;
+        
+        /*
+         * Get current csr status to make sure
+         * we've cleared all interrupts
+         */
+        csr = LMC_CSR_READ (sc, csr_status);
+    }				/* end interrupt loop */
+    LMC_EVENT_LOG(LMC_EVENT_INT, firstcsr, csr);
+
+lmc_int_fail_out:
+
+    spin_unlock(&sc->lmc_lock);
+
+    lmc_trace(dev, "lmc_interrupt out");
+    return IRQ_RETVAL(handled);
+}
+
+static int lmc_start_xmit (struct sk_buff *skb, struct net_device *dev) /*fold00*/
+{
+    lmc_softc_t *sc;
+    u32 flag;
+    int entry;
+    int ret = 0;
+    unsigned long flags;
+
+    lmc_trace(dev, "lmc_start_xmit in");
+
+    sc = dev->priv;
+
+    spin_lock_irqsave(&sc->lmc_lock, flags);
+
+    /* normal path, tbusy known to be zero */
+
+    entry = sc->lmc_next_tx % LMC_TXDESCS;
+
+    sc->lmc_txq[entry] = skb;
+    sc->lmc_txring[entry].buffer1 = virt_to_bus (skb->data);
+
+    LMC_CONSOLE_LOG("xmit", skb->data, skb->len);
+
+#ifndef GCOM
+    /* If the queue is less than half full, don't interrupt */
+    if (sc->lmc_next_tx - sc->lmc_taint_tx < LMC_TXDESCS / 2)
+    {
+        /* Do not interrupt on completion of this packet */
+        flag = 0x60000000;
+        netif_wake_queue(dev);
+    }
+    else if (sc->lmc_next_tx - sc->lmc_taint_tx == LMC_TXDESCS / 2)
+    {
+        /* This generates an interrupt on completion of this packet */
+        flag = 0xe0000000;
+        netif_wake_queue(dev);
+    }
+    else if (sc->lmc_next_tx - sc->lmc_taint_tx < LMC_TXDESCS - 1)
+    {
+        /* Do not interrupt on completion of this packet */
+        flag = 0x60000000;
+        netif_wake_queue(dev);
+    }
+    else
+    {
+        /* This generates an interrupt on completion of this packet */
+        flag = 0xe0000000;
+        sc->lmc_txfull = 1;
+        netif_stop_queue(dev);
+    }
+#else
+    flag = LMC_TDES_INTERRUPT_ON_COMPLETION;
+
+    if (sc->lmc_next_tx - sc->lmc_taint_tx >= LMC_TXDESCS - 1)
+    {				/* ring full, go busy */
+        sc->lmc_txfull = 1;
+        netif_stop_queue(dev);
+        sc->stats.tx_tbusy1++ ;
+        LMC_EVENT_LOG(LMC_EVENT_TBUSY1, entry, 0);
+    }
+#endif
+
+
+    if (entry == LMC_TXDESCS - 1)	/* last descriptor in ring */
+	flag |= LMC_TDES_END_OF_RING;	/* flag as such for Tulip */
+
+    /* don't pad small packets either */
+    flag = sc->lmc_txring[entry].length = (skb->len) | flag |
+						sc->TxDescriptControlInit;
+
+    /* set the transmit timeout flag to be checked in
+     * the watchdog timer handler. -baz
+     */
+
+    sc->stats.tx_NoCompleteCnt++;
+    sc->lmc_next_tx++;
+
+    /* give ownership to the chip */
+    LMC_EVENT_LOG(LMC_EVENT_XMT, flag, entry);
+    sc->lmc_txring[entry].status = 0x80000000;
+
+    /* send now! */
+    LMC_CSR_WRITE (sc, csr_txpoll, 0);
+
+    dev->trans_start = jiffies;
+
+    spin_unlock_irqrestore(&sc->lmc_lock, flags);
+
+    lmc_trace(dev, "lmc_start_xmit_out");
+    return ret;
+}
+
+
+static int lmc_rx (struct net_device *dev) /*fold00*/
+{
+    lmc_softc_t *sc;
+    int i;
+    int rx_work_limit = LMC_RXDESCS;
+    unsigned int next_rx;
+    int rxIntLoopCnt;		/* debug -baz */
+    int localLengthErrCnt = 0;
+    long stat;
+    struct sk_buff *skb, *nsb;
+    u16 len;
+
+    lmc_trace(dev, "lmc_rx in");
+
+    sc = dev->priv;
+
+    lmc_led_on(sc, LMC_DS3_LED3);
+
+    rxIntLoopCnt = 0;		/* debug -baz */
+
+    i = sc->lmc_next_rx % LMC_RXDESCS;
+    next_rx = sc->lmc_next_rx;
+
+    while (((stat = sc->lmc_rxring[i].status) & LMC_RDES_OWN_BIT) != DESC_OWNED_BY_DC21X4)
+    {
+        rxIntLoopCnt++;		/* debug -baz */
+        len = ((stat & LMC_RDES_FRAME_LENGTH) >> RDES_FRAME_LENGTH_BIT_NUMBER);
+        if ((stat & 0x0300) != 0x0300) {  /* Check first segment and last segment */
+            if ((stat & 0x0000ffff) != 0x7fff) {
+                /* Oversized frame */
+                sc->stats.rx_length_errors++;
+                goto skip_packet;
+            }
+        }
+
+        if(stat & 0x00000008){ /* Catch a dribbling bit error */
+            sc->stats.rx_errors++;
+            sc->stats.rx_frame_errors++;
+            goto skip_packet;
+        }
+
+
+        if(stat & 0x00000004){ /* Catch a CRC error by the Xilinx */
+            sc->stats.rx_errors++;
+            sc->stats.rx_crc_errors++;
+            goto skip_packet;
+        }
+
+
+        if (len > LMC_PKT_BUF_SZ){
+            sc->stats.rx_length_errors++;
+            localLengthErrCnt++;
+            goto skip_packet;
+        }
+
+        if (len < sc->lmc_crcSize + 2) {
+            sc->stats.rx_length_errors++;
+            sc->stats.rx_SmallPktCnt++;
+            localLengthErrCnt++;
+            goto skip_packet;
+        }
+
+        if(stat & 0x00004000){
+            printk(KERN_WARNING "%s: Receiver descriptor error, receiver out of sync?\n", dev->name);
+        }
+
+        len -= sc->lmc_crcSize;
+
+        skb = sc->lmc_rxq[i];
+
+        /*
+         * We ran out of memory at some point
+         * just allocate an skb buff and continue.
+         */
+        
+        if(skb == 0x0){
+            nsb = dev_alloc_skb (LMC_PKT_BUF_SZ + 2);
+            if (nsb) {
+                sc->lmc_rxq[i] = nsb;
+                nsb->dev = dev;
+                sc->lmc_rxring[i].buffer1 = virt_to_bus (nsb->tail);
+            }
+            sc->failed_recv_alloc = 1;
+            goto skip_packet;
+        }
+        
+        dev->last_rx = jiffies;
+        sc->stats.rx_packets++;
+        sc->stats.rx_bytes += len;
+
+        LMC_CONSOLE_LOG("recv", skb->data, len);
+
+        /*
+         * I'm not sure of the sanity of this
+         * Packets could be arriving at a constant
+         * 44.210mbits/sec and we're going to copy
+         * them into a new buffer??
+         */
+        
+        if(len > (LMC_MTU - (LMC_MTU>>2))){ /* len > LMC_MTU * 0.75 */
+            /*
+             * If it's a large packet don't copy it just hand it up
+             */
+        give_it_anyways:
+
+            sc->lmc_rxq[i] = NULL;
+            sc->lmc_rxring[i].buffer1 = 0x0;
+
+            skb_put (skb, len);
+            skb->protocol = lmc_proto_type(sc, skb);
+            skb->protocol = htons(ETH_P_WAN_PPP);
+            skb->mac.raw = skb->data;
+//            skb->nh.raw = skb->data;
+            skb->dev = dev;
+            lmc_proto_netif(sc, skb);
+
+            /*
+             * This skb will be destroyed by the upper layers, make a new one
+             */
+            nsb = dev_alloc_skb (LMC_PKT_BUF_SZ + 2);
+            if (nsb) {
+                sc->lmc_rxq[i] = nsb;
+                nsb->dev = dev;
+                sc->lmc_rxring[i].buffer1 = virt_to_bus (nsb->tail);
+                /* Transferred to 21140 below */
+            }
+            else {
+                /*
+                 * We've run out of memory, stop trying to allocate
+                 * memory and exit the interrupt handler
+                 *
+                 * The chip may run out of receivers and stop
+                 * in which care we'll try to allocate the buffer
+                 * again.  (once a second)
+                 */
+                sc->stats.rx_BuffAllocErr++;
+                LMC_EVENT_LOG(LMC_EVENT_RCVINT, stat, len);
+                sc->failed_recv_alloc = 1;
+                goto skip_out_of_mem;
+            }
+        }
+        else {
+            nsb = dev_alloc_skb(len);
+            if(!nsb) {
+                goto give_it_anyways;
+            }
+            memcpy(skb_put(nsb, len), skb->data, len);
+            
+            nsb->protocol = lmc_proto_type(sc, skb);
+            nsb->mac.raw = nsb->data;
+//            nsb->nh.raw = nsb->data;
+            nsb->dev = dev;
+            lmc_proto_netif(sc, nsb);
+        }
+
+    skip_packet:
+        LMC_EVENT_LOG(LMC_EVENT_RCVINT, stat, len);
+        sc->lmc_rxring[i].status = DESC_OWNED_BY_DC21X4;
+
+        sc->lmc_next_rx++;
+        i = sc->lmc_next_rx % LMC_RXDESCS;
+        rx_work_limit--;
+        if (rx_work_limit < 0)
+            break;
+    }
+
+    /* detect condition for LMC1000 where DSU cable attaches and fills
+     * descriptors with bogus packets
+     *
+    if (localLengthErrCnt > LMC_RXDESCS - 3) {
+        sc->stats.rx_BadPktSurgeCnt++;
+        LMC_EVENT_LOG(LMC_EVENT_BADPKTSURGE,
+                      localLengthErrCnt,
+                      sc->stats.rx_BadPktSurgeCnt);
+    } */
+
+    /* save max count of receive descriptors serviced */
+    if (rxIntLoopCnt > sc->stats.rxIntLoopCnt) {
+        sc->stats.rxIntLoopCnt = rxIntLoopCnt;	/* debug -baz */
+    }
+
+#ifdef DEBUG
+    if (rxIntLoopCnt == 0)
+    {
+        for (i = 0; i < LMC_RXDESCS; i++)
+        {
+            if ((sc->lmc_rxring[i].status & LMC_RDES_OWN_BIT)
+                != DESC_OWNED_BY_DC21X4)
+            {
+                rxIntLoopCnt++;
+            }
+        }
+        LMC_EVENT_LOG(LMC_EVENT_RCVEND, rxIntLoopCnt, 0);
+    }
+#endif
+
+
+    lmc_led_off(sc, LMC_DS3_LED3);
+
+skip_out_of_mem:
+
+    lmc_trace(dev, "lmc_rx out");
+
+    return 0;
+}
+
+static struct net_device_stats *lmc_get_stats (struct net_device *dev) /*fold00*/
+{
+    lmc_softc_t *sc = dev->priv;
+    unsigned long flags;
+
+    lmc_trace(dev, "lmc_get_stats in");
+
+
+    spin_lock_irqsave(&sc->lmc_lock, flags);
+
+    sc->stats.rx_missed_errors += LMC_CSR_READ (sc, csr_missed_frames) & 0xffff;
+
+    spin_unlock_irqrestore(&sc->lmc_lock, flags);
+
+    lmc_trace(dev, "lmc_get_stats out");
+
+    return (struct net_device_stats *) &sc->stats;
+}
+
+static struct pci_driver lmc_driver = {
+	.name		= "lmc",
+	.id_table	= lmc_pci_tbl,
+	.probe		= lmc_init_one,
+	.remove		= __devexit_p(lmc_remove_one),
+};
+
+static int __init init_lmc(void)
+{
+    return pci_module_init(&lmc_driver);
+}
+
+static void __exit exit_lmc(void)
+{
+    pci_unregister_driver(&lmc_driver);
+}
+
+module_init(init_lmc);
+module_exit(exit_lmc);
+
+unsigned lmc_mii_readreg (lmc_softc_t * const sc, unsigned devaddr, unsigned regno) /*fold00*/
+{
+    int i;
+    int command = (0xf6 << 10) | (devaddr << 5) | regno;
+    int retval = 0;
+
+    lmc_trace(sc->lmc_device, "lmc_mii_readreg in");
+
+    LMC_MII_SYNC (sc);
+
+    lmc_trace(sc->lmc_device, "lmc_mii_readreg: done sync");
+
+    for (i = 15; i >= 0; i--)
+    {
+        int dataval = (command & (1 << i)) ? 0x20000 : 0;
+
+        LMC_CSR_WRITE (sc, csr_9, dataval);
+        lmc_delay ();
+        /* __SLOW_DOWN_IO; */
+        LMC_CSR_WRITE (sc, csr_9, dataval | 0x10000);
+        lmc_delay ();
+        /* __SLOW_DOWN_IO; */
+    }
+
+    lmc_trace(sc->lmc_device, "lmc_mii_readreg: done1");
+
+    for (i = 19; i > 0; i--)
+    {
+        LMC_CSR_WRITE (sc, csr_9, 0x40000);
+        lmc_delay ();
+        /* __SLOW_DOWN_IO; */
+        retval = (retval << 1) | ((LMC_CSR_READ (sc, csr_9) & 0x80000) ? 1 : 0);
+        LMC_CSR_WRITE (sc, csr_9, 0x40000 | 0x10000);
+        lmc_delay ();
+        /* __SLOW_DOWN_IO; */
+    }
+
+    lmc_trace(sc->lmc_device, "lmc_mii_readreg out");
+
+    return (retval >> 1) & 0xffff;
+}
+
+void lmc_mii_writereg (lmc_softc_t * const sc, unsigned devaddr, unsigned regno, unsigned data) /*fold00*/
+{
+    int i = 32;
+    int command = (0x5002 << 16) | (devaddr << 23) | (regno << 18) | data;
+
+    lmc_trace(sc->lmc_device, "lmc_mii_writereg in");
+
+    LMC_MII_SYNC (sc);
+
+    i = 31;
+    while (i >= 0)
+    {
+        int datav;
+
+        if (command & (1 << i))
+            datav = 0x20000;
+        else
+            datav = 0x00000;
+
+        LMC_CSR_WRITE (sc, csr_9, datav);
+        lmc_delay ();
+        /* __SLOW_DOWN_IO; */
+        LMC_CSR_WRITE (sc, csr_9, (datav | 0x10000));
+        lmc_delay ();
+        /* __SLOW_DOWN_IO; */
+        i--;
+    }
+
+    i = 2;
+    while (i > 0)
+    {
+        LMC_CSR_WRITE (sc, csr_9, 0x40000);
+        lmc_delay ();
+        /* __SLOW_DOWN_IO; */
+        LMC_CSR_WRITE (sc, csr_9, 0x50000);
+        lmc_delay ();
+        /* __SLOW_DOWN_IO; */
+        i--;
+    }
+
+    lmc_trace(sc->lmc_device, "lmc_mii_writereg out");
+}
+
+static void lmc_softreset (lmc_softc_t * const sc) /*fold00*/
+{
+    int i;
+
+    lmc_trace(sc->lmc_device, "lmc_softreset in");
+
+    /* Initialize the receive rings and buffers. */
+    sc->lmc_txfull = 0;
+    sc->lmc_next_rx = 0;
+    sc->lmc_next_tx = 0;
+    sc->lmc_taint_rx = 0;
+    sc->lmc_taint_tx = 0;
+
+    /*
+     * Setup each one of the receiver buffers
+     * allocate an skbuff for each one, setup the descriptor table
+     * and point each buffer at the next one
+     */
+
+    for (i = 0; i < LMC_RXDESCS; i++)
+    {
+        struct sk_buff *skb;
+
+        if (sc->lmc_rxq[i] == NULL)
+        {
+            skb = dev_alloc_skb (LMC_PKT_BUF_SZ + 2);
+            if(skb == NULL){
+                printk(KERN_WARNING "%s: Failed to allocate receiver ring, will try again\n", sc->name);
+                sc->failed_ring = 1;
+                break;
+            }
+            else{
+                sc->lmc_rxq[i] = skb;
+            }
+        }
+        else
+        {
+            skb = sc->lmc_rxq[i];
+        }
+
+        skb->dev = sc->lmc_device;
+
+        /* owned by 21140 */
+        sc->lmc_rxring[i].status = 0x80000000;
+
+        /* used to be PKT_BUF_SZ now uses skb since we lose some to head room */
+        sc->lmc_rxring[i].length = skb->end - skb->data;
+
+        /* use to be tail which is dumb since you're thinking why write
+         * to the end of the packj,et but since there's nothing there tail == data
+         */
+        sc->lmc_rxring[i].buffer1 = virt_to_bus (skb->data);
+
+        /* This is fair since the structure is static and we have the next address */
+        sc->lmc_rxring[i].buffer2 = virt_to_bus (&sc->lmc_rxring[i + 1]);
+
+    }
+
+    /*
+     * Sets end of ring
+     */
+    sc->lmc_rxring[i - 1].length |= 0x02000000; /* Set end of buffers flag */
+    sc->lmc_rxring[i - 1].buffer2 = virt_to_bus (&sc->lmc_rxring[0]); /* Point back to the start */
+    LMC_CSR_WRITE (sc, csr_rxlist, virt_to_bus (sc->lmc_rxring)); /* write base address */
+
+
+    /* Initialize the transmit rings and buffers */
+    for (i = 0; i < LMC_TXDESCS; i++)
+    {
+        if (sc->lmc_txq[i] != NULL){		/* have buffer */
+            dev_kfree_skb(sc->lmc_txq[i]);	/* free it */
+            sc->stats.tx_dropped++;      /* We just dropped a packet */
+        }
+        sc->lmc_txq[i] = NULL;
+        sc->lmc_txring[i].status = 0x00000000;
+        sc->lmc_txring[i].buffer2 = virt_to_bus (&sc->lmc_txring[i + 1]);
+    }
+    sc->lmc_txring[i - 1].buffer2 = virt_to_bus (&sc->lmc_txring[0]);
+    LMC_CSR_WRITE (sc, csr_txlist, virt_to_bus (sc->lmc_txring));
+
+    lmc_trace(sc->lmc_device, "lmc_softreset out");
+}
+
+void lmc_gpio_mkinput(lmc_softc_t * const sc, u_int32_t bits) /*fold00*/
+{
+    lmc_trace(sc->lmc_device, "lmc_gpio_mkinput in");
+    sc->lmc_gpio_io &= ~bits;
+    LMC_CSR_WRITE(sc, csr_gp, TULIP_GP_PINSET | (sc->lmc_gpio_io));
+    lmc_trace(sc->lmc_device, "lmc_gpio_mkinput out");
+}
+
+void lmc_gpio_mkoutput(lmc_softc_t * const sc, u_int32_t bits) /*fold00*/
+{
+    lmc_trace(sc->lmc_device, "lmc_gpio_mkoutput in");
+    sc->lmc_gpio_io |= bits;
+    LMC_CSR_WRITE(sc, csr_gp, TULIP_GP_PINSET | (sc->lmc_gpio_io));
+    lmc_trace(sc->lmc_device, "lmc_gpio_mkoutput out");
+}
+
+void lmc_led_on(lmc_softc_t * const sc, u_int32_t led) /*fold00*/
+{
+    lmc_trace(sc->lmc_device, "lmc_led_on in");
+    if((~sc->lmc_miireg16) & led){ /* Already on! */
+        lmc_trace(sc->lmc_device, "lmc_led_on aon out");
+        return;
+    }
+    
+    sc->lmc_miireg16 &= ~led;
+    lmc_mii_writereg(sc, 0, 16, sc->lmc_miireg16);
+    lmc_trace(sc->lmc_device, "lmc_led_on out");
+}
+
+void lmc_led_off(lmc_softc_t * const sc, u_int32_t led) /*fold00*/
+{
+    lmc_trace(sc->lmc_device, "lmc_led_off in");
+    if(sc->lmc_miireg16 & led){ /* Already set don't do anything */
+        lmc_trace(sc->lmc_device, "lmc_led_off aoff out");
+        return;
+    }
+    
+    sc->lmc_miireg16 |= led;
+    lmc_mii_writereg(sc, 0, 16, sc->lmc_miireg16);
+    lmc_trace(sc->lmc_device, "lmc_led_off out");
+}
+
+static void lmc_reset(lmc_softc_t * const sc) /*fold00*/
+{
+    lmc_trace(sc->lmc_device, "lmc_reset in");
+    sc->lmc_miireg16 |= LMC_MII16_FIFO_RESET;
+    lmc_mii_writereg(sc, 0, 16, sc->lmc_miireg16);
+
+    sc->lmc_miireg16 &= ~LMC_MII16_FIFO_RESET;
+    lmc_mii_writereg(sc, 0, 16, sc->lmc_miireg16);
+
+    /*
+     * make some of the GPIO pins be outputs
+     */
+    lmc_gpio_mkoutput(sc, LMC_GEP_RESET);
+
+    /*
+     * RESET low to force state reset.  This also forces
+     * the transmitter clock to be internal, but we expect to reset
+     * that later anyway.
+     */
+    sc->lmc_gpio &= ~(LMC_GEP_RESET);
+    LMC_CSR_WRITE(sc, csr_gp, sc->lmc_gpio);
+
+    /*
+     * hold for more than 10 microseconds
+     */
+    udelay(50);
+
+    /*
+     * stop driving Xilinx-related signals
+     */
+    lmc_gpio_mkinput(sc, LMC_GEP_RESET);
+
+    /*
+     * Call media specific init routine
+     */
+    sc->lmc_media->init(sc);
+
+    sc->stats.resetCount++;
+    lmc_trace(sc->lmc_device, "lmc_reset out");
+}
+
+static void lmc_dec_reset(lmc_softc_t * const sc) /*fold00*/
+{
+    u_int32_t val;
+    lmc_trace(sc->lmc_device, "lmc_dec_reset in");
+
+    /*
+     * disable all interrupts
+     */
+    sc->lmc_intrmask = 0;
+    LMC_CSR_WRITE(sc, csr_intr, sc->lmc_intrmask);
+
+    /*
+     * Reset the chip with a software reset command.
+     * Wait 10 microseconds (actually 50 PCI cycles but at
+     * 33MHz that comes to two microseconds but wait a
+     * bit longer anyways)
+     */
+    LMC_CSR_WRITE(sc, csr_busmode, TULIP_BUSMODE_SWRESET);
+    udelay(25);
+#ifdef __sparc__
+    sc->lmc_busmode = LMC_CSR_READ(sc, csr_busmode);
+    sc->lmc_busmode = 0x00100000;
+    sc->lmc_busmode &= ~TULIP_BUSMODE_SWRESET;
+    LMC_CSR_WRITE(sc, csr_busmode, sc->lmc_busmode);
+#endif
+    sc->lmc_cmdmode = LMC_CSR_READ(sc, csr_command);
+
+    /*
+     * We want:
+     *   no ethernet address in frames we write
+     *   disable padding (txdesc, padding disable)
+     *   ignore runt frames (rdes0 bit 15)
+     *   no receiver watchdog or transmitter jabber timer
+     *       (csr15 bit 0,14 == 1)
+     *   if using 16-bit CRC, turn off CRC (trans desc, crc disable)
+     */
+
+    sc->lmc_cmdmode |= ( TULIP_CMD_PROMISCUOUS
+                         | TULIP_CMD_FULLDUPLEX
+                         | TULIP_CMD_PASSBADPKT
+                         | TULIP_CMD_NOHEARTBEAT
+                         | TULIP_CMD_PORTSELECT
+                         | TULIP_CMD_RECEIVEALL
+                         | TULIP_CMD_MUSTBEONE
+                       );
+    sc->lmc_cmdmode &= ~( TULIP_CMD_OPERMODE
+                          | TULIP_CMD_THRESHOLDCTL
+                          | TULIP_CMD_STOREFWD
+                          | TULIP_CMD_TXTHRSHLDCTL
+                        );
+
+    LMC_CSR_WRITE(sc, csr_command, sc->lmc_cmdmode);
+
+    /*
+     * disable receiver watchdog and transmit jabber
+     */
+    val = LMC_CSR_READ(sc, csr_sia_general);
+    val |= (TULIP_WATCHDOG_TXDISABLE | TULIP_WATCHDOG_RXDISABLE);
+    LMC_CSR_WRITE(sc, csr_sia_general, val);
+
+    lmc_trace(sc->lmc_device, "lmc_dec_reset out");
+}
+
+static void lmc_initcsrs(lmc_softc_t * const sc, lmc_csrptr_t csr_base, /*fold00*/
+                         size_t csr_size)
+{
+    lmc_trace(sc->lmc_device, "lmc_initcsrs in");
+    sc->lmc_csrs.csr_busmode	        = csr_base +  0 * csr_size;
+    sc->lmc_csrs.csr_txpoll		= csr_base +  1 * csr_size;
+    sc->lmc_csrs.csr_rxpoll		= csr_base +  2 * csr_size;
+    sc->lmc_csrs.csr_rxlist		= csr_base +  3 * csr_size;
+    sc->lmc_csrs.csr_txlist		= csr_base +  4 * csr_size;
+    sc->lmc_csrs.csr_status		= csr_base +  5 * csr_size;
+    sc->lmc_csrs.csr_command	        = csr_base +  6 * csr_size;
+    sc->lmc_csrs.csr_intr		= csr_base +  7 * csr_size;
+    sc->lmc_csrs.csr_missed_frames	= csr_base +  8 * csr_size;
+    sc->lmc_csrs.csr_9		        = csr_base +  9 * csr_size;
+    sc->lmc_csrs.csr_10		        = csr_base + 10 * csr_size;
+    sc->lmc_csrs.csr_11		        = csr_base + 11 * csr_size;
+    sc->lmc_csrs.csr_12		        = csr_base + 12 * csr_size;
+    sc->lmc_csrs.csr_13		        = csr_base + 13 * csr_size;
+    sc->lmc_csrs.csr_14		        = csr_base + 14 * csr_size;
+    sc->lmc_csrs.csr_15		        = csr_base + 15 * csr_size;
+    lmc_trace(sc->lmc_device, "lmc_initcsrs out");
+}
+
+static void lmc_driver_timeout(struct net_device *dev) { /*fold00*/
+    lmc_softc_t *sc;
+    u32 csr6;
+    unsigned long flags;
+
+    lmc_trace(dev, "lmc_driver_timeout in");
+
+    sc = dev->priv;
+
+    spin_lock_irqsave(&sc->lmc_lock, flags);
+
+    printk("%s: Xmitter busy|\n", dev->name);
+
+    sc->stats.tx_tbusy_calls++ ;
+    if (jiffies - dev->trans_start < TX_TIMEOUT) {
+        goto bug_out;
+    }
+
+    /*
+     * Chip seems to have locked up
+     * Reset it
+     * This whips out all our decriptor
+     * table and starts from scartch
+     */
+
+    LMC_EVENT_LOG(LMC_EVENT_XMTPRCTMO,
+                  LMC_CSR_READ (sc, csr_status),
+                  sc->stats.tx_ProcTimeout);
+
+    lmc_running_reset (dev);
+
+    LMC_EVENT_LOG(LMC_EVENT_RESET1, LMC_CSR_READ (sc, csr_status), 0);
+    LMC_EVENT_LOG(LMC_EVENT_RESET2,
+                  lmc_mii_readreg (sc, 0, 16),
+                  lmc_mii_readreg (sc, 0, 17));
+
+    /* restart the tx processes */
+    csr6 = LMC_CSR_READ (sc, csr_command);
+    LMC_CSR_WRITE (sc, csr_command, csr6 | 0x0002);
+    LMC_CSR_WRITE (sc, csr_command, csr6 | 0x2002);
+
+    /* immediate transmit */
+    LMC_CSR_WRITE (sc, csr_txpoll, 0);
+
+    sc->stats.tx_errors++;
+    sc->stats.tx_ProcTimeout++;	/* -baz */
+
+    dev->trans_start = jiffies;
+
+bug_out:
+
+    spin_unlock_irqrestore(&sc->lmc_lock, flags);
+
+    lmc_trace(dev, "lmc_driver_timout out");
+
+
+}
diff --git a/drivers/net/wan/lmc/lmc_media.c b/drivers/net/wan/lmc/lmc_media.c
new file mode 100644
index 0000000..f55ce76
--- /dev/null
+++ b/drivers/net/wan/lmc/lmc_media.c
@@ -0,0 +1,1246 @@
+/* $Id: lmc_media.c,v 1.13 2000/04/11 05:25:26 asj Exp $ */
+
+#include <linux/config.h>
+#include <linux/kernel.h>
+#include <linux/string.h>
+#include <linux/timer.h>
+#include <linux/ptrace.h>
+#include <linux/errno.h>
+#include <linux/ioport.h>
+#include <linux/slab.h>
+#include <linux/interrupt.h>
+#include <linux/pci.h>
+#include <linux/in.h>
+#include <linux/if_arp.h>
+#include <linux/netdevice.h>
+#include <linux/etherdevice.h>
+#include <linux/skbuff.h>
+#include <linux/inet.h>
+#include <linux/bitops.h>
+
+#include <net/syncppp.h>
+
+#include <asm/processor.h>             /* Processor type for cache alignment. */
+#include <asm/io.h>
+#include <asm/dma.h>
+
+#include <asm/uaccess.h>
+
+#include "lmc.h"
+#include "lmc_var.h"
+#include "lmc_ioctl.h"
+#include "lmc_debug.h"
+
+#define CONFIG_LMC_IGNORE_HARDWARE_HANDSHAKE 1
+
+ /*
+  * Copyright (c) 1997-2000 LAN Media Corporation (LMC)
+  * All rights reserved.  www.lanmedia.com
+  *
+  * This code is written by:
+  * Andrew Stanley-Jones (asj@cban.com)
+  * Rob Braun (bbraun@vix.com),
+  * Michael Graff (explorer@vix.com) and
+  * Matt Thomas (matt@3am-software.com).
+  *
+  * This software may be used and distributed according to the terms
+  * of the GNU General Public License version 2, incorporated herein by reference.
+  */
+
+/*
+ * For lack of a better place, put the SSI cable stuff here.
+ */
+char *lmc_t1_cables[] = {
+  "V.10/RS423", "EIA530A", "reserved", "X.21", "V.35",
+  "EIA449/EIA530/V.36", "V.28/EIA232", "none", NULL
+};
+
+/*
+ * protocol independent method.
+ */
+static void lmc_set_protocol (lmc_softc_t * const, lmc_ctl_t *);
+
+/*
+ * media independent methods to check on media status, link, light LEDs,
+ * etc.
+ */
+static void lmc_ds3_init (lmc_softc_t * const);
+static void lmc_ds3_default (lmc_softc_t * const);
+static void lmc_ds3_set_status (lmc_softc_t * const, lmc_ctl_t *);
+static void lmc_ds3_set_100ft (lmc_softc_t * const, int);
+static int lmc_ds3_get_link_status (lmc_softc_t * const);
+static void lmc_ds3_set_crc_length (lmc_softc_t * const, int);
+static void lmc_ds3_set_scram (lmc_softc_t * const, int);
+static void lmc_ds3_watchdog (lmc_softc_t * const);
+
+static void lmc_hssi_init (lmc_softc_t * const);
+static void lmc_hssi_default (lmc_softc_t * const);
+static void lmc_hssi_set_status (lmc_softc_t * const, lmc_ctl_t *);
+static void lmc_hssi_set_clock (lmc_softc_t * const, int);
+static int lmc_hssi_get_link_status (lmc_softc_t * const);
+static void lmc_hssi_set_link_status (lmc_softc_t * const, int);
+static void lmc_hssi_set_crc_length (lmc_softc_t * const, int);
+static void lmc_hssi_watchdog (lmc_softc_t * const);
+
+static void lmc_ssi_init (lmc_softc_t * const);
+static void lmc_ssi_default (lmc_softc_t * const);
+static void lmc_ssi_set_status (lmc_softc_t * const, lmc_ctl_t *);
+static void lmc_ssi_set_clock (lmc_softc_t * const, int);
+static void lmc_ssi_set_speed (lmc_softc_t * const, lmc_ctl_t *);
+static int lmc_ssi_get_link_status (lmc_softc_t * const);
+static void lmc_ssi_set_link_status (lmc_softc_t * const, int);
+static void lmc_ssi_set_crc_length (lmc_softc_t * const, int);
+static void lmc_ssi_watchdog (lmc_softc_t * const);
+
+static void lmc_t1_init (lmc_softc_t * const);
+static void lmc_t1_default (lmc_softc_t * const);
+static void lmc_t1_set_status (lmc_softc_t * const, lmc_ctl_t *);
+static int lmc_t1_get_link_status (lmc_softc_t * const);
+static void lmc_t1_set_circuit_type (lmc_softc_t * const, int);
+static void lmc_t1_set_crc_length (lmc_softc_t * const, int);
+static void lmc_t1_set_clock (lmc_softc_t * const, int);
+static void lmc_t1_watchdog (lmc_softc_t * const);
+
+static void lmc_dummy_set_1 (lmc_softc_t * const, int);
+static void lmc_dummy_set2_1 (lmc_softc_t * const, lmc_ctl_t *);
+
+static inline void write_av9110_bit (lmc_softc_t *, int);
+static void write_av9110 (lmc_softc_t *, u_int32_t, u_int32_t, u_int32_t,
+			  u_int32_t, u_int32_t);
+
+lmc_media_t lmc_ds3_media = {
+  lmc_ds3_init,			/* special media init stuff */
+  lmc_ds3_default,		/* reset to default state */
+  lmc_ds3_set_status,		/* reset status to state provided */
+  lmc_dummy_set_1,		/* set clock source */
+  lmc_dummy_set2_1,		/* set line speed */
+  lmc_ds3_set_100ft,		/* set cable length */
+  lmc_ds3_set_scram,		/* set scrambler */
+  lmc_ds3_get_link_status,	/* get link status */
+  lmc_dummy_set_1,		/* set link status */
+  lmc_ds3_set_crc_length,	/* set CRC length */
+  lmc_dummy_set_1,		/* set T1 or E1 circuit type */
+  lmc_ds3_watchdog
+};
+
+lmc_media_t lmc_hssi_media = {
+  lmc_hssi_init,		/* special media init stuff */
+  lmc_hssi_default,		/* reset to default state */
+  lmc_hssi_set_status,		/* reset status to state provided */
+  lmc_hssi_set_clock,		/* set clock source */
+  lmc_dummy_set2_1,		/* set line speed */
+  lmc_dummy_set_1,		/* set cable length */
+  lmc_dummy_set_1,		/* set scrambler */
+  lmc_hssi_get_link_status,	/* get link status */
+  lmc_hssi_set_link_status,	/* set link status */
+  lmc_hssi_set_crc_length,	/* set CRC length */
+  lmc_dummy_set_1,		/* set T1 or E1 circuit type */
+  lmc_hssi_watchdog
+};
+
+lmc_media_t lmc_ssi_media = { lmc_ssi_init,	/* special media init stuff */
+  lmc_ssi_default,		/* reset to default state */
+  lmc_ssi_set_status,		/* reset status to state provided */
+  lmc_ssi_set_clock,		/* set clock source */
+  lmc_ssi_set_speed,		/* set line speed */
+  lmc_dummy_set_1,		/* set cable length */
+  lmc_dummy_set_1,		/* set scrambler */
+  lmc_ssi_get_link_status,	/* get link status */
+  lmc_ssi_set_link_status,	/* set link status */
+  lmc_ssi_set_crc_length,	/* set CRC length */
+  lmc_dummy_set_1,		/* set T1 or E1 circuit type */
+  lmc_ssi_watchdog
+};
+
+lmc_media_t lmc_t1_media = {
+  lmc_t1_init,			/* special media init stuff */
+  lmc_t1_default,		/* reset to default state */
+  lmc_t1_set_status,		/* reset status to state provided */
+  lmc_t1_set_clock,		/* set clock source */
+  lmc_dummy_set2_1,		/* set line speed */
+  lmc_dummy_set_1,		/* set cable length */
+  lmc_dummy_set_1,		/* set scrambler */
+  lmc_t1_get_link_status,	/* get link status */
+  lmc_dummy_set_1,		/* set link status */
+  lmc_t1_set_crc_length,	/* set CRC length */
+  lmc_t1_set_circuit_type,	/* set T1 or E1 circuit type */
+  lmc_t1_watchdog
+};
+
+static void
+lmc_dummy_set_1 (lmc_softc_t * const sc, int a)
+{
+}
+
+static void
+lmc_dummy_set2_1 (lmc_softc_t * const sc, lmc_ctl_t * a)
+{
+}
+
+/*
+ *  HSSI methods
+ */
+
+static void
+lmc_hssi_init (lmc_softc_t * const sc)
+{
+  sc->ictl.cardtype = LMC_CTL_CARDTYPE_LMC5200;
+
+  lmc_gpio_mkoutput (sc, LMC_GEP_HSSI_CLOCK);
+}
+
+static void
+lmc_hssi_default (lmc_softc_t * const sc)
+{
+  sc->lmc_miireg16 = LMC_MII16_LED_ALL;
+
+  sc->lmc_media->set_link_status (sc, LMC_LINK_DOWN);
+  sc->lmc_media->set_clock_source (sc, LMC_CTL_CLOCK_SOURCE_EXT);
+  sc->lmc_media->set_crc_length (sc, LMC_CTL_CRC_LENGTH_16);
+}
+
+/*
+ * Given a user provided state, set ourselves up to match it.  This will
+ * always reset the card if needed.
+ */
+static void
+lmc_hssi_set_status (lmc_softc_t * const sc, lmc_ctl_t * ctl)
+{
+  if (ctl == NULL)
+    {
+      sc->lmc_media->set_clock_source (sc, sc->ictl.clock_source);
+      lmc_set_protocol (sc, NULL);
+
+      return;
+    }
+
+  /*
+   * check for change in clock source
+   */
+  if (ctl->clock_source && !sc->ictl.clock_source)
+    {
+      sc->lmc_media->set_clock_source (sc, LMC_CTL_CLOCK_SOURCE_INT);
+      sc->lmc_timing = LMC_CTL_CLOCK_SOURCE_INT;
+    }
+  else if (!ctl->clock_source && sc->ictl.clock_source)
+    {
+      sc->lmc_timing = LMC_CTL_CLOCK_SOURCE_EXT;
+      sc->lmc_media->set_clock_source (sc, LMC_CTL_CLOCK_SOURCE_EXT);
+    }
+
+  lmc_set_protocol (sc, ctl);
+}
+
+/*
+ * 1 == internal, 0 == external
+ */
+static void
+lmc_hssi_set_clock (lmc_softc_t * const sc, int ie)
+{
+  int old;
+  old = sc->ictl.clock_source;
+  if (ie == LMC_CTL_CLOCK_SOURCE_EXT)
+    {
+      sc->lmc_gpio |= LMC_GEP_HSSI_CLOCK;
+      LMC_CSR_WRITE (sc, csr_gp, sc->lmc_gpio);
+      sc->ictl.clock_source = LMC_CTL_CLOCK_SOURCE_EXT;
+      if(old != ie)
+        printk (LMC_PRINTF_FMT ": clock external\n", LMC_PRINTF_ARGS);
+    }
+  else
+    {
+      sc->lmc_gpio &= ~(LMC_GEP_HSSI_CLOCK);
+      LMC_CSR_WRITE (sc, csr_gp, sc->lmc_gpio);
+      sc->ictl.clock_source = LMC_CTL_CLOCK_SOURCE_INT;
+      if(old != ie)
+        printk (LMC_PRINTF_FMT ": clock internal\n", LMC_PRINTF_ARGS);
+    }
+}
+
+/*
+ * return hardware link status.
+ * 0 == link is down, 1 == link is up.
+ */
+static int
+lmc_hssi_get_link_status (lmc_softc_t * const sc)
+{
+    /*
+     * We're using the same code as SSI since
+     * they're practically the same
+     */
+    return lmc_ssi_get_link_status(sc);
+}
+
+static void
+lmc_hssi_set_link_status (lmc_softc_t * const sc, int state)
+{
+  if (state == LMC_LINK_UP)
+    sc->lmc_miireg16 |= LMC_MII16_HSSI_TA;
+  else
+    sc->lmc_miireg16 &= ~LMC_MII16_HSSI_TA;
+
+  lmc_mii_writereg (sc, 0, 16, sc->lmc_miireg16);
+}
+
+/*
+ * 0 == 16bit, 1 == 32bit
+ */
+static void
+lmc_hssi_set_crc_length (lmc_softc_t * const sc, int state)
+{
+  if (state == LMC_CTL_CRC_LENGTH_32)
+    {
+      /* 32 bit */
+      sc->lmc_miireg16 |= LMC_MII16_HSSI_CRC;
+      sc->ictl.crc_length = LMC_CTL_CRC_LENGTH_32;
+    }
+  else
+    {
+      /* 16 bit */
+      sc->lmc_miireg16 &= ~LMC_MII16_HSSI_CRC;
+      sc->ictl.crc_length = LMC_CTL_CRC_LENGTH_16;
+    }
+
+  lmc_mii_writereg (sc, 0, 16, sc->lmc_miireg16);
+}
+
+static void
+lmc_hssi_watchdog (lmc_softc_t * const sc)
+{
+  /* HSSI is blank */
+}
+
+/*
+ *  DS3 methods
+ */
+
+/*
+ * Set cable length
+ */
+static void
+lmc_ds3_set_100ft (lmc_softc_t * const sc, int ie)
+{
+  if (ie == LMC_CTL_CABLE_LENGTH_GT_100FT)
+    {
+      sc->lmc_miireg16 &= ~LMC_MII16_DS3_ZERO;
+      sc->ictl.cable_length = LMC_CTL_CABLE_LENGTH_GT_100FT;
+    }
+  else if (ie == LMC_CTL_CABLE_LENGTH_LT_100FT)
+    {
+      sc->lmc_miireg16 |= LMC_MII16_DS3_ZERO;
+      sc->ictl.cable_length = LMC_CTL_CABLE_LENGTH_LT_100FT;
+    }
+  lmc_mii_writereg (sc, 0, 16, sc->lmc_miireg16);
+}
+
+static void
+lmc_ds3_default (lmc_softc_t * const sc)
+{
+  sc->lmc_miireg16 = LMC_MII16_LED_ALL;
+
+  sc->lmc_media->set_link_status (sc, LMC_LINK_DOWN);
+  sc->lmc_media->set_cable_length (sc, LMC_CTL_CABLE_LENGTH_LT_100FT);
+  sc->lmc_media->set_scrambler (sc, LMC_CTL_OFF);
+  sc->lmc_media->set_crc_length (sc, LMC_CTL_CRC_LENGTH_16);
+}
+
+/*
+ * Given a user provided state, set ourselves up to match it.  This will
+ * always reset the card if needed.
+ */
+static void
+lmc_ds3_set_status (lmc_softc_t * const sc, lmc_ctl_t * ctl)
+{
+  if (ctl == NULL)
+    {
+      sc->lmc_media->set_cable_length (sc, sc->ictl.cable_length);
+      sc->lmc_media->set_scrambler (sc, sc->ictl.scrambler_onoff);
+      lmc_set_protocol (sc, NULL);
+
+      return;
+    }
+
+  /*
+   * check for change in cable length setting
+   */
+  if (ctl->cable_length && !sc->ictl.cable_length)
+    lmc_ds3_set_100ft (sc, LMC_CTL_CABLE_LENGTH_GT_100FT);
+  else if (!ctl->cable_length && sc->ictl.cable_length)
+    lmc_ds3_set_100ft (sc, LMC_CTL_CABLE_LENGTH_LT_100FT);
+
+  /*
+   * Check for change in scrambler setting (requires reset)
+   */
+  if (ctl->scrambler_onoff && !sc->ictl.scrambler_onoff)
+    lmc_ds3_set_scram (sc, LMC_CTL_ON);
+  else if (!ctl->scrambler_onoff && sc->ictl.scrambler_onoff)
+    lmc_ds3_set_scram (sc, LMC_CTL_OFF);
+
+  lmc_set_protocol (sc, ctl);
+}
+
+static void
+lmc_ds3_init (lmc_softc_t * const sc)
+{
+  int i;
+
+  sc->ictl.cardtype = LMC_CTL_CARDTYPE_LMC5245;
+
+  /* writes zeros everywhere */
+  for (i = 0; i < 21; i++)
+    {
+      lmc_mii_writereg (sc, 0, 17, i);
+      lmc_mii_writereg (sc, 0, 18, 0);
+    }
+
+  /* set some essential bits */
+  lmc_mii_writereg (sc, 0, 17, 1);
+  lmc_mii_writereg (sc, 0, 18, 0x25);	/* ser, xtx */
+
+  lmc_mii_writereg (sc, 0, 17, 5);
+  lmc_mii_writereg (sc, 0, 18, 0x80);	/* emode */
+
+  lmc_mii_writereg (sc, 0, 17, 14);
+  lmc_mii_writereg (sc, 0, 18, 0x30);	/* rcgen, tcgen */
+
+  /* clear counters and latched bits */
+  for (i = 0; i < 21; i++)
+    {
+      lmc_mii_writereg (sc, 0, 17, i);
+      lmc_mii_readreg (sc, 0, 18);
+    }
+}
+
+/*
+ * 1 == DS3 payload scrambled, 0 == not scrambled
+ */
+static void
+lmc_ds3_set_scram (lmc_softc_t * const sc, int ie)
+{
+  if (ie == LMC_CTL_ON)
+    {
+      sc->lmc_miireg16 |= LMC_MII16_DS3_SCRAM;
+      sc->ictl.scrambler_onoff = LMC_CTL_ON;
+    }
+  else
+    {
+      sc->lmc_miireg16 &= ~LMC_MII16_DS3_SCRAM;
+      sc->ictl.scrambler_onoff = LMC_CTL_OFF;
+    }
+  lmc_mii_writereg (sc, 0, 16, sc->lmc_miireg16);
+}
+
+/*
+ * return hardware link status.
+ * 0 == link is down, 1 == link is up.
+ */
+static int
+lmc_ds3_get_link_status (lmc_softc_t * const sc)
+{
+    u_int16_t link_status, link_status_11;
+    int ret = 1;
+
+    lmc_mii_writereg (sc, 0, 17, 7);
+    link_status = lmc_mii_readreg (sc, 0, 18);
+
+    /* LMC5245 (DS3) & LMC1200 (DS1) LED definitions
+     * led0 yellow = far-end adapter is in Red alarm condition
+     * led1 blue   = received an Alarm Indication signal
+     *               (upstream failure)
+     * led2 Green  = power to adapter, Gate Array loaded & driver
+     *               attached
+     * led3 red    = Loss of Signal (LOS) or out of frame (OOF)
+     *               conditions detected on T3 receive signal
+     */
+
+    lmc_led_on(sc, LMC_DS3_LED2);
+
+    if ((link_status & LMC_FRAMER_REG0_DLOS) ||
+        (link_status & LMC_FRAMER_REG0_OOFS)){
+        ret = 0;
+        if(sc->last_led_err[3] != 1){
+            u16 r1;
+            lmc_mii_writereg (sc, 0, 17, 01); /* Turn on Xbit error as our cisco does */
+            r1 = lmc_mii_readreg (sc, 0, 18);
+            r1 &= 0xfe;
+            lmc_mii_writereg(sc, 0, 18, r1);
+            printk(KERN_WARNING "%s: Red Alarm - Loss of Signal or Loss of Framing\n", sc->name);
+        }
+        lmc_led_on(sc, LMC_DS3_LED3);	/* turn on red LED */
+        sc->last_led_err[3] = 1;
+    }
+    else {
+        lmc_led_off(sc, LMC_DS3_LED3);	/* turn on red LED */
+        if(sc->last_led_err[3] == 1){
+            u16 r1;
+            lmc_mii_writereg (sc, 0, 17, 01); /* Turn off Xbit error */
+            r1 = lmc_mii_readreg (sc, 0, 18);
+            r1 |= 0x01;
+            lmc_mii_writereg(sc, 0, 18, r1);
+        }
+        sc->last_led_err[3] = 0;
+    }
+
+    lmc_mii_writereg(sc, 0, 17, 0x10);
+    link_status_11 = lmc_mii_readreg(sc, 0, 18);
+    if((link_status & LMC_FRAMER_REG0_AIS) ||
+       (link_status_11 & LMC_FRAMER_REG10_XBIT)) {
+        ret = 0;
+        if(sc->last_led_err[0] != 1){
+            printk(KERN_WARNING "%s: AIS Alarm or XBit Error\n", sc->name);
+            printk(KERN_WARNING "%s: Remote end has loss of signal or framing\n", sc->name);
+        }
+        lmc_led_on(sc, LMC_DS3_LED0);
+        sc->last_led_err[0] = 1;
+    }
+    else {
+        lmc_led_off(sc, LMC_DS3_LED0);
+        sc->last_led_err[0] = 0;
+    }
+
+    lmc_mii_writereg (sc, 0, 17, 9);
+    link_status = lmc_mii_readreg (sc, 0, 18);
+    
+    if(link_status & LMC_FRAMER_REG9_RBLUE){
+        ret = 0;
+        if(sc->last_led_err[1] != 1){
+            printk(KERN_WARNING "%s: Blue Alarm - Receiving all 1's\n", sc->name);
+        }
+        lmc_led_on(sc, LMC_DS3_LED1);
+        sc->last_led_err[1] = 1;
+    }
+    else {
+        lmc_led_off(sc, LMC_DS3_LED1);
+        sc->last_led_err[1] = 0;
+    }
+
+    return ret;
+}
+
+/*
+ * 0 == 16bit, 1 == 32bit
+ */
+static void
+lmc_ds3_set_crc_length (lmc_softc_t * const sc, int state)
+{
+  if (state == LMC_CTL_CRC_LENGTH_32)
+    {
+      /* 32 bit */
+      sc->lmc_miireg16 |= LMC_MII16_DS3_CRC;
+      sc->ictl.crc_length = LMC_CTL_CRC_LENGTH_32;
+    }
+  else
+    {
+      /* 16 bit */
+      sc->lmc_miireg16 &= ~LMC_MII16_DS3_CRC;
+      sc->ictl.crc_length = LMC_CTL_CRC_LENGTH_16;
+    }
+
+  lmc_mii_writereg (sc, 0, 16, sc->lmc_miireg16);
+}
+
+static void
+lmc_ds3_watchdog (lmc_softc_t * const sc)
+{
+    
+}
+
+
+/*
+ *  SSI methods
+ */
+
+static void
+lmc_ssi_init (lmc_softc_t * const sc)
+{
+  u_int16_t mii17;
+  int cable;
+
+  sc->ictl.cardtype = LMC_CTL_CARDTYPE_LMC1000;
+
+  mii17 = lmc_mii_readreg (sc, 0, 17);
+
+  cable = (mii17 & LMC_MII17_SSI_CABLE_MASK) >> LMC_MII17_SSI_CABLE_SHIFT;
+  sc->ictl.cable_type = cable;
+
+  lmc_gpio_mkoutput (sc, LMC_GEP_SSI_TXCLOCK);
+}
+
+static void
+lmc_ssi_default (lmc_softc_t * const sc)
+{
+  sc->lmc_miireg16 = LMC_MII16_LED_ALL;
+
+  /*
+   * make TXCLOCK always be an output
+   */
+  lmc_gpio_mkoutput (sc, LMC_GEP_SSI_TXCLOCK);
+
+  sc->lmc_media->set_link_status (sc, LMC_LINK_DOWN);
+  sc->lmc_media->set_clock_source (sc, LMC_CTL_CLOCK_SOURCE_EXT);
+  sc->lmc_media->set_speed (sc, NULL);
+  sc->lmc_media->set_crc_length (sc, LMC_CTL_CRC_LENGTH_16);
+}
+
+/*
+ * Given a user provided state, set ourselves up to match it.  This will
+ * always reset the card if needed.
+ */
+static void
+lmc_ssi_set_status (lmc_softc_t * const sc, lmc_ctl_t * ctl)
+{
+  if (ctl == NULL)
+    {
+      sc->lmc_media->set_clock_source (sc, sc->ictl.clock_source);
+      sc->lmc_media->set_speed (sc, &sc->ictl);
+      lmc_set_protocol (sc, NULL);
+
+      return;
+    }
+
+  /*
+   * check for change in clock source
+   */
+  if (ctl->clock_source == LMC_CTL_CLOCK_SOURCE_INT
+      && sc->ictl.clock_source == LMC_CTL_CLOCK_SOURCE_EXT)
+    {
+      sc->lmc_media->set_clock_source (sc, LMC_CTL_CLOCK_SOURCE_INT);
+      sc->lmc_timing = LMC_CTL_CLOCK_SOURCE_INT;
+    }
+  else if (ctl->clock_source == LMC_CTL_CLOCK_SOURCE_EXT
+	   && sc->ictl.clock_source == LMC_CTL_CLOCK_SOURCE_INT)
+    {
+      sc->lmc_media->set_clock_source (sc, LMC_CTL_CLOCK_SOURCE_EXT);
+      sc->lmc_timing = LMC_CTL_CLOCK_SOURCE_EXT;
+    }
+
+  if (ctl->clock_rate != sc->ictl.clock_rate)
+    sc->lmc_media->set_speed (sc, ctl);
+
+  lmc_set_protocol (sc, ctl);
+}
+
+/*
+ * 1 == internal, 0 == external
+ */
+static void
+lmc_ssi_set_clock (lmc_softc_t * const sc, int ie)
+{
+  int old;
+  old = ie;
+  if (ie == LMC_CTL_CLOCK_SOURCE_EXT)
+    {
+      sc->lmc_gpio &= ~(LMC_GEP_SSI_TXCLOCK);
+      LMC_CSR_WRITE (sc, csr_gp, sc->lmc_gpio);
+      sc->ictl.clock_source = LMC_CTL_CLOCK_SOURCE_EXT;
+      if(ie != old)
+        printk (LMC_PRINTF_FMT ": clock external\n", LMC_PRINTF_ARGS);
+    }
+  else
+    {
+      sc->lmc_gpio |= LMC_GEP_SSI_TXCLOCK;
+      LMC_CSR_WRITE (sc, csr_gp, sc->lmc_gpio);
+      sc->ictl.clock_source = LMC_CTL_CLOCK_SOURCE_INT;
+      if(ie != old)
+        printk (LMC_PRINTF_FMT ": clock internal\n", LMC_PRINTF_ARGS);
+    }
+}
+
+static void
+lmc_ssi_set_speed (lmc_softc_t * const sc, lmc_ctl_t * ctl)
+{
+  lmc_ctl_t *ictl = &sc->ictl;
+  lmc_av9110_t *av;
+
+  /* original settings for clock rate of:
+   *  100 Khz (8,25,0,0,2) were incorrect
+   *  they should have been 80,125,1,3,3
+   *  There are 17 param combinations to produce this freq.
+   *  For 1.5 Mhz use 120,100,1,1,2 (226 param. combinations)
+   */
+  if (ctl == NULL)
+    {
+      av = &ictl->cardspec.ssi;
+      ictl->clock_rate = 1500000;
+      av->f = ictl->clock_rate;
+      av->n = 120;
+      av->m = 100;
+      av->v = 1;
+      av->x = 1;
+      av->r = 2;
+
+      write_av9110 (sc, av->n, av->m, av->v, av->x, av->r);
+      return;
+    }
+
+  av = &ctl->cardspec.ssi;
+
+  if (av->f == 0)
+    return;
+
+  ictl->clock_rate = av->f;	/* really, this is the rate we are */
+  ictl->cardspec.ssi = *av;
+
+  write_av9110 (sc, av->n, av->m, av->v, av->x, av->r);
+}
+
+/*
+ * return hardware link status.
+ * 0 == link is down, 1 == link is up.
+ */
+static int
+lmc_ssi_get_link_status (lmc_softc_t * const sc)
+{
+  u_int16_t link_status;
+  u_int32_t ticks;
+  int ret = 1;
+  int hw_hdsk = 1;
+  
+  /*
+   * missing CTS?  Hmm.  If we require CTS on, we may never get the
+   * link to come up, so omit it in this test.
+   *
+   * Also, it seems that with a loopback cable, DCD isn't asserted,
+   * so just check for things like this:
+   *      DSR _must_ be asserted.
+   *      One of DCD or CTS must be asserted.
+   */
+
+  /* LMC 1000 (SSI) LED definitions
+   * led0 Green = power to adapter, Gate Array loaded &
+   *              driver attached
+   * led1 Green = DSR and DTR and RTS and CTS are set
+   * led2 Green = Cable detected
+   * led3 red   = No timing is available from the
+   *              cable or the on-board frequency
+   *              generator.
+   */
+
+  link_status = lmc_mii_readreg (sc, 0, 16);
+
+  /* Is the transmit clock still available */
+  ticks = LMC_CSR_READ (sc, csr_gp_timer);
+  ticks = 0x0000ffff - (ticks & 0x0000ffff);
+
+  lmc_led_on (sc, LMC_MII16_LED0);
+
+  /* ====== transmit clock determination ===== */
+  if (sc->lmc_timing == LMC_CTL_CLOCK_SOURCE_INT) {
+      lmc_led_off(sc, LMC_MII16_LED3);
+  }
+  else if (ticks == 0 ) {				/* no clock found ? */
+      ret = 0;
+      if(sc->last_led_err[3] != 1){
+          sc->stats.tx_lossOfClockCnt++;
+          printk(KERN_WARNING "%s: Lost Clock, Link Down\n", sc->name);
+      }
+      sc->last_led_err[3] = 1;
+      lmc_led_on (sc, LMC_MII16_LED3);	/* turn ON red LED */
+  }
+  else {
+      if(sc->last_led_err[3] == 1)
+          printk(KERN_WARNING "%s: Clock Returned\n", sc->name);
+      sc->last_led_err[3] = 0;
+      lmc_led_off (sc, LMC_MII16_LED3);		/* turn OFF red LED */
+  }
+
+  if ((link_status & LMC_MII16_SSI_DSR) == 0) { /* Also HSSI CA */
+      ret = 0;
+      hw_hdsk = 0;
+  }
+
+#ifdef CONFIG_LMC_IGNORE_HARDWARE_HANDSHAKE
+  if ((link_status & (LMC_MII16_SSI_CTS | LMC_MII16_SSI_DCD)) == 0){
+      ret = 0;
+      hw_hdsk = 0;
+  }
+#endif
+
+  if(hw_hdsk == 0){
+      if(sc->last_led_err[1] != 1)
+          printk(KERN_WARNING "%s: DSR not asserted\n", sc->name);
+      sc->last_led_err[1] = 1;
+      lmc_led_off(sc, LMC_MII16_LED1);
+  }
+  else {
+      if(sc->last_led_err[1] != 0)
+          printk(KERN_WARNING "%s: DSR now asserted\n", sc->name);
+      sc->last_led_err[1] = 0;
+      lmc_led_on(sc, LMC_MII16_LED1);
+  }
+
+  if(ret == 1) {
+      lmc_led_on(sc, LMC_MII16_LED2); /* Over all good status? */
+  }
+  
+  return ret;
+}
+
+static void
+lmc_ssi_set_link_status (lmc_softc_t * const sc, int state)
+{
+  if (state == LMC_LINK_UP)
+    {
+      sc->lmc_miireg16 |= (LMC_MII16_SSI_DTR | LMC_MII16_SSI_RTS);
+      printk (LMC_PRINTF_FMT ": asserting DTR and RTS\n", LMC_PRINTF_ARGS);
+    }
+  else
+    {
+      sc->lmc_miireg16 &= ~(LMC_MII16_SSI_DTR | LMC_MII16_SSI_RTS);
+      printk (LMC_PRINTF_FMT ": deasserting DTR and RTS\n", LMC_PRINTF_ARGS);
+    }
+
+  lmc_mii_writereg (sc, 0, 16, sc->lmc_miireg16);
+
+}
+
+/*
+ * 0 == 16bit, 1 == 32bit
+ */
+static void
+lmc_ssi_set_crc_length (lmc_softc_t * const sc, int state)
+{
+  if (state == LMC_CTL_CRC_LENGTH_32)
+    {
+      /* 32 bit */
+      sc->lmc_miireg16 |= LMC_MII16_SSI_CRC;
+      sc->ictl.crc_length = LMC_CTL_CRC_LENGTH_32;
+      sc->lmc_crcSize = LMC_CTL_CRC_BYTESIZE_4;
+
+    }
+  else
+    {
+      /* 16 bit */
+      sc->lmc_miireg16 &= ~LMC_MII16_SSI_CRC;
+      sc->ictl.crc_length = LMC_CTL_CRC_LENGTH_16;
+      sc->lmc_crcSize = LMC_CTL_CRC_BYTESIZE_2;
+    }
+
+  lmc_mii_writereg (sc, 0, 16, sc->lmc_miireg16);
+}
+
+/*
+ * These are bits to program the ssi frequency generator
+ */
+static inline void
+write_av9110_bit (lmc_softc_t * sc, int c)
+{
+  /*
+   * set the data bit as we need it.
+   */
+  sc->lmc_gpio &= ~(LMC_GEP_CLK);
+  if (c & 0x01)
+    sc->lmc_gpio |= LMC_GEP_DATA;
+  else
+    sc->lmc_gpio &= ~(LMC_GEP_DATA);
+  LMC_CSR_WRITE (sc, csr_gp, sc->lmc_gpio);
+
+  /*
+   * set the clock to high
+   */
+  sc->lmc_gpio |= LMC_GEP_CLK;
+  LMC_CSR_WRITE (sc, csr_gp, sc->lmc_gpio);
+
+  /*
+   * set the clock to low again.
+   */
+  sc->lmc_gpio &= ~(LMC_GEP_CLK);
+  LMC_CSR_WRITE (sc, csr_gp, sc->lmc_gpio);
+}
+
+static void
+write_av9110 (lmc_softc_t * sc, u_int32_t n, u_int32_t m, u_int32_t v,
+	      u_int32_t x, u_int32_t r)
+{
+  int i;
+
+#if 0
+  printk (LMC_PRINTF_FMT ": speed %u, %d %d %d %d %d\n",
+	  LMC_PRINTF_ARGS, sc->ictl.clock_rate, n, m, v, x, r);
+#endif
+
+  sc->lmc_gpio |= LMC_GEP_SSI_GENERATOR;
+  sc->lmc_gpio &= ~(LMC_GEP_DATA | LMC_GEP_CLK);
+  LMC_CSR_WRITE (sc, csr_gp, sc->lmc_gpio);
+
+  /*
+   * Set the TXCLOCK, GENERATOR, SERIAL, and SERIALCLK
+   * as outputs.
+   */
+  lmc_gpio_mkoutput (sc, (LMC_GEP_DATA | LMC_GEP_CLK
+			  | LMC_GEP_SSI_GENERATOR));
+
+  sc->lmc_gpio &= ~(LMC_GEP_SSI_GENERATOR);
+  LMC_CSR_WRITE (sc, csr_gp, sc->lmc_gpio);
+
+  /*
+   * a shifting we will go...
+   */
+  for (i = 0; i < 7; i++)
+    write_av9110_bit (sc, n >> i);
+  for (i = 0; i < 7; i++)
+    write_av9110_bit (sc, m >> i);
+  for (i = 0; i < 1; i++)
+    write_av9110_bit (sc, v >> i);
+  for (i = 0; i < 2; i++)
+    write_av9110_bit (sc, x >> i);
+  for (i = 0; i < 2; i++)
+    write_av9110_bit (sc, r >> i);
+  for (i = 0; i < 5; i++)
+    write_av9110_bit (sc, 0x17 >> i);
+
+  /*
+   * stop driving serial-related signals
+   */
+  lmc_gpio_mkinput (sc,
+		    (LMC_GEP_DATA | LMC_GEP_CLK
+		     | LMC_GEP_SSI_GENERATOR));
+}
+
+static void
+lmc_ssi_watchdog (lmc_softc_t * const sc)
+{
+  u_int16_t mii17;
+  struct ssicsr2
+  {
+    unsigned short dtr:1, dsr:1, rts:1, cable:3, crc:1, led0:1, led1:1,
+      led2:1, led3:1, fifo:1, ll:1, rl:1, tm:1, loop:1;
+  };
+  struct ssicsr2 *ssicsr;
+  mii17 = lmc_mii_readreg (sc, 0, 17);
+  ssicsr = (struct ssicsr2 *) &mii17;
+  if (ssicsr->cable == 7)
+    {
+      lmc_led_off (sc, LMC_MII16_LED2);
+    }
+  else
+    {
+      lmc_led_on (sc, LMC_MII16_LED2);
+    }
+
+}
+
+/*
+ *  T1 methods
+ */
+
+/*
+ * The framer regs are multiplexed through MII regs 17 & 18
+ *  write the register address to MII reg 17 and the *  data to MII reg 18. */
+static void
+lmc_t1_write (lmc_softc_t * const sc, int a, int d)
+{
+  lmc_mii_writereg (sc, 0, 17, a);
+  lmc_mii_writereg (sc, 0, 18, d);
+}
+
+/* Save a warning
+static int
+lmc_t1_read (lmc_softc_t * const sc, int a)
+{
+  lmc_mii_writereg (sc, 0, 17, a);
+  return lmc_mii_readreg (sc, 0, 18);
+}
+*/
+
+
+static void
+lmc_t1_init (lmc_softc_t * const sc)
+{
+  u_int16_t mii16;
+  int i;
+
+  sc->ictl.cardtype = LMC_CTL_CARDTYPE_LMC1200;
+  mii16 = lmc_mii_readreg (sc, 0, 16);
+
+  /* reset 8370 */
+  mii16 &= ~LMC_MII16_T1_RST;
+  lmc_mii_writereg (sc, 0, 16, mii16 | LMC_MII16_T1_RST);
+  lmc_mii_writereg (sc, 0, 16, mii16);
+
+  /* set T1 or E1 line.  Uses sc->lmcmii16 reg in function so update it */
+  sc->lmc_miireg16 = mii16;
+  lmc_t1_set_circuit_type(sc, LMC_CTL_CIRCUIT_TYPE_T1);
+  mii16 = sc->lmc_miireg16;
+
+  lmc_t1_write (sc, 0x01, 0x1B);	/* CR0     - primary control             */
+  lmc_t1_write (sc, 0x02, 0x42);	/* JAT_CR  - jitter atten config         */
+  lmc_t1_write (sc, 0x14, 0x00);	/* LOOP    - loopback config             */
+  lmc_t1_write (sc, 0x15, 0x00);	/* DL3_TS  - external data link timeslot */
+  lmc_t1_write (sc, 0x18, 0xFF);	/* PIO     - programmable I/O            */
+  lmc_t1_write (sc, 0x19, 0x30);	/* POE     - programmable OE             */
+  lmc_t1_write (sc, 0x1A, 0x0F);	/* CMUX    - clock input mux             */
+  lmc_t1_write (sc, 0x20, 0x41);	/* LIU_CR  - RX LIU config               */
+  lmc_t1_write (sc, 0x22, 0x76);	/* RLIU_CR - RX LIU config               */
+  lmc_t1_write (sc, 0x40, 0x03);	/* RCR0    - RX config                   */
+  lmc_t1_write (sc, 0x45, 0x00);	/* RALM    - RX alarm config             */
+  lmc_t1_write (sc, 0x46, 0x05);	/* LATCH   - RX alarm/err/cntr latch     */
+  lmc_t1_write (sc, 0x68, 0x40);	/* TLIU_CR - TX LIU config               */
+  lmc_t1_write (sc, 0x70, 0x0D);	/* TCR0    - TX framer config            */
+  lmc_t1_write (sc, 0x71, 0x05);	/* TCR1    - TX config                   */
+  lmc_t1_write (sc, 0x72, 0x0B);	/* TFRM    - TX frame format             */
+  lmc_t1_write (sc, 0x73, 0x00);	/* TERROR  - TX error insert             */
+  lmc_t1_write (sc, 0x74, 0x00);	/* TMAN    - TX manual Sa/FEBE config    */
+  lmc_t1_write (sc, 0x75, 0x00);	/* TALM    - TX alarm signal config      */
+  lmc_t1_write (sc, 0x76, 0x00);	/* TPATT   - TX test pattern config      */
+  lmc_t1_write (sc, 0x77, 0x00);	/* TLB     - TX inband loopback config   */
+  lmc_t1_write (sc, 0x90, 0x05);	/* CLAD_CR - clock rate adapter config   */
+  lmc_t1_write (sc, 0x91, 0x05);	/* CSEL    - clad freq sel               */
+  lmc_t1_write (sc, 0xA6, 0x00);	/* DL1_CTL - DL1 control                 */
+  lmc_t1_write (sc, 0xB1, 0x00);	/* DL2_CTL - DL2 control                 */
+  lmc_t1_write (sc, 0xD0, 0x47);	/* SBI_CR  - sys bus iface config        */
+  lmc_t1_write (sc, 0xD1, 0x70);	/* RSB_CR  - RX sys bus config           */
+  lmc_t1_write (sc, 0xD4, 0x30);	/* TSB_CR  - TX sys bus config           */
+  for (i = 0; i < 32; i++)
+    {
+      lmc_t1_write (sc, 0x0E0 + i, 0x00);	/* SBCn - sys bus per-channel ctl    */
+      lmc_t1_write (sc, 0x100 + i, 0x00);	/* TPCn - TX per-channel ctl         */
+      lmc_t1_write (sc, 0x180 + i, 0x00);	/* RPCn - RX per-channel ctl         */
+    }
+  for (i = 1; i < 25; i++)
+    {
+      lmc_t1_write (sc, 0x0E0 + i, 0x0D);	/* SBCn - sys bus per-channel ctl    */
+    }
+
+  mii16 |= LMC_MII16_T1_XOE;
+  lmc_mii_writereg (sc, 0, 16, mii16);
+  sc->lmc_miireg16 = mii16;
+}
+
+static void
+lmc_t1_default (lmc_softc_t * const sc)
+{
+  sc->lmc_miireg16 = LMC_MII16_LED_ALL;
+  sc->lmc_media->set_link_status (sc, LMC_LINK_DOWN);
+  sc->lmc_media->set_circuit_type (sc, LMC_CTL_CIRCUIT_TYPE_T1);
+  sc->lmc_media->set_crc_length (sc, LMC_CTL_CRC_LENGTH_16);
+  /* Right now we can only clock from out internal source */
+  sc->ictl.clock_source = LMC_CTL_CLOCK_SOURCE_INT;
+}
+/* * Given a user provided state, set ourselves up to match it.  This will * always reset the card if needed.
+ */
+static void
+lmc_t1_set_status (lmc_softc_t * const sc, lmc_ctl_t * ctl)
+{
+  if (ctl == NULL)
+    {
+      sc->lmc_media->set_circuit_type (sc, sc->ictl.circuit_type);
+      lmc_set_protocol (sc, NULL);
+
+      return;
+    }
+  /*
+   * check for change in circuit type         */
+  if (ctl->circuit_type == LMC_CTL_CIRCUIT_TYPE_T1
+      && sc->ictl.circuit_type ==
+      LMC_CTL_CIRCUIT_TYPE_E1) sc->lmc_media->set_circuit_type (sc,
+								LMC_CTL_CIRCUIT_TYPE_E1);
+  else if (ctl->circuit_type == LMC_CTL_CIRCUIT_TYPE_E1
+	   && sc->ictl.circuit_type == LMC_CTL_CIRCUIT_TYPE_T1)
+    sc->lmc_media->set_circuit_type (sc, LMC_CTL_CIRCUIT_TYPE_T1);
+  lmc_set_protocol (sc, ctl);
+}
+/*
+ * return hardware link status.
+ * 0 == link is down, 1 == link is up.
+ */ static int
+lmc_t1_get_link_status (lmc_softc_t * const sc)
+{
+    u_int16_t link_status;
+    int ret = 1;
+
+  /* LMC5245 (DS3) & LMC1200 (DS1) LED definitions
+   * led0 yellow = far-end adapter is in Red alarm condition
+   * led1 blue   = received an Alarm Indication signal
+   *               (upstream failure)
+   * led2 Green  = power to adapter, Gate Array loaded & driver
+   *               attached
+   * led3 red    = Loss of Signal (LOS) or out of frame (OOF)
+   *               conditions detected on T3 receive signal
+   */
+    lmc_trace(sc->lmc_device, "lmc_t1_get_link_status in");
+    lmc_led_on(sc, LMC_DS3_LED2);
+
+    lmc_mii_writereg (sc, 0, 17, T1FRAMER_ALARM1_STATUS);
+    link_status = lmc_mii_readreg (sc, 0, 18);
+
+
+    if (link_status & T1F_RAIS) {			/* turn on blue LED */
+        ret = 0;
+        if(sc->last_led_err[1] != 1){
+            printk(KERN_WARNING "%s: Receive AIS/Blue Alarm. Far end in RED alarm\n", sc->name);
+        }
+        lmc_led_on(sc, LMC_DS3_LED1);
+        sc->last_led_err[1] = 1;
+    }
+    else {
+        if(sc->last_led_err[1] != 0){
+            printk(KERN_WARNING "%s: End AIS/Blue Alarm\n", sc->name);
+        }
+        lmc_led_off (sc, LMC_DS3_LED1);
+        sc->last_led_err[1] = 0;
+    }
+
+    /*
+     * Yellow Alarm is nasty evil stuff, looks at data patterns
+     * inside the channel and confuses it with HDLC framing
+     * ignore all yellow alarms.
+     *
+     * Do listen to MultiFrame Yellow alarm which while implemented
+     * different ways isn't in the channel and hence somewhat
+     * more reliable
+     */
+
+    if (link_status & T1F_RMYEL) {
+        ret = 0;
+        if(sc->last_led_err[0] != 1){
+            printk(KERN_WARNING "%s: Receive Yellow AIS Alarm\n", sc->name);
+        }
+        lmc_led_on(sc, LMC_DS3_LED0);
+        sc->last_led_err[0] = 1;
+    }
+    else {
+        if(sc->last_led_err[0] != 0){
+            printk(KERN_WARNING "%s: End of Yellow AIS Alarm\n", sc->name);
+        }
+        lmc_led_off(sc, LMC_DS3_LED0);
+        sc->last_led_err[0] = 0;
+    }
+
+    /*
+     * Loss of signal and los of frame
+     * Use the green bit to identify which one lit the led
+     */
+    if(link_status & T1F_RLOF){
+        ret = 0;
+        if(sc->last_led_err[3] != 1){
+            printk(KERN_WARNING "%s: Local Red Alarm: Loss of Framing\n", sc->name);
+        }
+        lmc_led_on(sc, LMC_DS3_LED3);
+        sc->last_led_err[3] = 1;
+
+    }
+    else {
+        if(sc->last_led_err[3] != 0){
+            printk(KERN_WARNING "%s: End Red Alarm (LOF)\n", sc->name);
+        }
+        if( ! (link_status & T1F_RLOS))
+            lmc_led_off(sc, LMC_DS3_LED3);
+        sc->last_led_err[3] = 0;
+    }
+    
+    if(link_status & T1F_RLOS){
+        ret = 0;
+        if(sc->last_led_err[2] != 1){
+            printk(KERN_WARNING "%s: Local Red Alarm: Loss of Signal\n", sc->name);
+        }
+        lmc_led_on(sc, LMC_DS3_LED3);
+        sc->last_led_err[2] = 1;
+
+    }
+    else {
+        if(sc->last_led_err[2] != 0){
+            printk(KERN_WARNING "%s: End Red Alarm (LOS)\n", sc->name);
+        }
+        if( ! (link_status & T1F_RLOF))
+            lmc_led_off(sc, LMC_DS3_LED3);
+        sc->last_led_err[2] = 0;
+    }
+
+    sc->lmc_xinfo.t1_alarm1_status = link_status;
+
+    lmc_mii_writereg (sc, 0, 17, T1FRAMER_ALARM2_STATUS);
+    sc->lmc_xinfo.t1_alarm2_status = lmc_mii_readreg (sc, 0, 18);
+
+    
+    lmc_trace(sc->lmc_device, "lmc_t1_get_link_status out");
+
+    return ret;
+}
+
+/*
+ * 1 == T1 Circuit Type , 0 == E1 Circuit Type
+ */
+static void
+lmc_t1_set_circuit_type (lmc_softc_t * const sc, int ie)
+{
+  if (ie == LMC_CTL_CIRCUIT_TYPE_T1) {
+      sc->lmc_miireg16 |= LMC_MII16_T1_Z;
+      sc->ictl.circuit_type = LMC_CTL_CIRCUIT_TYPE_T1;
+      printk(KERN_INFO "%s: In T1 Mode\n", sc->name);
+  }
+  else {
+      sc->lmc_miireg16 &= ~LMC_MII16_T1_Z;
+      sc->ictl.circuit_type = LMC_CTL_CIRCUIT_TYPE_E1;
+      printk(KERN_INFO "%s: In E1 Mode\n", sc->name);
+  }
+
+  lmc_mii_writereg (sc, 0, 16, sc->lmc_miireg16);
+  
+}
+
+/*
+ * 0 == 16bit, 1 == 32bit */
+static void
+lmc_t1_set_crc_length (lmc_softc_t * const sc, int state)
+{
+  if (state == LMC_CTL_CRC_LENGTH_32)
+    {
+      /* 32 bit */
+      sc->lmc_miireg16 |= LMC_MII16_T1_CRC;
+      sc->ictl.crc_length = LMC_CTL_CRC_LENGTH_32;
+      sc->lmc_crcSize = LMC_CTL_CRC_BYTESIZE_4;
+
+    }
+  else
+    {
+      /* 16 bit */ sc->lmc_miireg16 &= ~LMC_MII16_T1_CRC;
+      sc->ictl.crc_length = LMC_CTL_CRC_LENGTH_16;
+      sc->lmc_crcSize = LMC_CTL_CRC_BYTESIZE_2;
+
+    }
+
+  lmc_mii_writereg (sc, 0, 16, sc->lmc_miireg16);
+}
+
+/*
+ * 1 == internal, 0 == external
+ */
+static void
+lmc_t1_set_clock (lmc_softc_t * const sc, int ie)
+{
+  int old;
+  old = ie;
+  if (ie == LMC_CTL_CLOCK_SOURCE_EXT)
+    {
+      sc->lmc_gpio &= ~(LMC_GEP_SSI_TXCLOCK);
+      LMC_CSR_WRITE (sc, csr_gp, sc->lmc_gpio);
+      sc->ictl.clock_source = LMC_CTL_CLOCK_SOURCE_EXT;
+      if(old != ie)
+        printk (LMC_PRINTF_FMT ": clock external\n", LMC_PRINTF_ARGS);
+    }
+  else
+    {
+      sc->lmc_gpio |= LMC_GEP_SSI_TXCLOCK;
+      LMC_CSR_WRITE (sc, csr_gp, sc->lmc_gpio);
+      sc->ictl.clock_source = LMC_CTL_CLOCK_SOURCE_INT;
+      if(old != ie)
+        printk (LMC_PRINTF_FMT ": clock internal\n", LMC_PRINTF_ARGS);
+    }
+}
+
+static void
+lmc_t1_watchdog (lmc_softc_t * const sc)
+{
+}
+
+static void
+lmc_set_protocol (lmc_softc_t * const sc, lmc_ctl_t * ctl)
+{
+  if (ctl == 0)
+    {
+      sc->ictl.keepalive_onoff = LMC_CTL_ON;
+
+      return;
+    }
+}
diff --git a/drivers/net/wan/lmc/lmc_media.h b/drivers/net/wan/lmc/lmc_media.h
new file mode 100644
index 0000000..ddcc004
--- /dev/null
+++ b/drivers/net/wan/lmc/lmc_media.h
@@ -0,0 +1,65 @@
+#ifndef _LMC_MEDIA_H_
+#define _LMC_MEDIA_H_
+
+lmc_media_t lmc_ds3_media = {
+  lmc_ds3_init,			/* special media init stuff */
+  lmc_ds3_default,		/* reset to default state */
+  lmc_ds3_set_status,		/* reset status to state provided */
+  lmc_dummy_set_1,		/* set clock source */
+  lmc_dummy_set2_1,		/* set line speed */
+  lmc_ds3_set_100ft,		/* set cable length */
+  lmc_ds3_set_scram,		/* set scrambler */
+  lmc_ds3_get_link_status,	/* get link status */
+  lmc_dummy_set_1,		/* set link status */
+  lmc_ds3_set_crc_length,	/* set CRC length */
+  lmc_dummy_set_1,		/* set T1 or E1 circuit type */
+  lmc_ds3_watchdog
+};
+
+lmc_media_t lmc_hssi_media = {
+  lmc_hssi_init,		/* special media init stuff */
+  lmc_hssi_default,		/* reset to default state */
+  lmc_hssi_set_status,		/* reset status to state provided */
+  lmc_hssi_set_clock,		/* set clock source */
+  lmc_dummy_set2_1,		/* set line speed */
+  lmc_dummy_set_1,		/* set cable length */
+  lmc_dummy_set_1,		/* set scrambler */
+  lmc_hssi_get_link_status,	/* get link status */
+  lmc_hssi_set_link_status,	/* set link status */
+  lmc_hssi_set_crc_length,	/* set CRC length */
+  lmc_dummy_set_1,		/* set T1 or E1 circuit type */
+  lmc_hssi_watchdog
+};
+
+lmc_media_t lmc_ssi_media = { lmc_ssi_init,	/* special media init stuff */
+  lmc_ssi_default,		/* reset to default state */
+  lmc_ssi_set_status,		/* reset status to state provided */
+  lmc_ssi_set_clock,		/* set clock source */
+  lmc_ssi_set_speed,		/* set line speed */
+  lmc_dummy_set_1,		/* set cable length */
+  lmc_dummy_set_1,		/* set scrambler */
+  lmc_ssi_get_link_status,	/* get link status */
+  lmc_ssi_set_link_status,	/* set link status */
+  lmc_ssi_set_crc_length,	/* set CRC length */
+  lmc_dummy_set_1,		/* set T1 or E1 circuit type */
+  lmc_ssi_watchdog
+};
+
+lmc_media_t lmc_t1_media = {
+  lmc_t1_init,			/* special media init stuff */
+  lmc_t1_default,		/* reset to default state */
+  lmc_t1_set_status,		/* reset status to state provided */
+  lmc_t1_set_clock,		/* set clock source */
+  lmc_dummy_set2_1,		/* set line speed */
+  lmc_dummy_set_1,		/* set cable length */
+  lmc_dummy_set_1,		/* set scrambler */
+  lmc_t1_get_link_status,	/* get link status */
+  lmc_dummy_set_1,		/* set link status */
+  lmc_t1_set_crc_length,	/* set CRC length */
+  lmc_t1_set_circuit_type,	/* set T1 or E1 circuit type */
+  lmc_t1_watchdog
+};
+
+
+#endif
+
diff --git a/drivers/net/wan/lmc/lmc_prot.h b/drivers/net/wan/lmc/lmc_prot.h
new file mode 100644
index 0000000..f3b1df9
--- /dev/null
+++ b/drivers/net/wan/lmc/lmc_prot.h
@@ -0,0 +1,15 @@
+#ifndef _LMC_PROTO_H_
+#define _LMC_PROTO_H_
+
+void lmc_proto_init(lmc_softc_t * const)
+void lmc_proto_attach(lmc_softc_t *sc const)
+void lmc_proto_detach(lmc_softc *sc const)
+void lmc_proto_reopen(lmc_softc_t *sc const)
+int lmc_proto_ioctl(lmc_softc_t *sc const, struct ifreq *ifr, int cmd)
+void lmc_proto_open(lmc_softc_t *sc const)
+void lmc_proto_close(lmc_softc_t *sc const)
+unsigned short lmc_proto_type(lmc_softc_t *sc const, struct skbuff *skb)
+
+
+#endif
+
diff --git a/drivers/net/wan/lmc/lmc_proto.c b/drivers/net/wan/lmc/lmc_proto.c
new file mode 100644
index 0000000..74876c0
--- /dev/null
+++ b/drivers/net/wan/lmc/lmc_proto.c
@@ -0,0 +1,249 @@
+ /*
+  * Copyright (c) 1997-2000 LAN Media Corporation (LMC)
+  * All rights reserved.  www.lanmedia.com
+  *
+  * This code is written by:
+  * Andrew Stanley-Jones (asj@cban.com)
+  * Rob Braun (bbraun@vix.com),
+  * Michael Graff (explorer@vix.com) and
+  * Matt Thomas (matt@3am-software.com).
+  *
+  * With Help By:
+  * David Boggs
+  * Ron Crane
+  * Allan Cox
+  *
+  * This software may be used and distributed according to the terms
+  * of the GNU General Public License version 2, incorporated herein by reference.
+  *
+  * Driver for the LanMedia LMC5200, LMC5245, LMC1000, LMC1200 cards.
+  */
+
+#include <linux/kernel.h>
+#include <linux/string.h>
+#include <linux/timer.h>
+#include <linux/ptrace.h>
+#include <linux/errno.h>
+#include <linux/ioport.h>
+#include <linux/slab.h>
+#include <linux/interrupt.h>
+#include <linux/pci.h>
+#include <linux/in.h>
+#include <linux/if_arp.h>
+#include <linux/netdevice.h>
+#include <linux/etherdevice.h>
+#include <linux/skbuff.h>
+#include <linux/inet.h>
+#include <linux/workqueue.h>
+#include <linux/proc_fs.h>
+#include <linux/bitops.h>
+
+#include <net/syncppp.h>
+
+#include <asm/processor.h>             /* Processor type for cache alignment. */
+#include <asm/io.h>
+#include <asm/dma.h>
+#include <linux/smp.h>
+
+#include "lmc.h"
+#include "lmc_var.h"
+#include "lmc_debug.h"
+#include "lmc_ioctl.h"
+#include "lmc_proto.h"
+
+/*
+ * The compile-time variable SPPPSTUP causes the module to be
+ * compiled without referencing any of the sync ppp routines.
+ */
+#ifdef SPPPSTUB
+#define SPPP_detach(d)	(void)0
+#define SPPP_open(d)	0
+#define SPPP_reopen(d)	(void)0
+#define SPPP_close(d)	(void)0
+#define SPPP_attach(d)	(void)0
+#define SPPP_do_ioctl(d,i,c)	-EOPNOTSUPP
+#else
+#define SPPP_attach(x)	sppp_attach((x)->pd)
+#define SPPP_detach(x)	sppp_detach((x)->pd->dev)
+#define SPPP_open(x)	sppp_open((x)->pd->dev)
+#define SPPP_reopen(x)	sppp_reopen((x)->pd->dev)
+#define SPPP_close(x)	sppp_close((x)->pd->dev)
+#define SPPP_do_ioctl(x, y, z)	sppp_do_ioctl((x)->pd->dev, (y), (z))
+#endif
+
+// init
+void lmc_proto_init(lmc_softc_t *sc) /*FOLD00*/
+{
+    lmc_trace(sc->lmc_device, "lmc_proto_init in");
+    switch(sc->if_type){
+    case LMC_PPP:
+        sc->pd = kmalloc(sizeof(struct ppp_device), GFP_KERNEL);
+	if (!sc->pd) {
+		printk("lmc_proto_init(): kmalloc failure!\n");
+		return;
+	}
+        sc->pd->dev = sc->lmc_device;
+        sc->if_ptr = sc->pd;
+        break;
+    case LMC_RAW:
+        break;
+    default:
+        break;
+    }
+    lmc_trace(sc->lmc_device, "lmc_proto_init out");
+}
+
+// attach
+void lmc_proto_attach(lmc_softc_t *sc) /*FOLD00*/
+{
+    lmc_trace(sc->lmc_device, "lmc_proto_attach in");
+    switch(sc->if_type){
+    case LMC_PPP:
+        {
+            struct net_device *dev = sc->lmc_device;
+            SPPP_attach(sc);
+            dev->do_ioctl = lmc_ioctl;
+        }
+        break;
+    case LMC_NET:
+        {
+            struct net_device *dev = sc->lmc_device;
+            /*
+             * They set a few basics because they don't use sync_ppp
+             */
+            dev->flags |= IFF_POINTOPOINT;
+            dev->hard_header = NULL;
+            dev->hard_header_len = 0;
+            dev->addr_len = 0;
+        }
+    case LMC_RAW: /* Setup the task queue, maybe we should notify someone? */
+        {
+        }
+    default:
+        break;
+    }
+    lmc_trace(sc->lmc_device, "lmc_proto_attach out");
+}
+
+// detach
+void lmc_proto_detach(lmc_softc_t *sc) /*FOLD00*/
+{
+    switch(sc->if_type){
+    case LMC_PPP:
+        SPPP_detach(sc);
+        break;
+    case LMC_RAW: /* Tell someone we're detaching? */
+        break;
+    default:
+        break;
+    }
+
+}
+
+// reopen
+void lmc_proto_reopen(lmc_softc_t *sc) /*FOLD00*/
+{
+    lmc_trace(sc->lmc_device, "lmc_proto_reopen in");
+    switch(sc->if_type){
+    case LMC_PPP:
+        SPPP_reopen(sc);
+        break;
+    case LMC_RAW: /* Reset the interface after being down, prerape to receive packets again */
+        break;
+    default:
+        break;
+    }
+    lmc_trace(sc->lmc_device, "lmc_proto_reopen out");
+}
+
+
+// ioctl
+int lmc_proto_ioctl(lmc_softc_t *sc, struct ifreq *ifr, int cmd) /*FOLD00*/
+{
+    lmc_trace(sc->lmc_device, "lmc_proto_ioctl out");
+    switch(sc->if_type){
+    case LMC_PPP:
+        return SPPP_do_ioctl (sc, ifr, cmd);
+        break;
+    default:
+        return -EOPNOTSUPP;
+        break;
+    }
+    lmc_trace(sc->lmc_device, "lmc_proto_ioctl out");
+}
+
+// open
+void lmc_proto_open(lmc_softc_t *sc) /*FOLD00*/
+{
+    int ret;
+
+    lmc_trace(sc->lmc_device, "lmc_proto_open in");
+    switch(sc->if_type){
+    case LMC_PPP:
+        ret = SPPP_open(sc);
+        if(ret < 0)
+            printk("%s: syncPPP open failed: %d\n", sc->name, ret);
+        break;
+    case LMC_RAW: /* We're about to start getting packets! */
+        break;
+    default:
+        break;
+    }
+    lmc_trace(sc->lmc_device, "lmc_proto_open out");
+}
+
+// close
+
+void lmc_proto_close(lmc_softc_t *sc) /*FOLD00*/
+{
+    lmc_trace(sc->lmc_device, "lmc_proto_close in");
+    switch(sc->if_type){
+    case LMC_PPP:
+        SPPP_close(sc);
+        break;
+    case LMC_RAW: /* Interface going down */
+        break;
+    default:
+        break;
+    }
+    lmc_trace(sc->lmc_device, "lmc_proto_close out");
+}
+
+unsigned short lmc_proto_type(lmc_softc_t *sc, struct sk_buff *skb) /*FOLD00*/
+{
+    lmc_trace(sc->lmc_device, "lmc_proto_type in");
+    switch(sc->if_type){
+    case LMC_PPP:
+        return htons(ETH_P_WAN_PPP);
+        break;
+    case LMC_NET:
+        return htons(ETH_P_802_2);
+        break;
+    case LMC_RAW: /* Packet type for skbuff kind of useless */
+        return htons(ETH_P_802_2);
+        break;
+    default:
+        printk(KERN_WARNING "%s: No protocol set for this interface, assuming 802.2 (which is wrong!!)\n", sc->name);
+        return htons(ETH_P_802_2);
+        break;
+    }
+    lmc_trace(sc->lmc_device, "lmc_proto_tye out");
+
+}
+
+void lmc_proto_netif(lmc_softc_t *sc, struct sk_buff *skb) /*FOLD00*/
+{
+    lmc_trace(sc->lmc_device, "lmc_proto_netif in");
+    switch(sc->if_type){
+    case LMC_PPP:
+    case LMC_NET:
+    default:
+        skb->dev->last_rx = jiffies;
+        netif_rx(skb);
+        break;
+    case LMC_RAW:
+        break;
+    }
+    lmc_trace(sc->lmc_device, "lmc_proto_netif out");
+}
+
diff --git a/drivers/net/wan/lmc/lmc_proto.h b/drivers/net/wan/lmc/lmc_proto.h
new file mode 100644
index 0000000..080a557
--- /dev/null
+++ b/drivers/net/wan/lmc/lmc_proto.h
@@ -0,0 +1,16 @@
+#ifndef _LMC_PROTO_H_
+#define _LMC_PROTO_H_
+
+void lmc_proto_init(lmc_softc_t *sc);
+void lmc_proto_attach(lmc_softc_t *sc);
+void lmc_proto_detach(lmc_softc_t *sc);
+void lmc_proto_reopen(lmc_softc_t *sc);
+int lmc_proto_ioctl(lmc_softc_t *sc, struct ifreq *ifr, int cmd);
+void lmc_proto_open(lmc_softc_t *sc);
+void lmc_proto_close(lmc_softc_t *sc);
+unsigned short lmc_proto_type(lmc_softc_t *sc, struct sk_buff *skb);
+void lmc_proto_netif(lmc_softc_t *sc, struct sk_buff *skb);
+int lmc_skb_rawpackets(char *buf, char **start, off_t offset, int len, int unused);
+
+#endif
+
diff --git a/drivers/net/wan/lmc/lmc_var.h b/drivers/net/wan/lmc/lmc_var.h
new file mode 100644
index 0000000..6d003a3
--- /dev/null
+++ b/drivers/net/wan/lmc/lmc_var.h
@@ -0,0 +1,570 @@
+#ifndef _LMC_VAR_H_
+#define _LMC_VAR_H_
+
+/* $Id: lmc_var.h,v 1.17 2000/04/06 12:16:47 asj Exp $ */
+
+ /*
+  * Copyright (c) 1997-2000 LAN Media Corporation (LMC)
+  * All rights reserved.  www.lanmedia.com
+  *
+  * This code is written by:
+  * Andrew Stanley-Jones (asj@cban.com)
+  * Rob Braun (bbraun@vix.com),
+  * Michael Graff (explorer@vix.com) and
+  * Matt Thomas (matt@3am-software.com).
+  *
+  * This software may be used and distributed according to the terms
+  * of the GNU General Public License version 2, incorporated herein by reference.
+  */
+
+#include <linux/timer.h>
+
+#ifndef __KERNEL__
+typedef signed char s8;
+typedef unsigned char u8;
+
+typedef signed short s16;
+typedef unsigned short u16;
+
+typedef signed int s32;
+typedef unsigned int u32;
+
+typedef signed long long s64;
+typedef unsigned long long u64;
+
+#define BITS_PER_LONG 32 
+
+#endif
+
+/*
+ * basic definitions used in lmc include files
+ */
+
+typedef struct lmc___softc lmc_softc_t;
+typedef struct lmc___media lmc_media_t;
+typedef struct lmc___ctl lmc_ctl_t;
+
+#define lmc_csrptr_t    unsigned long
+#define u_int16_t	u16
+#define u_int8_t	u8
+#define tulip_uint32_t	u32
+
+#define LMC_REG_RANGE 0x80
+
+#define LMC_PRINTF_FMT  "%s"
+#define LMC_PRINTF_ARGS	(sc->lmc_device->name)
+
+#define TX_TIMEOUT (2*HZ)
+
+#define LMC_TXDESCS            32
+#define LMC_RXDESCS            32
+
+#define LMC_LINK_UP            1
+#define LMC_LINK_DOWN          0
+
+/* These macros for generic read and write to and from the dec chip */
+#define LMC_CSR_READ(sc, csr) \
+	inl((sc)->lmc_csrs.csr)
+#define LMC_CSR_WRITE(sc, reg, val) \
+	outl((val), (sc)->lmc_csrs.reg)
+
+//#ifdef _LINUX_DELAY_H
+//	#define SLOW_DOWN_IO udelay(2);
+//	#undef __SLOW_DOWN_IO
+//	#define __SLOW_DOWN_IO udelay(2);
+//#endif
+
+#define DELAY(n) SLOW_DOWN_IO
+
+#define lmc_delay() inl(sc->lmc_csrs.csr_9)
+
+/* This macro sync's up with the mii so that reads and writes can take place */
+#define LMC_MII_SYNC(sc) do {int n=32; while( n >= 0 ) { \
+                LMC_CSR_WRITE((sc), csr_9, 0x20000); \
+		lmc_delay(); \
+		LMC_CSR_WRITE((sc), csr_9, 0x30000); \
+                lmc_delay(); \
+		n--; }} while(0)
+
+struct lmc_regfile_t {
+    lmc_csrptr_t csr_busmode;                  /* CSR0 */
+    lmc_csrptr_t csr_txpoll;                   /* CSR1 */
+    lmc_csrptr_t csr_rxpoll;                   /* CSR2 */
+    lmc_csrptr_t csr_rxlist;                   /* CSR3 */
+    lmc_csrptr_t csr_txlist;                   /* CSR4 */
+    lmc_csrptr_t csr_status;                   /* CSR5 */
+    lmc_csrptr_t csr_command;                  /* CSR6 */
+    lmc_csrptr_t csr_intr;                     /* CSR7 */
+    lmc_csrptr_t csr_missed_frames;            /* CSR8 */
+    lmc_csrptr_t csr_9;                        /* CSR9 */
+    lmc_csrptr_t csr_10;                       /* CSR10 */
+    lmc_csrptr_t csr_11;                       /* CSR11 */
+    lmc_csrptr_t csr_12;                       /* CSR12 */
+    lmc_csrptr_t csr_13;                       /* CSR13 */
+    lmc_csrptr_t csr_14;                       /* CSR14 */
+    lmc_csrptr_t csr_15;                       /* CSR15 */
+};
+
+#define csr_enetrom             csr_9   /* 21040 */
+#define csr_reserved            csr_10  /* 21040 */
+#define csr_full_duplex         csr_11  /* 21040 */
+#define csr_bootrom             csr_10  /* 21041/21140A/?? */
+#define csr_gp                  csr_12  /* 21140* */
+#define csr_watchdog            csr_15  /* 21140* */
+#define csr_gp_timer            csr_11  /* 21041/21140* */
+#define csr_srom_mii            csr_9   /* 21041/21140* */
+#define csr_sia_status          csr_12  /* 2104x */
+#define csr_sia_connectivity    csr_13  /* 2104x */
+#define csr_sia_tx_rx           csr_14  /* 2104x */
+#define csr_sia_general         csr_15  /* 2104x */
+
+/* tulip length/control transmit descriptor definitions
+ *  used to define bits in the second tulip_desc_t field (length)
+ *  for the transmit descriptor -baz */
+
+#define LMC_TDES_FIRST_BUFFER_SIZE       ((u_int32_t)(0x000007FF))
+#define LMC_TDES_SECOND_BUFFER_SIZE      ((u_int32_t)(0x003FF800))
+#define LMC_TDES_HASH_FILTERING          ((u_int32_t)(0x00400000))
+#define LMC_TDES_DISABLE_PADDING         ((u_int32_t)(0x00800000))
+#define LMC_TDES_SECOND_ADDR_CHAINED     ((u_int32_t)(0x01000000))
+#define LMC_TDES_END_OF_RING             ((u_int32_t)(0x02000000))
+#define LMC_TDES_ADD_CRC_DISABLE         ((u_int32_t)(0x04000000))
+#define LMC_TDES_SETUP_PACKET            ((u_int32_t)(0x08000000))
+#define LMC_TDES_INVERSE_FILTERING       ((u_int32_t)(0x10000000))
+#define LMC_TDES_FIRST_SEGMENT           ((u_int32_t)(0x20000000))
+#define LMC_TDES_LAST_SEGMENT            ((u_int32_t)(0x40000000))
+#define LMC_TDES_INTERRUPT_ON_COMPLETION ((u_int32_t)(0x80000000))
+
+#define TDES_SECOND_BUFFER_SIZE_BIT_NUMBER  11
+#define TDES_COLLISION_COUNT_BIT_NUMBER     3
+
+/* Constants for the RCV descriptor RDES */
+
+#define LMC_RDES_OVERFLOW             ((u_int32_t)(0x00000001))
+#define LMC_RDES_CRC_ERROR            ((u_int32_t)(0x00000002))
+#define LMC_RDES_DRIBBLING_BIT            ((u_int32_t)(0x00000004))
+#define LMC_RDES_REPORT_ON_MII_ERR    ((u_int32_t)(0x00000008))
+#define LMC_RDES_RCV_WATCHDOG_TIMEOUT ((u_int32_t)(0x00000010))
+#define LMC_RDES_FRAME_TYPE           ((u_int32_t)(0x00000020))
+#define LMC_RDES_COLLISION_SEEN       ((u_int32_t)(0x00000040))
+#define LMC_RDES_FRAME_TOO_LONG       ((u_int32_t)(0x00000080))
+#define LMC_RDES_LAST_DESCRIPTOR      ((u_int32_t)(0x00000100))
+#define LMC_RDES_FIRST_DESCRIPTOR     ((u_int32_t)(0x00000200))
+#define LMC_RDES_MULTICAST_FRAME      ((u_int32_t)(0x00000400))
+#define LMC_RDES_RUNT_FRAME           ((u_int32_t)(0x00000800))
+#define LMC_RDES_DATA_TYPE            ((u_int32_t)(0x00003000))
+#define LMC_RDES_LENGTH_ERROR         ((u_int32_t)(0x00004000))
+#define LMC_RDES_ERROR_SUMMARY        ((u_int32_t)(0x00008000))
+#define LMC_RDES_FRAME_LENGTH         ((u_int32_t)(0x3FFF0000))
+#define LMC_RDES_OWN_BIT              ((u_int32_t)(0x80000000))
+
+#define RDES_FRAME_LENGTH_BIT_NUMBER       16
+
+#define LMC_RDES_ERROR_MASK ( (u_int32_t)( \
+	  LMC_RDES_OVERFLOW \
+	| LMC_RDES_DRIBBLING_BIT \
+	| LMC_RDES_REPORT_ON_MII_ERR \
+        | LMC_RDES_COLLISION_SEEN ) )
+
+
+/*
+ * Ioctl info
+ */
+
+typedef struct {
+	u_int32_t	n;
+	u_int32_t	m;
+	u_int32_t	v;
+	u_int32_t	x;
+	u_int32_t	r;
+	u_int32_t	f;
+	u_int32_t	exact;
+} lmc_av9110_t;
+
+/*
+ * Common structure passed to the ioctl code.
+ */
+struct lmc___ctl {
+	u_int32_t	cardtype;
+	u_int32_t	clock_source;		/* HSSI, T1 */
+	u_int32_t	clock_rate;		/* T1 */
+	u_int32_t	crc_length;
+	u_int32_t	cable_length;		/* DS3 */
+	u_int32_t	scrambler_onoff;	/* DS3 */
+	u_int32_t	cable_type;		/* T1 */
+	u_int32_t	keepalive_onoff;	/* protocol */
+	u_int32_t	ticks;			/* ticks/sec */
+	union {
+		lmc_av9110_t	ssi;
+	} cardspec;
+	u_int32_t       circuit_type;   /* T1 or E1 */
+};
+
+
+/*
+ * Carefull, look at the data sheet, there's more to this
+ * structure than meets the eye.  It should probably be:
+ *
+ * struct tulip_desc_t {
+ *         u8  own:1;
+ *         u32 status:31;
+ *         u32 control:10;
+ *         u32 buffer1;
+ *         u32 buffer2;
+ * };
+ * You could also expand status control to provide more bit information
+ */
+
+struct tulip_desc_t {
+	s32 status;
+	s32 length;
+	u32 buffer1;
+	u32 buffer2;
+};
+
+/*
+ * media independent methods to check on media status, link, light LEDs,
+ * etc.
+ */
+struct lmc___media {
+	void	(* init)(lmc_softc_t * const);
+	void	(* defaults)(lmc_softc_t * const);
+	void	(* set_status)(lmc_softc_t * const, lmc_ctl_t *);
+	void	(* set_clock_source)(lmc_softc_t * const, int);
+	void	(* set_speed)(lmc_softc_t * const, lmc_ctl_t *);
+	void	(* set_cable_length)(lmc_softc_t * const, int);
+	void	(* set_scrambler)(lmc_softc_t * const, int);
+	int	(* get_link_status)(lmc_softc_t * const);
+	void	(* set_link_status)(lmc_softc_t * const, int);
+	void	(* set_crc_length)(lmc_softc_t * const, int);
+        void    (* set_circuit_type)(lmc_softc_t * const, int);
+        void	(* watchdog)(lmc_softc_t * const);
+};
+
+
+#define STATCHECK     0xBEEFCAFE
+
+/*  Included in this structure are first
+ *   - standard net_device_stats
+ *   - some other counters used for debug and driver performance
+ *  evaluation -baz
+ */
+struct lmc_statistics
+{
+        unsigned long     rx_packets;             /* total packets received       */
+        unsigned long     tx_packets;             /* total packets transmitted    */
+	unsigned long     rx_bytes;
+        unsigned long     tx_bytes;
+        
+        unsigned long     rx_errors;              /* bad packets received         */
+        unsigned long     tx_errors;              /* packet transmit problems     */
+        unsigned long     rx_dropped;             /* no space in linux buffers    */
+        unsigned long     tx_dropped;             /* no space available in linux  */
+        unsigned long     multicast;              /* multicast packets received   */
+        unsigned long     collisions;
+
+        /* detailed rx_errors: */
+        unsigned long     rx_length_errors;
+        unsigned long     rx_over_errors;         /* receiver ring buff overflow  */
+        unsigned long     rx_crc_errors;          /* recved pkt with crc error    */
+        unsigned long     rx_frame_errors;        /* recv'd frame alignment error */
+        unsigned long     rx_fifo_errors;         /* recv'r fifo overrun          */
+        unsigned long     rx_missed_errors;       /* receiver missed packet       */
+
+        /* detailed tx_errors */
+        unsigned long     tx_aborted_errors;
+        unsigned long     tx_carrier_errors;
+        unsigned long     tx_fifo_errors;
+        unsigned long     tx_heartbeat_errors;
+        unsigned long     tx_window_errors;
+
+        /* for cslip etc */
+        unsigned long rx_compressed;
+        unsigned long tx_compressed;
+
+        /* -------------------------------------
+         * Custom stats & counters follow -baz */
+        u_int32_t       version_size;
+        u_int32_t       lmc_cardtype;
+
+        u_int32_t       tx_ProcTimeout;
+        u_int32_t       tx_IntTimeout;
+        u_int32_t       tx_NoCompleteCnt;
+        u_int32_t       tx_MaxXmtsB4Int;
+        u_int32_t       tx_TimeoutCnt;
+        u_int32_t       tx_OutOfSyncPtr;
+        u_int32_t       tx_tbusy0;
+        u_int32_t       tx_tbusy1;
+        u_int32_t       tx_tbusy_calls;
+        u_int32_t       resetCount;
+        u_int32_t       lmc_txfull;
+        u_int32_t       tbusy;
+        u_int32_t       dirtyTx;
+        u_int32_t       lmc_next_tx;
+        u_int32_t       otherTypeCnt;
+        u_int32_t       lastType;
+        u_int32_t       lastTypeOK;
+        u_int32_t       txLoopCnt;
+        u_int32_t       usedXmtDescripCnt;
+        u_int32_t       txIndexCnt;
+        u_int32_t       rxIntLoopCnt;
+
+        u_int32_t       rx_SmallPktCnt;
+        u_int32_t       rx_BadPktSurgeCnt;
+        u_int32_t       rx_BuffAllocErr;
+        u_int32_t       tx_lossOfClockCnt;
+
+        /* T1 error counters */
+        u_int32_t       framingBitErrorCount;
+        u_int32_t       lineCodeViolationCount;
+
+        u_int32_t       lossOfFrameCount;
+        u_int32_t       changeOfFrameAlignmentCount;
+        u_int32_t       severelyErroredFrameCount;
+
+        u_int32_t       check;
+};
+
+
+typedef struct lmc_xinfo {
+        u_int32_t       Magic0;                         /* BEEFCAFE */
+
+        u_int32_t       PciCardType;
+        u_int32_t       PciSlotNumber;          /* PCI slot number       */
+
+        u_int16_t       DriverMajorVersion;
+        u_int16_t       DriverMinorVersion;
+        u_int16_t       DriverSubVersion;
+
+        u_int16_t       XilinxRevisionNumber;
+        u_int16_t       MaxFrameSize;
+
+        u_int16_t       t1_alarm1_status;
+        u_int16_t       t1_alarm2_status;
+
+        int                     link_status;
+        u_int32_t       mii_reg16;
+
+        u_int32_t       Magic1;                         /* DEADBEEF */
+} LMC_XINFO;
+
+
+/*
+ * forward decl
+ */
+struct lmc___softc {
+        void *if_ptr;   /* General purpose pointer (used by SPPP) */
+	char                   *name;
+	u8			board_idx;
+	struct lmc_statistics   stats;
+	struct net_device          *lmc_device;
+
+	int                     hang, rxdesc, bad_packet, some_counter;
+	u_int32_t               txgo;
+	struct lmc_regfile_t	lmc_csrs;
+	volatile u_int32_t	lmc_txtick;
+	volatile u_int32_t	lmc_rxtick;
+	u_int32_t		lmc_flags;
+	u_int32_t		lmc_intrmask;	/* our copy of csr_intr */
+	u_int32_t		lmc_cmdmode;	/* our copy of csr_cmdmode */
+	u_int32_t		lmc_busmode;	/* our copy of csr_busmode */
+	u_int32_t		lmc_gpio_io;	/* state of in/out settings */
+	u_int32_t		lmc_gpio;	/* state of outputs */
+	struct sk_buff*		lmc_txq[LMC_TXDESCS];
+	struct sk_buff*		lmc_rxq[LMC_RXDESCS];
+	volatile
+	struct tulip_desc_t	lmc_rxring[LMC_RXDESCS];
+	volatile
+	struct tulip_desc_t	lmc_txring[LMC_TXDESCS];
+	unsigned int		lmc_next_rx, lmc_next_tx;
+	volatile
+	unsigned int		lmc_taint_tx, lmc_taint_rx;
+	int			lmc_tx_start, lmc_txfull;
+	int			lmc_txbusy;
+	u_int16_t		lmc_miireg16;
+	int			lmc_ok;
+	int			last_link_status;
+	int			lmc_cardtype;
+	u_int32_t               last_frameerr;
+	lmc_media_t	       *lmc_media;
+	struct timer_list	timer;
+	lmc_ctl_t		ictl;
+	u_int32_t		TxDescriptControlInit;  
+
+	int                     tx_TimeoutInd; /* additional driver state */
+	int                     tx_TimeoutDisplay;
+	unsigned int		lastlmc_taint_tx;
+	int                     lasttx_packets;
+	u_int32_t		tx_clockState;
+	u_int32_t		lmc_crcSize;
+	LMC_XINFO		lmc_xinfo; 
+	char                    lmc_yel, lmc_blue, lmc_red; /* for T1 and DS3 */
+        char                    lmc_timing; /* for HSSI and SSI */
+        int                     got_irq;
+
+        char                    last_led_err[4];
+
+        u32                     last_int;
+        u32                     num_int;
+
+	spinlock_t              lmc_lock;
+        u_int16_t               if_type;       /* PPP or NET */
+        struct ppp_device       *pd;
+
+        /* Failure cases */
+        u8                       failed_ring;
+        u8                       failed_recv_alloc;
+
+        /* Structure check */
+        u32                     check;
+};
+
+#define LMC_PCI_TIME 1
+#define LMC_EXT_TIME 0
+
+#define PKT_BUF_SZ              1542  /* was 1536 */
+
+/* CSR5 settings */
+#define TIMER_INT     0x00000800
+#define TP_LINK_FAIL  0x00001000
+#define TP_LINK_PASS  0x00000010
+#define NORMAL_INT    0x00010000
+#define ABNORMAL_INT  0x00008000
+#define RX_JABBER_INT 0x00000200
+#define RX_DIED       0x00000100
+#define RX_NOBUFF     0x00000080
+#define RX_INT        0x00000040
+#define TX_FIFO_UNDER 0x00000020
+#define TX_JABBER     0x00000008
+#define TX_NOBUFF     0x00000004
+#define TX_DIED       0x00000002
+#define TX_INT        0x00000001
+
+/* CSR6 settings */
+#define OPERATION_MODE  0x00000200 /* Full Duplex      */
+#define PROMISC_MODE    0x00000040 /* Promiscuous Mode */
+#define RECIEVE_ALL     0x40000000 /* Recieve All      */
+#define PASS_BAD_FRAMES 0x00000008 /* Pass Bad Frames  */
+
+/* Dec control registers  CSR6 as well */
+#define LMC_DEC_ST 0x00002000
+#define LMC_DEC_SR 0x00000002
+
+/* CSR15 settings */
+#define RECV_WATCHDOG_DISABLE 0x00000010
+#define JABBER_DISABLE        0x00000001
+
+/* More settings */
+/*
+ * aSR6 -- Command (Operation Mode) Register
+ */
+#define TULIP_CMD_RECEIVEALL    0x40000000L /* (RW)  Receivel all frames? */
+#define TULIP_CMD_MUSTBEONE     0x02000000L /* (RW)  Must Be One (21140) */
+#define TULIP_CMD_TXTHRSHLDCTL  0x00400000L /* (RW)  Transmit Threshold Mode (21140) */
+#define TULIP_CMD_STOREFWD      0x00200000L /* (RW)  Store and Foward (21140) */
+#define TULIP_CMD_NOHEARTBEAT   0x00080000L /* (RW)  No Heartbeat (21140) */
+#define TULIP_CMD_PORTSELECT    0x00040000L /* (RW)  Post Select (100Mb) (21140) */
+#define TULIP_CMD_FULLDUPLEX    0x00000200L /* (RW)  Full Duplex Mode */
+#define TULIP_CMD_OPERMODE      0x00000C00L /* (RW)  Operating Mode */
+#define TULIP_CMD_PROMISCUOUS   0x00000041L /* (RW)  Promiscuous Mode */
+#define TULIP_CMD_PASSBADPKT    0x00000008L /* (RW)  Pass Bad Frames */
+#define TULIP_CMD_THRESHOLDCTL  0x0000C000L /* (RW)  Threshold Control */
+
+#define TULIP_GP_PINSET         0x00000100L
+#define TULIP_BUSMODE_SWRESET   0x00000001L
+#define TULIP_WATCHDOG_TXDISABLE 0x00000001L
+#define TULIP_WATCHDOG_RXDISABLE 0x00000010L
+
+#define TULIP_STS_NORMALINTR    0x00010000L /* (RW)  Normal Interrupt */
+#define TULIP_STS_ABNRMLINTR    0x00008000L /* (RW)  Abnormal Interrupt */
+#define TULIP_STS_ERI           0x00004000L /* (RW)  Early Receive Interrupt */
+#define TULIP_STS_SYSERROR      0x00002000L /* (RW)  System Error */
+#define TULIP_STS_GTE           0x00000800L /* (RW)  General Pupose Timer Exp */
+#define TULIP_STS_ETI           0x00000400L /* (RW)  Early Transmit Interrupt */
+#define TULIP_STS_RXWT          0x00000200L /* (RW)  Receiver Watchdog Timeout */
+#define TULIP_STS_RXSTOPPED     0x00000100L /* (RW)  Receiver Process Stopped */
+#define TULIP_STS_RXNOBUF       0x00000080L /* (RW)  Receive Buf Unavail */
+#define TULIP_STS_RXINTR        0x00000040L /* (RW)  Receive Interrupt */
+#define TULIP_STS_TXUNDERFLOW   0x00000020L /* (RW)  Transmit Underflow */
+#define TULIP_STS_TXJABER       0x00000008L /* (RW)  Jabber timeout */
+#define TULIP_STS_TXNOBUF       0x00000004L
+#define TULIP_STS_TXSTOPPED     0x00000002L /* (RW)  Transmit Process Stopped */
+#define TULIP_STS_TXINTR        0x00000001L /* (RW)  Transmit Interrupt */
+
+#define TULIP_STS_RXS_STOPPED   0x00000000L /*        000 - Stopped */
+
+#define TULIP_STS_RXSTOPPED     0x00000100L             /* (RW)  Receive Process Stopped */
+#define TULIP_STS_RXNOBUF       0x00000080L
+
+#define TULIP_CMD_TXRUN         0x00002000L /* (RW)  Start/Stop Transmitter */
+#define TULIP_CMD_RXRUN         0x00000002L /* (RW)  Start/Stop Receive Filtering */
+#define TULIP_DSTS_TxDEFERRED   0x00000001      /* Initially Deferred */
+#define TULIP_DSTS_OWNER        0x80000000      /* Owner (1 = 21040) */
+#define TULIP_DSTS_RxMIIERR     0x00000008
+#define LMC_DSTS_ERRSUM         (TULIP_DSTS_RxMIIERR)
+
+#define TULIP_DEFAULT_INTR_MASK  (TULIP_STS_NORMALINTR \
+  | TULIP_STS_RXINTR       \
+  | TULIP_STS_TXINTR     \
+  | TULIP_STS_ABNRMLINTR \
+  | TULIP_STS_SYSERROR   \
+  | TULIP_STS_TXSTOPPED  \
+  | TULIP_STS_TXUNDERFLOW\
+  | TULIP_STS_RXSTOPPED )
+
+#define DESC_OWNED_BY_SYSTEM   ((u_int32_t)(0x00000000))
+#define DESC_OWNED_BY_DC21X4   ((u_int32_t)(0x80000000))
+
+#ifndef TULIP_CMD_RECEIVEALL
+#define TULIP_CMD_RECEIVEALL 0x40000000L
+#endif
+
+/* Adapter module number */
+#define LMC_ADAP_HSSI           2
+#define LMC_ADAP_DS3            3
+#define LMC_ADAP_SSI            4
+#define LMC_ADAP_T1             5
+
+#define HDLC_HDR_LEN  4
+#define HDLC_ADDR_LEN 1
+#define HDLC_SLARP    0x8035
+#define LMC_MTU 1500
+#define SLARP_LINECHECK 2
+
+#define LMC_CRC_LEN_16 2  /* 16-bit CRC */
+#define LMC_CRC_LEN_32 4
+
+#ifdef LMC_HDLC
+/* definition of an hdlc header. */
+struct hdlc_hdr
+{
+	u8  address;
+	u8  control;
+	u16 type;
+};
+
+/* definition of a slarp header. */
+struct slarp
+{
+	long code;
+	union sl
+	{
+		struct
+		{
+			ulong address;
+			ulong mask;
+			ushort unused;
+		} add;
+		struct
+		{
+			ulong mysequence;
+			ulong yoursequence;
+			ushort reliability;
+			ulong time;
+		} chk;
+	} t;
+};
+#endif /* LMC_HDLC */
+
+
+#endif /* _LMC_VAR_H_ */
diff --git a/drivers/net/wan/n2.c b/drivers/net/wan/n2.c
new file mode 100644
index 0000000..cd32751
--- /dev/null
+++ b/drivers/net/wan/n2.c
@@ -0,0 +1,562 @@
+/*
+ * SDL Inc. RISCom/N2 synchronous serial card driver for Linux
+ *
+ * Copyright (C) 1998-2003 Krzysztof Halasa <khc@pm.waw.pl>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of version 2 of the GNU General Public License
+ * as published by the Free Software Foundation.
+ *
+ * For information see http://hq.pm.waw.pl/hdlc/
+ *
+ * Note: integrated CSU/DSU/DDS are not supported by this driver
+ *
+ * Sources of information:
+ *    Hitachi HD64570 SCA User's Manual
+ *    SDL Inc. PPP/HDLC/CISCO driver
+ */
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include <linux/types.h>
+#include <linux/fcntl.h>
+#include <linux/in.h>
+#include <linux/string.h>
+#include <linux/errno.h>
+#include <linux/init.h>
+#include <linux/ioport.h>
+#include <linux/moduleparam.h>
+#include <linux/netdevice.h>
+#include <linux/hdlc.h>
+#include <asm/io.h>
+#include "hd64570.h"
+
+
+static const char* version = "SDL RISCom/N2 driver version: 1.15";
+static const char* devname = "RISCom/N2";
+
+#undef DEBUG_PKT
+#define DEBUG_RINGS
+
+#define USE_WINDOWSIZE 16384
+#define USE_BUS16BITS 1
+#define CLOCK_BASE 9830400	/* 9.8304 MHz */
+#define MAX_PAGES      16	/* 16 RAM pages at max */
+#define MAX_RAM_SIZE 0x80000	/* 512 KB */
+#if MAX_RAM_SIZE > MAX_PAGES * USE_WINDOWSIZE
+#undef MAX_RAM_SIZE
+#define MAX_RAM_SIZE (MAX_PAGES * USE_WINDOWSIZE)
+#endif
+#define N2_IOPORTS 0x10
+#define NEED_DETECT_RAM
+#define NEED_SCA_MSCI_INTR
+#define MAX_TX_BUFFERS 10
+
+static char *hw = NULL;	/* pointer to hw=xxx command line string */
+
+/* RISCom/N2 Board Registers */
+
+/* PC Control Register */
+#define N2_PCR 0
+#define PCR_RUNSCA 1     /* Run 64570 */
+#define PCR_VPM    2     /* Enable VPM - needed if using RAM above 1 MB */
+#define PCR_ENWIN  4     /* Open window */
+#define PCR_BUS16  8     /* 16-bit bus */
+
+
+/* Memory Base Address Register */
+#define N2_BAR 2
+
+
+/* Page Scan Register  */
+#define N2_PSR 4
+#define WIN16K       0x00
+#define WIN32K       0x20
+#define WIN64K       0x40
+#define PSR_WINBITS  0x60
+#define PSR_DMAEN    0x80
+#define PSR_PAGEBITS 0x0F
+
+
+/* Modem Control Reg */
+#define N2_MCR 6
+#define CLOCK_OUT_PORT1 0x80
+#define CLOCK_OUT_PORT0 0x40
+#define TX422_PORT1     0x20
+#define TX422_PORT0     0x10
+#define DSR_PORT1       0x08
+#define DSR_PORT0       0x04
+#define DTR_PORT1       0x02
+#define DTR_PORT0       0x01
+
+
+typedef struct port_s {
+	struct net_device *dev;
+	struct card_s *card;
+	spinlock_t lock;	/* TX lock */
+	sync_serial_settings settings;
+	int valid;		/* port enabled */
+	int rxpart;		/* partial frame received, next frame invalid*/
+	unsigned short encoding;
+	unsigned short parity;
+	u16 rxin;		/* rx ring buffer 'in' pointer */
+	u16 txin;		/* tx ring buffer 'in' and 'last' pointers */
+	u16 txlast;
+	u8 rxs, txs, tmc;	/* SCA registers */
+	u8 phy_node;		/* physical port # - 0 or 1 */
+	u8 log_node;		/* logical port # */
+}port_t;
+
+
+
+typedef struct card_s {
+	u8 __iomem *winbase;		/* ISA window base address */
+	u32 phy_winbase;	/* ISA physical base address */
+	u32 ram_size;		/* number of bytes */
+	u16 io;			/* IO Base address */
+	u16 buff_offset;	/* offset of first buffer of first channel */
+	u16 rx_ring_buffers;	/* number of buffers in a ring */
+	u16 tx_ring_buffers;
+	u8 irq;			/* IRQ (3-15) */
+
+	port_t ports[2];
+	struct card_s *next_card;
+}card_t;
+
+
+static card_t *first_card;
+static card_t **new_card = &first_card;
+
+
+#define sca_reg(reg, card) (0x8000 | (card)->io | \
+			    ((reg) & 0x0F) | (((reg) & 0xF0) << 6))
+#define sca_in(reg, card)		inb(sca_reg(reg, card))
+#define sca_out(value, reg, card)	outb(value, sca_reg(reg, card))
+#define sca_inw(reg, card)		inw(sca_reg(reg, card))
+#define sca_outw(value, reg, card)	outw(value, sca_reg(reg, card))
+
+#define port_to_card(port)		((port)->card)
+#define log_node(port)			((port)->log_node)
+#define phy_node(port)			((port)->phy_node)
+#define winsize(card)			(USE_WINDOWSIZE)
+#define winbase(card)      	     	((card)->winbase)
+#define get_port(card, port)		((card)->ports[port].valid ? \
+					 &(card)->ports[port] : NULL)
+
+
+
+static __inline__ u8 sca_get_page(card_t *card)
+{
+	return inb(card->io + N2_PSR) & PSR_PAGEBITS;
+}
+
+
+static __inline__ void openwin(card_t *card, u8 page)
+{
+	u8 psr = inb(card->io + N2_PSR);
+	outb((psr & ~PSR_PAGEBITS) | page, card->io + N2_PSR);
+}
+
+
+
+#include "hd6457x.c"
+
+
+
+static void n2_set_iface(port_t *port)
+{
+	card_t *card = port->card;
+	int io = card->io;
+	u8 mcr = inb(io + N2_MCR);
+	u8 msci = get_msci(port);
+	u8 rxs = port->rxs & CLK_BRG_MASK;
+	u8 txs = port->txs & CLK_BRG_MASK;
+
+	switch(port->settings.clock_type) {
+	case CLOCK_INT:
+		mcr |= port->phy_node ? CLOCK_OUT_PORT1 : CLOCK_OUT_PORT0;
+		rxs |= CLK_BRG_RX; /* BRG output */
+		txs |= CLK_RXCLK_TX; /* RX clock */
+		break;
+
+	case CLOCK_TXINT:
+		mcr |= port->phy_node ? CLOCK_OUT_PORT1 : CLOCK_OUT_PORT0;
+		rxs |= CLK_LINE_RX; /* RXC input */
+		txs |= CLK_BRG_TX; /* BRG output */
+		break;
+
+	case CLOCK_TXFROMRX:
+		mcr |= port->phy_node ? CLOCK_OUT_PORT1 : CLOCK_OUT_PORT0;
+		rxs |= CLK_LINE_RX; /* RXC input */
+		txs |= CLK_RXCLK_TX; /* RX clock */
+		break;
+
+	default:		/* Clock EXTernal */
+		mcr &= port->phy_node ? ~CLOCK_OUT_PORT1 : ~CLOCK_OUT_PORT0;
+		rxs |= CLK_LINE_RX; /* RXC input */
+		txs |= CLK_LINE_TX; /* TXC input */
+	}
+
+	outb(mcr, io + N2_MCR);
+	port->rxs = rxs;
+	port->txs = txs;
+	sca_out(rxs, msci + RXS, card);
+	sca_out(txs, msci + TXS, card);
+	sca_set_port(port);
+}
+
+
+
+static int n2_open(struct net_device *dev)
+{
+	port_t *port = dev_to_port(dev);
+	int io = port->card->io;
+	u8 mcr = inb(io + N2_MCR) | (port->phy_node ? TX422_PORT1:TX422_PORT0);
+	int result;
+
+	result = hdlc_open(dev);
+	if (result)
+		return result;
+
+	mcr &= port->phy_node ? ~DTR_PORT1 : ~DTR_PORT0; /* set DTR ON */
+	outb(mcr, io + N2_MCR);
+
+	outb(inb(io + N2_PCR) | PCR_ENWIN, io + N2_PCR); /* open window */
+	outb(inb(io + N2_PSR) | PSR_DMAEN, io + N2_PSR); /* enable dma */
+	sca_open(dev);
+	n2_set_iface(port);
+	return 0;
+}
+
+
+
+static int n2_close(struct net_device *dev)
+{
+	port_t *port = dev_to_port(dev);
+	int io = port->card->io;
+	u8 mcr = inb(io+N2_MCR) | (port->phy_node ? TX422_PORT1 : TX422_PORT0);
+
+	sca_close(dev);
+	mcr |= port->phy_node ? DTR_PORT1 : DTR_PORT0; /* set DTR OFF */
+	outb(mcr, io + N2_MCR);
+	hdlc_close(dev);
+	return 0;
+}
+
+
+
+static int n2_ioctl(struct net_device *dev, struct ifreq *ifr, int cmd)
+{
+	const size_t size = sizeof(sync_serial_settings);
+	sync_serial_settings new_line;
+	sync_serial_settings __user *line = ifr->ifr_settings.ifs_ifsu.sync;
+	port_t *port = dev_to_port(dev);
+
+#ifdef DEBUG_RINGS
+	if (cmd == SIOCDEVPRIVATE) {
+		sca_dump_rings(dev);
+		return 0;
+	}
+#endif
+	if (cmd != SIOCWANDEV)
+		return hdlc_ioctl(dev, ifr, cmd);
+
+	switch(ifr->ifr_settings.type) {
+	case IF_GET_IFACE:
+		ifr->ifr_settings.type = IF_IFACE_SYNC_SERIAL;
+		if (ifr->ifr_settings.size < size) {
+			ifr->ifr_settings.size = size; /* data size wanted */
+			return -ENOBUFS;
+		}
+		if (copy_to_user(line, &port->settings, size))
+			return -EFAULT;
+		return 0;
+
+	case IF_IFACE_SYNC_SERIAL:
+		if(!capable(CAP_NET_ADMIN))
+			return -EPERM;
+
+		if (copy_from_user(&new_line, line, size))
+			return -EFAULT;
+
+		if (new_line.clock_type != CLOCK_EXT &&
+		    new_line.clock_type != CLOCK_TXFROMRX &&
+		    new_line.clock_type != CLOCK_INT &&
+		    new_line.clock_type != CLOCK_TXINT)
+		return -EINVAL;	/* No such clock setting */
+
+		if (new_line.loopback != 0 && new_line.loopback != 1)
+			return -EINVAL;
+
+		memcpy(&port->settings, &new_line, size); /* Update settings */
+		n2_set_iface(port);
+		return 0;
+
+	default:
+		return hdlc_ioctl(dev, ifr, cmd);
+	}
+}
+
+
+
+static void n2_destroy_card(card_t *card)
+{
+	int cnt;
+
+	for (cnt = 0; cnt < 2; cnt++)
+		if (card->ports[cnt].card) {
+			struct net_device *dev = port_to_dev(&card->ports[cnt]);
+			unregister_hdlc_device(dev);
+		}
+
+	if (card->irq)
+		free_irq(card->irq, card);
+
+	if (card->winbase) {
+		iounmap(card->winbase);
+		release_mem_region(card->phy_winbase, USE_WINDOWSIZE);
+	}
+
+	if (card->io)
+		release_region(card->io, N2_IOPORTS);
+	if (card->ports[0].dev)
+		free_netdev(card->ports[0].dev);
+	if (card->ports[1].dev)
+		free_netdev(card->ports[1].dev);
+	kfree(card);
+}
+
+
+
+static int __init n2_run(unsigned long io, unsigned long irq,
+			 unsigned long winbase, long valid0, long valid1)
+{
+	card_t *card;
+	u8 cnt, pcr;
+	int i;
+
+	if (io < 0x200 || io > 0x3FF || (io % N2_IOPORTS) != 0) {
+		printk(KERN_ERR "n2: invalid I/O port value\n");
+		return -ENODEV;
+	}
+
+	if (irq < 3 || irq > 15 || irq == 6) /* FIXME */ {
+		printk(KERN_ERR "n2: invalid IRQ value\n");
+		return -ENODEV;
+	}
+
+	if (winbase < 0xA0000 || winbase > 0xFFFFF || (winbase & 0xFFF) != 0) {
+		printk(KERN_ERR "n2: invalid RAM value\n");
+		return -ENODEV;
+	}
+
+	card = kmalloc(sizeof(card_t), GFP_KERNEL);
+	if (card == NULL) {
+		printk(KERN_ERR "n2: unable to allocate memory\n");
+		return -ENOBUFS;
+	}
+	memset(card, 0, sizeof(card_t));
+
+	card->ports[0].dev = alloc_hdlcdev(&card->ports[0]);
+	card->ports[1].dev = alloc_hdlcdev(&card->ports[1]);
+	if (!card->ports[0].dev || !card->ports[1].dev) {
+		printk(KERN_ERR "n2: unable to allocate memory\n");
+		n2_destroy_card(card);
+		return -ENOMEM;
+	}
+
+	if (!request_region(io, N2_IOPORTS, devname)) {
+		printk(KERN_ERR "n2: I/O port region in use\n");
+		n2_destroy_card(card);
+		return -EBUSY;
+	}
+	card->io = io;
+
+	if (request_irq(irq, &sca_intr, 0, devname, card)) {
+		printk(KERN_ERR "n2: could not allocate IRQ\n");
+		n2_destroy_card(card);
+		return(-EBUSY);
+	}
+	card->irq = irq;
+
+	if (!request_mem_region(winbase, USE_WINDOWSIZE, devname)) {
+		printk(KERN_ERR "n2: could not request RAM window\n");
+		n2_destroy_card(card);
+		return(-EBUSY);
+	}
+	card->phy_winbase = winbase;
+	card->winbase = ioremap(winbase, USE_WINDOWSIZE);
+
+	outb(0, io + N2_PCR);
+	outb(winbase >> 12, io + N2_BAR);
+
+	switch (USE_WINDOWSIZE) {
+	case 16384:
+		outb(WIN16K, io + N2_PSR);
+		break;
+
+	case 32768:
+		outb(WIN32K, io + N2_PSR);
+		break;
+
+	case 65536:
+		outb(WIN64K, io + N2_PSR);
+		break;
+
+	default:
+		printk(KERN_ERR "n2: invalid window size\n");
+		n2_destroy_card(card);
+		return -ENODEV;
+	}
+
+	pcr = PCR_ENWIN | PCR_VPM | (USE_BUS16BITS ? PCR_BUS16 : 0);
+	outb(pcr, io + N2_PCR);
+
+	card->ram_size = sca_detect_ram(card, card->winbase, MAX_RAM_SIZE);
+
+	/* number of TX + RX buffers for one port */
+	i = card->ram_size / ((valid0 + valid1) * (sizeof(pkt_desc) +
+						   HDLC_MAX_MRU));
+
+	card->tx_ring_buffers = min(i / 2, MAX_TX_BUFFERS);
+	card->rx_ring_buffers = i - card->tx_ring_buffers;
+
+	card->buff_offset = (valid0 + valid1) * sizeof(pkt_desc) *
+		(card->tx_ring_buffers + card->rx_ring_buffers);
+
+	printk(KERN_INFO "n2: RISCom/N2 %u KB RAM, IRQ%u, "
+	       "using %u TX + %u RX packets rings\n", card->ram_size / 1024,
+	       card->irq, card->tx_ring_buffers, card->rx_ring_buffers);
+
+	if (card->tx_ring_buffers < 1) {
+		printk(KERN_ERR "n2: RAM test failed\n");
+		n2_destroy_card(card);
+		return -EIO;
+	}
+
+	pcr |= PCR_RUNSCA;		/* run SCA */
+	outb(pcr, io + N2_PCR);
+	outb(0, io + N2_MCR);
+
+	sca_init(card, 0);
+	for (cnt = 0; cnt < 2; cnt++) {
+		port_t *port = &card->ports[cnt];
+		struct net_device *dev = port_to_dev(port);
+		hdlc_device *hdlc = dev_to_hdlc(dev);
+
+		if ((cnt == 0 && !valid0) || (cnt == 1 && !valid1))
+			continue;
+
+		port->phy_node = cnt;
+		port->valid = 1;
+
+		if ((cnt == 1) && valid0)
+			port->log_node = 1;
+
+		spin_lock_init(&port->lock);
+		SET_MODULE_OWNER(dev);
+		dev->irq = irq;
+		dev->mem_start = winbase;
+		dev->mem_end = winbase + USE_WINDOWSIZE - 1;
+		dev->tx_queue_len = 50;
+		dev->do_ioctl = n2_ioctl;
+		dev->open = n2_open;
+		dev->stop = n2_close;
+		hdlc->attach = sca_attach;
+		hdlc->xmit = sca_xmit;
+		port->settings.clock_type = CLOCK_EXT;
+		port->card = card;
+
+		if (register_hdlc_device(dev)) {
+			printk(KERN_WARNING "n2: unable to register hdlc "
+			       "device\n");
+			port->card = NULL;
+			n2_destroy_card(card);
+			return -ENOBUFS;
+		}
+		sca_init_sync_port(port); /* Set up SCA memory */
+
+		printk(KERN_INFO "%s: RISCom/N2 node %d\n",
+		       dev->name, port->phy_node);
+	}
+
+	*new_card = card;
+	new_card = &card->next_card;
+
+	return 0;
+}
+
+
+
+static int __init n2_init(void)
+{
+	if (hw==NULL) {
+#ifdef MODULE
+		printk(KERN_INFO "n2: no card initialized\n");
+#endif
+		return -ENOSYS;	/* no parameters specified, abort */
+	}
+
+	printk(KERN_INFO "%s\n", version);
+
+	do {
+		unsigned long io, irq, ram;
+		long valid[2] = { 0, 0 }; /* Default = both ports disabled */
+
+		io = simple_strtoul(hw, &hw, 0);
+
+		if (*hw++ != ',')
+			break;
+		irq = simple_strtoul(hw, &hw, 0);
+
+		if (*hw++ != ',')
+			break;
+		ram = simple_strtoul(hw, &hw, 0);
+
+		if (*hw++ != ',')
+			break;
+		while(1) {
+			if (*hw == '0' && !valid[0])
+				valid[0] = 1; /* Port 0 enabled */
+			else if (*hw == '1' && !valid[1])
+				valid[1] = 1; /* Port 1 enabled */
+			else
+				break;
+			hw++;
+		}
+
+		if (!valid[0] && !valid[1])
+			break;	/* at least one port must be used */
+
+		if (*hw == ':' || *hw == '\x0')
+			n2_run(io, irq, ram, valid[0], valid[1]);
+
+		if (*hw == '\x0')
+			return first_card ? 0 : -ENOSYS;
+	}while(*hw++ == ':');
+
+	printk(KERN_ERR "n2: invalid hardware parameters\n");
+	return first_card ? 0 : -ENOSYS;
+}
+
+
+static void __exit n2_cleanup(void)
+{
+	card_t *card = first_card;
+
+	while (card) {
+		card_t *ptr = card;
+		card = card->next_card;
+		n2_destroy_card(ptr);
+	}
+}
+
+
+module_init(n2_init);
+module_exit(n2_cleanup);
+
+MODULE_AUTHOR("Krzysztof Halasa <khc@pm.waw.pl>");
+MODULE_DESCRIPTION("RISCom/N2 serial port driver");
+MODULE_LICENSE("GPL v2");
+module_param(hw, charp, 0444);	/* hw=io,irq,ram,ports:io,irq,... */
diff --git a/drivers/net/wan/pc300-falc-lh.h b/drivers/net/wan/pc300-falc-lh.h
new file mode 100644
index 0000000..01ed23c
--- /dev/null
+++ b/drivers/net/wan/pc300-falc-lh.h
@@ -0,0 +1,1238 @@
+/*
+ * falc.h	Description of the Siemens FALC T1/E1 framer.
+ *
+ * Author:	Ivan Passos <ivan@cyclades.com>
+ *
+ * Copyright:	(c) 2000-2001 Cyclades Corp.
+ *
+ *	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.
+ *
+ * $Log: falc-lh.h,v $
+ * Revision 3.1  2001/06/15 12:41:10  regina
+ * upping major version number
+ *
+ * Revision 1.1.1.1  2001/06/13 20:24:47  daniela
+ * PC300 initial CVS version (3.4.0-pre1)
+ *
+ * Revision 1.1 2000/05/15 ivan
+ * Included DJA bits for the LIM2 register.
+ *
+ * Revision 1.0 2000/02/22 ivan
+ * Initial version.
+ *
+ */
+
+#ifndef _FALC_LH_H
+#define _FALC_LH_H
+
+#define NUM_OF_T1_CHANNELS	24
+#define NUM_OF_E1_CHANNELS	32
+
+/*>>>>>>>>>>>>>>>>>  FALC Register Bits (Transmit Mode)  <<<<<<<<<<<<<<<<<<< */
+
+/* CMDR (Command Register)
+   ---------------- E1 & T1 ------------------------------ */
+#define CMDR_RMC	0x80
+#define CMDR_RRES	0x40
+#define CMDR_XREP	0x20
+#define CMDR_XRES	0x10
+#define CMDR_XHF	0x08
+#define CMDR_XTF	0x04
+#define CMDR_XME	0x02
+#define CMDR_SRES	0x01
+
+/* MODE (Mode Register)
+   ----------------- E1 & T1 ----------------------------- */
+#define MODE_MDS2	0x80
+#define MODE_MDS1	0x40
+#define MODE_MDS0	0x20
+#define MODE_BRAC	0x10
+#define MODE_HRAC	0x08
+
+/* IPC (Interrupt Port Configuration)
+   ----------------- E1 & T1 ----------------------------- */
+#define IPC_VIS		0x80
+#define IPC_SCI		0x04
+#define IPC_IC1		0x02
+#define IPC_IC0		0x01
+
+/* CCR1 (Common Configuration Register 1)
+   ----------------- E1 & T1 ----------------------------- */
+#define CCR1_SFLG       0x80
+#define CCR1_XTS16RA    0x40
+#define CCR1_BRM        0x40
+#define CCR1_CASSYM     0x20
+#define CCR1_EDLX       0x20
+#define CCR1_EITS       0x10
+#define CCR1_ITF        0x08
+#define CCR1_RFT1       0x02
+#define CCR1_RFT0       0x01
+
+/* CCR3 (Common Configuration Register 3)
+   ---------------- E1 & T1 ------------------------------ */
+
+#define CCR3_PRE1       0x80
+#define CCR3_PRE0       0x40
+#define CCR3_EPT        0x20
+#define CCR3_RADD       0x10
+#define CCR3_RCRC       0x04
+#define CCR3_XCRC       0x02
+
+
+/* RTR1-4 (Receive Timeslot Register 1-4)
+   ---------------- E1 & T1 ------------------------------ */
+
+#define RTR1_TS0        0x80
+#define RTR1_TS1        0x40
+#define RTR1_TS2        0x20
+#define RTR1_TS3        0x10
+#define RTR1_TS4        0x08
+#define RTR1_TS5        0x04
+#define RTR1_TS6        0x02
+#define RTR1_TS7        0x01
+
+#define RTR2_TS8        0x80
+#define RTR2_TS9        0x40
+#define RTR2_TS10       0x20
+#define RTR2_TS11       0x10
+#define RTR2_TS12       0x08
+#define RTR2_TS13       0x04
+#define RTR2_TS14       0x02
+#define RTR2_TS15       0x01
+
+#define RTR3_TS16       0x80
+#define RTR3_TS17       0x40
+#define RTR3_TS18       0x20
+#define RTR3_TS19       0x10
+#define RTR3_TS20       0x08
+#define RTR3_TS21       0x04
+#define RTR3_TS22       0x02
+#define RTR3_TS23       0x01
+
+#define RTR4_TS24       0x80
+#define RTR4_TS25       0x40
+#define RTR4_TS26       0x20
+#define RTR4_TS27       0x10
+#define RTR4_TS28       0x08
+#define RTR4_TS29       0x04
+#define RTR4_TS30       0x02
+#define RTR4_TS31       0x01
+
+
+/* TTR1-4 (Transmit Timeslot Register 1-4)
+   ---------------- E1 & T1 ------------------------------ */
+
+#define TTR1_TS0        0x80
+#define TTR1_TS1        0x40
+#define TTR1_TS2        0x20
+#define TTR1_TS3        0x10
+#define TTR1_TS4        0x08
+#define TTR1_TS5        0x04
+#define TTR1_TS6        0x02
+#define TTR1_TS7        0x01
+
+#define TTR2_TS8        0x80
+#define TTR2_TS9        0x40
+#define TTR2_TS10       0x20
+#define TTR2_TS11       0x10
+#define TTR2_TS12       0x08
+#define TTR2_TS13       0x04
+#define TTR2_TS14       0x02
+#define TTR2_TS15       0x01
+
+#define TTR3_TS16       0x80
+#define TTR3_TS17       0x40
+#define TTR3_TS18       0x20
+#define TTR3_TS19       0x10
+#define TTR3_TS20       0x08
+#define TTR3_TS21       0x04
+#define TTR3_TS22       0x02
+#define TTR3_TS23       0x01
+
+#define TTR4_TS24       0x80
+#define TTR4_TS25       0x40
+#define TTR4_TS26       0x20
+#define TTR4_TS27       0x10
+#define TTR4_TS28       0x08
+#define TTR4_TS29       0x04
+#define TTR4_TS30       0x02
+#define TTR4_TS31       0x01
+
+
+
+/* IMR0-4 (Interrupt Mask Register 0-4)
+
+   ----------------- E1 & T1 ----------------------------- */
+
+#define IMR0_RME        0x80
+#define IMR0_RFS        0x40
+#define IMR0_T8MS       0x20
+#define IMR0_ISF        0x20
+#define IMR0_RMB        0x10
+#define IMR0_CASC       0x08
+#define IMR0_RSC        0x08
+#define IMR0_CRC6       0x04
+#define IMR0_CRC4       0x04
+#define IMR0_PDEN	0x02
+#define IMR0_RPF        0x01
+
+#define IMR1_CASE       0x80
+#define IMR1_RDO        0x40
+#define IMR1_ALLS       0x20
+#define IMR1_XDU        0x10
+#define IMR1_XMB        0x08
+#define IMR1_XLSC       0x02
+#define IMR1_XPR        0x01
+#define IMR1_LLBSC	0x80
+
+#define IMR2_FAR        0x80
+#define IMR2_LFA        0x40
+#define IMR2_MFAR       0x20
+#define IMR2_T400MS     0x10
+#define IMR2_LMFA       0x10
+#define IMR2_AIS        0x08
+#define IMR2_LOS        0x04
+#define IMR2_RAR        0x02
+#define IMR2_RA         0x01
+
+#define IMR3_ES         0x80
+#define IMR3_SEC        0x40
+#define IMR3_LMFA16     0x20
+#define IMR3_AIS16      0x10
+#define IMR3_RA16       0x08
+#define IMR3_API        0x04
+#define IMR3_XSLP       0x20
+#define IMR3_XSLN       0x10
+#define IMR3_LLBSC      0x08
+#define IMR3_XRS        0x04
+#define IMR3_SLN        0x02
+#define IMR3_SLP        0x01
+
+#define IMR4_LFA        0x80
+#define IMR4_FER        0x40
+#define IMR4_CER        0x20
+#define IMR4_AIS        0x10
+#define IMR4_LOS        0x08
+#define IMR4_CVE        0x04
+#define IMR4_SLIP       0x02
+#define IMR4_EBE        0x01
+
+/* FMR0-5 for E1 and T1  (Framer Mode Register ) */
+
+#define FMR0_XC1        0x80
+#define FMR0_XC0        0x40
+#define FMR0_RC1        0x20
+#define FMR0_RC0        0x10
+#define FMR0_EXTD       0x08
+#define FMR0_ALM        0x04
+#define E1_FMR0_FRS     0x02
+#define T1_FMR0_FRS     0x08
+#define FMR0_SRAF       0x04
+#define FMR0_EXLS       0x02
+#define FMR0_SIM        0x01
+
+#define FMR1_MFCS       0x80
+#define FMR1_AFR        0x40
+#define FMR1_ENSA       0x20
+#define FMR1_CTM        0x80
+#define FMR1_SIGM       0x40
+#define FMR1_EDL        0x20
+#define FMR1_PMOD       0x10
+#define FMR1_XFS        0x08
+#define FMR1_CRC        0x08
+#define FMR1_ECM        0x04
+#define FMR1_IMOD       0x02
+#define FMR1_XAIS       0x01
+
+#define FMR2_RFS1       0x80
+#define FMR2_RFS0       0x40
+#define FMR2_MCSP	0x40
+#define FMR2_RTM        0x20
+#define FMR2_SSP        0x20
+#define FMR2_DAIS       0x10
+#define FMR2_SAIS       0x08
+#define FMR2_PLB        0x04
+#define FMR2_AXRA       0x02
+#define FMR2_ALMF       0x01
+#define FMR2_EXZE       0x01
+
+#define LOOP_RTM	0x40
+#define LOOP_SFM	0x40
+#define LOOP_ECLB	0x20
+#define LOOP_CLA	0x1f
+
+/*--------------------- E1 ----------------------------*/
+#define FMR3_XLD	0x20
+#define FMR3_XLU	0x10
+
+/*--------------------- T1 ----------------------------*/
+#define FMR4_AIS3       0x80
+#define FMR4_TM         0x40
+#define FMR4_XRA        0x20
+#define FMR4_SSC1       0x10
+#define FMR4_SSC0       0x08
+#define FMR4_AUTO       0x04
+#define FMR4_FM1        0x02
+#define FMR4_FM0        0x01
+
+#define FMR5_SRS        0x80
+#define FMR5_EIBR       0x40
+#define FMR5_XLD        0x20
+#define FMR5_XLU        0x10
+
+
+/* LOOP (Channel Loop Back)
+
+   ------------------ E1 & T1 ---------------------------- */
+
+#define LOOP_SFM        0x40
+#define LOOP_ECLB       0x20
+#define LOOP_CLA4       0x10
+#define LOOP_CLA3       0x08
+#define LOOP_CLA2       0x04
+#define LOOP_CLA1       0x02
+#define LOOP_CLA0       0x01
+
+
+
+/* XSW (Transmit Service Word Pulseframe)
+
+   ------------------- E1 --------------------------- */
+
+#define XSW_XSIS        0x80
+#define XSW_XTM         0x40
+#define XSW_XRA         0x20
+#define XSW_XY0         0x10
+#define XSW_XY1         0x08
+#define XSW_XY2         0x04
+#define XSW_XY3         0x02
+#define XSW_XY4         0x01
+
+
+/* XSP (Transmit Spare Bits)
+
+   ------------------- E1 --------------------------- */
+
+#define XSP_XAP         0x80
+#define XSP_CASEN       0x40
+#define XSP_TT0         0x20
+#define XSP_EBP         0x10
+#define XSP_AXS         0x08
+#define XSP_XSIF        0x04
+#define XSP_XS13        0x02
+#define XSP_XS15        0x01
+
+
+/* XC0/1 (Transmit Control 0/1)
+   ------------------ E1 & T1 ---------------------------- */
+
+#define XC0_SA8E        0x80
+#define XC0_SA7E        0x40
+#define XC0_SA6E        0x20
+#define XC0_SA5E        0x10
+#define XC0_SA4E        0x08
+#define XC0_BRM         0x80
+#define XC0_MFBS        0x40
+#define XC0_SFRZ        0x10
+#define XC0_XCO2        0x04
+#define XC0_XCO1        0x02
+#define XC0_XCO0        0x01
+
+#define XC1_XTO5        0x20
+#define XC1_XTO4        0x10
+#define XC1_XTO3        0x08
+#define XC1_XTO2        0x04
+#define XC1_XTO1        0x02
+#define XC1_XTO0        0x01
+
+
+/* RC0/1 (Receive Control 0/1)
+   ------------------ E1 & T1 ---------------------------- */
+
+#define RC0_SICS        0x40
+#define RC0_CRCI        0x20
+#define RC0_XCRCI       0x10
+#define RC0_RDIS        0x08
+#define RC0_RCO2        0x04
+#define RC0_RCO1        0x02
+#define RC0_RCO0        0x01
+
+#define RC1_SWD         0x80
+#define RC1_ASY4        0x40
+#define RC1_RRAM        0x40
+#define RC1_RTO5        0x20
+#define RC1_RTO4        0x10
+#define RC1_RTO3        0x08
+#define RC1_RTO2        0x04
+#define RC1_RTO1        0x02
+#define RC1_RTO0        0x01
+
+
+
+/* XPM0-2 (Transmit Pulse Mask 0-2)
+   --------------------- E1 & T1 ------------------------- */
+
+#define XPM0_XP12       0x80
+#define XPM0_XP11       0x40
+#define XPM0_XP10       0x20
+#define XPM0_XP04       0x10
+#define XPM0_XP03       0x08
+#define XPM0_XP02       0x04
+#define XPM0_XP01       0x02
+#define XPM0_XP00       0x01
+
+#define XPM1_XP30       0x80
+#define XPM1_XP24       0x40
+#define XPM1_XP23       0x20
+#define XPM1_XP22       0x10
+#define XPM1_XP21       0x08
+#define XPM1_XP20       0x04
+#define XPM1_XP14       0x02
+#define XPM1_XP13       0x01
+
+#define XPM2_XLHP       0x80
+#define XPM2_XLT        0x40
+#define XPM2_DAXLT      0x20
+#define XPM2_XP34       0x08
+#define XPM2_XP33       0x04
+#define XPM2_XP32       0x02
+#define XPM2_XP31       0x01
+
+
+/* TSWM (Transparent Service Word Mask)
+   ------------------ E1 ---------------------------- */
+
+#define TSWM_TSIS       0x80
+#define TSWM_TSIF       0x40
+#define TSWM_TRA        0x20
+#define TSWM_TSA4       0x10
+#define TSWM_TSA5       0x08
+#define TSWM_TSA6       0x04
+#define TSWM_TSA7       0x02
+#define TSWM_TSA8       0x01
+
+/* IDLE <Idle Channel Code Register>
+
+   ------------------ E1 & T1 ----------------------- */
+
+#define IDLE_IDL7       0x80
+#define IDLE_IDL6       0x40
+#define IDLE_IDL5       0x20
+#define IDLE_IDL4       0x10
+#define IDLE_IDL3       0x08
+#define IDLE_IDL2       0x04
+#define IDLE_IDL1       0x02
+#define IDLE_IDL0       0x01
+
+
+/* XSA4-8 <Transmit SA4-8 Register(Read/Write) >
+   -------------------E1 ----------------------------- */
+
+#define XSA4_XS47       0x80
+#define XSA4_XS46       0x40
+#define XSA4_XS45       0x20
+#define XSA4_XS44       0x10
+#define XSA4_XS43       0x08
+#define XSA4_XS42       0x04
+#define XSA4_XS41       0x02
+#define XSA4_XS40       0x01
+
+#define XSA5_XS57       0x80
+#define XSA5_XS56       0x40
+#define XSA5_XS55       0x20
+#define XSA5_XS54       0x10
+#define XSA5_XS53       0x08
+#define XSA5_XS52       0x04
+#define XSA5_XS51       0x02
+#define XSA5_XS50       0x01
+
+#define XSA6_XS67       0x80
+#define XSA6_XS66       0x40
+#define XSA6_XS65       0x20
+#define XSA6_XS64       0x10
+#define XSA6_XS63       0x08
+#define XSA6_XS62       0x04
+#define XSA6_XS61       0x02
+#define XSA6_XS60       0x01
+
+#define XSA7_XS77       0x80
+#define XSA7_XS76       0x40
+#define XSA7_XS75       0x20
+#define XSA7_XS74       0x10
+#define XSA7_XS73       0x08
+#define XSA7_XS72       0x04
+#define XSA7_XS71       0x02
+#define XSA7_XS70       0x01
+
+#define XSA8_XS87       0x80
+#define XSA8_XS86       0x40
+#define XSA8_XS85       0x20
+#define XSA8_XS84       0x10
+#define XSA8_XS83       0x08
+#define XSA8_XS82       0x04
+#define XSA8_XS81       0x02
+#define XSA8_XS80       0x01
+
+
+/* XDL1-3 (Transmit DL-Bit Register1-3 (read/write))
+   ----------------------- T1 --------------------- */
+
+#define XDL1_XDL17      0x80
+#define XDL1_XDL16      0x40
+#define XDL1_XDL15      0x20
+#define XDL1_XDL14      0x10
+#define XDL1_XDL13      0x08
+#define XDL1_XDL12      0x04
+#define XDL1_XDL11      0x02
+#define XDL1_XDL10      0x01
+
+#define XDL2_XDL27      0x80
+#define XDL2_XDL26      0x40
+#define XDL2_XDL25      0x20
+#define XDL2_XDL24      0x10
+#define XDL2_XDL23      0x08
+#define XDL2_XDL22      0x04
+#define XDL2_XDL21      0x02
+#define XDL2_XDL20      0x01
+
+#define XDL3_XDL37      0x80
+#define XDL3_XDL36      0x40
+#define XDL3_XDL35      0x20
+#define XDL3_XDL34      0x10
+#define XDL3_XDL33      0x08
+#define XDL3_XDL32      0x04
+#define XDL3_XDL31      0x02
+#define XDL3_XDL30      0x01
+
+
+/* ICB1-4 (Idle Channel Register 1-4)
+   ------------------ E1 ---------------------------- */
+
+#define E1_ICB1_IC0	0x80
+#define E1_ICB1_IC1	0x40
+#define E1_ICB1_IC2	0x20
+#define E1_ICB1_IC3	0x10
+#define E1_ICB1_IC4	0x08
+#define E1_ICB1_IC5	0x04
+#define E1_ICB1_IC6	0x02
+#define E1_ICB1_IC7	0x01
+
+#define E1_ICB2_IC8	0x80
+#define E1_ICB2_IC9	0x40
+#define E1_ICB2_IC10	0x20
+#define E1_ICB2_IC11	0x10
+#define E1_ICB2_IC12	0x08
+#define E1_ICB2_IC13	0x04
+#define E1_ICB2_IC14	0x02
+#define E1_ICB2_IC15	0x01
+
+#define E1_ICB3_IC16	0x80
+#define E1_ICB3_IC17	0x40
+#define E1_ICB3_IC18	0x20
+#define E1_ICB3_IC19	0x10
+#define E1_ICB3_IC20	0x08
+#define E1_ICB3_IC21	0x04
+#define E1_ICB3_IC22	0x02
+#define E1_ICB3_IC23	0x01
+
+#define E1_ICB4_IC24	0x80
+#define E1_ICB4_IC25	0x40
+#define E1_ICB4_IC26	0x20
+#define E1_ICB4_IC27	0x10
+#define E1_ICB4_IC28	0x08
+#define E1_ICB4_IC29	0x04
+#define E1_ICB4_IC30	0x02
+#define E1_ICB4_IC31	0x01
+
+/* ICB1-4 (Idle Channel Register 1-4)
+   ------------------ T1 ---------------------------- */
+
+#define T1_ICB1_IC1	0x80
+#define T1_ICB1_IC2	0x40
+#define T1_ICB1_IC3	0x20
+#define T1_ICB1_IC4	0x10
+#define T1_ICB1_IC5	0x08
+#define T1_ICB1_IC6	0x04
+#define T1_ICB1_IC7	0x02
+#define T1_ICB1_IC8	0x01
+
+#define T1_ICB2_IC9	0x80
+#define T1_ICB2_IC10	0x40
+#define T1_ICB2_IC11	0x20
+#define T1_ICB2_IC12	0x10
+#define T1_ICB2_IC13	0x08
+#define T1_ICB2_IC14	0x04
+#define T1_ICB2_IC15	0x02
+#define T1_ICB2_IC16	0x01
+
+#define T1_ICB3_IC17	0x80
+#define T1_ICB3_IC18	0x40
+#define T1_ICB3_IC19	0x20
+#define T1_ICB3_IC20	0x10
+#define T1_ICB3_IC21	0x08
+#define T1_ICB3_IC22	0x04
+#define T1_ICB3_IC23	0x02
+#define T1_ICB3_IC24	0x01
+
+/* FMR3 (Framer Mode Register 3)
+   --------------------E1------------------------ */
+
+#define FMR3_CMI        0x08
+#define FMR3_SYNSA      0x04
+#define FMR3_CFRZ       0x02
+#define FMR3_EXTIW      0x01
+
+
+
+/* CCB1-3 (Clear Channel Register)
+   ------------------- T1 ----------------------- */
+
+#define CCB1_CH1        0x80
+#define CCB1_CH2        0x40
+#define CCB1_CH3        0x20
+#define CCB1_CH4        0x10
+#define CCB1_CH5        0x08
+#define CCB1_CH6        0x04
+#define CCB1_CH7        0x02
+#define CCB1_CH8        0x01
+
+#define CCB2_CH9        0x80
+#define CCB2_CH10       0x40
+#define CCB2_CH11       0x20
+#define CCB2_CH12       0x10
+#define CCB2_CH13       0x08
+#define CCB2_CH14       0x04
+#define CCB2_CH15       0x02
+#define CCB2_CH16       0x01
+
+#define CCB3_CH17       0x80
+#define CCB3_CH18       0x40
+#define CCB3_CH19       0x20
+#define CCB3_CH20       0x10
+#define CCB3_CH21       0x08
+#define CCB3_CH22       0x04
+#define CCB3_CH23       0x02
+#define CCB3_CH24       0x01
+
+
+/* LIM0/1 (Line Interface Mode 0/1)
+   ------------------- E1 & T1 --------------------------- */
+
+#define LIM0_XFB        0x80
+#define LIM0_XDOS       0x40
+#define LIM0_SCL1       0x20
+#define LIM0_SCL0       0x10
+#define LIM0_EQON       0x08
+#define LIM0_ELOS       0x04
+#define LIM0_LL         0x02
+#define LIM0_MAS        0x01
+
+#define LIM1_EFSC       0x80
+#define LIM1_RIL2       0x40
+#define LIM1_RIL1       0x20
+#define LIM1_RIL0       0x10
+#define LIM1_DCOC       0x08
+#define LIM1_JATT       0x04
+#define LIM1_RL         0x02
+#define LIM1_DRS        0x01
+
+
+/* PCDR (Pulse Count Detection Register(Read/Write))
+   ------------------ E1 & T1 ------------------------- */
+
+#define PCDR_PCD7	0x80
+#define PCDR_PCD6	0x40
+#define PCDR_PCD5	0x20
+#define PCDR_PCD4	0x10
+#define PCDR_PCD3	0x08
+#define PCDR_PCD2	0x04
+#define PCDR_PCD1	0x02
+#define PCDR_PCD0	0x01
+
+#define PCRR_PCR7	0x80
+#define PCRR_PCR6	0x40
+#define PCRR_PCR5	0x20
+#define PCRR_PCR4	0x10
+#define PCRR_PCR3	0x08
+#define PCRR_PCR2	0x04
+#define PCRR_PCR1	0x02
+#define PCRR_PCR0	0x01
+
+
+/* LIM2 (Line Interface Mode 2)
+
+   ------------------ E1 & T1 ---------------------------- */
+
+#define LIM2_DJA2	0x20
+#define LIM2_DJA1	0x10
+#define LIM2_LOS2	0x02
+#define LIM2_LOS1	0x01
+
+/* LCR1 (Loop Code Register 1) */
+
+#define LCR1_EPRM	0x80
+#define	LCR1_XPRBS	0x40
+
+/* SIC1 (System Interface Control 1) */
+#define SIC1_SRSC	0x80
+#define SIC1_RBS1	0x20
+#define SIC1_RBS0	0x10
+#define SIC1_SXSC	0x08
+#define SIC1_XBS1	0x02
+#define SIC1_XBS0	0x01
+
+/* DEC (Disable Error Counter)
+   ------------------ E1 & T1 ---------------------------- */
+
+#define DEC_DCEC3       0x20
+#define DEC_DBEC        0x10
+#define DEC_DCEC1       0x08
+#define DEC_DCEC        0x08
+#define DEC_DEBC        0x04
+#define DEC_DCVC        0x02
+#define DEC_DFEC        0x01
+
+
+/* FALC Register Bits (Receive Mode)
+   ---------------------------------------------------------------------------- */
+
+
+/* FRS0/1 (Framer Receive Status Register 0/1)
+   ----------------- E1 & T1 ---------------------------------- */
+
+#define FRS0_LOS        0x80
+#define FRS0_AIS        0x40
+#define FRS0_LFA        0x20
+#define FRS0_RRA        0x10
+#define FRS0_API        0x08
+#define FRS0_NMF        0x04
+#define FRS0_LMFA       0x02
+#define FRS0_FSRF       0x01
+
+#define FRS1_TS16RA     0x40
+#define FRS1_TS16LOS    0x20
+#define FRS1_TS16AIS    0x10
+#define FRS1_TS16LFA    0x08
+#define FRS1_EXZD       0x80
+#define FRS1_LLBDD      0x10
+#define FRS1_LLBAD      0x08
+#define FRS1_XLS        0x02
+#define FRS1_XLO        0x01
+#define FRS1_PDEN	0x40
+
+/* FRS2/3 (Framer Receive Status Register 2/3)
+   ----------------- T1 ---------------------------------- */
+
+#define FRS2_ESC2       0x80
+#define FRS2_ESC1       0x40
+#define FRS2_ESC0       0x20
+
+#define FRS3_FEH5       0x20
+#define FRS3_FEH4       0x10
+#define FRS3_FEH3       0x08
+#define FRS3_FEH2       0x04
+#define FRS3_FEH1       0x02
+#define FRS3_FEH0       0x01
+
+
+/* RSW (Receive Service Word Pulseframe)
+   ----------------- E1 ------------------------------ */
+
+#define RSW_RSI         0x80
+#define RSW_RRA         0x20
+#define RSW_RYO         0x10
+#define RSW_RY1         0x08
+#define RSW_RY2         0x04
+#define RSW_RY3         0x02
+#define RSW_RY4         0x01
+
+
+/* RSP (Receive Spare Bits / Additional Status)
+   ---------------- E1 ------------------------------- */
+
+#define RSP_SI1         0x80
+#define RSP_SI2         0x40
+#define RSP_LLBDD	0x10
+#define RSP_LLBAD	0x08
+#define RSP_RSIF        0x04
+#define RSP_RS13        0x02
+#define RSP_RS15        0x01
+
+
+/* FECL (Framing Error Counter)
+   ---------------- E1 & T1 -------------------------- */
+
+#define FECL_FE7        0x80
+#define FECL_FE6        0x40
+#define FECL_FE5        0x20
+#define FECL_FE4        0x10
+#define FECL_FE3        0x08
+#define FECL_FE2        0x04
+#define FECL_FE1        0x02
+#define FECL_FE0        0x01
+
+#define FECH_FE15       0x80
+#define FECH_FE14       0x40
+#define FECH_FE13       0x20
+#define FECH_FE12       0x10
+#define FECH_FE11       0x08
+#define FECH_FE10       0x04
+#define FECH_FE9        0x02
+#define FECH_FE8        0x01
+
+
+/* CVCl (Code Violation Counter)
+   ----------------- E1 ------------------------- */
+
+#define CVCL_CV7        0x80
+#define CVCL_CV6        0x40
+#define CVCL_CV5        0x20
+#define CVCL_CV4        0x10
+#define CVCL_CV3        0x08
+#define CVCL_CV2        0x04
+#define CVCL_CV1        0x02
+#define CVCL_CV0        0x01
+
+#define CVCH_CV15       0x80
+#define CVCH_CV14       0x40
+#define CVCH_CV13       0x20
+#define CVCH_CV12       0x10
+#define CVCH_CV11       0x08
+#define CVCH_CV10       0x04
+#define CVCH_CV9        0x02
+#define CVCH_CV8        0x01
+
+
+/* CEC1-3L (CRC Error Counter)
+   ------------------ E1 ----------------------------- */
+
+#define CEC1L_CR7       0x80
+#define CEC1L_CR6       0x40
+#define CEC1L_CR5       0x20
+#define CEC1L_CR4       0x10
+#define CEC1L_CR3       0x08
+#define CEC1L_CR2       0x04
+#define CEC1L_CR1       0x02
+#define CEC1L_CR0       0x01
+
+#define CEC1H_CR15      0x80
+#define CEC1H_CR14      0x40
+#define CEC1H_CR13      0x20
+#define CEC1H_CR12      0x10
+#define CEC1H_CR11      0x08
+#define CEC1H_CR10      0x04
+#define CEC1H_CR9       0x02
+#define CEC1H_CR8       0x01
+
+#define CEC2L_CR7       0x80
+#define CEC2L_CR6       0x40
+#define CEC2L_CR5       0x20
+#define CEC2L_CR4       0x10
+#define CEC2L_CR3       0x08
+#define CEC2L_CR2       0x04
+#define CEC2L_CR1       0x02
+#define CEC2L_CR0       0x01
+
+#define CEC2H_CR15      0x80
+#define CEC2H_CR14      0x40
+#define CEC2H_CR13      0x20
+#define CEC2H_CR12      0x10
+#define CEC2H_CR11      0x08
+#define CEC2H_CR10      0x04
+#define CEC2H_CR9       0x02
+#define CEC2H_CR8       0x01
+
+#define CEC3L_CR7       0x80
+#define CEC3L_CR6       0x40
+#define CEC3L_CR5       0x20
+#define CEC3L_CR4       0x10
+#define CEC3L_CR3       0x08
+#define CEC3L_CR2       0x04
+#define CEC3L_CR1       0x02
+#define CEC3L_CR0       0x01
+
+#define CEC3H_CR15      0x80
+#define CEC3H_CR14      0x40
+#define CEC3H_CR13      0x20
+#define CEC3H_CR12      0x10
+#define CEC3H_CR11      0x08
+#define CEC3H_CR10      0x04
+#define CEC3H_CR9       0x02
+#define CEC3H_CR8       0x01
+
+
+/* CECL (CRC Error Counter)
+
+   ------------------ T1 ----------------------------- */
+
+#define CECL_CR7        0x80
+#define CECL_CR6        0x40
+#define CECL_CR5        0x20
+#define CECL_CR4        0x10
+#define CECL_CR3        0x08
+#define CECL_CR2        0x04
+#define CECL_CR1        0x02
+#define CECL_CR0        0x01
+
+#define CECH_CR15       0x80
+#define CECH_CR14       0x40
+#define CECH_CR13       0x20
+#define CECH_CR12       0x10
+#define CECH_CR11       0x08
+#define CECH_CR10       0x04
+#define CECH_CR9        0x02
+#define CECH_CR8        0x01
+
+/* EBCL (E Bit Error Counter)
+   ------------------- E1 & T1 ------------------------- */
+
+#define EBCL_EB7        0x80
+#define EBCL_EB6        0x40
+#define EBCL_EB5        0x20
+#define EBCL_EB4        0x10
+#define EBCL_EB3        0x08
+#define EBCL_EB2        0x04
+#define EBCL_EB1        0x02
+#define EBCL_EB0        0x01
+
+#define EBCH_EB15       0x80
+#define EBCH_EB14       0x40
+#define EBCH_EB13       0x20
+#define EBCH_EB12       0x10
+#define EBCH_EB11       0x08
+#define EBCH_EB10       0x04
+#define EBCH_EB9        0x02
+#define EBCH_EB8        0x01
+
+
+/* RSA4-8 (Receive Sa4-8-Bit Register)
+   -------------------- E1 --------------------------- */
+
+#define RSA4_RS47       0x80
+#define RSA4_RS46       0x40
+#define RSA4_RS45       0x20
+#define RSA4_RS44       0x10
+#define RSA4_RS43       0x08
+#define RSA4_RS42       0x04
+#define RSA4_RS41       0x02
+#define RSA4_RS40       0x01
+
+#define RSA5_RS57       0x80
+#define RSA5_RS56       0x40
+#define RSA5_RS55       0x20
+#define RSA5_RS54       0x10
+#define RSA5_RS53       0x08
+#define RSA5_RS52       0x04
+#define RSA5_RS51       0x02
+#define RSA5_RS50       0x01
+
+#define RSA6_RS67       0x80
+#define RSA6_RS66       0x40
+#define RSA6_RS65       0x20
+#define RSA6_RS64       0x10
+#define RSA6_RS63       0x08
+#define RSA6_RS62       0x04
+#define RSA6_RS61       0x02
+#define RSA6_RS60       0x01
+
+#define RSA7_RS77       0x80
+#define RSA7_RS76       0x40
+#define RSA7_RS75       0x20
+#define RSA7_RS74       0x10
+#define RSA7_RS73       0x08
+#define RSA7_RS72       0x04
+#define RSA7_RS71       0x02
+#define RSA7_RS70       0x01
+
+#define RSA8_RS87       0x80
+#define RSA8_RS86       0x40
+#define RSA8_RS85       0x20
+#define RSA8_RS84       0x10
+#define RSA8_RS83       0x08
+#define RSA8_RS82       0x04
+#define RSA8_RS81       0x02
+#define RSA8_RS80       0x01
+
+/* RSA6S (Receive Sa6 Bit Status Register)
+   ------------------------ T1 ------------------------- */
+
+#define RSA6S_SX        0x20
+#define RSA6S_SF        0x10
+#define RSA6S_SE        0x08
+#define RSA6S_SC        0x04
+#define RSA6S_SA        0x02
+#define RSA6S_S8        0x01
+
+
+/* RDL1-3 Receive DL-Bit Register1-3)
+   ------------------------ T1 ------------------------- */
+
+#define RDL1_RDL17      0x80
+#define RDL1_RDL16      0x40
+#define RDL1_RDL15      0x20
+#define RDL1_RDL14      0x10
+#define RDL1_RDL13      0x08
+#define RDL1_RDL12      0x04
+#define RDL1_RDL11      0x02
+#define RDL1_RDL10      0x01
+
+#define RDL2_RDL27      0x80
+#define RDL2_RDL26      0x40
+#define RDL2_RDL25      0x20
+#define RDL2_RDL24      0x10
+#define RDL2_RDL23      0x08
+#define RDL2_RDL22      0x04
+#define RDL2_RDL21      0x02
+#define RDL2_RDL20      0x01
+
+#define RDL3_RDL37      0x80
+#define RDL3_RDL36      0x40
+#define RDL3_RDL35      0x20
+#define RDL3_RDL34      0x10
+#define RDL3_RDL33      0x08
+#define RDL3_RDL32      0x04
+#define RDL3_RDL31      0x02
+#define RDL3_RDL30      0x01
+
+
+/* SIS (Signaling Status Register)
+
+   -------------------- E1 & T1 -------------------------- */
+
+#define SIS_XDOV        0x80
+#define SIS_XFW         0x40
+#define SIS_XREP        0x20
+#define SIS_RLI         0x08
+#define SIS_CEC         0x04
+#define SIS_BOM         0x01
+
+
+/* RSIS (Receive Signaling Status Register)
+
+   -------------------- E1 & T1 --------------------------- */
+
+#define RSIS_VFR        0x80
+#define RSIS_RDO        0x40
+#define RSIS_CRC16      0x20
+#define RSIS_RAB        0x10
+#define RSIS_HA1        0x08
+#define RSIS_HA0        0x04
+#define RSIS_HFR        0x02
+#define RSIS_LA         0x01
+
+
+/* RBCL/H (Receive Byte Count Low/High)
+
+   ------------------- E1 & T1 ----------------------- */
+
+#define RBCL_RBC7       0x80
+#define RBCL_RBC6       0x40
+#define RBCL_RBC5       0x20
+#define RBCL_RBC4       0x10
+#define RBCL_RBC3       0x08
+#define RBCL_RBC2       0x04
+#define RBCL_RBC1       0x02
+#define RBCL_RBC0       0x01
+
+#define RBCH_OV         0x10
+#define RBCH_RBC11      0x08
+#define RBCH_RBC10      0x04
+#define RBCH_RBC9       0x02
+#define RBCH_RBC8       0x01
+
+
+/* ISR1-3  (Interrupt Status Register 1-3)
+
+   ------------------ E1 & T1 ------------------------------ */
+
+#define  FISR0_RME	0x80
+#define  FISR0_RFS	0x40
+#define  FISR0_T8MS	0x20
+#define  FISR0_ISF	0x20
+#define  FISR0_RMB	0x10
+#define  FISR0_CASC	0x08
+#define  FISR0_RSC	0x08
+#define  FISR0_CRC6	0x04
+#define  FISR0_CRC4	0x04
+#define  FISR0_PDEN	0x02
+#define  FISR0_RPF	0x01
+
+#define  FISR1_CASE	0x80
+#define  FISR1_LLBSC	0x80
+#define  FISR1_RDO	0x40
+#define  FISR1_ALLS	0x20
+#define  FISR1_XDU	0x10
+#define  FISR1_XMB	0x08
+#define  FISR1_XLSC	0x02
+#define  FISR1_XPR	0x01
+
+#define  FISR2_FAR	0x80
+#define  FISR2_LFA	0x40
+#define  FISR2_MFAR	0x20
+#define  FISR2_T400MS	0x10
+#define  FISR2_LMFA	0x10
+#define  FISR2_AIS	0x08
+#define  FISR2_LOS	0x04
+#define  FISR2_RAR	0x02
+#define  FISR2_RA	0x01
+
+#define  FISR3_ES	0x80
+#define  FISR3_SEC	0x40
+#define  FISR3_LMFA16	0x20
+#define  FISR3_AIS16	0x10
+#define  FISR3_RA16	0x08
+#define  FISR3_API	0x04
+#define  FISR3_XSLP	0x20
+#define  FISR3_XSLN	0x10
+#define  FISR3_LLBSC	0x08
+#define  FISR3_XRS	0x04
+#define  FISR3_SLN	0x02
+#define  FISR3_SLP	0x01
+
+
+/* GIS  (Global Interrupt Status Register)
+
+   --------------------- E1 & T1 --------------------- */
+
+#define  GIS_ISR3	0x08
+#define  GIS_ISR2	0x04
+#define  GIS_ISR1	0x02
+#define  GIS_ISR0	0x01
+
+
+/* VSTR  (Version Status Register)
+
+   --------------------- E1 & T1 --------------------- */
+
+#define  VSTR_VN3	0x08
+#define  VSTR_VN2	0x04
+#define  VSTR_VN1	0x02
+#define  VSTR_VN0	0x01
+
+
+/*>>>>>>>>>>>>>>>>>>>>>  Local Control Structures  <<<<<<<<<<<<<<<<<<<<<<<<< */
+
+/* Write-only Registers (E1/T1 control mode write registers) */
+#define XFIFOH	0x00		/* Tx FIFO High Byte */
+#define XFIFOL	0x01		/* Tx FIFO Low Byte */
+#define CMDR	0x02		/* Command Reg */
+#define DEC	0x60		/* Disable Error Counter */
+#define TEST2	0x62		/* Manuf. Test Reg 2 */
+#define XS(nbr)	(0x70 + (nbr))	/* Tx CAS Reg (0 to 15) */
+
+/* Read-write Registers (E1/T1 status mode read registers) */
+#define MODE	0x03	/* Mode Reg */
+#define RAH1	0x04	/* Receive Address High 1 */
+#define RAH2	0x05	/* Receive Address High 2 */
+#define RAL1	0x06	/* Receive Address Low 1 */
+#define RAL2	0x07	/* Receive Address Low 2 */
+#define IPC	0x08	/* Interrupt Port Configuration */
+#define CCR1	0x09	/* Common Configuration Reg 1 */
+#define CCR3	0x0A	/* Common Configuration Reg 3 */
+#define PRE	0x0B	/* Preamble Reg */
+#define RTR1	0x0C	/* Receive Timeslot Reg 1 */
+#define RTR2	0x0D	/* Receive Timeslot Reg 2 */
+#define RTR3	0x0E	/* Receive Timeslot Reg 3 */
+#define RTR4	0x0F	/* Receive Timeslot Reg 4 */
+#define TTR1	0x10	/* Transmit Timeslot Reg 1 */
+#define TTR2	0x11	/* Transmit Timeslot Reg 2 */
+#define TTR3	0x12	/* Transmit Timeslot Reg 3 */
+#define TTR4	0x13	/* Transmit Timeslot Reg 4 */
+#define IMR0	0x14	/* Interrupt Mask Reg 0 */
+#define IMR1	0x15	/* Interrupt Mask Reg 1 */
+#define IMR2	0x16	/* Interrupt Mask Reg 2 */
+#define IMR3	0x17	/* Interrupt Mask Reg 3 */
+#define IMR4	0x18	/* Interrupt Mask Reg 4 */
+#define IMR5	0x19	/* Interrupt Mask Reg 5 */
+#define FMR0	0x1A	/* Framer Mode Reigster 0 */
+#define FMR1	0x1B	/* Framer Mode Reigster 1 */
+#define FMR2	0x1C	/* Framer Mode Reigster 2 */
+#define LOOP	0x1D	/* Channel Loop Back */
+#define XSW	0x1E	/* Transmit Service Word */
+#define FMR4	0x1E	/* Framer Mode Reg 4 */
+#define XSP	0x1F	/* Transmit Spare Bits */
+#define FMR5	0x1F	/* Framer Mode Reg 5 */
+#define XC0	0x20	/* Transmit Control 0 */
+#define XC1	0x21	/* Transmit Control 1 */
+#define RC0	0x22	/* Receive Control 0 */
+#define RC1	0x23	/* Receive Control 1 */
+#define XPM0	0x24	/* Transmit Pulse Mask 0 */
+#define XPM1	0x25	/* Transmit Pulse Mask 1 */
+#define XPM2	0x26	/* Transmit Pulse Mask 2 */
+#define TSWM	0x27	/* Transparent Service Word Mask */
+#define TEST1	0x28	/* Manuf. Test Reg 1 */
+#define IDLE	0x29	/* Idle Channel Code */
+#define XSA4    0x2A	/* Transmit SA4 Bit Reg */
+#define XDL1	0x2A	/* Transmit DL-Bit Reg 2 */
+#define XSA5    0x2B	/* Transmit SA4 Bit Reg */
+#define XDL2	0x2B	/* Transmit DL-Bit Reg 2 */
+#define XSA6    0x2C	/* Transmit SA4 Bit Reg */
+#define XDL3	0x2C	/* Transmit DL-Bit Reg 2 */
+#define XSA7    0x2D	/* Transmit SA4 Bit Reg */
+#define CCB1	0x2D	/* Clear Channel Reg 1 */
+#define XSA8    0x2E	/* Transmit SA4 Bit Reg */
+#define CCB2	0x2E	/* Clear Channel Reg 2 */
+#define FMR3	0x2F	/* Framer Mode Reg. 3 */
+#define CCB3	0x2F	/* Clear Channel Reg 3 */
+#define ICB1	0x30	/* Idle Channel Reg 1 */
+#define ICB2	0x31	/* Idle Channel Reg 2 */
+#define ICB3	0x32	/* Idle Channel Reg 3 */
+#define ICB4	0x33	/* Idle Channel Reg 4 */
+#define LIM0	0x34	/* Line Interface Mode 0 */
+#define LIM1	0x35	/* Line Interface Mode 1 */
+#define PCDR	0x36	/* Pulse Count Detection */
+#define PCRR	0x37	/* Pulse Count Recovery */
+#define LIM2	0x38	/* Line Interface Mode Reg 2 */
+#define LCR1	0x39	/* Loop Code Reg 1 */
+#define LCR2	0x3A	/* Loop Code Reg 2 */
+#define LCR3	0x3B	/* Loop Code Reg 3 */
+#define SIC1	0x3C	/* System Interface Control 1 */
+
+/* Read-only Registers (E1/T1 control mode read registers) */
+#define RFIFOH	0x00		/* Receive FIFO */
+#define RFIFOL	0x01		/* Receive FIFO */
+#define FRS0	0x4C		/* Framer Receive Status 0 */
+#define FRS1	0x4D		/* Framer Receive Status 1 */
+#define RSW	0x4E		/* Receive Service Word */
+#define FRS2	0x4E		/* Framer Receive Status 2 */
+#define RSP	0x4F		/* Receive Spare Bits */
+#define FRS3	0x4F		/* Framer Receive Status 3 */
+#define FECL	0x50		/* Framing Error Counter */
+#define FECH	0x51		/* Framing Error Counter */
+#define CVCL	0x52		/* Code Violation Counter */
+#define CVCH	0x53		/* Code Violation Counter */
+#define CECL	0x54		/* CRC Error Counter 1 */
+#define CECH	0x55		/* CRC Error Counter 1 */
+#define EBCL	0x56		/* E-Bit Error Counter */
+#define EBCH	0x57		/* E-Bit Error Counter */
+#define BECL	0x58		/* Bit Error Counter Low */
+#define BECH	0x59		/* Bit Error Counter Low */
+#define CEC3	0x5A		/* CRC Error Counter 3 (16-bit) */
+#define RSA4	0x5C		/* Receive SA4 Bit Reg */
+#define RDL1	0x5C		/* Receive DL-Bit Reg 1 */
+#define RSA5	0x5D		/* Receive SA5 Bit Reg */
+#define RDL2	0x5D		/* Receive DL-Bit Reg 2 */
+#define RSA6	0x5E		/* Receive SA6 Bit Reg */
+#define RDL3	0x5E		/* Receive DL-Bit Reg 3 */
+#define RSA7	0x5F		/* Receive SA7 Bit Reg */
+#define RSA8	0x60		/* Receive SA8 Bit Reg */
+#define RSA6S	0x61		/* Receive SA6 Bit Status Reg */
+#define TSR0	0x62		/* Manuf. Test Reg 0 */
+#define TSR1	0x63		/* Manuf. Test Reg 1 */
+#define SIS	0x64		/* Signaling Status Reg */
+#define RSIS	0x65		/* Receive Signaling Status Reg */
+#define RBCL	0x66		/* Receive Byte Control */
+#define RBCH	0x67		/* Receive Byte Control */
+#define FISR0	0x68		/* Interrupt Status Reg 0 */
+#define FISR1	0x69		/* Interrupt Status Reg 1 */
+#define FISR2	0x6A		/* Interrupt Status Reg 2 */
+#define FISR3	0x6B		/* Interrupt Status Reg 3 */
+#define GIS	0x6E		/* Global Interrupt Status */
+#define VSTR	0x6F		/* Version Status */
+#define RS(nbr)	(0x70 + (nbr))	/* Rx CAS Reg (0 to 15) */
+
+#endif	/* _FALC_LH_H */
+
diff --git a/drivers/net/wan/pc300.h b/drivers/net/wan/pc300.h
new file mode 100644
index 0000000..73401b0
--- /dev/null
+++ b/drivers/net/wan/pc300.h
@@ -0,0 +1,497 @@
+/*
+ * pc300.h	Cyclades-PC300(tm) Kernel API Definitions.
+ *
+ * Author:	Ivan Passos <ivan@cyclades.com>
+ *
+ * Copyright:	(c) 1999-2002 Cyclades Corp.
+ *
+ *	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.
+ *
+ * $Log: pc300.h,v $
+ * Revision 3.12  2002/03/07 14:17:09  henrique
+ * License data fixed
+ *
+ * Revision 3.11  2002/01/28 21:09:39  daniela
+ * Included ';' after pc300hw.bus.
+ *
+ * Revision 3.10  2002/01/17 17:58:52  ivan
+ * Support for PC300-TE/M (PMC).
+ *
+ * Revision 3.9  2001/09/28 13:30:53  daniela
+ * Renamed dma_start routine to rx_dma_start.
+ *
+ * Revision 3.8  2001/09/24 13:03:45  daniela
+ * Fixed BOF interrupt treatment. Created dma_start routine.
+ *
+ * Revision 3.7  2001/08/10 17:19:58  daniela
+ * Fixed IOCTLs defines.
+ *
+ * Revision 3.6  2001/07/18 19:24:42  daniela
+ * Included kernel version.
+ *
+ * Revision 3.5  2001/07/05 18:38:08  daniela
+ * DMA transmission bug fix.
+ *
+ * Revision 3.4  2001/06/26 17:10:40  daniela
+ * New configuration parameters (line code, CRC calculation and clock).
+ *
+ * Revision 3.3  2001/06/22 13:13:02  regina
+ * MLPPP implementation
+ *
+ * Revision 3.2  2001/06/18 17:56:09  daniela
+ * Increased DEF_MTU and TX_QUEUE_LEN.
+ *
+ * Revision 3.1  2001/06/15 12:41:10  regina
+ * upping major version number
+ *
+ * Revision 1.1.1.1  2001/06/13 20:25:06  daniela
+ * PC300 initial CVS version (3.4.0-pre1)
+ *
+ * Revision 2.3 2001/03/05 daniela
+ * Created struct pc300conf, to provide the hardware information to pc300util.
+ * Inclusion of 'alloc_ramsize' field on structure 'pc300hw'.
+ * 
+ * Revision 2.2 2000/12/22 daniela
+ * Structures and defines to support pc300util: statistics, status, 
+ * loopback tests, trace.
+ * 
+ * Revision 2.1 2000/09/28 ivan
+ * Inclusion of 'iophys' and 'iosize' fields on structure 'pc300hw', to 
+ * allow release of I/O region at module unload.
+ * Changed location of include files.
+ *
+ * Revision 2.0 2000/03/27 ivan
+ * Added support for the PC300/TE cards.
+ *
+ * Revision 1.1 2000/01/31 ivan
+ * Replaced 'pc300[drv|sca].h' former PC300 driver include files.
+ *
+ * Revision 1.0 1999/12/16 ivan
+ * First official release.
+ * Inclusion of 'nchan' field on structure 'pc300hw', to allow variable 
+ * number of ports per card.
+ * Inclusion of 'if_ptr' field on structure 'pc300dev'.
+ *
+ * Revision 0.6 1999/11/17 ivan
+ * Changed X.25-specific function names to comply with adopted convention.
+ *
+ * Revision 0.5 1999/11/16 Daniela Squassoni
+ * X.25 support.
+ *
+ * Revision 0.4 1999/11/15 ivan
+ * Inclusion of 'clock' field on structure 'pc300hw'.
+ *
+ * Revision 0.3 1999/11/10 ivan
+ * IOCTL name changing.
+ * Inclusion of driver function prototypes.
+ *
+ * Revision 0.2 1999/11/03 ivan
+ * Inclusion of 'tx_skb' and union 'ifu' on structure 'pc300dev'.
+ *
+ * Revision 0.1 1999/01/15 ivan
+ * Initial version.
+ *
+ */
+
+#ifndef	_PC300_H
+#define	_PC300_H
+
+#include <linux/hdlc.h>
+#include "hd64572.h"
+#include "pc300-falc-lh.h"
+
+#ifndef CY_TYPES
+#define CY_TYPES
+typedef	__u64	ucdouble;	/* 64 bits, unsigned */
+typedef	__u32	uclong;		/* 32 bits, unsigned */
+typedef	__u16	ucshort;	/* 16 bits, unsigned */
+typedef	__u8	ucchar;		/* 8 bits, unsigned */
+#endif /* CY_TYPES */
+
+#define PC300_PROTO_MLPPP 1		
+
+#define PC300_KERNEL	"2.4.x"	/* Kernel supported by this driver */
+
+#define	PC300_DEVNAME	"hdlc"	/* Dev. name base (for hdlc0, hdlc1, etc.) */
+#define PC300_MAXINDEX	100	/* Max dev. name index (the '0' in hdlc0) */
+
+#define	PC300_MAXCARDS	4	/* Max number of cards per system */
+#define	PC300_MAXCHAN	2	/* Number of channels per card */
+
+#define	PC300_PLX_WIN	0x80    /* PLX control window size (128b) */
+#define	PC300_RAMSIZE	0x40000 /* RAM window size (256Kb) */
+#define	PC300_SCASIZE	0x400   /* SCA window size (1Kb) */
+#define	PC300_FALCSIZE	0x400	/* FALC window size (1Kb) */
+
+#define PC300_OSC_CLOCK	24576000
+#define PC300_PCI_CLOCK	33000000
+
+#define BD_DEF_LEN	0x0800	/* DMA buffer length (2KB) */
+#define DMA_TX_MEMSZ	0x8000	/* Total DMA Tx memory size (32KB/ch) */
+#define DMA_RX_MEMSZ	0x10000	/* Total DMA Rx memory size (64KB/ch) */
+
+#define N_DMA_TX_BUF	(DMA_TX_MEMSZ / BD_DEF_LEN)	/* DMA Tx buffers */
+#define N_DMA_RX_BUF	(DMA_RX_MEMSZ / BD_DEF_LEN)	/* DMA Rx buffers */
+
+/* DMA Buffer Offsets */
+#define DMA_TX_BASE	((N_DMA_TX_BUF + N_DMA_RX_BUF) *	\
+			 PC300_MAXCHAN * sizeof(pcsca_bd_t))
+#define DMA_RX_BASE	(DMA_TX_BASE + PC300_MAXCHAN*DMA_TX_MEMSZ)
+
+/* DMA Descriptor Offsets */
+#define DMA_TX_BD_BASE	0x0000
+#define DMA_RX_BD_BASE	(DMA_TX_BD_BASE + ((PC300_MAXCHAN*DMA_TX_MEMSZ / \
+				BD_DEF_LEN) * sizeof(pcsca_bd_t)))
+
+/* DMA Descriptor Macros */
+#define TX_BD_ADDR(chan, n)	(DMA_TX_BD_BASE + \
+				 ((N_DMA_TX_BUF*chan) + n) * sizeof(pcsca_bd_t))
+#define RX_BD_ADDR(chan, n)	(DMA_RX_BD_BASE + \
+				 ((N_DMA_RX_BUF*chan) + n) * sizeof(pcsca_bd_t))
+
+/* Macro to access the FALC registers (TE only) */
+#define F_REG(reg, chan)	(0x200*(chan) + ((reg)<<2))
+
+/***************************************
+ * Memory access functions/macros      *
+ * (required to support Alpha systems) *
+ ***************************************/
+#ifdef __KERNEL__
+#define cpc_writeb(port,val)	{writeb((ucchar)(val),(port)); mb();}
+#define cpc_writew(port,val)	{writew((ushort)(val),(port)); mb();}
+#define cpc_writel(port,val)	{writel((uclong)(val),(port)); mb();}
+
+#define cpc_readb(port)		readb(port)
+#define cpc_readw(port)		readw(port)
+#define cpc_readl(port)		readl(port)
+
+#else /* __KERNEL__ */
+#define cpc_writeb(port,val)	(*(volatile ucchar *)(port) = (ucchar)(val))
+#define cpc_writew(port,val)	(*(volatile ucshort *)(port) = (ucshort)(val))
+#define cpc_writel(port,val)	(*(volatile uclong *)(port) = (uclong)(val))
+
+#define cpc_readb(port)		(*(volatile ucchar *)(port))
+#define cpc_readw(port)		(*(volatile ucshort *)(port))
+#define cpc_readl(port)		(*(volatile uclong *)(port))
+
+#endif /* __KERNEL__ */
+
+/****** Data Structures *****************************************************/
+
+/*
+ *      RUNTIME_9050 - PLX PCI9050-1 local configuration and shared runtime
+ *      registers. This structure can be used to access the 9050 registers
+ *      (memory mapped).
+ */
+struct RUNTIME_9050 {
+	uclong	loc_addr_range[4];	/* 00-0Ch : Local Address Ranges */
+	uclong	loc_rom_range;		/* 10h : Local ROM Range */
+	uclong	loc_addr_base[4];	/* 14-20h : Local Address Base Addrs */
+	uclong	loc_rom_base;		/* 24h : Local ROM Base */
+	uclong	loc_bus_descr[4];	/* 28-34h : Local Bus Descriptors */
+	uclong	rom_bus_descr;		/* 38h : ROM Bus Descriptor */
+	uclong	cs_base[4];		/* 3C-48h : Chip Select Base Addrs */
+	uclong	intr_ctrl_stat;		/* 4Ch : Interrupt Control/Status */
+	uclong	init_ctrl;		/* 50h : EEPROM ctrl, Init Ctrl, etc */
+};
+
+#define PLX_9050_LINT1_ENABLE	0x01
+#define PLX_9050_LINT1_POL	0x02
+#define PLX_9050_LINT1_STATUS	0x04
+#define PLX_9050_LINT2_ENABLE	0x08
+#define PLX_9050_LINT2_POL	0x10
+#define PLX_9050_LINT2_STATUS	0x20
+#define PLX_9050_INTR_ENABLE	0x40
+#define PLX_9050_SW_INTR	0x80
+
+/* Masks to access the init_ctrl PLX register */
+#define	PC300_CLKSEL_MASK		(0x00000004UL)
+#define	PC300_CHMEDIA_MASK(chan)	(0x00000020UL<<(chan*3))
+#define	PC300_CTYPE_MASK		(0x00000800UL)
+
+/* CPLD Registers (base addr = falcbase, TE only) */
+/* CPLD v. 0 */
+#define CPLD_REG1	0x140	/* Chip resets, DCD/CTS status */
+#define CPLD_REG2	0x144	/* Clock enable , LED control */
+/* CPLD v. 2 or higher */
+#define CPLD_V2_REG1	0x100	/* Chip resets, DCD/CTS status */
+#define CPLD_V2_REG2	0x104	/* Clock enable , LED control */
+#define CPLD_ID_REG	0x108	/* CPLD version */
+
+/* CPLD Register bit description: for the FALC bits, they should always be 
+   set based on the channel (use (bit<<(2*ch)) to access the correct bit for 
+   that channel) */
+#define CPLD_REG1_FALC_RESET	0x01
+#define CPLD_REG1_SCA_RESET	0x02
+#define CPLD_REG1_GLOBAL_CLK	0x08
+#define CPLD_REG1_FALC_DCD	0x10
+#define CPLD_REG1_FALC_CTS	0x20
+
+#define CPLD_REG2_FALC_TX_CLK	0x01
+#define CPLD_REG2_FALC_RX_CLK	0x02
+#define CPLD_REG2_FALC_LED1	0x10
+#define CPLD_REG2_FALC_LED2	0x20
+
+/* Structure with FALC-related fields (TE only) */
+#define PC300_FALC_MAXLOOP	0x0000ffff	/* for falc_issue_cmd() */
+
+typedef struct falc {
+	ucchar sync;		/* If true FALC is synchronized */
+	ucchar active;		/* if TRUE then already active */
+	ucchar loop_active;	/* if TRUE a line loopback UP was received */
+	ucchar loop_gen;	/* if TRUE a line loopback UP was issued */
+
+	ucchar num_channels;
+	ucchar offset;		/* 1 for T1, 0 for E1 */
+	ucchar full_bandwidth;
+
+	ucchar xmb_cause;
+	ucchar multiframe_mode;
+
+	/* Statistics */
+	ucshort pden;	/* Pulse Density violation count */
+	ucshort los;	/* Loss of Signal count */
+	ucshort losr;	/* Loss of Signal recovery count */
+	ucshort lfa;	/* Loss of frame alignment count */
+	ucshort farec;	/* Frame Alignment Recovery count */
+	ucshort lmfa;	/* Loss of multiframe alignment count */
+	ucshort ais;	/* Remote Alarm indication Signal count */
+	ucshort sec;	/* One-second timer */
+	ucshort es;	/* Errored second */
+	ucshort rai;	/* remote alarm received */
+	ucshort bec;
+	ucshort fec;
+	ucshort cvc;
+	ucshort cec;
+	ucshort ebc;
+
+	/* Status */
+	ucchar red_alarm;
+	ucchar blue_alarm;
+	ucchar loss_fa;
+	ucchar yellow_alarm;
+	ucchar loss_mfa;
+	ucchar prbs;
+} falc_t;
+
+typedef struct falc_status {
+	ucchar sync;  /* If true FALC is synchronized */
+	ucchar red_alarm;
+	ucchar blue_alarm;
+	ucchar loss_fa;
+	ucchar yellow_alarm;
+	ucchar loss_mfa;
+	ucchar prbs;
+} falc_status_t;
+
+typedef struct rsv_x21_status {
+	ucchar dcd;
+	ucchar dsr;
+	ucchar cts;
+	ucchar rts;
+	ucchar dtr;
+} rsv_x21_status_t;
+
+typedef struct pc300stats {
+	int hw_type;
+	uclong line_on;
+	uclong line_off;
+	struct net_device_stats gen_stats;
+	falc_t te_stats;
+} pc300stats_t;
+
+typedef struct pc300status {
+	int hw_type;
+	rsv_x21_status_t gen_status;
+	falc_status_t te_status;
+} pc300status_t;
+
+typedef struct pc300loopback {
+	char loop_type;
+	char loop_on;
+} pc300loopback_t;
+
+typedef struct pc300patterntst {
+	char patrntst_on;       /* 0 - off; 1 - on; 2 - read num_errors */
+	ucshort num_errors;
+} pc300patterntst_t;
+
+typedef struct pc300dev {
+	void *if_ptr;		/* General purpose pointer */
+	struct pc300ch *chan;
+	ucchar trace_on;
+	uclong line_on;		/* DCD(X.21, RSV) / sync(TE) change counters */
+	uclong line_off;
+#ifdef __KERNEL__
+	char name[16];
+	struct net_device *dev;
+
+	void *private;
+	struct sk_buff *tx_skb;
+	union {	/* This union has all the protocol-specific structures */
+		struct ppp_device pppdev;
+	}ifu;
+#ifdef CONFIG_PC300_MLPPP
+	void *cpc_tty;	/* information to PC300 TTY driver */
+#endif
+#endif /* __KERNEL__ */
+}pc300dev_t;
+
+typedef struct pc300hw {
+	int type;		/* RSV, X21, etc. */
+	int bus;		/* Bus (PCI, PMC, etc.) */
+	int nchan;		/* number of channels */
+	int irq;		/* interrupt request level */
+	uclong clock;		/* Board clock */
+	ucchar cpld_id;		/* CPLD ID (TE only) */
+	ucshort cpld_reg1;	/* CPLD reg 1 (TE only) */
+	ucshort cpld_reg2;	/* CPLD reg 2 (TE only) */
+	ucshort gpioc_reg;	/* PLX GPIOC reg */
+	ucshort intctl_reg;	/* PLX Int Ctrl/Status reg */
+	uclong iophys;		/* PLX registers I/O base */
+	uclong iosize;		/* PLX registers I/O size */
+	uclong plxphys;		/* PLX registers MMIO base (physical) */
+	void __iomem * plxbase;	/* PLX registers MMIO base (virtual) */
+	uclong plxsize;		/* PLX registers MMIO size */
+	uclong scaphys;		/* SCA registers MMIO base (physical) */
+	void __iomem * scabase;	/* SCA registers MMIO base (virtual) */
+	uclong scasize;		/* SCA registers MMIO size */
+	uclong ramphys;		/* On-board RAM MMIO base (physical) */
+	void __iomem * rambase;	/* On-board RAM MMIO base (virtual) */
+	uclong alloc_ramsize;	/* RAM MMIO size allocated by the PCI bridge */
+	uclong ramsize;		/* On-board RAM MMIO size */
+	uclong falcphys;	/* FALC registers MMIO base (physical) */
+	void __iomem * falcbase;/* FALC registers MMIO base (virtual) */
+	uclong falcsize;	/* FALC registers MMIO size */
+} pc300hw_t;
+
+typedef struct pc300chconf {
+	sync_serial_settings	phys_settings;	/* Clock type/rate (in bps), 
+						   loopback mode */
+	raw_hdlc_proto		proto_settings;	/* Encoding, parity (CRC) */
+	uclong media;		/* HW media (RS232, V.35, etc.) */
+	uclong proto;		/* Protocol (PPP, X.25, etc.) */
+	ucchar monitor;		/* Monitor mode (0 = off, !0 = on) */
+
+	/* TE-specific parameters */
+	ucchar lcode;		/* Line Code (AMI, B8ZS, etc.) */
+	ucchar fr_mode;		/* Frame Mode (ESF, D4, etc.) */
+	ucchar lbo;		/* Line Build Out */
+	ucchar rx_sens;		/* Rx Sensitivity (long- or short-haul) */
+	uclong tslot_bitmap;	/* bit[i]=1  =>  timeslot _i_ is active */
+} pc300chconf_t;
+
+typedef struct pc300ch {
+	struct pc300 *card;
+	int channel;
+	pc300dev_t d;
+	pc300chconf_t conf;
+	ucchar tx_first_bd;	/* First TX DMA block descr. w/ data */
+	ucchar tx_next_bd;	/* Next free TX DMA block descriptor */
+	ucchar rx_first_bd;	/* First free RX DMA block descriptor */
+	ucchar rx_last_bd;	/* Last free RX DMA block descriptor */
+	ucchar nfree_tx_bd;	/* Number of free TX DMA block descriptors */
+	falc_t falc;		/* FALC structure (TE only) */
+} pc300ch_t;
+
+typedef struct pc300 {
+	pc300hw_t hw;			/* hardware config. */
+	pc300ch_t chan[PC300_MAXCHAN];
+#ifdef __KERNEL__
+	spinlock_t card_lock;
+#endif /* __KERNEL__ */
+} pc300_t;
+
+typedef struct pc300conf {
+	pc300hw_t hw;
+	pc300chconf_t conf;
+} pc300conf_t;
+
+/* DEV ioctl() commands */
+#define	N_SPPP_IOCTLS	2
+
+enum pc300_ioctl_cmds {
+	SIOCCPCRESERVED = (SIOCDEVPRIVATE + N_SPPP_IOCTLS),
+	SIOCGPC300CONF,
+	SIOCSPC300CONF,
+	SIOCGPC300STATUS,
+	SIOCGPC300FALCSTATUS,
+	SIOCGPC300UTILSTATS,
+	SIOCGPC300UTILSTATUS,
+	SIOCSPC300TRACE,
+	SIOCSPC300LOOPBACK,
+	SIOCSPC300PATTERNTEST,
+};
+
+/* Loopback types - PC300/TE boards */
+enum pc300_loopback_cmds {
+	PC300LOCLOOP = 1,
+	PC300REMLOOP,
+	PC300PAYLOADLOOP,
+	PC300GENLOOPUP,
+	PC300GENLOOPDOWN,
+};
+
+/* Control Constant Definitions */
+#define	PC300_RSV	0x01
+#define	PC300_X21	0x02
+#define	PC300_TE	0x03
+
+#define	PC300_PCI	0x00
+#define	PC300_PMC	0x01
+
+#define PC300_LC_AMI	0x01
+#define PC300_LC_B8ZS	0x02
+#define PC300_LC_NRZ	0x03
+#define PC300_LC_HDB3	0x04
+
+/* Framing (T1) */
+#define PC300_FR_ESF		0x01
+#define PC300_FR_D4		0x02
+#define PC300_FR_ESF_JAPAN	0x03
+
+/* Framing (E1) */
+#define PC300_FR_MF_CRC4	0x04
+#define PC300_FR_MF_NON_CRC4	0x05
+#define PC300_FR_UNFRAMED	0x06
+
+#define PC300_LBO_0_DB		0x00
+#define PC300_LBO_7_5_DB	0x01
+#define PC300_LBO_15_DB		0x02
+#define PC300_LBO_22_5_DB	0x03
+
+#define PC300_RX_SENS_SH	0x01
+#define PC300_RX_SENS_LH	0x02
+
+#define PC300_TX_TIMEOUT	(2*HZ)
+#define PC300_TX_QUEUE_LEN	100
+#define	PC300_DEF_MTU		1600
+
+#ifdef __KERNEL__
+/* Function Prototypes */
+int dma_buf_write(pc300_t *, int, ucchar *, int);
+int dma_buf_read(pc300_t *, int, struct sk_buff *);
+void tx_dma_start(pc300_t *, int);
+void rx_dma_start(pc300_t *, int);
+void tx_dma_stop(pc300_t *, int);
+void rx_dma_stop(pc300_t *, int);
+int cpc_queue_xmit(struct sk_buff *, struct net_device *);
+void cpc_net_rx(struct net_device *);
+void cpc_sca_status(pc300_t *, int);
+int cpc_change_mtu(struct net_device *, int);
+int cpc_ioctl(struct net_device *, struct ifreq *, int);
+int ch_config(pc300dev_t *);
+int rx_config(pc300dev_t *);
+int tx_config(pc300dev_t *);
+void cpc_opench(pc300dev_t *);
+void cpc_closech(pc300dev_t *);
+int cpc_open(struct net_device *dev);
+int cpc_close(struct net_device *dev);
+int cpc_set_media(hdlc_device *, int);
+#endif /* __KERNEL__ */
+
+#endif	/* _PC300_H */
+
diff --git a/drivers/net/wan/pc300_drv.c b/drivers/net/wan/pc300_drv.c
new file mode 100644
index 0000000..d67be25
--- /dev/null
+++ b/drivers/net/wan/pc300_drv.c
@@ -0,0 +1,3692 @@
+#define	USE_PCI_CLOCK
+static char rcsid[] = 
+"Revision: 3.4.5 Date: 2002/03/07 ";
+
+/*
+ * pc300.c	Cyclades-PC300(tm) Driver.
+ *
+ * Author:	Ivan Passos <ivan@cyclades.com>
+ * Maintainer:	PC300 Maintainer <pc300@cyclades.com>
+ *
+ * Copyright:	(c) 1999-2003 Cyclades Corp.
+ *
+ *	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.
+ *	
+ *	Using tabstop = 4.
+ * 
+ * $Log: pc300_drv.c,v $
+ * Revision 3.23  2002/03/20 13:58:40  henrique
+ * Fixed ortographic mistakes
+ *
+ * Revision 3.22  2002/03/13 16:56:56  henrique
+ * Take out the debug messages
+ *
+ * Revision 3.21  2002/03/07 14:17:09  henrique
+ * License data fixed
+ *
+ * Revision 3.20  2002/01/17 17:58:52  ivan
+ * Support for PC300-TE/M (PMC).
+ *
+ * Revision 3.19  2002/01/03 17:08:47  daniela
+ * Enables DMA reception when the SCA-II disables it improperly.
+ *
+ * Revision 3.18  2001/12/03 18:47:50  daniela
+ * Esthetic changes.
+ *
+ * Revision 3.17  2001/10/19 16:50:13  henrique
+ * Patch to kernel 2.4.12 and new generic hdlc.
+ *
+ * Revision 3.16  2001/10/16 15:12:31  regina
+ * clear statistics
+ *
+ * Revision 3.11 to 3.15  2001/10/11 20:26:04  daniela
+ * More DMA fixes for noisy lines.
+ * Return the size of bad frames in dma_get_rx_frame_size, so that the Rx buffer
+ * descriptors can be cleaned by dma_buf_read (called in cpc_net_rx).
+ * Renamed dma_start routine to rx_dma_start. Improved Rx statistics.
+ * Fixed BOF interrupt treatment. Created dma_start routine.
+ * Changed min and max to cpc_min and cpc_max.
+ *
+ * Revision 3.10  2001/08/06 12:01:51  regina
+ * Fixed problem in DSR_DE bit.
+ *
+ * Revision 3.9  2001/07/18 19:27:26  daniela
+ * Added some history comments.
+ *
+ * Revision 3.8  2001/07/12 13:11:19  regina
+ * bug fix - DCD-OFF in pc300 tty driver
+ *
+ * Revision 3.3 to 3.7  2001/07/06 15:00:20  daniela
+ * Removing kernel 2.4.3 and previous support.
+ * DMA transmission bug fix.
+ * MTU check in cpc_net_rx fixed.
+ * Boot messages reviewed.
+ * New configuration parameters (line code, CRC calculation and clock).
+ *
+ * Revision 3.2 2001/06/22 13:13:02  regina
+ * MLPPP implementation. Changed the header of message trace to include
+ * the device name. New format : "hdlcX[R/T]: ".
+ * Default configuration changed.
+ *
+ * Revision 3.1 2001/06/15 regina
+ * in cpc_queue_xmit, netif_stop_queue is called if don't have free descriptor
+ * upping major version number
+ *
+ * Revision 1.1.1.1  2001/06/13 20:25:04  daniela
+ * PC300 initial CVS version (3.4.0-pre1)
+ *
+ * Revision 3.0.1.2 2001/06/08 daniela
+ * Did some changes in the DMA programming implementation to avoid the 
+ * occurrence of a SCA-II bug when CDA is accessed during a DMA transfer.
+ *
+ * Revision 3.0.1.1 2001/05/02 daniela
+ * Added kernel 2.4.3 support.
+ * 
+ * Revision 3.0.1.0 2001/03/13 daniela, henrique
+ * Added Frame Relay Support.
+ * Driver now uses HDLC generic driver to provide protocol support.
+ * 
+ * Revision 3.0.0.8 2001/03/02 daniela
+ * Fixed ram size detection. 
+ * Changed SIOCGPC300CONF ioctl, to give hw information to pc300util.
+ * 
+ * Revision 3.0.0.7 2001/02/23 daniela
+ * netif_stop_queue called before the SCA-II transmition commands in 
+ * cpc_queue_xmit, and with interrupts disabled to avoid race conditions with 
+ * transmition interrupts.
+ * Fixed falc_check_status for Unframed E1.
+ * 
+ * Revision 3.0.0.6 2000/12/13 daniela
+ * Implemented pc300util support: trace, statistics, status and loopback
+ * tests for the PC300 TE boards.
+ * 
+ * Revision 3.0.0.5 2000/12/12 ivan
+ * Added support for Unframed E1.
+ * Implemented monitor mode.
+ * Fixed DCD sensitivity on the second channel.
+ * Driver now complies with new PCI kernel architecture.
+ *
+ * Revision 3.0.0.4 2000/09/28 ivan
+ * Implemented DCD sensitivity.
+ * Moved hardware-specific open to the end of cpc_open, to avoid race
+ * conditions with early reception interrupts.
+ * Included code for [request|release]_mem_region().
+ * Changed location of pc300.h .
+ * Minor code revision (contrib. of Jeff Garzik).
+ *
+ * Revision 3.0.0.3 2000/07/03 ivan
+ * Previous bugfix for the framing errors with external clock made X21
+ * boards stop working. This version fixes it.
+ *
+ * Revision 3.0.0.2 2000/06/23 ivan
+ * Revisited cpc_queue_xmit to prevent race conditions on Tx DMA buffer
+ * handling when Tx timeouts occur.
+ * Revisited Rx statistics.
+ * Fixed a bug in the SCA-II programming that would cause framing errors
+ * when external clock was configured.
+ *
+ * Revision 3.0.0.1 2000/05/26 ivan
+ * Added logic in the SCA interrupt handler so that no board can monopolize
+ * the driver.
+ * Request PLX I/O region, although driver doesn't use it, to avoid
+ * problems with other drivers accessing it.
+ *
+ * Revision 3.0.0.0 2000/05/15 ivan
+ * Did some changes in the DMA programming implementation to avoid the
+ * occurrence of a SCA-II bug in the second channel.
+ * Implemented workaround for PLX9050 bug that would cause a system lockup
+ * in certain systems, depending on the MMIO addresses allocated to the
+ * board.
+ * Fixed the FALC chip programming to avoid synchronization problems in the
+ * second channel (TE only).
+ * Implemented a cleaner and faster Tx DMA descriptor cleanup procedure in
+ * cpc_queue_xmit().
+ * Changed the built-in driver implementation so that the driver can use the
+ * general 'hdlcN' naming convention instead of proprietary device names.
+ * Driver load messages are now device-centric, instead of board-centric.
+ * Dynamic allocation of net_device structures.
+ * Code is now compliant with the new module interface (module_[init|exit]).
+ * Make use of the PCI helper functions to access PCI resources.
+ *
+ * Revision 2.0.0.0 2000/04/15 ivan
+ * Added support for the PC300/TE boards (T1/FT1/E1/FE1).
+ *
+ * Revision 1.1.0.0 2000/02/28 ivan
+ * Major changes in the driver architecture.
+ * Softnet compliancy implemented.
+ * Driver now reports physical instead of virtual memory addresses.
+ * Added cpc_change_mtu function.
+ *
+ * Revision 1.0.0.0 1999/12/16 ivan
+ * First official release.
+ * Support for 1- and 2-channel boards (which use distinct PCI Device ID's).
+ * Support for monolythic installation (i.e., drv built into the kernel).
+ * X.25 additional checking when lapb_[dis]connect_request returns an error.
+ * SCA programming now covers X.21 as well.
+ *
+ * Revision 0.3.1.0 1999/11/18 ivan
+ * Made X.25 support configuration-dependent (as it depends on external 
+ * modules to work).
+ * Changed X.25-specific function names to comply with adopted convention.
+ * Fixed typos in X.25 functions that would cause compile errors (Daniela).
+ * Fixed bug in ch_config that would disable interrupts on a previously 
+ * enabled channel if the other channel on the same board was enabled later.
+ *
+ * Revision 0.3.0.0 1999/11/16 daniela
+ * X.25 support.
+ *
+ * Revision 0.2.3.0 1999/11/15 ivan
+ * Function cpc_ch_status now provides more detailed information.
+ * Added support for X.21 clock configuration.
+ * Changed TNR1 setting in order to prevent Tx FIFO overaccesses by the SCA.
+ * Now using PCI clock instead of internal oscillator clock for the SCA.
+ *
+ * Revision 0.2.2.0 1999/11/10 ivan
+ * Changed the *_dma_buf_check functions so that they would print only 
+ * the useful info instead of the whole buffer descriptor bank.
+ * Fixed bug in cpc_queue_xmit that would eventually crash the system 
+ * in case of a packet drop.
+ * Implemented TX underrun handling.
+ * Improved SCA fine tuning to boost up its performance.
+ *
+ * Revision 0.2.1.0 1999/11/03 ivan
+ * Added functions *dma_buf_pt_init to allow independent initialization 
+ * of the next-descr. and DMA buffer pointers on the DMA descriptors.
+ * Kernel buffer release and tbusy clearing is now done in the interrupt 
+ * handler.
+ * Fixed bug in cpc_open that would cause an interface reopen to fail.
+ * Added a protocol-specific code section in cpc_net_rx.
+ * Removed printk level defs (they might be added back after the beta phase).
+ *
+ * Revision 0.2.0.0 1999/10/28 ivan
+ * Revisited the code so that new protocols can be easily added / supported. 
+ *
+ * Revision 0.1.0.1 1999/10/20 ivan
+ * Mostly "esthetic" changes.
+ *
+ * Revision 0.1.0.0 1999/10/11 ivan
+ * Initial version.
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/mm.h>
+#include <linux/ioport.h>
+#include <linux/pci.h>
+#include <linux/errno.h>
+#include <linux/string.h>
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <linux/net.h>
+#include <linux/skbuff.h>
+#include <linux/if_arp.h>
+#include <linux/netdevice.h>
+#include <linux/spinlock.h>
+#include <linux/if.h>
+
+#include <net/syncppp.h>
+#include <net/arp.h>
+
+#include <asm/io.h>
+#include <asm/uaccess.h>
+
+#include "pc300.h"
+
+#define	CPC_LOCK(card,flags)		\
+		do {						\
+		spin_lock_irqsave(&card->card_lock, flags);	\
+		} while (0)
+
+#define CPC_UNLOCK(card,flags)			\
+		do {							\
+		spin_unlock_irqrestore(&card->card_lock, flags);	\
+		} while (0)
+
+#undef	PC300_DEBUG_PCI
+#undef	PC300_DEBUG_INTR
+#undef	PC300_DEBUG_TX
+#undef	PC300_DEBUG_RX
+#undef	PC300_DEBUG_OTHER
+
+static struct pci_device_id cpc_pci_dev_id[] __devinitdata = {
+	/* PC300/RSV or PC300/X21, 2 chan */
+	{0x120e, 0x300, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0x300},
+	/* PC300/RSV or PC300/X21, 1 chan */
+	{0x120e, 0x301, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0x301},
+	/* PC300/TE, 2 chan */
+	{0x120e, 0x310, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0x310},
+	/* PC300/TE, 1 chan */
+	{0x120e, 0x311, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0x311},
+	/* PC300/TE-M, 2 chan */
+	{0x120e, 0x320, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0x320},
+	/* PC300/TE-M, 1 chan */
+	{0x120e, 0x321, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0x321},
+	/* End of table */
+	{0,},
+};
+MODULE_DEVICE_TABLE(pci, cpc_pci_dev_id);
+
+#ifndef cpc_min
+#define	cpc_min(a,b)	(((a)<(b))?(a):(b))
+#endif
+#ifndef cpc_max
+#define	cpc_max(a,b)	(((a)>(b))?(a):(b))
+#endif
+
+/* prototypes */
+static void tx_dma_buf_pt_init(pc300_t *, int);
+static void tx_dma_buf_init(pc300_t *, int);
+static void rx_dma_buf_pt_init(pc300_t *, int);
+static void rx_dma_buf_init(pc300_t *, int);
+static void tx_dma_buf_check(pc300_t *, int);
+static void rx_dma_buf_check(pc300_t *, int);
+static irqreturn_t cpc_intr(int, void *, struct pt_regs *);
+static struct net_device_stats *cpc_get_stats(struct net_device *);
+static int clock_rate_calc(uclong, uclong, int *);
+static uclong detect_ram(pc300_t *);
+static void plx_init(pc300_t *);
+static void cpc_trace(struct net_device *, struct sk_buff *, char);
+static int cpc_attach(struct net_device *, unsigned short, unsigned short);
+
+#ifdef CONFIG_PC300_MLPPP
+void cpc_tty_init(pc300dev_t * dev);
+void cpc_tty_unregister_service(pc300dev_t * pc300dev);
+void cpc_tty_receive(pc300dev_t * pc300dev);
+void cpc_tty_trigger_poll(pc300dev_t * pc300dev);
+void cpc_tty_reset_var(void);
+#endif
+
+/************************/
+/***   DMA Routines   ***/
+/************************/
+static void tx_dma_buf_pt_init(pc300_t * card, int ch)
+{
+	int i;
+	int ch_factor = ch * N_DMA_TX_BUF;
+	volatile pcsca_bd_t __iomem *ptdescr = (card->hw.rambase
+			               + DMA_TX_BD_BASE + ch_factor * sizeof(pcsca_bd_t));
+
+	for (i = 0; i < N_DMA_TX_BUF; i++, ptdescr++) {
+		cpc_writel(&ptdescr->next, (uclong) (DMA_TX_BD_BASE +
+			(ch_factor + ((i + 1) & (N_DMA_TX_BUF - 1))) * sizeof(pcsca_bd_t)));
+		cpc_writel(&ptdescr->ptbuf, 
+						(uclong) (DMA_TX_BASE + (ch_factor + i) * BD_DEF_LEN));
+	}
+}
+
+static void tx_dma_buf_init(pc300_t * card, int ch)
+{
+	int i;
+	int ch_factor = ch * N_DMA_TX_BUF;
+	volatile pcsca_bd_t __iomem *ptdescr = (card->hw.rambase
+			       + DMA_TX_BD_BASE + ch_factor * sizeof(pcsca_bd_t));
+
+	for (i = 0; i < N_DMA_TX_BUF; i++, ptdescr++) {
+		memset_io(ptdescr, 0, sizeof(pcsca_bd_t));
+		cpc_writew(&ptdescr->len, 0);
+		cpc_writeb(&ptdescr->status, DST_OSB);
+	}
+	tx_dma_buf_pt_init(card, ch);
+}
+
+static void rx_dma_buf_pt_init(pc300_t * card, int ch)
+{
+	int i;
+	int ch_factor = ch * N_DMA_RX_BUF;
+	volatile pcsca_bd_t __iomem *ptdescr = (card->hw.rambase
+				       + DMA_RX_BD_BASE + ch_factor * sizeof(pcsca_bd_t));
+
+	for (i = 0; i < N_DMA_RX_BUF; i++, ptdescr++) {
+		cpc_writel(&ptdescr->next, (uclong) (DMA_RX_BD_BASE +
+	     	(ch_factor + ((i + 1) & (N_DMA_RX_BUF - 1))) * sizeof(pcsca_bd_t)));
+		cpc_writel(&ptdescr->ptbuf,
+			   (uclong) (DMA_RX_BASE + (ch_factor + i) * BD_DEF_LEN));
+	}
+}
+
+static void rx_dma_buf_init(pc300_t * card, int ch)
+{
+	int i;
+	int ch_factor = ch * N_DMA_RX_BUF;
+	volatile pcsca_bd_t __iomem *ptdescr = (card->hw.rambase
+				       + DMA_RX_BD_BASE + ch_factor * sizeof(pcsca_bd_t));
+
+	for (i = 0; i < N_DMA_RX_BUF; i++, ptdescr++) {
+		memset_io(ptdescr, 0, sizeof(pcsca_bd_t));
+		cpc_writew(&ptdescr->len, 0);
+		cpc_writeb(&ptdescr->status, 0);
+	}
+	rx_dma_buf_pt_init(card, ch);
+}
+
+static void tx_dma_buf_check(pc300_t * card, int ch)
+{
+	volatile pcsca_bd_t __iomem *ptdescr;
+	int i;
+	ucshort first_bd = card->chan[ch].tx_first_bd;
+	ucshort next_bd = card->chan[ch].tx_next_bd;
+
+	printk("#CH%d: f_bd = %d(0x%08zx), n_bd = %d(0x%08zx)\n", ch,
+	       first_bd, TX_BD_ADDR(ch, first_bd),
+	       next_bd, TX_BD_ADDR(ch, next_bd));
+	for (i = first_bd,
+	     ptdescr = (card->hw.rambase + TX_BD_ADDR(ch, first_bd));
+	     i != ((next_bd + 1) & (N_DMA_TX_BUF - 1));
+	     i = (i + 1) & (N_DMA_TX_BUF - 1), 
+		 ptdescr = (card->hw.rambase + TX_BD_ADDR(ch, i))) {
+		printk("\n CH%d TX%d: next=0x%x, ptbuf=0x%x, ST=0x%x, len=%d",
+		       ch, i, cpc_readl(&ptdescr->next),
+		       cpc_readl(&ptdescr->ptbuf),
+		       cpc_readb(&ptdescr->status), cpc_readw(&ptdescr->len));
+	}
+	printk("\n");
+}
+
+#ifdef	PC300_DEBUG_OTHER
+/* Show all TX buffer descriptors */
+static void tx1_dma_buf_check(pc300_t * card, int ch)
+{
+	volatile pcsca_bd_t __iomem *ptdescr;
+	int i;
+	ucshort first_bd = card->chan[ch].tx_first_bd;
+	ucshort next_bd = card->chan[ch].tx_next_bd;
+	uclong scabase = card->hw.scabase;
+
+	printk ("\nnfree_tx_bd = %d \n", card->chan[ch].nfree_tx_bd);
+	printk("#CH%d: f_bd = %d(0x%08x), n_bd = %d(0x%08x)\n", ch,
+	       first_bd, TX_BD_ADDR(ch, first_bd),
+	       next_bd, TX_BD_ADDR(ch, next_bd));
+	printk("TX_CDA=0x%08x, TX_EDA=0x%08x\n",
+	       cpc_readl(scabase + DTX_REG(CDAL, ch)),
+	       cpc_readl(scabase + DTX_REG(EDAL, ch)));
+	for (i = 0; i < N_DMA_TX_BUF; i++) {
+		ptdescr = (card->hw.rambase + TX_BD_ADDR(ch, i));
+		printk("\n CH%d TX%d: next=0x%x, ptbuf=0x%x, ST=0x%x, len=%d",
+		       ch, i, cpc_readl(&ptdescr->next),
+		       cpc_readl(&ptdescr->ptbuf),
+		       cpc_readb(&ptdescr->status), cpc_readw(&ptdescr->len));
+	}
+	printk("\n");
+}
+#endif
+			 
+static void rx_dma_buf_check(pc300_t * card, int ch)
+{
+	volatile pcsca_bd_t __iomem *ptdescr;
+	int i;
+	ucshort first_bd = card->chan[ch].rx_first_bd;
+	ucshort last_bd = card->chan[ch].rx_last_bd;
+	int ch_factor;
+
+	ch_factor = ch * N_DMA_RX_BUF;
+	printk("#CH%d: f_bd = %d, l_bd = %d\n", ch, first_bd, last_bd);
+	for (i = 0, ptdescr = (card->hw.rambase +
+					      DMA_RX_BD_BASE + ch_factor * sizeof(pcsca_bd_t));
+	     i < N_DMA_RX_BUF; i++, ptdescr++) {
+		if (cpc_readb(&ptdescr->status) & DST_OSB)
+			printk ("\n CH%d RX%d: next=0x%x, ptbuf=0x%x, ST=0x%x, len=%d",
+				 ch, i, cpc_readl(&ptdescr->next),
+				 cpc_readl(&ptdescr->ptbuf),
+				 cpc_readb(&ptdescr->status),
+				 cpc_readw(&ptdescr->len));
+	}
+	printk("\n");
+}
+
+int dma_get_rx_frame_size(pc300_t * card, int ch)
+{
+	volatile pcsca_bd_t __iomem *ptdescr;
+	ucshort first_bd = card->chan[ch].rx_first_bd;
+	int rcvd = 0;
+	volatile ucchar status;
+
+	ptdescr = (card->hw.rambase + RX_BD_ADDR(ch, first_bd));
+	while ((status = cpc_readb(&ptdescr->status)) & DST_OSB) {
+		rcvd += cpc_readw(&ptdescr->len);
+		first_bd = (first_bd + 1) & (N_DMA_RX_BUF - 1);
+		if ((status & DST_EOM) || (first_bd == card->chan[ch].rx_last_bd)) {
+			/* Return the size of a good frame or incomplete bad frame 
+			* (dma_buf_read will clean the buffer descriptors in this case). */
+			return (rcvd);
+		}
+		ptdescr = (card->hw.rambase + cpc_readl(&ptdescr->next));
+	}
+	return (-1);
+}
+
+/*
+ * dma_buf_write: writes a frame to the Tx DMA buffers
+ * NOTE: this function writes one frame at a time.
+ */
+int dma_buf_write(pc300_t * card, int ch, ucchar * ptdata, int len)
+{
+	int i, nchar;
+	volatile pcsca_bd_t __iomem *ptdescr;
+	int tosend = len;
+	ucchar nbuf = ((len - 1) / BD_DEF_LEN) + 1;
+
+	if (nbuf >= card->chan[ch].nfree_tx_bd) {
+		return -ENOMEM;
+	}
+
+	for (i = 0; i < nbuf; i++) {
+		ptdescr = (card->hw.rambase +
+					  TX_BD_ADDR(ch, card->chan[ch].tx_next_bd));
+		nchar = cpc_min(BD_DEF_LEN, tosend);
+		if (cpc_readb(&ptdescr->status) & DST_OSB) {
+			memcpy_toio((card->hw.rambase + cpc_readl(&ptdescr->ptbuf)),
+				    &ptdata[len - tosend], nchar);
+			cpc_writew(&ptdescr->len, nchar);
+			card->chan[ch].nfree_tx_bd--;
+			if ((i + 1) == nbuf) {
+				/* This must be the last BD to be used */
+				cpc_writeb(&ptdescr->status, DST_EOM);
+			} else {
+				cpc_writeb(&ptdescr->status, 0);
+			}
+		} else {
+			return -ENOMEM;
+		}
+		tosend -= nchar;
+		card->chan[ch].tx_next_bd =
+			(card->chan[ch].tx_next_bd + 1) & (N_DMA_TX_BUF - 1);
+	}
+	/* If it gets to here, it means we have sent the whole frame */
+	return 0;
+}
+
+/*
+ * dma_buf_read: reads a frame from the Rx DMA buffers
+ * NOTE: this function reads one frame at a time.
+ */
+int dma_buf_read(pc300_t * card, int ch, struct sk_buff *skb)
+{
+	int nchar;
+	pc300ch_t *chan = (pc300ch_t *) & card->chan[ch];
+	volatile pcsca_bd_t __iomem *ptdescr;
+	int rcvd = 0;
+	volatile ucchar status;
+
+	ptdescr = (card->hw.rambase +
+				  RX_BD_ADDR(ch, chan->rx_first_bd));
+	while ((status = cpc_readb(&ptdescr->status)) & DST_OSB) {
+		nchar = cpc_readw(&ptdescr->len);
+		if ((status & (DST_OVR | DST_CRC | DST_RBIT | DST_SHRT | DST_ABT))
+		    || (nchar > BD_DEF_LEN)) {
+
+			if (nchar > BD_DEF_LEN)
+				status |= DST_RBIT;
+			rcvd = -status;
+			/* Discard remaining descriptors used by the bad frame */
+			while (chan->rx_first_bd != chan->rx_last_bd) {
+				cpc_writeb(&ptdescr->status, 0);
+				chan->rx_first_bd = (chan->rx_first_bd+1) & (N_DMA_RX_BUF-1);
+				if (status & DST_EOM)
+					break;
+				ptdescr = (card->hw.rambase +
+							  cpc_readl(&ptdescr->next));
+				status = cpc_readb(&ptdescr->status);
+			}
+			break;
+		}
+		if (nchar != 0) {
+			if (skb) {
+				memcpy_fromio(skb_put(skb, nchar),
+				 (card->hw.rambase+cpc_readl(&ptdescr->ptbuf)),nchar);
+			}
+			rcvd += nchar;
+		}
+		cpc_writeb(&ptdescr->status, 0);
+		cpc_writeb(&ptdescr->len, 0);
+		chan->rx_first_bd = (chan->rx_first_bd + 1) & (N_DMA_RX_BUF - 1);
+
+		if (status & DST_EOM)
+			break;
+
+		ptdescr = (card->hw.rambase + cpc_readl(&ptdescr->next));
+	}
+
+	if (rcvd != 0) {
+		/* Update pointer */
+		chan->rx_last_bd = (chan->rx_first_bd - 1) & (N_DMA_RX_BUF - 1);
+		/* Update EDA */
+		cpc_writel(card->hw.scabase + DRX_REG(EDAL, ch),
+			   RX_BD_ADDR(ch, chan->rx_last_bd));
+	}
+	return (rcvd);
+}
+
+void tx_dma_stop(pc300_t * card, int ch)
+{
+	void __iomem *scabase = card->hw.scabase;
+	ucchar drr_ena_bit = 1 << (5 + 2 * ch);
+	ucchar drr_rst_bit = 1 << (1 + 2 * ch);
+
+	/* Disable DMA */
+	cpc_writeb(scabase + DRR, drr_ena_bit);
+	cpc_writeb(scabase + DRR, drr_rst_bit & ~drr_ena_bit);
+}
+
+void rx_dma_stop(pc300_t * card, int ch)
+{
+	void __iomem *scabase = card->hw.scabase;
+	ucchar drr_ena_bit = 1 << (4 + 2 * ch);
+	ucchar drr_rst_bit = 1 << (2 * ch);
+
+	/* Disable DMA */
+	cpc_writeb(scabase + DRR, drr_ena_bit);
+	cpc_writeb(scabase + DRR, drr_rst_bit & ~drr_ena_bit);
+}
+
+void rx_dma_start(pc300_t * card, int ch)
+{
+	void __iomem *scabase = card->hw.scabase;
+	pc300ch_t *chan = (pc300ch_t *) & card->chan[ch];
+	
+	/* Start DMA */
+	cpc_writel(scabase + DRX_REG(CDAL, ch),
+		   RX_BD_ADDR(ch, chan->rx_first_bd));
+	if (cpc_readl(scabase + DRX_REG(CDAL,ch)) !=
+				  RX_BD_ADDR(ch, chan->rx_first_bd)) {
+		cpc_writel(scabase + DRX_REG(CDAL, ch),
+				   RX_BD_ADDR(ch, chan->rx_first_bd));
+	}
+	cpc_writel(scabase + DRX_REG(EDAL, ch),
+		   RX_BD_ADDR(ch, chan->rx_last_bd));
+	cpc_writew(scabase + DRX_REG(BFLL, ch), BD_DEF_LEN);
+	cpc_writeb(scabase + DSR_RX(ch), DSR_DE);
+	if (!(cpc_readb(scabase + DSR_RX(ch)) & DSR_DE)) {
+	cpc_writeb(scabase + DSR_RX(ch), DSR_DE);
+	}
+}
+
+/*************************/
+/***   FALC Routines   ***/
+/*************************/
+void falc_issue_cmd(pc300_t * card, int ch, ucchar cmd)
+{
+	void __iomem *falcbase = card->hw.falcbase;
+	unsigned long i = 0;
+
+	while (cpc_readb(falcbase + F_REG(SIS, ch)) & SIS_CEC) {
+		if (i++ >= PC300_FALC_MAXLOOP) {
+			printk("%s: FALC command locked(cmd=0x%x).\n",
+			       card->chan[ch].d.name, cmd);
+			break;
+		}
+	}
+	cpc_writeb(falcbase + F_REG(CMDR, ch), cmd);
+}
+
+void falc_intr_enable(pc300_t * card, int ch)
+{
+	pc300ch_t *chan = (pc300ch_t *) & card->chan[ch];
+	pc300chconf_t *conf = (pc300chconf_t *) & chan->conf;
+	falc_t *pfalc = (falc_t *) & chan->falc;
+	void __iomem *falcbase = card->hw.falcbase;
+
+	/* Interrupt pins are open-drain */
+	cpc_writeb(falcbase + F_REG(IPC, ch),
+		   cpc_readb(falcbase + F_REG(IPC, ch)) & ~IPC_IC0);
+	/* Conters updated each second */
+	cpc_writeb(falcbase + F_REG(FMR1, ch),
+		   cpc_readb(falcbase + F_REG(FMR1, ch)) | FMR1_ECM);
+	/* Enable SEC and ES interrupts  */
+	cpc_writeb(falcbase + F_REG(IMR3, ch),
+		   cpc_readb(falcbase + F_REG(IMR3, ch)) & ~(IMR3_SEC | IMR3_ES));
+	if (conf->fr_mode == PC300_FR_UNFRAMED) {
+		cpc_writeb(falcbase + F_REG(IMR4, ch),
+			   cpc_readb(falcbase + F_REG(IMR4, ch)) & ~(IMR4_LOS));
+	} else {
+		cpc_writeb(falcbase + F_REG(IMR4, ch),
+			   cpc_readb(falcbase + F_REG(IMR4, ch)) &
+			   ~(IMR4_LFA | IMR4_AIS | IMR4_LOS | IMR4_SLIP));
+	}
+	if (conf->media == IF_IFACE_T1) {
+		cpc_writeb(falcbase + F_REG(IMR3, ch),
+			   cpc_readb(falcbase + F_REG(IMR3, ch)) & ~IMR3_LLBSC);
+	} else {
+		cpc_writeb(falcbase + F_REG(IPC, ch),
+			   cpc_readb(falcbase + F_REG(IPC, ch)) | IPC_SCI);
+		if (conf->fr_mode == PC300_FR_UNFRAMED) {
+			cpc_writeb(falcbase + F_REG(IMR2, ch),
+				   cpc_readb(falcbase + F_REG(IMR2, ch)) & ~(IMR2_LOS));
+		} else {
+			cpc_writeb(falcbase + F_REG(IMR2, ch),
+				   cpc_readb(falcbase + F_REG(IMR2, ch)) &
+				   ~(IMR2_FAR | IMR2_LFA | IMR2_AIS | IMR2_LOS));
+			if (pfalc->multiframe_mode) {
+				cpc_writeb(falcbase + F_REG(IMR2, ch),
+					   cpc_readb(falcbase + F_REG(IMR2, ch)) & 
+					   ~(IMR2_T400MS | IMR2_MFAR));
+			} else {
+				cpc_writeb(falcbase + F_REG(IMR2, ch),
+					   cpc_readb(falcbase + F_REG(IMR2, ch)) | 
+					   IMR2_T400MS | IMR2_MFAR);
+			}
+		}
+	}
+}
+
+void falc_open_timeslot(pc300_t * card, int ch, int timeslot)
+{
+	void __iomem *falcbase = card->hw.falcbase;
+	ucchar tshf = card->chan[ch].falc.offset;
+
+	cpc_writeb(falcbase + F_REG((ICB1 + (timeslot - tshf) / 8), ch),
+		   cpc_readb(falcbase + F_REG((ICB1 + (timeslot - tshf) / 8), ch)) & 
+		   	~(0x80 >> ((timeslot - tshf) & 0x07)));
+	cpc_writeb(falcbase + F_REG((TTR1 + timeslot / 8), ch),
+		   cpc_readb(falcbase + F_REG((TTR1 + timeslot / 8), ch)) | 
+   			(0x80 >> (timeslot & 0x07)));
+	cpc_writeb(falcbase + F_REG((RTR1 + timeslot / 8), ch),
+		   cpc_readb(falcbase + F_REG((RTR1 + timeslot / 8), ch)) | 
+			(0x80 >> (timeslot & 0x07)));
+}
+
+void falc_close_timeslot(pc300_t * card, int ch, int timeslot)
+{
+	void __iomem *falcbase = card->hw.falcbase;
+	ucchar tshf = card->chan[ch].falc.offset;
+
+	cpc_writeb(falcbase + F_REG((ICB1 + (timeslot - tshf) / 8), ch),
+		   cpc_readb(falcbase + F_REG((ICB1 + (timeslot - tshf) / 8), ch)) | 
+		   (0x80 >> ((timeslot - tshf) & 0x07)));
+	cpc_writeb(falcbase + F_REG((TTR1 + timeslot / 8), ch),
+		   cpc_readb(falcbase + F_REG((TTR1 + timeslot / 8), ch)) & 
+		   ~(0x80 >> (timeslot & 0x07)));
+	cpc_writeb(falcbase + F_REG((RTR1 + timeslot / 8), ch),
+		   cpc_readb(falcbase + F_REG((RTR1 + timeslot / 8), ch)) & 
+		   ~(0x80 >> (timeslot & 0x07)));
+}
+
+void falc_close_all_timeslots(pc300_t * card, int ch)
+{
+	pc300ch_t *chan = (pc300ch_t *) & card->chan[ch];
+	pc300chconf_t *conf = (pc300chconf_t *) & chan->conf;
+	void __iomem *falcbase = card->hw.falcbase;
+
+	cpc_writeb(falcbase + F_REG(ICB1, ch), 0xff);
+	cpc_writeb(falcbase + F_REG(TTR1, ch), 0);
+	cpc_writeb(falcbase + F_REG(RTR1, ch), 0);
+	cpc_writeb(falcbase + F_REG(ICB2, ch), 0xff);
+	cpc_writeb(falcbase + F_REG(TTR2, ch), 0);
+	cpc_writeb(falcbase + F_REG(RTR2, ch), 0);
+	cpc_writeb(falcbase + F_REG(ICB3, ch), 0xff);
+	cpc_writeb(falcbase + F_REG(TTR3, ch), 0);
+	cpc_writeb(falcbase + F_REG(RTR3, ch), 0);
+	if (conf->media == IF_IFACE_E1) {
+		cpc_writeb(falcbase + F_REG(ICB4, ch), 0xff);
+		cpc_writeb(falcbase + F_REG(TTR4, ch), 0);
+		cpc_writeb(falcbase + F_REG(RTR4, ch), 0);
+	}
+}
+
+void falc_open_all_timeslots(pc300_t * card, int ch)
+{
+	pc300ch_t *chan = (pc300ch_t *) & card->chan[ch];
+	pc300chconf_t *conf = (pc300chconf_t *) & chan->conf;
+	void __iomem *falcbase = card->hw.falcbase;
+
+	cpc_writeb(falcbase + F_REG(ICB1, ch), 0);
+	if (conf->fr_mode == PC300_FR_UNFRAMED) {
+		cpc_writeb(falcbase + F_REG(TTR1, ch), 0xff);
+		cpc_writeb(falcbase + F_REG(RTR1, ch), 0xff);
+	} else {
+		/* Timeslot 0 is never enabled */
+		cpc_writeb(falcbase + F_REG(TTR1, ch), 0x7f);
+		cpc_writeb(falcbase + F_REG(RTR1, ch), 0x7f);
+	}
+	cpc_writeb(falcbase + F_REG(ICB2, ch), 0);
+	cpc_writeb(falcbase + F_REG(TTR2, ch), 0xff);
+	cpc_writeb(falcbase + F_REG(RTR2, ch), 0xff);
+	cpc_writeb(falcbase + F_REG(ICB3, ch), 0);
+	cpc_writeb(falcbase + F_REG(TTR3, ch), 0xff);
+	cpc_writeb(falcbase + F_REG(RTR3, ch), 0xff);
+	if (conf->media == IF_IFACE_E1) {
+		cpc_writeb(falcbase + F_REG(ICB4, ch), 0);
+		cpc_writeb(falcbase + F_REG(TTR4, ch), 0xff);
+		cpc_writeb(falcbase + F_REG(RTR4, ch), 0xff);
+	} else {
+		cpc_writeb(falcbase + F_REG(ICB4, ch), 0xff);
+		cpc_writeb(falcbase + F_REG(TTR4, ch), 0x80);
+		cpc_writeb(falcbase + F_REG(RTR4, ch), 0x80);
+	}
+}
+
+void falc_init_timeslot(pc300_t * card, int ch)
+{
+	pc300ch_t *chan = (pc300ch_t *) & card->chan[ch];
+	pc300chconf_t *conf = (pc300chconf_t *) & chan->conf;
+	falc_t *pfalc = (falc_t *) & chan->falc;
+	int tslot;
+
+	for (tslot = 0; tslot < pfalc->num_channels; tslot++) {
+		if (conf->tslot_bitmap & (1 << tslot)) {
+			// Channel enabled
+			falc_open_timeslot(card, ch, tslot + 1);
+		} else {
+			// Channel disabled
+			falc_close_timeslot(card, ch, tslot + 1);
+		}
+	}
+}
+
+void falc_enable_comm(pc300_t * card, int ch)
+{
+	pc300ch_t *chan = (pc300ch_t *) & card->chan[ch];
+	falc_t *pfalc = (falc_t *) & chan->falc;
+
+	if (pfalc->full_bandwidth) {
+		falc_open_all_timeslots(card, ch);
+	} else {
+		falc_init_timeslot(card, ch);
+	}
+	// CTS/DCD ON
+	cpc_writeb(card->hw.falcbase + card->hw.cpld_reg1,
+		   cpc_readb(card->hw.falcbase + card->hw.cpld_reg1) &
+		   ~((CPLD_REG1_FALC_DCD | CPLD_REG1_FALC_CTS) << (2 * ch)));
+}
+
+void falc_disable_comm(pc300_t * card, int ch)
+{
+	pc300ch_t *chan = (pc300ch_t *) & card->chan[ch];
+	falc_t *pfalc = (falc_t *) & chan->falc;
+
+	if (pfalc->loop_active != 2) {
+		falc_close_all_timeslots(card, ch);
+	}
+	// CTS/DCD OFF
+	cpc_writeb(card->hw.falcbase + card->hw.cpld_reg1,
+		   cpc_readb(card->hw.falcbase + card->hw.cpld_reg1) |
+		   ((CPLD_REG1_FALC_DCD | CPLD_REG1_FALC_CTS) << (2 * ch)));
+}
+
+void falc_init_t1(pc300_t * card, int ch)
+{
+	pc300ch_t *chan = (pc300ch_t *) & card->chan[ch];
+	pc300chconf_t *conf = (pc300chconf_t *) & chan->conf;
+	falc_t *pfalc = (falc_t *) & chan->falc;
+	void __iomem *falcbase = card->hw.falcbase;
+	ucchar dja = (ch ? (LIM2_DJA2 | LIM2_DJA1) : 0);
+
+	/* Switch to T1 mode (PCM 24) */
+	cpc_writeb(falcbase + F_REG(FMR1, ch), FMR1_PMOD);
+
+	/* Wait 20 us for setup */
+	udelay(20);
+
+	/* Transmit Buffer Size (1 frame) */
+	cpc_writeb(falcbase + F_REG(SIC1, ch), SIC1_XBS0);
+
+	/* Clock mode */
+	if (conf->phys_settings.clock_type == CLOCK_INT) { /* Master mode */
+		cpc_writeb(falcbase + F_REG(LIM0, ch),
+			   cpc_readb(falcbase + F_REG(LIM0, ch)) | LIM0_MAS);
+	} else { /* Slave mode */
+		cpc_writeb(falcbase + F_REG(LIM0, ch),
+			   cpc_readb(falcbase + F_REG(LIM0, ch)) & ~LIM0_MAS);
+		cpc_writeb(falcbase + F_REG(LOOP, ch),
+			   cpc_readb(falcbase + F_REG(LOOP, ch)) & ~LOOP_RTM);
+	}
+
+	cpc_writeb(falcbase + F_REG(IPC, ch), IPC_SCI);
+	cpc_writeb(falcbase + F_REG(FMR0, ch),
+		   cpc_readb(falcbase + F_REG(FMR0, ch)) &
+		   ~(FMR0_XC0 | FMR0_XC1 | FMR0_RC0 | FMR0_RC1));
+
+	switch (conf->lcode) {
+		case PC300_LC_AMI:
+			cpc_writeb(falcbase + F_REG(FMR0, ch),
+				   cpc_readb(falcbase + F_REG(FMR0, ch)) |
+				   FMR0_XC1 | FMR0_RC1);
+			/* Clear Channel register to ON for all channels */
+			cpc_writeb(falcbase + F_REG(CCB1, ch), 0xff);
+			cpc_writeb(falcbase + F_REG(CCB2, ch), 0xff);
+			cpc_writeb(falcbase + F_REG(CCB3, ch), 0xff);
+			break;
+
+		case PC300_LC_B8ZS:
+			cpc_writeb(falcbase + F_REG(FMR0, ch),
+				   cpc_readb(falcbase + F_REG(FMR0, ch)) |
+				   FMR0_XC0 | FMR0_XC1 | FMR0_RC0 | FMR0_RC1);
+			break;
+
+		case PC300_LC_NRZ:
+			cpc_writeb(falcbase + F_REG(FMR0, ch),
+				   cpc_readb(falcbase + F_REG(FMR0, ch)) | 0x00);
+			break;
+	}
+
+	cpc_writeb(falcbase + F_REG(LIM0, ch),
+		   cpc_readb(falcbase + F_REG(LIM0, ch)) | LIM0_ELOS);
+	cpc_writeb(falcbase + F_REG(LIM0, ch),
+		   cpc_readb(falcbase + F_REG(LIM0, ch)) & ~(LIM0_SCL1 | LIM0_SCL0));
+	/* Set interface mode to 2 MBPS */
+	cpc_writeb(falcbase + F_REG(FMR1, ch),
+		   cpc_readb(falcbase + F_REG(FMR1, ch)) | FMR1_IMOD);
+
+	switch (conf->fr_mode) {
+		case PC300_FR_ESF:
+			pfalc->multiframe_mode = 0;
+			cpc_writeb(falcbase + F_REG(FMR4, ch),
+				   cpc_readb(falcbase + F_REG(FMR4, ch)) | FMR4_FM1);
+			cpc_writeb(falcbase + F_REG(FMR1, ch),
+				   cpc_readb(falcbase + F_REG(FMR1, ch)) | 
+				   FMR1_CRC | FMR1_EDL);
+			cpc_writeb(falcbase + F_REG(XDL1, ch), 0);
+			cpc_writeb(falcbase + F_REG(XDL2, ch), 0);
+			cpc_writeb(falcbase + F_REG(XDL3, ch), 0);
+			cpc_writeb(falcbase + F_REG(FMR0, ch),
+				   cpc_readb(falcbase + F_REG(FMR0, ch)) & ~FMR0_SRAF);
+			cpc_writeb(falcbase + F_REG(FMR2, ch),
+				   cpc_readb(falcbase + F_REG(FMR2,ch)) | FMR2_MCSP | FMR2_SSP);
+			break;
+
+		case PC300_FR_D4:
+			pfalc->multiframe_mode = 1;
+			cpc_writeb(falcbase + F_REG(FMR4, ch),
+				   cpc_readb(falcbase + F_REG(FMR4, ch)) &
+				   ~(FMR4_FM1 | FMR4_FM0));
+			cpc_writeb(falcbase + F_REG(FMR0, ch),
+				   cpc_readb(falcbase + F_REG(FMR0, ch)) | FMR0_SRAF);
+			cpc_writeb(falcbase + F_REG(FMR2, ch),
+				   cpc_readb(falcbase + F_REG(FMR2, ch)) & ~FMR2_SSP);
+			break;
+	}
+
+	/* Enable Automatic Resynchronization */
+	cpc_writeb(falcbase + F_REG(FMR4, ch),
+		   cpc_readb(falcbase + F_REG(FMR4, ch)) | FMR4_AUTO);
+
+	/* Transmit Automatic Remote Alarm */
+	cpc_writeb(falcbase + F_REG(FMR2, ch),
+		   cpc_readb(falcbase + F_REG(FMR2, ch)) | FMR2_AXRA);
+
+	/* Channel translation mode 1 : one to one */
+	cpc_writeb(falcbase + F_REG(FMR1, ch),
+		   cpc_readb(falcbase + F_REG(FMR1, ch)) | FMR1_CTM);
+
+	/* No signaling */
+	cpc_writeb(falcbase + F_REG(FMR1, ch),
+		   cpc_readb(falcbase + F_REG(FMR1, ch)) & ~FMR1_SIGM);
+	cpc_writeb(falcbase + F_REG(FMR5, ch),
+		   cpc_readb(falcbase + F_REG(FMR5, ch)) &
+		   ~(FMR5_EIBR | FMR5_SRS));
+	cpc_writeb(falcbase + F_REG(CCR1, ch), 0);
+
+	cpc_writeb(falcbase + F_REG(LIM1, ch),
+		   cpc_readb(falcbase + F_REG(LIM1, ch)) | LIM1_RIL0 | LIM1_RIL1);
+
+	switch (conf->lbo) {
+			/* Provides proper Line Build Out */
+		case PC300_LBO_0_DB:
+			cpc_writeb(falcbase + F_REG(LIM2, ch), (LIM2_LOS1 | dja));
+			cpc_writeb(falcbase + F_REG(XPM0, ch), 0x5a);
+			cpc_writeb(falcbase + F_REG(XPM1, ch), 0x8f);
+			cpc_writeb(falcbase + F_REG(XPM2, ch), 0x20);
+			break;
+		case PC300_LBO_7_5_DB:
+			cpc_writeb(falcbase + F_REG(LIM2, ch), (0x40 | LIM2_LOS1 | dja));
+			cpc_writeb(falcbase + F_REG(XPM0, ch), 0x11);
+			cpc_writeb(falcbase + F_REG(XPM1, ch), 0x02);
+			cpc_writeb(falcbase + F_REG(XPM2, ch), 0x20);
+			break;
+		case PC300_LBO_15_DB:
+			cpc_writeb(falcbase + F_REG(LIM2, ch), (0x80 | LIM2_LOS1 | dja));
+			cpc_writeb(falcbase + F_REG(XPM0, ch), 0x8e);
+			cpc_writeb(falcbase + F_REG(XPM1, ch), 0x01);
+			cpc_writeb(falcbase + F_REG(XPM2, ch), 0x20);
+			break;
+		case PC300_LBO_22_5_DB:
+			cpc_writeb(falcbase + F_REG(LIM2, ch), (0xc0 | LIM2_LOS1 | dja));
+			cpc_writeb(falcbase + F_REG(XPM0, ch), 0x09);
+			cpc_writeb(falcbase + F_REG(XPM1, ch), 0x01);
+			cpc_writeb(falcbase + F_REG(XPM2, ch), 0x20);
+			break;
+	}
+
+	/* Transmit Clock-Slot Offset */
+	cpc_writeb(falcbase + F_REG(XC0, ch),
+		   cpc_readb(falcbase + F_REG(XC0, ch)) | 0x01);
+	/* Transmit Time-slot Offset */
+	cpc_writeb(falcbase + F_REG(XC1, ch), 0x3e);
+	/* Receive  Clock-Slot offset */
+	cpc_writeb(falcbase + F_REG(RC0, ch), 0x05);
+	/* Receive  Time-slot offset */
+	cpc_writeb(falcbase + F_REG(RC1, ch), 0x00);
+
+	/* LOS Detection after 176 consecutive 0s */
+	cpc_writeb(falcbase + F_REG(PCDR, ch), 0x0a);
+	/* LOS Recovery after 22 ones in the time window of PCD */
+	cpc_writeb(falcbase + F_REG(PCRR, ch), 0x15);
+
+	cpc_writeb(falcbase + F_REG(IDLE, ch), 0x7f);
+
+	if (conf->fr_mode == PC300_FR_ESF_JAPAN) {
+		cpc_writeb(falcbase + F_REG(RC1, ch),
+			   cpc_readb(falcbase + F_REG(RC1, ch)) | 0x80);
+	}
+
+	falc_close_all_timeslots(card, ch);
+}
+
+void falc_init_e1(pc300_t * card, int ch)
+{
+	pc300ch_t *chan = (pc300ch_t *) & card->chan[ch];
+	pc300chconf_t *conf = (pc300chconf_t *) & chan->conf;
+	falc_t *pfalc = (falc_t *) & chan->falc;
+	void __iomem *falcbase = card->hw.falcbase;
+	ucchar dja = (ch ? (LIM2_DJA2 | LIM2_DJA1) : 0);
+
+	/* Switch to E1 mode (PCM 30) */
+	cpc_writeb(falcbase + F_REG(FMR1, ch),
+		   cpc_readb(falcbase + F_REG(FMR1, ch)) & ~FMR1_PMOD);
+
+	/* Clock mode */
+	if (conf->phys_settings.clock_type == CLOCK_INT) { /* Master mode */
+		cpc_writeb(falcbase + F_REG(LIM0, ch),
+			   cpc_readb(falcbase + F_REG(LIM0, ch)) | LIM0_MAS);
+	} else { /* Slave mode */
+		cpc_writeb(falcbase + F_REG(LIM0, ch),
+			   cpc_readb(falcbase + F_REG(LIM0, ch)) & ~LIM0_MAS);
+	}
+	cpc_writeb(falcbase + F_REG(LOOP, ch),
+		   cpc_readb(falcbase + F_REG(LOOP, ch)) & ~LOOP_SFM);
+
+	cpc_writeb(falcbase + F_REG(IPC, ch), IPC_SCI);
+	cpc_writeb(falcbase + F_REG(FMR0, ch),
+		   cpc_readb(falcbase + F_REG(FMR0, ch)) &
+		   ~(FMR0_XC0 | FMR0_XC1 | FMR0_RC0 | FMR0_RC1));
+
+	switch (conf->lcode) {
+		case PC300_LC_AMI:
+			cpc_writeb(falcbase + F_REG(FMR0, ch),
+				   cpc_readb(falcbase + F_REG(FMR0, ch)) |
+				   FMR0_XC1 | FMR0_RC1);
+			break;
+
+		case PC300_LC_HDB3:
+			cpc_writeb(falcbase + F_REG(FMR0, ch),
+				   cpc_readb(falcbase + F_REG(FMR0, ch)) |
+				   FMR0_XC0 | FMR0_XC1 | FMR0_RC0 | FMR0_RC1);
+			break;
+
+		case PC300_LC_NRZ:
+			break;
+	}
+
+	cpc_writeb(falcbase + F_REG(LIM0, ch),
+		   cpc_readb(falcbase + F_REG(LIM0, ch)) & ~(LIM0_SCL1 | LIM0_SCL0));
+	/* Set interface mode to 2 MBPS */
+	cpc_writeb(falcbase + F_REG(FMR1, ch),
+		   cpc_readb(falcbase + F_REG(FMR1, ch)) | FMR1_IMOD);
+
+	cpc_writeb(falcbase + F_REG(XPM0, ch), 0x18);
+	cpc_writeb(falcbase + F_REG(XPM1, ch), 0x03);
+	cpc_writeb(falcbase + F_REG(XPM2, ch), 0x00);
+
+	switch (conf->fr_mode) {
+		case PC300_FR_MF_CRC4:
+			pfalc->multiframe_mode = 1;
+			cpc_writeb(falcbase + F_REG(FMR1, ch),
+				   cpc_readb(falcbase + F_REG(FMR1, ch)) | FMR1_XFS);
+			cpc_writeb(falcbase + F_REG(FMR2, ch),
+				   cpc_readb(falcbase + F_REG(FMR2, ch)) | FMR2_RFS1);
+			cpc_writeb(falcbase + F_REG(FMR2, ch),
+				   cpc_readb(falcbase + F_REG(FMR2, ch)) & ~FMR2_RFS0);
+			cpc_writeb(falcbase + F_REG(FMR3, ch),
+				   cpc_readb(falcbase + F_REG(FMR3, ch)) & ~FMR3_EXTIW);
+
+			/* MultiFrame Resynchronization */
+			cpc_writeb(falcbase + F_REG(FMR1, ch),
+				   cpc_readb(falcbase + F_REG(FMR1, ch)) | FMR1_MFCS);
+
+			/* Automatic Loss of Multiframe > 914 CRC errors */
+			cpc_writeb(falcbase + F_REG(FMR2, ch),
+				   cpc_readb(falcbase + F_REG(FMR2, ch)) | FMR2_ALMF);
+
+			/* S1 and SI1/SI2 spare Bits set to 1 */
+			cpc_writeb(falcbase + F_REG(XSP, ch),
+				   cpc_readb(falcbase + F_REG(XSP, ch)) & ~XSP_AXS);
+			cpc_writeb(falcbase + F_REG(XSP, ch),
+				   cpc_readb(falcbase + F_REG(XSP, ch)) | XSP_EBP);
+			cpc_writeb(falcbase + F_REG(XSP, ch),
+				   cpc_readb(falcbase + F_REG(XSP, ch)) | XSP_XS13 | XSP_XS15);
+
+			/* Automatic Force Resynchronization */
+			cpc_writeb(falcbase + F_REG(FMR1, ch),
+				   cpc_readb(falcbase + F_REG(FMR1, ch)) | FMR1_AFR);
+
+			/* Transmit Automatic Remote Alarm */
+			cpc_writeb(falcbase + F_REG(FMR2, ch),
+				   cpc_readb(falcbase + F_REG(FMR2, ch)) | FMR2_AXRA);
+
+			/* Transmit Spare Bits for National Use (Y, Sn, Sa) */
+			cpc_writeb(falcbase + F_REG(XSW, ch),
+				   cpc_readb(falcbase + F_REG(XSW, ch)) |
+				   XSW_XY0 | XSW_XY1 | XSW_XY2 | XSW_XY3 | XSW_XY4);
+			break;
+
+		case PC300_FR_MF_NON_CRC4:
+		case PC300_FR_D4:
+			pfalc->multiframe_mode = 0;
+			cpc_writeb(falcbase + F_REG(FMR1, ch),
+				   cpc_readb(falcbase + F_REG(FMR1, ch)) & ~FMR1_XFS);
+			cpc_writeb(falcbase + F_REG(FMR2, ch),
+				   cpc_readb(falcbase + F_REG(FMR2, ch)) & 
+				   ~(FMR2_RFS1 | FMR2_RFS0));
+			cpc_writeb(falcbase + F_REG(XSW, ch),
+				   cpc_readb(falcbase + F_REG(XSW, ch)) | XSW_XSIS);
+			cpc_writeb(falcbase + F_REG(XSP, ch),
+				   cpc_readb(falcbase + F_REG(XSP, ch)) | XSP_XSIF);
+
+			/* Automatic Force Resynchronization */
+			cpc_writeb(falcbase + F_REG(FMR1, ch),
+				   cpc_readb(falcbase + F_REG(FMR1, ch)) | FMR1_AFR);
+
+			/* Transmit Automatic Remote Alarm */
+			cpc_writeb(falcbase + F_REG(FMR2, ch),
+				   cpc_readb(falcbase + F_REG(FMR2, ch)) | FMR2_AXRA);
+
+			/* Transmit Spare Bits for National Use (Y, Sn, Sa) */
+			cpc_writeb(falcbase + F_REG(XSW, ch),
+				   cpc_readb(falcbase + F_REG(XSW, ch)) |
+				   XSW_XY0 | XSW_XY1 | XSW_XY2 | XSW_XY3 | XSW_XY4);
+			break;
+
+		case PC300_FR_UNFRAMED:
+			pfalc->multiframe_mode = 0;
+			cpc_writeb(falcbase + F_REG(FMR1, ch),
+				   cpc_readb(falcbase + F_REG(FMR1, ch)) & ~FMR1_XFS);
+			cpc_writeb(falcbase + F_REG(FMR2, ch),
+				   cpc_readb(falcbase + F_REG(FMR2, ch)) & 
+				   ~(FMR2_RFS1 | FMR2_RFS0));
+			cpc_writeb(falcbase + F_REG(XSP, ch),
+				   cpc_readb(falcbase + F_REG(XSP, ch)) | XSP_TT0);
+			cpc_writeb(falcbase + F_REG(XSW, ch),
+				   cpc_readb(falcbase + F_REG(XSW, ch)) & 
+				   ~(XSW_XTM|XSW_XY0|XSW_XY1|XSW_XY2|XSW_XY3|XSW_XY4));
+			cpc_writeb(falcbase + F_REG(TSWM, ch), 0xff);
+			cpc_writeb(falcbase + F_REG(FMR2, ch),
+				   cpc_readb(falcbase + F_REG(FMR2, ch)) |
+				   (FMR2_RTM | FMR2_DAIS));
+			cpc_writeb(falcbase + F_REG(FMR2, ch),
+				   cpc_readb(falcbase + F_REG(FMR2, ch)) & ~FMR2_AXRA);
+			cpc_writeb(falcbase + F_REG(FMR1, ch),
+				   cpc_readb(falcbase + F_REG(FMR1, ch)) & ~FMR1_AFR);
+			pfalc->sync = 1;
+			cpc_writeb(falcbase + card->hw.cpld_reg2,
+				   cpc_readb(falcbase + card->hw.cpld_reg2) |
+				   (CPLD_REG2_FALC_LED2 << (2 * ch)));
+			break;
+	}
+
+	/* No signaling */
+	cpc_writeb(falcbase + F_REG(XSP, ch),
+		   cpc_readb(falcbase + F_REG(XSP, ch)) & ~XSP_CASEN);
+	cpc_writeb(falcbase + F_REG(CCR1, ch), 0);
+
+	cpc_writeb(falcbase + F_REG(LIM1, ch),
+		   cpc_readb(falcbase + F_REG(LIM1, ch)) | LIM1_RIL0 | LIM1_RIL1);
+	cpc_writeb(falcbase + F_REG(LIM2, ch), (LIM2_LOS1 | dja));
+
+	/* Transmit Clock-Slot Offset */
+	cpc_writeb(falcbase + F_REG(XC0, ch),
+		   cpc_readb(falcbase + F_REG(XC0, ch)) | 0x01);
+	/* Transmit Time-slot Offset */
+	cpc_writeb(falcbase + F_REG(XC1, ch), 0x3e);
+	/* Receive  Clock-Slot offset */
+	cpc_writeb(falcbase + F_REG(RC0, ch), 0x05);
+	/* Receive  Time-slot offset */
+	cpc_writeb(falcbase + F_REG(RC1, ch), 0x00);
+
+	/* LOS Detection after 176 consecutive 0s */
+	cpc_writeb(falcbase + F_REG(PCDR, ch), 0x0a);
+	/* LOS Recovery after 22 ones in the time window of PCD */
+	cpc_writeb(falcbase + F_REG(PCRR, ch), 0x15);
+
+	cpc_writeb(falcbase + F_REG(IDLE, ch), 0x7f);
+
+	falc_close_all_timeslots(card, ch);
+}
+
+void falc_init_hdlc(pc300_t * card, int ch)
+{
+	void __iomem *falcbase = card->hw.falcbase;
+	pc300ch_t *chan = (pc300ch_t *) & card->chan[ch];
+	pc300chconf_t *conf = (pc300chconf_t *) & chan->conf;
+
+	/* Enable transparent data transfer */
+	if (conf->fr_mode == PC300_FR_UNFRAMED) {
+		cpc_writeb(falcbase + F_REG(MODE, ch), 0);
+	} else {
+		cpc_writeb(falcbase + F_REG(MODE, ch),
+			   cpc_readb(falcbase + F_REG(MODE, ch)) |
+			   (MODE_HRAC | MODE_MDS2));
+		cpc_writeb(falcbase + F_REG(RAH2, ch), 0xff);
+		cpc_writeb(falcbase + F_REG(RAH1, ch), 0xff);
+		cpc_writeb(falcbase + F_REG(RAL2, ch), 0xff);
+		cpc_writeb(falcbase + F_REG(RAL1, ch), 0xff);
+	}
+
+	/* Tx/Rx reset  */
+	falc_issue_cmd(card, ch, CMDR_RRES | CMDR_XRES | CMDR_SRES);
+
+	/* Enable interrupt sources */
+	falc_intr_enable(card, ch);
+}
+
+void te_config(pc300_t * card, int ch)
+{
+	pc300ch_t *chan = (pc300ch_t *) & card->chan[ch];
+	pc300chconf_t *conf = (pc300chconf_t *) & chan->conf;
+	falc_t *pfalc = (falc_t *) & chan->falc;
+	void __iomem *falcbase = card->hw.falcbase;
+	ucchar dummy;
+	unsigned long flags;
+
+	memset(pfalc, 0, sizeof(falc_t));
+	switch (conf->media) {
+		case IF_IFACE_T1:
+			pfalc->num_channels = NUM_OF_T1_CHANNELS;
+			pfalc->offset = 1;
+			break;
+		case IF_IFACE_E1:
+			pfalc->num_channels = NUM_OF_E1_CHANNELS;
+			pfalc->offset = 0;
+			break;
+	}
+	if (conf->tslot_bitmap == 0xffffffffUL)
+		pfalc->full_bandwidth = 1;
+	else
+		pfalc->full_bandwidth = 0;
+
+	CPC_LOCK(card, flags);
+	/* Reset the FALC chip */
+	cpc_writeb(card->hw.falcbase + card->hw.cpld_reg1,
+		   cpc_readb(card->hw.falcbase + card->hw.cpld_reg1) |
+		   (CPLD_REG1_FALC_RESET << (2 * ch)));
+	udelay(10000);
+	cpc_writeb(card->hw.falcbase + card->hw.cpld_reg1,
+		   cpc_readb(card->hw.falcbase + card->hw.cpld_reg1) &
+		   ~(CPLD_REG1_FALC_RESET << (2 * ch)));
+
+	if (conf->media == IF_IFACE_T1) {
+		falc_init_t1(card, ch);
+	} else {
+		falc_init_e1(card, ch);
+	}
+	falc_init_hdlc(card, ch);
+	if (conf->rx_sens == PC300_RX_SENS_SH) {
+		cpc_writeb(falcbase + F_REG(LIM0, ch),
+			   cpc_readb(falcbase + F_REG(LIM0, ch)) & ~LIM0_EQON);
+	} else {
+		cpc_writeb(falcbase + F_REG(LIM0, ch),
+			   cpc_readb(falcbase + F_REG(LIM0, ch)) | LIM0_EQON);
+	}
+	cpc_writeb(card->hw.falcbase + card->hw.cpld_reg2,
+		   cpc_readb(card->hw.falcbase + card->hw.cpld_reg2) |
+		   ((CPLD_REG2_FALC_TX_CLK | CPLD_REG2_FALC_RX_CLK) << (2 * ch)));
+
+	/* Clear all interrupt registers */
+	dummy = cpc_readb(falcbase + F_REG(FISR0, ch)) +
+		cpc_readb(falcbase + F_REG(FISR1, ch)) +
+		cpc_readb(falcbase + F_REG(FISR2, ch)) +
+		cpc_readb(falcbase + F_REG(FISR3, ch));
+	CPC_UNLOCK(card, flags);
+}
+
+void falc_check_status(pc300_t * card, int ch, unsigned char frs0)
+{
+	pc300ch_t *chan = (pc300ch_t *) & card->chan[ch];
+	pc300chconf_t *conf = (pc300chconf_t *) & chan->conf;
+	falc_t *pfalc = (falc_t *) & chan->falc;
+	void __iomem *falcbase = card->hw.falcbase;
+
+	/* Verify LOS */
+	if (frs0 & FRS0_LOS) {
+		if (!pfalc->red_alarm) {
+			pfalc->red_alarm = 1;
+			pfalc->los++;
+			if (!pfalc->blue_alarm) {
+				// EVENT_FALC_ABNORMAL
+				if (conf->media == IF_IFACE_T1) {
+					/* Disable this interrupt as it may otherwise interfere 
+					 * with other working boards. */
+					cpc_writeb(falcbase + F_REG(IMR0, ch), 
+						   cpc_readb(falcbase + F_REG(IMR0, ch))
+						   | IMR0_PDEN);
+				}
+				falc_disable_comm(card, ch);
+				// EVENT_FALC_ABNORMAL
+			}
+		}
+	} else {
+		if (pfalc->red_alarm) {
+			pfalc->red_alarm = 0;
+			pfalc->losr++;
+		}
+	}
+
+	if (conf->fr_mode != PC300_FR_UNFRAMED) {
+		/* Verify AIS alarm */
+		if (frs0 & FRS0_AIS) {
+			if (!pfalc->blue_alarm) {
+				pfalc->blue_alarm = 1;
+				pfalc->ais++;
+				// EVENT_AIS
+				if (conf->media == IF_IFACE_T1) {
+					/* Disable this interrupt as it may otherwise interfere with                       other working boards. */
+					cpc_writeb(falcbase + F_REG(IMR0, ch),
+						   cpc_readb(falcbase + F_REG(IMR0, ch)) | IMR0_PDEN);
+				}
+				falc_disable_comm(card, ch);
+				// EVENT_AIS
+			}
+		} else {
+			pfalc->blue_alarm = 0;
+		}
+
+		/* Verify LFA */
+		if (frs0 & FRS0_LFA) {
+			if (!pfalc->loss_fa) {
+				pfalc->loss_fa = 1;
+				pfalc->lfa++;
+				if (!pfalc->blue_alarm && !pfalc->red_alarm) {
+					// EVENT_FALC_ABNORMAL
+					if (conf->media == IF_IFACE_T1) {
+						/* Disable this interrupt as it may otherwise 
+						 * interfere with other working boards. */
+						cpc_writeb(falcbase + F_REG(IMR0, ch),
+							   cpc_readb(falcbase + F_REG(IMR0, ch))
+							   | IMR0_PDEN);
+					}
+					falc_disable_comm(card, ch);
+					// EVENT_FALC_ABNORMAL
+				}
+			}
+		} else {
+			if (pfalc->loss_fa) {
+				pfalc->loss_fa = 0;
+				pfalc->farec++;
+			}
+		}
+
+		/* Verify LMFA */
+		if (pfalc->multiframe_mode && (frs0 & FRS0_LMFA)) {
+			/* D4 or CRC4 frame mode */
+			if (!pfalc->loss_mfa) {
+				pfalc->loss_mfa = 1;
+				pfalc->lmfa++;
+				if (!pfalc->blue_alarm && !pfalc->red_alarm &&
+				    !pfalc->loss_fa) {
+					// EVENT_FALC_ABNORMAL
+					if (conf->media == IF_IFACE_T1) {
+						/* Disable this interrupt as it may otherwise 
+						 * interfere with other working boards. */
+						cpc_writeb(falcbase + F_REG(IMR0, ch),
+							   cpc_readb(falcbase + F_REG(IMR0, ch))
+							   | IMR0_PDEN);
+					}
+					falc_disable_comm(card, ch);
+					// EVENT_FALC_ABNORMAL
+				}
+			}
+		} else {
+			pfalc->loss_mfa = 0;
+		}
+
+		/* Verify Remote Alarm */
+		if (frs0 & FRS0_RRA) {
+			if (!pfalc->yellow_alarm) {
+				pfalc->yellow_alarm = 1;
+				pfalc->rai++;
+				if (pfalc->sync) {
+					// EVENT_RAI
+					falc_disable_comm(card, ch);
+					// EVENT_RAI
+				}
+			}
+		} else {
+			pfalc->yellow_alarm = 0;
+		}
+	} /* if !PC300_UNFRAMED */
+
+	if (pfalc->red_alarm || pfalc->loss_fa ||
+	    pfalc->loss_mfa || pfalc->blue_alarm) {
+		if (pfalc->sync) {
+			pfalc->sync = 0;
+			chan->d.line_off++;
+			cpc_writeb(falcbase + card->hw.cpld_reg2,
+				   cpc_readb(falcbase + card->hw.cpld_reg2) &
+				   ~(CPLD_REG2_FALC_LED2 << (2 * ch)));
+		}
+	} else {
+		if (!pfalc->sync) {
+			pfalc->sync = 1;
+			chan->d.line_on++;
+			cpc_writeb(falcbase + card->hw.cpld_reg2,
+				   cpc_readb(falcbase + card->hw.cpld_reg2) |
+				   (CPLD_REG2_FALC_LED2 << (2 * ch)));
+		}
+	}
+
+	if (pfalc->sync && !pfalc->yellow_alarm) {
+		if (!pfalc->active) {
+			// EVENT_FALC_NORMAL
+			if (pfalc->loop_active) {
+				return;
+			}
+			if (conf->media == IF_IFACE_T1) {
+				cpc_writeb(falcbase + F_REG(IMR0, ch),
+					   cpc_readb(falcbase + F_REG(IMR0, ch)) & ~IMR0_PDEN);
+			}
+			falc_enable_comm(card, ch);
+			// EVENT_FALC_NORMAL
+			pfalc->active = 1;
+		}
+	} else {
+		if (pfalc->active) {
+			pfalc->active = 0;
+		}
+	}
+}
+
+void falc_update_stats(pc300_t * card, int ch)
+{
+	pc300ch_t *chan = (pc300ch_t *) & card->chan[ch];
+	pc300chconf_t *conf = (pc300chconf_t *) & chan->conf;
+	falc_t *pfalc = (falc_t *) & chan->falc;
+	void __iomem *falcbase = card->hw.falcbase;
+	ucshort counter;
+
+	counter = cpc_readb(falcbase + F_REG(FECL, ch));
+	counter |= cpc_readb(falcbase + F_REG(FECH, ch)) << 8;
+	pfalc->fec += counter;
+
+	counter = cpc_readb(falcbase + F_REG(CVCL, ch));
+	counter |= cpc_readb(falcbase + F_REG(CVCH, ch)) << 8;
+	pfalc->cvc += counter;
+
+	counter = cpc_readb(falcbase + F_REG(CECL, ch));
+	counter |= cpc_readb(falcbase + F_REG(CECH, ch)) << 8;
+	pfalc->cec += counter;
+
+	counter = cpc_readb(falcbase + F_REG(EBCL, ch));
+	counter |= cpc_readb(falcbase + F_REG(EBCH, ch)) << 8;
+	pfalc->ebc += counter;
+
+	if (cpc_readb(falcbase + F_REG(LCR1, ch)) & LCR1_EPRM) {
+		mdelay(10);
+		counter = cpc_readb(falcbase + F_REG(BECL, ch));
+		counter |= cpc_readb(falcbase + F_REG(BECH, ch)) << 8;
+		pfalc->bec += counter;
+
+		if (((conf->media == IF_IFACE_T1) &&
+		     (cpc_readb(falcbase + F_REG(FRS1, ch)) & FRS1_LLBAD) &&
+		     (!(cpc_readb(falcbase + F_REG(FRS1, ch)) & FRS1_PDEN)))
+		    ||
+		    ((conf->media == IF_IFACE_E1) &&
+		     (cpc_readb(falcbase + F_REG(RSP, ch)) & RSP_LLBAD))) {
+			pfalc->prbs = 2;
+		} else {
+			pfalc->prbs = 1;
+		}
+	}
+}
+
+/*----------------------------------------------------------------------------
+ * falc_remote_loop
+ *----------------------------------------------------------------------------
+ * Description:	In the remote loopback mode the clock and data recovered
+ *		from the line inputs RL1/2 or RDIP/RDIN are routed back
+ *		to the line outputs XL1/2 or XDOP/XDON via the analog
+ *		transmitter. As in normal mode they are processsed by
+ *		the synchronizer and then sent to the system interface.
+ *----------------------------------------------------------------------------
+ */
+void falc_remote_loop(pc300_t * card, int ch, int loop_on)
+{
+	pc300ch_t *chan = (pc300ch_t *) & card->chan[ch];
+	pc300chconf_t *conf = (pc300chconf_t *) & chan->conf;
+	falc_t *pfalc = (falc_t *) & chan->falc;
+	void __iomem *falcbase = card->hw.falcbase;
+
+	if (loop_on) {
+		// EVENT_FALC_ABNORMAL
+		if (conf->media == IF_IFACE_T1) {
+			/* Disable this interrupt as it may otherwise interfere with 
+			 * other working boards. */
+			cpc_writeb(falcbase + F_REG(IMR0, ch),
+				   cpc_readb(falcbase + F_REG(IMR0, ch)) | IMR0_PDEN);
+		}
+		falc_disable_comm(card, ch);
+		// EVENT_FALC_ABNORMAL
+		cpc_writeb(falcbase + F_REG(LIM1, ch),
+			   cpc_readb(falcbase + F_REG(LIM1, ch)) | LIM1_RL);
+		pfalc->loop_active = 1;
+	} else {
+		cpc_writeb(falcbase + F_REG(LIM1, ch),
+			   cpc_readb(falcbase + F_REG(LIM1, ch)) & ~LIM1_RL);
+		pfalc->sync = 0;
+		cpc_writeb(falcbase + card->hw.cpld_reg2,
+			   cpc_readb(falcbase + card->hw.cpld_reg2) &
+			   ~(CPLD_REG2_FALC_LED2 << (2 * ch)));
+		pfalc->active = 0;
+		falc_issue_cmd(card, ch, CMDR_XRES);
+		pfalc->loop_active = 0;
+	}
+}
+
+/*----------------------------------------------------------------------------
+ * falc_local_loop
+ *----------------------------------------------------------------------------
+ * Description: The local loopback mode disconnects the receive lines 
+ *		RL1/RL2 resp. RDIP/RDIN from the receiver. Instead of the
+ *		signals coming from the line the data provided by system
+ *		interface are routed through the analog receiver back to
+ *		the system interface. The unipolar bit stream will be
+ *		undisturbed transmitted on the line. Receiver and transmitter
+ *		coding must be identical.
+ *----------------------------------------------------------------------------
+ */
+void falc_local_loop(pc300_t * card, int ch, int loop_on)
+{
+	pc300ch_t *chan = (pc300ch_t *) & card->chan[ch];
+	falc_t *pfalc = (falc_t *) & chan->falc;
+	void __iomem *falcbase = card->hw.falcbase;
+
+	if (loop_on) {
+		cpc_writeb(falcbase + F_REG(LIM0, ch),
+			   cpc_readb(falcbase + F_REG(LIM0, ch)) | LIM0_LL);
+		pfalc->loop_active = 1;
+	} else {
+		cpc_writeb(falcbase + F_REG(LIM0, ch),
+			   cpc_readb(falcbase + F_REG(LIM0, ch)) & ~LIM0_LL);
+		pfalc->loop_active = 0;
+	}
+}
+
+/*----------------------------------------------------------------------------
+ * falc_payload_loop
+ *----------------------------------------------------------------------------
+ * Description: This routine allows to enable/disable payload loopback.
+ *		When the payload loop is activated, the received 192 bits
+ *		of payload data will be looped back to the transmit
+ *		direction. The framing bits, CRC6 and DL bits are not 
+ *		looped. They are originated by the FALC-LH transmitter.
+ *----------------------------------------------------------------------------
+ */
+void falc_payload_loop(pc300_t * card, int ch, int loop_on)
+{
+	pc300ch_t *chan = (pc300ch_t *) & card->chan[ch];
+	pc300chconf_t *conf = (pc300chconf_t *) & chan->conf;
+	falc_t *pfalc = (falc_t *) & chan->falc;
+	void __iomem *falcbase = card->hw.falcbase;
+
+	if (loop_on) {
+		// EVENT_FALC_ABNORMAL
+		if (conf->media == IF_IFACE_T1) {
+			/* Disable this interrupt as it may otherwise interfere with 
+			 * other working boards. */
+			cpc_writeb(falcbase + F_REG(IMR0, ch),
+				   cpc_readb(falcbase + F_REG(IMR0, ch)) | IMR0_PDEN);
+		}
+		falc_disable_comm(card, ch);
+		// EVENT_FALC_ABNORMAL
+		cpc_writeb(falcbase + F_REG(FMR2, ch),
+			   cpc_readb(falcbase + F_REG(FMR2, ch)) | FMR2_PLB);
+		if (conf->media == IF_IFACE_T1) {
+			cpc_writeb(falcbase + F_REG(FMR4, ch),
+				   cpc_readb(falcbase + F_REG(FMR4, ch)) | FMR4_TM);
+		} else {
+			cpc_writeb(falcbase + F_REG(FMR5, ch),
+				   cpc_readb(falcbase + F_REG(FMR5, ch)) | XSP_TT0);
+		}
+		falc_open_all_timeslots(card, ch);
+		pfalc->loop_active = 2;
+	} else {
+		cpc_writeb(falcbase + F_REG(FMR2, ch),
+			   cpc_readb(falcbase + F_REG(FMR2, ch)) & ~FMR2_PLB);
+		if (conf->media == IF_IFACE_T1) {
+			cpc_writeb(falcbase + F_REG(FMR4, ch),
+				   cpc_readb(falcbase + F_REG(FMR4, ch)) & ~FMR4_TM);
+		} else {
+			cpc_writeb(falcbase + F_REG(FMR5, ch),
+				   cpc_readb(falcbase + F_REG(FMR5, ch)) & ~XSP_TT0);
+		}
+		pfalc->sync = 0;
+		cpc_writeb(falcbase + card->hw.cpld_reg2,
+			   cpc_readb(falcbase + card->hw.cpld_reg2) &
+			   ~(CPLD_REG2_FALC_LED2 << (2 * ch)));
+		pfalc->active = 0;
+		falc_issue_cmd(card, ch, CMDR_XRES);
+		pfalc->loop_active = 0;
+	}
+}
+
+/*----------------------------------------------------------------------------
+ * turn_off_xlu
+ *----------------------------------------------------------------------------
+ * Description:	Turns XLU bit off in the proper register
+ *----------------------------------------------------------------------------
+ */
+void turn_off_xlu(pc300_t * card, int ch)
+{
+	pc300ch_t *chan = (pc300ch_t *) & card->chan[ch];
+	pc300chconf_t *conf = (pc300chconf_t *) & chan->conf;
+	void __iomem *falcbase = card->hw.falcbase;
+
+	if (conf->media == IF_IFACE_T1) {
+		cpc_writeb(falcbase + F_REG(FMR5, ch),
+			   cpc_readb(falcbase + F_REG(FMR5, ch)) & ~FMR5_XLU);
+	} else {
+		cpc_writeb(falcbase + F_REG(FMR3, ch),
+			   cpc_readb(falcbase + F_REG(FMR3, ch)) & ~FMR3_XLU);
+	}
+}
+
+/*----------------------------------------------------------------------------
+ * turn_off_xld
+ *----------------------------------------------------------------------------
+ * Description: Turns XLD bit off in the proper register
+ *----------------------------------------------------------------------------
+ */
+void turn_off_xld(pc300_t * card, int ch)
+{
+	pc300ch_t *chan = (pc300ch_t *) & card->chan[ch];
+	pc300chconf_t *conf = (pc300chconf_t *) & chan->conf;
+	void __iomem *falcbase = card->hw.falcbase;
+
+	if (conf->media == IF_IFACE_T1) {
+		cpc_writeb(falcbase + F_REG(FMR5, ch),
+			   cpc_readb(falcbase + F_REG(FMR5, ch)) & ~FMR5_XLD);
+	} else {
+		cpc_writeb(falcbase + F_REG(FMR3, ch),
+			   cpc_readb(falcbase + F_REG(FMR3, ch)) & ~FMR3_XLD);
+	}
+}
+
+/*----------------------------------------------------------------------------
+ * falc_generate_loop_up_code
+ *----------------------------------------------------------------------------
+ * Description:	This routine writes the proper FALC chip register in order
+ *		to generate a LOOP activation code over a T1/E1 line.
+ *----------------------------------------------------------------------------
+ */
+void falc_generate_loop_up_code(pc300_t * card, int ch)
+{
+	pc300ch_t *chan = (pc300ch_t *) & card->chan[ch];
+	pc300chconf_t *conf = (pc300chconf_t *) & chan->conf;
+	falc_t *pfalc = (falc_t *) & chan->falc;
+	void __iomem *falcbase = card->hw.falcbase;
+
+	if (conf->media == IF_IFACE_T1) {
+		cpc_writeb(falcbase + F_REG(FMR5, ch),
+			   cpc_readb(falcbase + F_REG(FMR5, ch)) | FMR5_XLU);
+	} else {
+		cpc_writeb(falcbase + F_REG(FMR3, ch),
+			   cpc_readb(falcbase + F_REG(FMR3, ch)) | FMR3_XLU);
+	}
+	// EVENT_FALC_ABNORMAL
+	if (conf->media == IF_IFACE_T1) {
+		/* Disable this interrupt as it may otherwise interfere with 
+		 * other working boards. */
+		cpc_writeb(falcbase + F_REG(IMR0, ch),
+			   cpc_readb(falcbase + F_REG(IMR0, ch)) | IMR0_PDEN);
+	}
+	falc_disable_comm(card, ch);
+	// EVENT_FALC_ABNORMAL
+	pfalc->loop_gen = 1;
+}
+
+/*----------------------------------------------------------------------------
+ * falc_generate_loop_down_code
+ *----------------------------------------------------------------------------
+ * Description:	This routine writes the proper FALC chip register in order
+ *		to generate a LOOP deactivation code over a T1/E1 line.
+ *----------------------------------------------------------------------------
+ */
+void falc_generate_loop_down_code(pc300_t * card, int ch)
+{
+	pc300ch_t *chan = (pc300ch_t *) & card->chan[ch];
+	pc300chconf_t *conf = (pc300chconf_t *) & chan->conf;
+	falc_t *pfalc = (falc_t *) & chan->falc;
+	void __iomem *falcbase = card->hw.falcbase;
+
+	if (conf->media == IF_IFACE_T1) {
+		cpc_writeb(falcbase + F_REG(FMR5, ch),
+			   cpc_readb(falcbase + F_REG(FMR5, ch)) | FMR5_XLD);
+	} else {
+		cpc_writeb(falcbase + F_REG(FMR3, ch),
+			   cpc_readb(falcbase + F_REG(FMR3, ch)) | FMR3_XLD);
+	}
+	pfalc->sync = 0;
+	cpc_writeb(falcbase + card->hw.cpld_reg2,
+		   cpc_readb(falcbase + card->hw.cpld_reg2) &
+		   ~(CPLD_REG2_FALC_LED2 << (2 * ch)));
+	pfalc->active = 0;
+//?    falc_issue_cmd(card, ch, CMDR_XRES);
+	pfalc->loop_gen = 0;
+}
+
+/*----------------------------------------------------------------------------
+ * falc_pattern_test
+ *----------------------------------------------------------------------------
+ * Description:	This routine generates a pattern code and checks
+ *		it on the reception side.
+ *----------------------------------------------------------------------------
+ */
+void falc_pattern_test(pc300_t * card, int ch, unsigned int activate)
+{
+	pc300ch_t *chan = (pc300ch_t *) & card->chan[ch];
+	pc300chconf_t *conf = (pc300chconf_t *) & chan->conf;
+	falc_t *pfalc = (falc_t *) & chan->falc;
+	void __iomem *falcbase = card->hw.falcbase;
+
+	if (activate) {
+		pfalc->prbs = 1;
+		pfalc->bec = 0;
+		if (conf->media == IF_IFACE_T1) {
+			/* Disable local loop activation/deactivation detect */
+			cpc_writeb(falcbase + F_REG(IMR3, ch),
+				   cpc_readb(falcbase + F_REG(IMR3, ch)) | IMR3_LLBSC);
+		} else {
+			/* Disable local loop activation/deactivation detect */
+			cpc_writeb(falcbase + F_REG(IMR1, ch),
+				   cpc_readb(falcbase + F_REG(IMR1, ch)) | IMR1_LLBSC);
+		}
+		/* Activates generation and monitoring of PRBS 
+		 * (Pseudo Random Bit Sequence) */
+		cpc_writeb(falcbase + F_REG(LCR1, ch),
+			   cpc_readb(falcbase + F_REG(LCR1, ch)) | LCR1_EPRM | LCR1_XPRBS);
+	} else {
+		pfalc->prbs = 0;
+		/* Deactivates generation and monitoring of PRBS 
+		 * (Pseudo Random Bit Sequence) */
+		cpc_writeb(falcbase + F_REG(LCR1, ch),
+			   cpc_readb(falcbase+F_REG(LCR1,ch)) & ~(LCR1_EPRM | LCR1_XPRBS));
+		if (conf->media == IF_IFACE_T1) {
+			/* Enable local loop activation/deactivation detect */
+			cpc_writeb(falcbase + F_REG(IMR3, ch),
+				   cpc_readb(falcbase + F_REG(IMR3, ch)) & ~IMR3_LLBSC);
+		} else {
+			/* Enable local loop activation/deactivation detect */
+			cpc_writeb(falcbase + F_REG(IMR1, ch),
+				   cpc_readb(falcbase + F_REG(IMR1, ch)) & ~IMR1_LLBSC);
+		}
+	}
+}
+
+/*----------------------------------------------------------------------------
+ * falc_pattern_test_error
+ *----------------------------------------------------------------------------
+ * Description:	This routine returns the bit error counter value
+ *----------------------------------------------------------------------------
+ */
+ucshort falc_pattern_test_error(pc300_t * card, int ch)
+{
+	pc300ch_t *chan = (pc300ch_t *) & card->chan[ch];
+	falc_t *pfalc = (falc_t *) & chan->falc;
+
+	return (pfalc->bec);
+}
+
+/**********************************/
+/***   Net Interface Routines   ***/
+/**********************************/
+
+static void
+cpc_trace(struct net_device *dev, struct sk_buff *skb_main, char rx_tx)
+{
+	struct sk_buff *skb;
+
+	if ((skb = dev_alloc_skb(10 + skb_main->len)) == NULL) {
+		printk("%s: out of memory\n", dev->name);
+		return;
+	}
+	skb_put(skb, 10 + skb_main->len);
+
+	skb->dev = dev;
+	skb->protocol = htons(ETH_P_CUST);
+	skb->mac.raw = skb->data;
+	skb->pkt_type = PACKET_HOST;
+	skb->len = 10 + skb_main->len;
+
+	memcpy(skb->data, dev->name, 5);
+	skb->data[5] = '[';
+	skb->data[6] = rx_tx;
+	skb->data[7] = ']';
+	skb->data[8] = ':';
+	skb->data[9] = ' ';
+	memcpy(&skb->data[10], skb_main->data, skb_main->len);
+
+	netif_rx(skb);
+}
+
+void cpc_tx_timeout(struct net_device *dev)
+{
+	pc300dev_t *d = (pc300dev_t *) dev->priv;
+	pc300ch_t *chan = (pc300ch_t *) d->chan;
+	pc300_t *card = (pc300_t *) chan->card;
+	struct net_device_stats *stats = hdlc_stats(dev);
+	int ch = chan->channel;
+	unsigned long flags;
+	ucchar ilar;
+
+	stats->tx_errors++;
+	stats->tx_aborted_errors++;
+	CPC_LOCK(card, flags);
+	if ((ilar = cpc_readb(card->hw.scabase + ILAR)) != 0) {
+		printk("%s: ILAR=0x%x\n", dev->name, ilar);
+		cpc_writeb(card->hw.scabase + ILAR, ilar);
+		cpc_writeb(card->hw.scabase + DMER, 0x80);
+	}
+	if (card->hw.type == PC300_TE) {
+		cpc_writeb(card->hw.falcbase + card->hw.cpld_reg2,
+			   cpc_readb(card->hw.falcbase + card->hw.cpld_reg2) &
+			   ~(CPLD_REG2_FALC_LED1 << (2 * ch)));
+	}
+	dev->trans_start = jiffies;
+	CPC_UNLOCK(card, flags);
+	netif_wake_queue(dev);
+}
+
+int cpc_queue_xmit(struct sk_buff *skb, struct net_device *dev)
+{
+	pc300dev_t *d = (pc300dev_t *) dev->priv;
+	pc300ch_t *chan = (pc300ch_t *) d->chan;
+	pc300_t *card = (pc300_t *) chan->card;
+	struct net_device_stats *stats = hdlc_stats(dev);
+	int ch = chan->channel;
+	unsigned long flags;
+#ifdef PC300_DEBUG_TX
+	int i;
+#endif
+
+	if (chan->conf.monitor) {
+		/* In monitor mode no Tx is done: ignore packet */
+		dev_kfree_skb(skb);
+		return 0;
+	} else if (!netif_carrier_ok(dev)) {
+		/* DCD must be OFF: drop packet */
+		dev_kfree_skb(skb);
+		stats->tx_errors++;
+		stats->tx_carrier_errors++;
+		return 0;
+	} else if (cpc_readb(card->hw.scabase + M_REG(ST3, ch)) & ST3_DCD) {
+		printk("%s: DCD is OFF. Going administrative down.\n", dev->name);
+		stats->tx_errors++;
+		stats->tx_carrier_errors++;
+		dev_kfree_skb(skb);
+		netif_carrier_off(dev);
+		CPC_LOCK(card, flags);
+		cpc_writeb(card->hw.scabase + M_REG(CMD, ch), CMD_TX_BUF_CLR);
+		if (card->hw.type == PC300_TE) {
+			cpc_writeb(card->hw.falcbase + card->hw.cpld_reg2,
+				   cpc_readb(card->hw.falcbase + card->hw.cpld_reg2) & 
+				   			~(CPLD_REG2_FALC_LED1 << (2 * ch)));
+		}
+		CPC_UNLOCK(card, flags);
+		netif_wake_queue(dev);
+		return 0;
+	}
+
+	/* Write buffer to DMA buffers */
+	if (dma_buf_write(card, ch, (ucchar *) skb->data, skb->len) != 0) {
+//		printk("%s: write error. Dropping TX packet.\n", dev->name);
+		netif_stop_queue(dev);
+		dev_kfree_skb(skb);
+		stats->tx_errors++;
+		stats->tx_dropped++;
+		return 0;
+	}
+#ifdef PC300_DEBUG_TX
+	printk("%s T:", dev->name);
+	for (i = 0; i < skb->len; i++)
+		printk(" %02x", *(skb->data + i));
+	printk("\n");
+#endif
+
+	if (d->trace_on) {
+		cpc_trace(dev, skb, 'T');
+	}
+	dev->trans_start = jiffies;
+
+	/* Start transmission */
+	CPC_LOCK(card, flags);
+	/* verify if it has more than one free descriptor */
+	if (card->chan[ch].nfree_tx_bd <= 1) {
+		/* don't have so stop the queue */
+		netif_stop_queue(dev);
+	}
+	cpc_writel(card->hw.scabase + DTX_REG(EDAL, ch),
+		   TX_BD_ADDR(ch, chan->tx_next_bd));
+	cpc_writeb(card->hw.scabase + M_REG(CMD, ch), CMD_TX_ENA);
+	cpc_writeb(card->hw.scabase + DSR_TX(ch), DSR_DE);
+	if (card->hw.type == PC300_TE) {
+		cpc_writeb(card->hw.falcbase + card->hw.cpld_reg2,
+			   cpc_readb(card->hw.falcbase + card->hw.cpld_reg2) |
+			   (CPLD_REG2_FALC_LED1 << (2 * ch)));
+	}
+	CPC_UNLOCK(card, flags);
+	dev_kfree_skb(skb);
+
+	return 0;
+}
+
+void cpc_net_rx(struct net_device *dev)
+{
+	pc300dev_t *d = (pc300dev_t *) dev->priv;
+	pc300ch_t *chan = (pc300ch_t *) d->chan;
+	pc300_t *card = (pc300_t *) chan->card;
+	struct net_device_stats *stats = hdlc_stats(dev);
+	int ch = chan->channel;
+#ifdef PC300_DEBUG_RX
+	int i;
+#endif
+	int rxb;
+	struct sk_buff *skb;
+
+	while (1) {
+		if ((rxb = dma_get_rx_frame_size(card, ch)) == -1)
+			return;
+
+		if (!netif_carrier_ok(dev)) {
+			/* DCD must be OFF: drop packet */
+		    printk("%s : DCD is OFF - drop %d rx bytes\n", dev->name, rxb); 
+			skb = NULL;
+		} else {
+			if (rxb > (dev->mtu + 40)) { /* add headers */
+				printk("%s : MTU exceeded %d\n", dev->name, rxb); 
+				skb = NULL;
+			} else {
+				skb = dev_alloc_skb(rxb);
+				if (skb == NULL) {
+					printk("%s: Memory squeeze!!\n", dev->name);
+					return;
+				}
+				skb->dev = dev;
+			}
+		}
+
+		if (((rxb = dma_buf_read(card, ch, skb)) <= 0) || (skb == NULL)) {
+#ifdef PC300_DEBUG_RX
+			printk("%s: rxb = %x\n", dev->name, rxb);
+#endif
+			if ((skb == NULL) && (rxb > 0)) {
+				/* rxb > dev->mtu */
+				stats->rx_errors++;
+				stats->rx_length_errors++;
+				continue;
+			}
+
+			if (rxb < 0) {	/* Invalid frame */
+				rxb = -rxb;
+				if (rxb & DST_OVR) {
+					stats->rx_errors++;
+					stats->rx_fifo_errors++;
+				}
+				if (rxb & DST_CRC) {
+					stats->rx_errors++;
+					stats->rx_crc_errors++;
+				}
+				if (rxb & (DST_RBIT | DST_SHRT | DST_ABT)) {
+					stats->rx_errors++;
+					stats->rx_frame_errors++;
+				}
+			}
+			if (skb) {
+				dev_kfree_skb_irq(skb);
+			}
+			continue;
+		}
+
+		stats->rx_bytes += rxb;
+
+#ifdef PC300_DEBUG_RX
+		printk("%s R:", dev->name);
+		for (i = 0; i < skb->len; i++)
+			printk(" %02x", *(skb->data + i));
+		printk("\n");
+#endif
+		if (d->trace_on) {
+			cpc_trace(dev, skb, 'R');
+		}
+		stats->rx_packets++;
+		skb->protocol = hdlc_type_trans(skb, dev);
+		netif_rx(skb);
+	}
+}
+
+/************************************/
+/***   PC300 Interrupt Routines   ***/
+/************************************/
+static void sca_tx_intr(pc300dev_t *dev)
+{
+	pc300ch_t *chan = (pc300ch_t *)dev->chan; 
+	pc300_t *card = (pc300_t *)chan->card; 
+	int ch = chan->channel; 
+	volatile pcsca_bd_t __iomem * ptdescr; 
+	struct net_device_stats *stats = hdlc_stats(dev->dev);
+
+    /* Clean up descriptors from previous transmission */
+	ptdescr = (card->hw.rambase +
+						TX_BD_ADDR(ch,chan->tx_first_bd));
+	while ((cpc_readl(card->hw.scabase + DTX_REG(CDAL,ch)) != 
+							TX_BD_ADDR(ch,chan->tx_first_bd)) && 
+			(cpc_readb(&ptdescr->status) & DST_OSB)) {
+		stats->tx_packets++;
+		stats->tx_bytes += cpc_readw(&ptdescr->len);
+		cpc_writeb(&ptdescr->status, DST_OSB);
+		cpc_writew(&ptdescr->len, 0);
+		chan->nfree_tx_bd++;
+		chan->tx_first_bd = (chan->tx_first_bd + 1) & (N_DMA_TX_BUF - 1);
+		ptdescr = (card->hw.rambase + TX_BD_ADDR(ch,chan->tx_first_bd));
+    }
+
+#ifdef CONFIG_PC300_MLPPP
+	if (chan->conf.proto == PC300_PROTO_MLPPP) {
+			cpc_tty_trigger_poll(dev);
+	} else {
+#endif
+	/* Tell the upper layer we are ready to transmit more packets */
+		netif_wake_queue(dev->dev);
+#ifdef CONFIG_PC300_MLPPP
+	}
+#endif
+}
+
+static void sca_intr(pc300_t * card)
+{
+	void __iomem *scabase = card->hw.scabase;
+	volatile uclong status;
+	int ch;
+	int intr_count = 0;
+	unsigned char dsr_rx;
+
+	while ((status = cpc_readl(scabase + ISR0)) != 0) {
+		for (ch = 0; ch < card->hw.nchan; ch++) {
+			pc300ch_t *chan = &card->chan[ch];
+			pc300dev_t *d = &chan->d;
+			struct net_device *dev = d->dev;
+			hdlc_device *hdlc = dev_to_hdlc(dev);
+
+			spin_lock(&card->card_lock);
+
+	    /**** Reception ****/
+			if (status & IR0_DRX((IR0_DMIA | IR0_DMIB), ch)) {
+				ucchar drx_stat = cpc_readb(scabase + DSR_RX(ch));
+
+				/* Clear RX interrupts */
+				cpc_writeb(scabase + DSR_RX(ch), drx_stat | DSR_DWE);
+
+#ifdef PC300_DEBUG_INTR
+				printk ("sca_intr: RX intr chan[%d] (st=0x%08lx, dsr=0x%02x)\n",
+					 ch, status, drx_stat);
+#endif
+				if (status & IR0_DRX(IR0_DMIA, ch)) {
+					if (drx_stat & DSR_BOF) {
+#ifdef CONFIG_PC300_MLPPP
+						if (chan->conf.proto == PC300_PROTO_MLPPP) {
+							/* verify if driver is TTY */
+							if ((cpc_readb(scabase + DSR_RX(ch)) & DSR_DE)) {
+								rx_dma_stop(card, ch);
+							}
+							cpc_tty_receive(d);
+							rx_dma_start(card, ch);
+						} else 
+#endif
+						{
+							if ((cpc_readb(scabase + DSR_RX(ch)) & DSR_DE)) {
+								rx_dma_stop(card, ch);
+							}
+							cpc_net_rx(dev);
+							/* Discard invalid frames */
+							hdlc->stats.rx_errors++;
+							hdlc->stats.rx_over_errors++;
+							chan->rx_first_bd = 0;
+							chan->rx_last_bd = N_DMA_RX_BUF - 1;
+							rx_dma_start(card, ch);
+						}
+					}
+				}
+				if (status & IR0_DRX(IR0_DMIB, ch)) {
+					if (drx_stat & DSR_EOM) {
+						if (card->hw.type == PC300_TE) {
+							cpc_writeb(card->hw.falcbase +
+								   card->hw.cpld_reg2,
+								   cpc_readb (card->hw.falcbase +
+								    	card->hw.cpld_reg2) |
+								   (CPLD_REG2_FALC_LED1 << (2 * ch)));
+						}
+#ifdef CONFIG_PC300_MLPPP
+						if (chan->conf.proto == PC300_PROTO_MLPPP) {
+							/* verify if driver is TTY */
+							cpc_tty_receive(d);
+						} else {
+							cpc_net_rx(dev);
+						}
+#else
+						cpc_net_rx(dev);
+#endif
+						if (card->hw.type == PC300_TE) {
+							cpc_writeb(card->hw.falcbase +
+								   card->hw.cpld_reg2,
+								   cpc_readb (card->hw.falcbase +
+								    		card->hw.cpld_reg2) &
+								   ~ (CPLD_REG2_FALC_LED1 << (2 * ch)));
+						}
+					}
+				}
+				if (!(dsr_rx = cpc_readb(scabase + DSR_RX(ch)) & DSR_DE)) {
+#ifdef PC300_DEBUG_INTR
+		printk("%s: RX intr chan[%d] (st=0x%08lx, dsr=0x%02x, dsr2=0x%02x)\n",
+			dev->name, ch, status, drx_stat, dsr_rx);
+#endif
+					cpc_writeb(scabase + DSR_RX(ch), (dsr_rx | DSR_DE) & 0xfe);
+				}
+			}
+
+	    /**** Transmission ****/
+			if (status & IR0_DTX((IR0_EFT | IR0_DMIA | IR0_DMIB), ch)) {
+				ucchar dtx_stat = cpc_readb(scabase + DSR_TX(ch));
+
+				/* Clear TX interrupts */
+				cpc_writeb(scabase + DSR_TX(ch), dtx_stat | DSR_DWE);
+
+#ifdef PC300_DEBUG_INTR
+				printk ("sca_intr: TX intr chan[%d] (st=0x%08lx, dsr=0x%02x)\n",
+					 ch, status, dtx_stat);
+#endif
+				if (status & IR0_DTX(IR0_EFT, ch)) {
+					if (dtx_stat & DSR_UDRF) {
+						if (cpc_readb (scabase + M_REG(TBN, ch)) != 0) {
+							cpc_writeb(scabase + M_REG(CMD,ch), CMD_TX_BUF_CLR);
+						}
+						if (card->hw.type == PC300_TE) {
+							cpc_writeb(card->hw.falcbase + card->hw.cpld_reg2,
+								   cpc_readb (card->hw.falcbase + 
+										   card->hw.cpld_reg2) &
+								   ~ (CPLD_REG2_FALC_LED1 << (2 * ch)));
+						}
+						hdlc->stats.tx_errors++;
+						hdlc->stats.tx_fifo_errors++;
+						sca_tx_intr(d);
+					}
+				}
+				if (status & IR0_DTX(IR0_DMIA, ch)) {
+					if (dtx_stat & DSR_BOF) {
+					}
+				}
+				if (status & IR0_DTX(IR0_DMIB, ch)) {
+					if (dtx_stat & DSR_EOM) {
+						if (card->hw.type == PC300_TE) {
+							cpc_writeb(card->hw.falcbase + card->hw.cpld_reg2,
+								   cpc_readb (card->hw.falcbase +
+								    			card->hw.cpld_reg2) &
+								   ~ (CPLD_REG2_FALC_LED1 << (2 * ch)));
+						}
+						sca_tx_intr(d);
+					}
+				}
+			}
+
+	    /**** MSCI ****/
+			if (status & IR0_M(IR0_RXINTA, ch)) {
+				ucchar st1 = cpc_readb(scabase + M_REG(ST1, ch));
+
+				/* Clear MSCI interrupts */
+				cpc_writeb(scabase + M_REG(ST1, ch), st1);
+
+#ifdef PC300_DEBUG_INTR
+				printk("sca_intr: MSCI intr chan[%d] (st=0x%08lx, st1=0x%02x)\n",
+					 ch, status, st1);
+#endif
+				if (st1 & ST1_CDCD) {	/* DCD changed */
+					if (cpc_readb(scabase + M_REG(ST3, ch)) & ST3_DCD) {
+						printk ("%s: DCD is OFF. Going administrative down.\n",
+							 dev->name);
+#ifdef CONFIG_PC300_MLPPP
+						if (chan->conf.proto != PC300_PROTO_MLPPP) {
+							netif_carrier_off(dev);
+						}
+#else
+						netif_carrier_off(dev);
+
+#endif
+						card->chan[ch].d.line_off++;
+					} else {	/* DCD = 1 */
+						printk ("%s: DCD is ON. Going administrative up.\n",
+							 dev->name);
+#ifdef CONFIG_PC300_MLPPP
+						if (chan->conf.proto != PC300_PROTO_MLPPP)
+							/* verify if driver is not TTY */
+#endif
+							netif_carrier_on(dev);
+						card->chan[ch].d.line_on++;
+					}
+				}
+			}
+			spin_unlock(&card->card_lock);
+		}
+		if (++intr_count == 10)
+			/* Too much work at this board. Force exit */
+			break;
+	}
+}
+
+static void falc_t1_loop_detection(pc300_t * card, int ch, ucchar frs1)
+{
+	pc300ch_t *chan = (pc300ch_t *) & card->chan[ch];
+	falc_t *pfalc = (falc_t *) & chan->falc;
+	void __iomem *falcbase = card->hw.falcbase;
+
+	if (((cpc_readb(falcbase + F_REG(LCR1, ch)) & LCR1_XPRBS) == 0) &&
+	    !pfalc->loop_gen) {
+		if (frs1 & FRS1_LLBDD) {
+			// A Line Loop Back Deactivation signal detected
+			if (pfalc->loop_active) {
+				falc_remote_loop(card, ch, 0);
+			}
+		} else {
+			if ((frs1 & FRS1_LLBAD) &&
+			    ((cpc_readb(falcbase + F_REG(LCR1, ch)) & LCR1_EPRM) == 0)) {
+				// A Line Loop Back Activation signal detected  
+				if (!pfalc->loop_active) {
+					falc_remote_loop(card, ch, 1);
+				}
+			}
+		}
+	}
+}
+
+static void falc_e1_loop_detection(pc300_t * card, int ch, ucchar rsp)
+{
+	pc300ch_t *chan = (pc300ch_t *) & card->chan[ch];
+	falc_t *pfalc = (falc_t *) & chan->falc;
+	void __iomem *falcbase = card->hw.falcbase;
+
+	if (((cpc_readb(falcbase + F_REG(LCR1, ch)) & LCR1_XPRBS) == 0) &&
+	    !pfalc->loop_gen) {
+		if (rsp & RSP_LLBDD) {
+			// A Line Loop Back Deactivation signal detected
+			if (pfalc->loop_active) {
+				falc_remote_loop(card, ch, 0);
+			}
+		} else {
+			if ((rsp & RSP_LLBAD) &&
+			    ((cpc_readb(falcbase + F_REG(LCR1, ch)) & LCR1_EPRM) == 0)) {
+				// A Line Loop Back Activation signal detected  
+				if (!pfalc->loop_active) {
+					falc_remote_loop(card, ch, 1);
+				}
+			}
+		}
+	}
+}
+
+static void falc_t1_intr(pc300_t * card, int ch)
+{
+	pc300ch_t *chan = (pc300ch_t *) & card->chan[ch];
+	falc_t *pfalc = (falc_t *) & chan->falc;
+	void __iomem *falcbase = card->hw.falcbase;
+	ucchar isr0, isr3, gis;
+	ucchar dummy;
+
+	while ((gis = cpc_readb(falcbase + F_REG(GIS, ch))) != 0) {
+		if (gis & GIS_ISR0) {
+			isr0 = cpc_readb(falcbase + F_REG(FISR0, ch));
+			if (isr0 & FISR0_PDEN) {
+				/* Read the bit to clear the situation */
+				if (cpc_readb(falcbase + F_REG(FRS1, ch)) &
+				    FRS1_PDEN) {
+					pfalc->pden++;
+				}
+			}
+		}
+
+		if (gis & GIS_ISR1) {
+			dummy = cpc_readb(falcbase + F_REG(FISR1, ch));
+		}
+
+		if (gis & GIS_ISR2) {
+			dummy = cpc_readb(falcbase + F_REG(FISR2, ch));
+		}
+
+		if (gis & GIS_ISR3) {
+			isr3 = cpc_readb(falcbase + F_REG(FISR3, ch));
+			if (isr3 & FISR3_SEC) {
+				pfalc->sec++;
+				falc_update_stats(card, ch);
+				falc_check_status(card, ch,
+						  cpc_readb(falcbase + F_REG(FRS0, ch)));
+			}
+			if (isr3 & FISR3_ES) {
+				pfalc->es++;
+			}
+			if (isr3 & FISR3_LLBSC) {
+				falc_t1_loop_detection(card, ch,
+						       cpc_readb(falcbase + F_REG(FRS1, ch)));
+			}
+		}
+	}
+}
+
+static void falc_e1_intr(pc300_t * card, int ch)
+{
+	pc300ch_t *chan = (pc300ch_t *) & card->chan[ch];
+	falc_t *pfalc = (falc_t *) & chan->falc;
+	void __iomem *falcbase = card->hw.falcbase;
+	ucchar isr1, isr2, isr3, gis, rsp;
+	ucchar dummy;
+
+	while ((gis = cpc_readb(falcbase + F_REG(GIS, ch))) != 0) {
+		rsp = cpc_readb(falcbase + F_REG(RSP, ch));
+
+		if (gis & GIS_ISR0) {
+			dummy = cpc_readb(falcbase + F_REG(FISR0, ch));
+		}
+		if (gis & GIS_ISR1) {
+			isr1 = cpc_readb(falcbase + F_REG(FISR1, ch));
+			if (isr1 & FISR1_XMB) {
+				if ((pfalc->xmb_cause & 2)
+				    && pfalc->multiframe_mode) {
+					if (cpc_readb (falcbase + F_REG(FRS0, ch)) & 
+									(FRS0_LOS | FRS0_AIS | FRS0_LFA)) {
+						cpc_writeb(falcbase + F_REG(XSP, ch),
+							   cpc_readb(falcbase + F_REG(XSP, ch))
+							   & ~XSP_AXS);
+					} else {
+						cpc_writeb(falcbase + F_REG(XSP, ch),
+							   cpc_readb(falcbase + F_REG(XSP, ch))
+							   | XSP_AXS);
+					}
+				}
+				pfalc->xmb_cause = 0;
+				cpc_writeb(falcbase + F_REG(IMR1, ch),
+					   cpc_readb(falcbase + F_REG(IMR1, ch)) | IMR1_XMB);
+			}
+			if (isr1 & FISR1_LLBSC) {
+				falc_e1_loop_detection(card, ch, rsp);
+			}
+		}
+		if (gis & GIS_ISR2) {
+			isr2 = cpc_readb(falcbase + F_REG(FISR2, ch));
+			if (isr2 & FISR2_T400MS) {
+				cpc_writeb(falcbase + F_REG(XSW, ch),
+					   cpc_readb(falcbase + F_REG(XSW, ch)) | XSW_XRA);
+			}
+			if (isr2 & FISR2_MFAR) {
+				cpc_writeb(falcbase + F_REG(XSW, ch),
+					   cpc_readb(falcbase + F_REG(XSW, ch)) & ~XSW_XRA);
+			}
+			if (isr2 & (FISR2_FAR | FISR2_LFA | FISR2_AIS | FISR2_LOS)) {
+				pfalc->xmb_cause |= 2;
+				cpc_writeb(falcbase + F_REG(IMR1, ch),
+					   cpc_readb(falcbase + F_REG(IMR1, ch)) & ~IMR1_XMB);
+			}
+		}
+		if (gis & GIS_ISR3) {
+			isr3 = cpc_readb(falcbase + F_REG(FISR3, ch));
+			if (isr3 & FISR3_SEC) {
+				pfalc->sec++;
+				falc_update_stats(card, ch);
+				falc_check_status(card, ch,
+						  cpc_readb(falcbase + F_REG(FRS0, ch)));
+			}
+			if (isr3 & FISR3_ES) {
+				pfalc->es++;
+			}
+		}
+	}
+}
+
+static void falc_intr(pc300_t * card)
+{
+	int ch;
+
+	for (ch = 0; ch < card->hw.nchan; ch++) {
+		pc300ch_t *chan = &card->chan[ch];
+		pc300chconf_t *conf = (pc300chconf_t *) & chan->conf;
+
+		if (conf->media == IF_IFACE_T1) {
+			falc_t1_intr(card, ch);
+		} else {
+			falc_e1_intr(card, ch);
+		}
+	}
+}
+
+static irqreturn_t cpc_intr(int irq, void *dev_id, struct pt_regs *regs)
+{
+	pc300_t *card;
+	volatile ucchar plx_status;
+
+	if ((card = (pc300_t *) dev_id) == 0) {
+#ifdef PC300_DEBUG_INTR
+		printk("cpc_intr: spurious intr %d\n", irq);
+#endif
+		return IRQ_NONE;		/* spurious intr */
+	}
+
+	if (card->hw.rambase == 0) {
+#ifdef PC300_DEBUG_INTR
+		printk("cpc_intr: spurious intr2 %d\n", irq);
+#endif
+		return IRQ_NONE;		/* spurious intr */
+	}
+
+	switch (card->hw.type) {
+		case PC300_RSV:
+		case PC300_X21:
+			sca_intr(card);
+			break;
+
+		case PC300_TE:
+			while ( (plx_status = (cpc_readb(card->hw.plxbase + card->hw.intctl_reg) &
+				 (PLX_9050_LINT1_STATUS | PLX_9050_LINT2_STATUS))) != 0) {
+				if (plx_status & PLX_9050_LINT1_STATUS) {	/* SCA Interrupt */
+					sca_intr(card);
+				}
+				if (plx_status & PLX_9050_LINT2_STATUS) {	/* FALC Interrupt */
+					falc_intr(card);
+				}
+			}
+			break;
+	}
+	return IRQ_HANDLED;
+}
+
+void cpc_sca_status(pc300_t * card, int ch)
+{
+	ucchar ilar;
+	void __iomem *scabase = card->hw.scabase;
+	unsigned long flags;
+
+	tx_dma_buf_check(card, ch);
+	rx_dma_buf_check(card, ch);
+	ilar = cpc_readb(scabase + ILAR);
+	printk ("ILAR=0x%02x, WCRL=0x%02x, PCR=0x%02x, BTCR=0x%02x, BOLR=0x%02x\n",
+		 ilar, cpc_readb(scabase + WCRL), cpc_readb(scabase + PCR),
+		 cpc_readb(scabase + BTCR), cpc_readb(scabase + BOLR));
+	printk("TX_CDA=0x%08x, TX_EDA=0x%08x\n",
+	       cpc_readl(scabase + DTX_REG(CDAL, ch)),
+	       cpc_readl(scabase + DTX_REG(EDAL, ch)));
+	printk("RX_CDA=0x%08x, RX_EDA=0x%08x, BFL=0x%04x\n",
+	       cpc_readl(scabase + DRX_REG(CDAL, ch)),
+	       cpc_readl(scabase + DRX_REG(EDAL, ch)),
+	       cpc_readw(scabase + DRX_REG(BFLL, ch)));
+	printk("DMER=0x%02x, DSR_TX=0x%02x, DSR_RX=0x%02x\n",
+	       cpc_readb(scabase + DMER), cpc_readb(scabase + DSR_TX(ch)),
+	       cpc_readb(scabase + DSR_RX(ch)));
+	printk("DMR_TX=0x%02x, DMR_RX=0x%02x, DIR_TX=0x%02x, DIR_RX=0x%02x\n",
+	       cpc_readb(scabase + DMR_TX(ch)), cpc_readb(scabase + DMR_RX(ch)),
+	       cpc_readb(scabase + DIR_TX(ch)),
+	       cpc_readb(scabase + DIR_RX(ch)));
+	printk("DCR_TX=0x%02x, DCR_RX=0x%02x, FCT_TX=0x%02x, FCT_RX=0x%02x\n",
+	       cpc_readb(scabase + DCR_TX(ch)), cpc_readb(scabase + DCR_RX(ch)),
+	       cpc_readb(scabase + FCT_TX(ch)),
+	       cpc_readb(scabase + FCT_RX(ch)));
+	printk("MD0=0x%02x, MD1=0x%02x, MD2=0x%02x, MD3=0x%02x, IDL=0x%02x\n",
+	       cpc_readb(scabase + M_REG(MD0, ch)),
+	       cpc_readb(scabase + M_REG(MD1, ch)),
+	       cpc_readb(scabase + M_REG(MD2, ch)),
+	       cpc_readb(scabase + M_REG(MD3, ch)),
+	       cpc_readb(scabase + M_REG(IDL, ch)));
+	printk("CMD=0x%02x, SA0=0x%02x, SA1=0x%02x, TFN=0x%02x, CTL=0x%02x\n",
+	       cpc_readb(scabase + M_REG(CMD, ch)),
+	       cpc_readb(scabase + M_REG(SA0, ch)),
+	       cpc_readb(scabase + M_REG(SA1, ch)),
+	       cpc_readb(scabase + M_REG(TFN, ch)),
+	       cpc_readb(scabase + M_REG(CTL, ch)));
+	printk("ST0=0x%02x, ST1=0x%02x, ST2=0x%02x, ST3=0x%02x, ST4=0x%02x\n",
+	       cpc_readb(scabase + M_REG(ST0, ch)),
+	       cpc_readb(scabase + M_REG(ST1, ch)),
+	       cpc_readb(scabase + M_REG(ST2, ch)),
+	       cpc_readb(scabase + M_REG(ST3, ch)),
+	       cpc_readb(scabase + M_REG(ST4, ch)));
+	printk ("CST0=0x%02x, CST1=0x%02x, CST2=0x%02x, CST3=0x%02x, FST=0x%02x\n",
+		 cpc_readb(scabase + M_REG(CST0, ch)),
+		 cpc_readb(scabase + M_REG(CST1, ch)),
+		 cpc_readb(scabase + M_REG(CST2, ch)),
+		 cpc_readb(scabase + M_REG(CST3, ch)),
+		 cpc_readb(scabase + M_REG(FST, ch)));
+	printk("TRC0=0x%02x, TRC1=0x%02x, RRC=0x%02x, TBN=0x%02x, RBN=0x%02x\n",
+	       cpc_readb(scabase + M_REG(TRC0, ch)),
+	       cpc_readb(scabase + M_REG(TRC1, ch)),
+	       cpc_readb(scabase + M_REG(RRC, ch)),
+	       cpc_readb(scabase + M_REG(TBN, ch)),
+	       cpc_readb(scabase + M_REG(RBN, ch)));
+	printk("TFS=0x%02x, TNR0=0x%02x, TNR1=0x%02x, RNR=0x%02x\n",
+	       cpc_readb(scabase + M_REG(TFS, ch)),
+	       cpc_readb(scabase + M_REG(TNR0, ch)),
+	       cpc_readb(scabase + M_REG(TNR1, ch)),
+	       cpc_readb(scabase + M_REG(RNR, ch)));
+	printk("TCR=0x%02x, RCR=0x%02x, TNR1=0x%02x, RNR=0x%02x\n",
+	       cpc_readb(scabase + M_REG(TCR, ch)),
+	       cpc_readb(scabase + M_REG(RCR, ch)),
+	       cpc_readb(scabase + M_REG(TNR1, ch)),
+	       cpc_readb(scabase + M_REG(RNR, ch)));
+	printk("TXS=0x%02x, RXS=0x%02x, EXS=0x%02x, TMCT=0x%02x, TMCR=0x%02x\n",
+	       cpc_readb(scabase + M_REG(TXS, ch)),
+	       cpc_readb(scabase + M_REG(RXS, ch)),
+	       cpc_readb(scabase + M_REG(EXS, ch)),
+	       cpc_readb(scabase + M_REG(TMCT, ch)),
+	       cpc_readb(scabase + M_REG(TMCR, ch)));
+	printk("IE0=0x%02x, IE1=0x%02x, IE2=0x%02x, IE4=0x%02x, FIE=0x%02x\n",
+	       cpc_readb(scabase + M_REG(IE0, ch)),
+	       cpc_readb(scabase + M_REG(IE1, ch)),
+	       cpc_readb(scabase + M_REG(IE2, ch)),
+	       cpc_readb(scabase + M_REG(IE4, ch)),
+	       cpc_readb(scabase + M_REG(FIE, ch)));
+	printk("IER0=0x%08x\n", cpc_readl(scabase + IER0));
+
+	if (ilar != 0) {
+		CPC_LOCK(card, flags);
+		cpc_writeb(scabase + ILAR, ilar);
+		cpc_writeb(scabase + DMER, 0x80);
+		CPC_UNLOCK(card, flags);
+	}
+}
+
+void cpc_falc_status(pc300_t * card, int ch)
+{
+	pc300ch_t *chan = &card->chan[ch];
+	falc_t *pfalc = (falc_t *) & chan->falc;
+	unsigned long flags;
+
+	CPC_LOCK(card, flags);
+	printk("CH%d:   %s %s  %d channels\n",
+	       ch, (pfalc->sync ? "SYNC" : ""), (pfalc->active ? "ACTIVE" : ""),
+	       pfalc->num_channels);
+
+	printk("        pden=%d,  los=%d,  losr=%d,  lfa=%d,  farec=%d\n",
+	       pfalc->pden, pfalc->los, pfalc->losr, pfalc->lfa, pfalc->farec);
+	printk("        lmfa=%d,  ais=%d,  sec=%d,  es=%d,  rai=%d\n",
+	       pfalc->lmfa, pfalc->ais, pfalc->sec, pfalc->es, pfalc->rai);
+	printk("        bec=%d,  fec=%d,  cvc=%d,  cec=%d,  ebc=%d\n",
+	       pfalc->bec, pfalc->fec, pfalc->cvc, pfalc->cec, pfalc->ebc);
+
+	printk("\n");
+	printk("        STATUS: %s  %s  %s  %s  %s  %s\n",
+	       (pfalc->red_alarm ? "RED" : ""),
+	       (pfalc->blue_alarm ? "BLU" : ""),
+	       (pfalc->yellow_alarm ? "YEL" : ""),
+	       (pfalc->loss_fa ? "LFA" : ""),
+	       (pfalc->loss_mfa ? "LMF" : ""), (pfalc->prbs ? "PRB" : ""));
+	CPC_UNLOCK(card, flags);
+}
+
+int cpc_change_mtu(struct net_device *dev, int new_mtu)
+{
+	if ((new_mtu < 128) || (new_mtu > PC300_DEF_MTU))
+		return -EINVAL;
+	dev->mtu = new_mtu;
+	return 0;
+}
+
+int cpc_ioctl(struct net_device *dev, struct ifreq *ifr, int cmd)
+{
+	hdlc_device *hdlc = dev_to_hdlc(dev);
+	pc300dev_t *d = (pc300dev_t *) dev->priv;
+	pc300ch_t *chan = (pc300ch_t *) d->chan;
+	pc300_t *card = (pc300_t *) chan->card;
+	pc300conf_t conf_aux;
+	pc300chconf_t *conf = (pc300chconf_t *) & chan->conf;
+	int ch = chan->channel;
+	void __user *arg = ifr->ifr_data;
+	struct if_settings *settings = &ifr->ifr_settings;
+	void __iomem *scabase = card->hw.scabase;
+
+	if (!capable(CAP_NET_ADMIN))
+		return -EPERM;
+
+	switch (cmd) {
+		case SIOCGPC300CONF:
+#ifdef CONFIG_PC300_MLPPP
+			if (conf->proto != PC300_PROTO_MLPPP) {
+				conf->proto = hdlc->proto.id;
+			}
+#else
+			conf->proto = hdlc->proto.id;
+#endif
+			memcpy(&conf_aux.conf, conf, sizeof(pc300chconf_t));
+			memcpy(&conf_aux.hw, &card->hw, sizeof(pc300hw_t));
+			if (!arg || 
+				copy_to_user(arg, &conf_aux, sizeof(pc300conf_t))) 
+				return -EINVAL;
+			return 0;
+		case SIOCSPC300CONF:
+			if (!capable(CAP_NET_ADMIN))
+				return -EPERM;
+			if (!arg || 
+				copy_from_user(&conf_aux.conf, arg, sizeof(pc300chconf_t)))
+				return -EINVAL;
+			if (card->hw.cpld_id < 0x02 &&
+			    conf_aux.conf.fr_mode == PC300_FR_UNFRAMED) {
+				/* CPLD_ID < 0x02 doesn't support Unframed E1 */
+				return -EINVAL;
+			}
+#ifdef CONFIG_PC300_MLPPP
+			if (conf_aux.conf.proto == PC300_PROTO_MLPPP) {
+				if (conf->proto != PC300_PROTO_MLPPP) {
+					memcpy(conf, &conf_aux.conf, sizeof(pc300chconf_t));
+					cpc_tty_init(d);	/* init TTY driver */
+				}
+			} else {
+				if (conf_aux.conf.proto == 0xffff) {
+					if (conf->proto == PC300_PROTO_MLPPP){ 
+						/* ifdown interface */
+						cpc_close(dev);
+					}
+				} else {
+					memcpy(conf, &conf_aux.conf, sizeof(pc300chconf_t));
+					hdlc->proto.id = conf->proto;
+				}
+			}
+#else
+			memcpy(conf, &conf_aux.conf, sizeof(pc300chconf_t));
+			hdlc->proto.id = conf->proto;
+#endif
+			return 0;
+		case SIOCGPC300STATUS:
+			cpc_sca_status(card, ch);
+			return 0;
+		case SIOCGPC300FALCSTATUS:
+			cpc_falc_status(card, ch);
+			return 0;
+
+		case SIOCGPC300UTILSTATS:
+			{
+				if (!arg) {	/* clear statistics */
+					memset(&hdlc->stats, 0, sizeof(struct net_device_stats));
+					if (card->hw.type == PC300_TE) {
+						memset(&chan->falc, 0, sizeof(falc_t));
+					}
+				} else {
+					pc300stats_t pc300stats;
+
+					memset(&pc300stats, 0, sizeof(pc300stats_t));
+					pc300stats.hw_type = card->hw.type;
+					pc300stats.line_on = card->chan[ch].d.line_on;
+					pc300stats.line_off = card->chan[ch].d.line_off;
+					memcpy(&pc300stats.gen_stats, &hdlc->stats,
+					       sizeof(struct net_device_stats));
+					if (card->hw.type == PC300_TE)
+						memcpy(&pc300stats.te_stats,&chan->falc,sizeof(falc_t));
+				    	if (copy_to_user(arg, &pc300stats, sizeof(pc300stats_t)))
+						return -EFAULT;
+				}
+				return 0;
+			}
+
+		case SIOCGPC300UTILSTATUS:
+			{
+				struct pc300status pc300status;
+
+				pc300status.hw_type = card->hw.type;
+				if (card->hw.type == PC300_TE) {
+					pc300status.te_status.sync = chan->falc.sync;
+					pc300status.te_status.red_alarm = chan->falc.red_alarm;
+					pc300status.te_status.blue_alarm = chan->falc.blue_alarm;
+					pc300status.te_status.loss_fa = chan->falc.loss_fa;
+					pc300status.te_status.yellow_alarm =chan->falc.yellow_alarm;
+					pc300status.te_status.loss_mfa = chan->falc.loss_mfa;
+					pc300status.te_status.prbs = chan->falc.prbs;
+				} else {
+					pc300status.gen_status.dcd =
+						!(cpc_readb (scabase + M_REG(ST3, ch)) & ST3_DCD);
+					pc300status.gen_status.cts =
+						!(cpc_readb (scabase + M_REG(ST3, ch)) & ST3_CTS);
+					pc300status.gen_status.rts =
+						!(cpc_readb (scabase + M_REG(CTL, ch)) & CTL_RTS);
+					pc300status.gen_status.dtr =
+						!(cpc_readb (scabase + M_REG(CTL, ch)) & CTL_DTR);
+					/* There is no DSR in HD64572 */
+				}
+				if (!arg
+				    || copy_to_user(arg, &pc300status, sizeof(pc300status_t)))
+						return -EINVAL;
+				return 0;
+			}
+
+		case SIOCSPC300TRACE:
+			/* Sets/resets a trace_flag for the respective device */
+			if (!arg || copy_from_user(&d->trace_on, arg,sizeof(unsigned char)))
+					return -EINVAL;
+			return 0;
+
+		case SIOCSPC300LOOPBACK:
+			{
+				struct pc300loopback pc300loop;
+
+				/* TE boards only */
+				if (card->hw.type != PC300_TE)
+					return -EINVAL;
+
+				if (!arg || 
+					copy_from_user(&pc300loop, arg, sizeof(pc300loopback_t)))
+						return -EINVAL;
+				switch (pc300loop.loop_type) {
+					case PC300LOCLOOP:	/* Turn the local loop on/off */
+						falc_local_loop(card, ch, pc300loop.loop_on);
+						return 0;
+
+					case PC300REMLOOP:	/* Turn the remote loop on/off */
+						falc_remote_loop(card, ch, pc300loop.loop_on);
+						return 0;
+
+					case PC300PAYLOADLOOP:	/* Turn the payload loop on/off */
+						falc_payload_loop(card, ch, pc300loop.loop_on);
+						return 0;
+
+					case PC300GENLOOPUP:	/* Generate loop UP */
+						if (pc300loop.loop_on) {
+							falc_generate_loop_up_code (card, ch);
+						} else {
+							turn_off_xlu(card, ch);
+						}
+						return 0;
+
+					case PC300GENLOOPDOWN:	/* Generate loop DOWN */
+						if (pc300loop.loop_on) {
+							falc_generate_loop_down_code (card, ch);
+						} else {
+							turn_off_xld(card, ch);
+						}
+						return 0;
+
+					default:
+						return -EINVAL;
+				}
+			}
+
+		case SIOCSPC300PATTERNTEST:
+			/* Turn the pattern test on/off and show the errors counter */
+			{
+				struct pc300patterntst pc300patrntst;
+
+				/* TE boards only */
+				if (card->hw.type != PC300_TE)
+					return -EINVAL;
+
+				if (card->hw.cpld_id < 0x02) {
+					/* CPLD_ID < 0x02 doesn't support pattern test */
+					return -EINVAL;
+				}
+
+				if (!arg || 
+					copy_from_user(&pc300patrntst,arg,sizeof(pc300patterntst_t)))
+						return -EINVAL;
+				if (pc300patrntst.patrntst_on == 2) {
+					if (chan->falc.prbs == 0) {
+						falc_pattern_test(card, ch, 1);
+					}
+					pc300patrntst.num_errors =
+						falc_pattern_test_error(card, ch);
+					if (!arg
+					    || copy_to_user(arg, &pc300patrntst,
+							    sizeof (pc300patterntst_t)))
+							return -EINVAL;
+				} else {
+					falc_pattern_test(card, ch, pc300patrntst.patrntst_on);
+				}
+				return 0;
+			}
+
+		case SIOCWANDEV:
+			switch (ifr->ifr_settings.type) {
+				case IF_GET_IFACE:
+				{
+					const size_t size = sizeof(sync_serial_settings);
+					ifr->ifr_settings.type = conf->media;
+					if (ifr->ifr_settings.size < size) {
+						/* data size wanted */
+						ifr->ifr_settings.size = size;
+						return -ENOBUFS;
+					}
+	
+					if (copy_to_user(settings->ifs_ifsu.sync,
+							 &conf->phys_settings, size)) {
+						return -EFAULT;
+					}
+					return 0;
+				}
+
+				case IF_IFACE_V35:
+				case IF_IFACE_V24:
+				case IF_IFACE_X21:
+				{
+					const size_t size = sizeof(sync_serial_settings);
+
+					if (!capable(CAP_NET_ADMIN)) {
+						return -EPERM;
+					}
+					/* incorrect data len? */
+					if (ifr->ifr_settings.size != size) {
+						return -ENOBUFS;
+					}
+
+					if (copy_from_user(&conf->phys_settings, 
+							   settings->ifs_ifsu.sync, size)) {
+						return -EFAULT;
+					}
+
+					if (conf->phys_settings.loopback) {
+						cpc_writeb(card->hw.scabase + M_REG(MD2, ch),
+							cpc_readb(card->hw.scabase + M_REG(MD2, ch)) | 
+							MD2_LOOP_MIR);
+					}
+					conf->media = ifr->ifr_settings.type;
+					return 0;
+				}
+
+				case IF_IFACE_T1:
+				case IF_IFACE_E1:
+				{
+					const size_t te_size = sizeof(te1_settings);
+					const size_t size = sizeof(sync_serial_settings);
+
+					if (!capable(CAP_NET_ADMIN)) {
+						return -EPERM;
+					}
+
+					/* incorrect data len? */
+					if (ifr->ifr_settings.size != te_size) {
+						return -ENOBUFS;
+					}
+
+					if (copy_from_user(&conf->phys_settings, 
+							   settings->ifs_ifsu.te1, size)) {
+						return -EFAULT;
+					}/* Ignoring HDLC slot_map for a while */
+					
+					if (conf->phys_settings.loopback) {
+						cpc_writeb(card->hw.scabase + M_REG(MD2, ch),
+							cpc_readb(card->hw.scabase + M_REG(MD2, ch)) | 
+							MD2_LOOP_MIR);
+					}
+					conf->media = ifr->ifr_settings.type;
+					return 0;
+				}
+				default:
+					return hdlc_ioctl(dev, ifr, cmd);
+			}
+
+		default:
+			return hdlc_ioctl(dev, ifr, cmd);
+	}
+}
+
+static struct net_device_stats *cpc_get_stats(struct net_device *dev)
+{
+	return hdlc_stats(dev);
+}
+
+static int clock_rate_calc(uclong rate, uclong clock, int *br_io)
+{
+	int br, tc;
+	int br_pwr, error;
+
+	if (rate == 0)
+		return (0);
+
+	for (br = 0, br_pwr = 1; br <= 9; br++, br_pwr <<= 1) {
+		if ((tc = clock / br_pwr / rate) <= 0xff) {
+			*br_io = br;
+			break;
+		}
+	}
+
+	if (tc <= 0xff) {
+		error = ((rate - (clock / br_pwr / rate)) / rate) * 1000;
+		/* Errors bigger than +/- 1% won't be tolerated */
+		if (error < -10 || error > 10)
+			return (-1);
+		else
+			return (tc);
+	} else {
+		return (-1);
+	}
+}
+
+int ch_config(pc300dev_t * d)
+{
+	pc300ch_t *chan = (pc300ch_t *) d->chan;
+	pc300chconf_t *conf = (pc300chconf_t *) & chan->conf;
+	pc300_t *card = (pc300_t *) chan->card;
+	void __iomem *scabase = card->hw.scabase;
+	void __iomem *plxbase = card->hw.plxbase;
+	int ch = chan->channel;
+	uclong clkrate = chan->conf.phys_settings.clock_rate;
+	uclong clktype = chan->conf.phys_settings.clock_type;
+	ucshort encoding = chan->conf.proto_settings.encoding;
+	ucshort parity = chan->conf.proto_settings.parity;   
+	int tmc, br;
+	ucchar md0, md2;
+    
+	/* Reset the channel */
+	cpc_writeb(scabase + M_REG(CMD, ch), CMD_CH_RST);
+
+	/* Configure the SCA registers */
+	switch (parity) {
+		case PARITY_NONE:
+			md0 = MD0_BIT_SYNC;
+			break;
+		case PARITY_CRC16_PR0:
+			md0 = MD0_CRC16_0|MD0_CRCC0|MD0_BIT_SYNC;
+			break;
+		case PARITY_CRC16_PR1:
+			md0 = MD0_CRC16_1|MD0_CRCC0|MD0_BIT_SYNC;
+			break;
+		case PARITY_CRC32_PR1_CCITT:
+			md0 = MD0_CRC32|MD0_CRCC0|MD0_BIT_SYNC;
+			break;
+		case PARITY_CRC16_PR1_CCITT:
+		default:
+			md0 = MD0_CRC_CCITT|MD0_CRCC0|MD0_BIT_SYNC;
+			break;
+	}
+	switch (encoding) {
+		case ENCODING_NRZI:
+			md2 = MD2_F_DUPLEX|MD2_ADPLL_X8|MD2_NRZI;
+			break;
+		case ENCODING_FM_MARK:	/* FM1 */
+			md2 = MD2_F_DUPLEX|MD2_ADPLL_X8|MD2_FM|MD2_FM1;
+			break;
+		case ENCODING_FM_SPACE:	/* FM0 */
+			md2 = MD2_F_DUPLEX|MD2_ADPLL_X8|MD2_FM|MD2_FM0;
+			break;
+		case ENCODING_MANCHESTER: /* It's not working... */
+			md2 = MD2_F_DUPLEX|MD2_ADPLL_X8|MD2_FM|MD2_MANCH;
+			break;
+		case ENCODING_NRZ:
+		default:
+			md2 = MD2_F_DUPLEX|MD2_ADPLL_X8|MD2_NRZ;
+			break;
+	}
+	cpc_writeb(scabase + M_REG(MD0, ch), md0);
+	cpc_writeb(scabase + M_REG(MD1, ch), 0);
+	cpc_writeb(scabase + M_REG(MD2, ch), md2);
+ 	cpc_writeb(scabase + M_REG(IDL, ch), 0x7e);
+	cpc_writeb(scabase + M_REG(CTL, ch), CTL_URSKP | CTL_IDLC);
+
+	/* Configure HW media */
+	switch (card->hw.type) {
+		case PC300_RSV:
+			if (conf->media == IF_IFACE_V35) {
+				cpc_writel((plxbase + card->hw.gpioc_reg),
+					   cpc_readl(plxbase + card->hw.gpioc_reg) | PC300_CHMEDIA_MASK(ch));
+			} else {
+				cpc_writel((plxbase + card->hw.gpioc_reg),
+					   cpc_readl(plxbase + card->hw.gpioc_reg) & ~PC300_CHMEDIA_MASK(ch));
+			}
+			break;
+
+		case PC300_X21:
+			break;
+
+		case PC300_TE:
+			te_config(card, ch);
+			break;
+	}
+
+	switch (card->hw.type) {
+		case PC300_RSV:
+		case PC300_X21:
+			if (clktype == CLOCK_INT || clktype == CLOCK_TXINT) {
+				/* Calculate the clkrate parameters */
+				tmc = clock_rate_calc(clkrate, card->hw.clock, &br);
+				cpc_writeb(scabase + M_REG(TMCT, ch), tmc);
+				cpc_writeb(scabase + M_REG(TXS, ch),
+					   (TXS_DTRXC | TXS_IBRG | br));
+				if (clktype == CLOCK_INT) {
+					cpc_writeb(scabase + M_REG(TMCR, ch), tmc);
+					cpc_writeb(scabase + M_REG(RXS, ch), 
+						   (RXS_IBRG | br));
+				} else {
+					cpc_writeb(scabase + M_REG(TMCR, ch), 1);
+					cpc_writeb(scabase + M_REG(RXS, ch), 0);
+				}
+	    			if (card->hw.type == PC300_X21) {
+					cpc_writeb(scabase + M_REG(GPO, ch), 1);
+					cpc_writeb(scabase + M_REG(EXS, ch), EXS_TES1 | EXS_RES1);
+				} else {
+					cpc_writeb(scabase + M_REG(EXS, ch), EXS_TES1);
+				}
+			} else {
+				cpc_writeb(scabase + M_REG(TMCT, ch), 1);
+				if (clktype == CLOCK_EXT) {
+					cpc_writeb(scabase + M_REG(TXS, ch), 
+						   TXS_DTRXC);
+				} else {
+					cpc_writeb(scabase + M_REG(TXS, ch), 
+						   TXS_DTRXC|TXS_RCLK);
+				}
+	    			cpc_writeb(scabase + M_REG(TMCR, ch), 1);
+				cpc_writeb(scabase + M_REG(RXS, ch), 0);
+				if (card->hw.type == PC300_X21) {
+					cpc_writeb(scabase + M_REG(GPO, ch), 0);
+					cpc_writeb(scabase + M_REG(EXS, ch), EXS_TES1 | EXS_RES1);
+				} else {
+					cpc_writeb(scabase + M_REG(EXS, ch), EXS_TES1);
+				}
+			}
+			break;
+
+		case PC300_TE:
+			/* SCA always receives clock from the FALC chip */
+			cpc_writeb(scabase + M_REG(TMCT, ch), 1);
+			cpc_writeb(scabase + M_REG(TXS, ch), 0);
+			cpc_writeb(scabase + M_REG(TMCR, ch), 1);
+			cpc_writeb(scabase + M_REG(RXS, ch), 0);
+			cpc_writeb(scabase + M_REG(EXS, ch), 0);
+			break;
+	}
+
+	/* Enable Interrupts */
+	cpc_writel(scabase + IER0,
+		   cpc_readl(scabase + IER0) |
+		   IR0_M(IR0_RXINTA, ch) |
+		   IR0_DRX(IR0_EFT | IR0_DMIA | IR0_DMIB, ch) |
+		   IR0_DTX(IR0_EFT | IR0_DMIA | IR0_DMIB, ch));
+	cpc_writeb(scabase + M_REG(IE0, ch),
+		   cpc_readl(scabase + M_REG(IE0, ch)) | IE0_RXINTA);
+	cpc_writeb(scabase + M_REG(IE1, ch),
+		   cpc_readl(scabase + M_REG(IE1, ch)) | IE1_CDCD);
+
+	return 0;
+}
+
+int rx_config(pc300dev_t * d)
+{
+	pc300ch_t *chan = (pc300ch_t *) d->chan;
+	pc300_t *card = (pc300_t *) chan->card;
+	void __iomem *scabase = card->hw.scabase;
+	int ch = chan->channel;
+
+	cpc_writeb(scabase + DSR_RX(ch), 0);
+
+	/* General RX settings */
+	cpc_writeb(scabase + M_REG(RRC, ch), 0);
+	cpc_writeb(scabase + M_REG(RNR, ch), 16);
+
+	/* Enable reception */
+	cpc_writeb(scabase + M_REG(CMD, ch), CMD_RX_CRC_INIT);
+	cpc_writeb(scabase + M_REG(CMD, ch), CMD_RX_ENA);
+
+	/* Initialize DMA stuff */
+	chan->rx_first_bd = 0;
+	chan->rx_last_bd = N_DMA_RX_BUF - 1;
+	rx_dma_buf_init(card, ch);
+	cpc_writeb(scabase + DCR_RX(ch), DCR_FCT_CLR);
+	cpc_writeb(scabase + DMR_RX(ch), (DMR_TMOD | DMR_NF));
+	cpc_writeb(scabase + DIR_RX(ch), (DIR_EOM | DIR_BOF));
+
+	/* Start DMA */
+	rx_dma_start(card, ch);
+
+	return 0;
+}
+
+int tx_config(pc300dev_t * d)
+{
+	pc300ch_t *chan = (pc300ch_t *) d->chan;
+	pc300_t *card = (pc300_t *) chan->card;
+	void __iomem *scabase = card->hw.scabase;
+	int ch = chan->channel;
+
+	cpc_writeb(scabase + DSR_TX(ch), 0);
+
+	/* General TX settings */
+	cpc_writeb(scabase + M_REG(TRC0, ch), 0);
+	cpc_writeb(scabase + M_REG(TFS, ch), 32);
+	cpc_writeb(scabase + M_REG(TNR0, ch), 20);
+	cpc_writeb(scabase + M_REG(TNR1, ch), 48);
+	cpc_writeb(scabase + M_REG(TCR, ch), 8);
+
+	/* Enable transmission */
+	cpc_writeb(scabase + M_REG(CMD, ch), CMD_TX_CRC_INIT);
+
+	/* Initialize DMA stuff */
+	chan->tx_first_bd = 0;
+	chan->tx_next_bd = 0;
+	tx_dma_buf_init(card, ch);
+	cpc_writeb(scabase + DCR_TX(ch), DCR_FCT_CLR);
+	cpc_writeb(scabase + DMR_TX(ch), (DMR_TMOD | DMR_NF));
+	cpc_writeb(scabase + DIR_TX(ch), (DIR_EOM | DIR_BOF | DIR_UDRF));
+	cpc_writel(scabase + DTX_REG(CDAL, ch), TX_BD_ADDR(ch, chan->tx_first_bd));
+	cpc_writel(scabase + DTX_REG(EDAL, ch), TX_BD_ADDR(ch, chan->tx_next_bd));
+
+	return 0;
+}
+
+static int cpc_attach(struct net_device *dev, unsigned short encoding,
+		      unsigned short parity)
+{
+	pc300dev_t *d = (pc300dev_t *)dev->priv;
+	pc300ch_t *chan = (pc300ch_t *)d->chan;
+	pc300_t *card = (pc300_t *)chan->card;
+	pc300chconf_t *conf = (pc300chconf_t *)&chan->conf;
+
+	if (card->hw.type == PC300_TE) {
+		if (encoding != ENCODING_NRZ && encoding != ENCODING_NRZI) {
+			return -EINVAL;
+		}
+	} else {
+		if (encoding != ENCODING_NRZ && encoding != ENCODING_NRZI &&
+		    encoding != ENCODING_FM_MARK && encoding != ENCODING_FM_SPACE) {
+			/* Driver doesn't support ENCODING_MANCHESTER yet */
+			return -EINVAL;
+		}
+	}
+
+	if (parity != PARITY_NONE && parity != PARITY_CRC16_PR0 &&
+	    parity != PARITY_CRC16_PR1 && parity != PARITY_CRC32_PR1_CCITT &&
+	    parity != PARITY_CRC16_PR1_CCITT) {
+		return -EINVAL;
+	}
+
+	conf->proto_settings.encoding = encoding;
+	conf->proto_settings.parity = parity;
+	return 0;
+}
+
+void cpc_opench(pc300dev_t * d)
+{
+	pc300ch_t *chan = (pc300ch_t *) d->chan;
+	pc300_t *card = (pc300_t *) chan->card;
+	int ch = chan->channel;
+	void __iomem *scabase = card->hw.scabase;
+
+	ch_config(d);
+
+	rx_config(d);
+
+	tx_config(d);
+
+	/* Assert RTS and DTR */
+	cpc_writeb(scabase + M_REG(CTL, ch),
+		   cpc_readb(scabase + M_REG(CTL, ch)) & ~(CTL_RTS | CTL_DTR));
+}
+
+void cpc_closech(pc300dev_t * d)
+{
+	pc300ch_t *chan = (pc300ch_t *) d->chan;
+	pc300_t *card = (pc300_t *) chan->card;
+	falc_t *pfalc = (falc_t *) & chan->falc;
+	int ch = chan->channel;
+
+	cpc_writeb(card->hw.scabase + M_REG(CMD, ch), CMD_CH_RST);
+	rx_dma_stop(card, ch);
+	tx_dma_stop(card, ch);
+
+	if (card->hw.type == PC300_TE) {
+		memset(pfalc, 0, sizeof(falc_t));
+		cpc_writeb(card->hw.falcbase + card->hw.cpld_reg2,
+			   cpc_readb(card->hw.falcbase + card->hw.cpld_reg2) &
+			   ~((CPLD_REG2_FALC_TX_CLK | CPLD_REG2_FALC_RX_CLK |
+			      CPLD_REG2_FALC_LED2) << (2 * ch)));
+		/* Reset the FALC chip */
+		cpc_writeb(card->hw.falcbase + card->hw.cpld_reg1,
+			   cpc_readb(card->hw.falcbase + card->hw.cpld_reg1) |
+			   (CPLD_REG1_FALC_RESET << (2 * ch)));
+		udelay(10000);
+		cpc_writeb(card->hw.falcbase + card->hw.cpld_reg1,
+			   cpc_readb(card->hw.falcbase + card->hw.cpld_reg1) &
+			   ~(CPLD_REG1_FALC_RESET << (2 * ch)));
+	}
+}
+
+int cpc_open(struct net_device *dev)
+{
+	hdlc_device *hdlc = dev_to_hdlc(dev);
+	pc300dev_t *d = (pc300dev_t *) dev->priv;
+	struct ifreq ifr;
+	int result;
+
+#ifdef	PC300_DEBUG_OTHER
+	printk("pc300: cpc_open");
+#endif
+
+	if (hdlc->proto.id == IF_PROTO_PPP) {
+		d->if_ptr = &hdlc->state.ppp.pppdev;
+	}
+
+	result = hdlc_open(dev);
+	if (hdlc->proto.id == IF_PROTO_PPP) {
+		dev->priv = d;
+	}
+	if (result) {
+		return result;
+	}
+
+	sprintf(ifr.ifr_name, "%s", dev->name);
+	cpc_opench(d);
+	netif_start_queue(dev);
+	return 0;
+}
+
+int cpc_close(struct net_device *dev)
+{
+	hdlc_device *hdlc = dev_to_hdlc(dev);
+	pc300dev_t *d = (pc300dev_t *) dev->priv;
+	pc300ch_t *chan = (pc300ch_t *) d->chan;
+	pc300_t *card = (pc300_t *) chan->card;
+	unsigned long flags;
+
+#ifdef	PC300_DEBUG_OTHER
+	printk("pc300: cpc_close");
+#endif
+
+	netif_stop_queue(dev);
+
+	CPC_LOCK(card, flags);
+	cpc_closech(d);
+	CPC_UNLOCK(card, flags);
+
+	hdlc_close(dev);
+	if (hdlc->proto.id == IF_PROTO_PPP) {
+		d->if_ptr = NULL;
+	}
+#ifdef CONFIG_PC300_MLPPP
+	if (chan->conf.proto == PC300_PROTO_MLPPP) {
+		cpc_tty_unregister_service(d);
+		chan->conf.proto = 0xffff;
+	}
+#endif
+
+	return 0;
+}
+
+static uclong detect_ram(pc300_t * card)
+{
+	uclong i;
+	ucchar data;
+	void __iomem *rambase = card->hw.rambase;
+
+	card->hw.ramsize = PC300_RAMSIZE;
+	/* Let's find out how much RAM is present on this board */
+	for (i = 0; i < card->hw.ramsize; i++) {
+		data = (ucchar) (i & 0xff);
+		cpc_writeb(rambase + i, data);
+		if (cpc_readb(rambase + i) != data) {
+			break;
+		}
+	}
+	return (i);
+}
+
+static void plx_init(pc300_t * card)
+{
+	struct RUNTIME_9050 __iomem *plx_ctl = card->hw.plxbase;
+
+	/* Reset PLX */
+	cpc_writel(&plx_ctl->init_ctrl,
+		   cpc_readl(&plx_ctl->init_ctrl) | 0x40000000);
+	udelay(10000L);
+	cpc_writel(&plx_ctl->init_ctrl,
+		   cpc_readl(&plx_ctl->init_ctrl) & ~0x40000000);
+
+	/* Reload Config. Registers from EEPROM */
+	cpc_writel(&plx_ctl->init_ctrl,
+		   cpc_readl(&plx_ctl->init_ctrl) | 0x20000000);
+	udelay(10000L);
+	cpc_writel(&plx_ctl->init_ctrl,
+		   cpc_readl(&plx_ctl->init_ctrl) & ~0x20000000);
+
+}
+
+static inline void show_version(void)
+{
+	char *rcsvers, *rcsdate, *tmp;
+
+	rcsvers = strchr(rcsid, ' ');
+	rcsvers++;
+	tmp = strchr(rcsvers, ' ');
+	*tmp++ = '\0';
+	rcsdate = strchr(tmp, ' ');
+	rcsdate++;
+	tmp = strrchr(rcsdate, ' ');
+	*tmp = '\0';
+	printk(KERN_INFO "Cyclades-PC300 driver %s %s (built %s %s)\n", 
+		rcsvers, rcsdate, __DATE__, __TIME__);
+}				/* show_version */
+
+static void cpc_init_card(pc300_t * card)
+{
+	int i, devcount = 0;
+	static int board_nbr = 1;
+
+	/* Enable interrupts on the PCI bridge */
+	plx_init(card);
+	cpc_writew(card->hw.plxbase + card->hw.intctl_reg,
+		   cpc_readw(card->hw.plxbase + card->hw.intctl_reg) | 0x0040);
+
+#ifdef USE_PCI_CLOCK
+	/* Set board clock to PCI clock */
+	cpc_writel(card->hw.plxbase + card->hw.gpioc_reg,
+		   cpc_readl(card->hw.plxbase + card->hw.gpioc_reg) | 0x00000004UL);
+	card->hw.clock = PC300_PCI_CLOCK;
+#else
+	/* Set board clock to internal oscillator clock */
+	cpc_writel(card->hw.plxbase + card->hw.gpioc_reg,
+		   cpc_readl(card->hw.plxbase + card->hw.gpioc_reg) & ~0x00000004UL);
+	card->hw.clock = PC300_OSC_CLOCK;
+#endif
+
+	/* Detect actual on-board RAM size */
+	card->hw.ramsize = detect_ram(card);
+
+	/* Set Global SCA-II registers */
+	cpc_writeb(card->hw.scabase + PCR, PCR_PR2);
+	cpc_writeb(card->hw.scabase + BTCR, 0x10);
+	cpc_writeb(card->hw.scabase + WCRL, 0);
+	cpc_writeb(card->hw.scabase + DMER, 0x80);
+
+	if (card->hw.type == PC300_TE) {
+		ucchar reg1;
+
+		/* Check CPLD version */
+		reg1 = cpc_readb(card->hw.falcbase + CPLD_REG1);
+		cpc_writeb(card->hw.falcbase + CPLD_REG1, (reg1 + 0x5a));
+		if (cpc_readb(card->hw.falcbase + CPLD_REG1) == reg1) {
+			/* New CPLD */
+			card->hw.cpld_id = cpc_readb(card->hw.falcbase + CPLD_ID_REG);
+			card->hw.cpld_reg1 = CPLD_V2_REG1;
+			card->hw.cpld_reg2 = CPLD_V2_REG2;
+		} else {
+			/* old CPLD */
+			card->hw.cpld_id = 0;
+			card->hw.cpld_reg1 = CPLD_REG1;
+			card->hw.cpld_reg2 = CPLD_REG2;
+			cpc_writeb(card->hw.falcbase + CPLD_REG1, reg1);
+		}
+
+		/* Enable the board's global clock */
+		cpc_writeb(card->hw.falcbase + card->hw.cpld_reg1,
+			   cpc_readb(card->hw.falcbase + card->hw.cpld_reg1) |
+			   CPLD_REG1_GLOBAL_CLK);
+
+	}
+
+	for (i = 0; i < card->hw.nchan; i++) {
+		pc300ch_t *chan = &card->chan[i];
+		pc300dev_t *d = &chan->d;
+		hdlc_device *hdlc;
+		struct net_device *dev;
+
+		chan->card = card;
+		chan->channel = i;
+		chan->conf.phys_settings.clock_rate = 0;
+		chan->conf.phys_settings.clock_type = CLOCK_EXT;
+		chan->conf.proto_settings.encoding = ENCODING_NRZ;
+		chan->conf.proto_settings.parity = PARITY_CRC16_PR1_CCITT;
+		switch (card->hw.type) {
+			case PC300_TE:
+				chan->conf.media = IF_IFACE_T1;
+				chan->conf.lcode = PC300_LC_B8ZS;
+				chan->conf.fr_mode = PC300_FR_ESF;
+				chan->conf.lbo = PC300_LBO_0_DB;
+				chan->conf.rx_sens = PC300_RX_SENS_SH;
+				chan->conf.tslot_bitmap = 0xffffffffUL;
+				break;
+
+			case PC300_X21:
+				chan->conf.media = IF_IFACE_X21;
+				break;
+
+			case PC300_RSV:
+			default:
+				chan->conf.media = IF_IFACE_V35;
+				break;
+		}
+		chan->conf.proto = IF_PROTO_PPP;
+		chan->tx_first_bd = 0;
+		chan->tx_next_bd = 0;
+		chan->rx_first_bd = 0;
+		chan->rx_last_bd = N_DMA_RX_BUF - 1;
+		chan->nfree_tx_bd = N_DMA_TX_BUF;
+
+		d->chan = chan;
+		d->tx_skb = NULL;
+		d->trace_on = 0;
+		d->line_on = 0;
+		d->line_off = 0;
+
+		dev = alloc_hdlcdev(NULL);
+		if (dev == NULL)
+			continue;
+
+		hdlc = dev_to_hdlc(dev);
+		hdlc->xmit = cpc_queue_xmit;
+		hdlc->attach = cpc_attach;
+		d->dev = dev;
+		dev->mem_start = card->hw.ramphys;
+		dev->mem_end = card->hw.ramphys + card->hw.ramsize - 1;
+		dev->irq = card->hw.irq;
+		dev->init = NULL;
+		dev->tx_queue_len = PC300_TX_QUEUE_LEN;
+		dev->mtu = PC300_DEF_MTU;
+
+		dev->open = cpc_open;
+		dev->stop = cpc_close;
+		dev->tx_timeout = cpc_tx_timeout;
+		dev->watchdog_timeo = PC300_TX_TIMEOUT;
+		dev->get_stats = cpc_get_stats;
+		dev->set_multicast_list = NULL;
+		dev->set_mac_address = NULL;
+		dev->change_mtu = cpc_change_mtu;
+		dev->do_ioctl = cpc_ioctl;
+
+		if (register_hdlc_device(dev) == 0) {
+			dev->priv = d;	/* We need 'priv', hdlc doesn't */
+			printk("%s: Cyclades-PC300/", dev->name);
+			switch (card->hw.type) {
+				case PC300_TE:
+					if (card->hw.bus == PC300_PMC) {
+						printk("TE-M");
+					} else {
+						printk("TE  ");
+					}
+					break;
+
+				case PC300_X21:
+					printk("X21 ");
+					break;
+
+				case PC300_RSV:
+				default:
+					printk("RSV ");
+					break;
+			}
+			printk (" #%d, %dKB of RAM at 0x%08x, IRQ%d, channel %d.\n",
+				 board_nbr, card->hw.ramsize / 1024,
+				 card->hw.ramphys, card->hw.irq, i + 1);
+			devcount++;
+		} else {
+			printk ("Dev%d on card(0x%08x): unable to allocate i/f name.\n",
+				 i + 1, card->hw.ramphys);
+			free_netdev(dev);
+			continue;
+		}
+	}
+	spin_lock_init(&card->card_lock);
+
+	board_nbr++;
+}
+
+static int __devinit
+cpc_init_one(struct pci_dev *pdev, const struct pci_device_id *ent)
+{
+	static int first_time = 1;
+	ucchar cpc_rev_id;
+	int err = 0, eeprom_outdated = 0;
+	ucshort device_id;
+	pc300_t *card;
+
+	if (first_time) {
+		first_time = 0;
+		show_version();
+#ifdef CONFIG_PC300_MLPPP
+		cpc_tty_reset_var();
+#endif
+	}
+
+	card = (pc300_t *) kmalloc(sizeof(pc300_t), GFP_KERNEL);
+	if (card == NULL) {
+		printk("PC300 found at RAM 0x%08lx, "
+		       "but could not allocate card structure.\n",
+		       pci_resource_start(pdev, 3));
+		return -ENOMEM;
+	}
+	memset(card, 0, sizeof(pc300_t));
+
+	/* read PCI configuration area */
+	device_id = ent->device;
+	card->hw.irq = pdev->irq;
+	card->hw.iophys = pci_resource_start(pdev, 1);
+	card->hw.iosize = pci_resource_len(pdev, 1);
+	card->hw.scaphys = pci_resource_start(pdev, 2);
+	card->hw.scasize = pci_resource_len(pdev, 2);
+	card->hw.ramphys = pci_resource_start(pdev, 3);
+	card->hw.alloc_ramsize = pci_resource_len(pdev, 3);
+	card->hw.falcphys = pci_resource_start(pdev, 4);
+	card->hw.falcsize = pci_resource_len(pdev, 4);
+	card->hw.plxphys = pci_resource_start(pdev, 5);
+	card->hw.plxsize = pci_resource_len(pdev, 5);
+	pci_read_config_byte(pdev, PCI_REVISION_ID, &cpc_rev_id);
+
+	switch (device_id) {
+		case PCI_DEVICE_ID_PC300_RX_1:
+		case PCI_DEVICE_ID_PC300_TE_1:
+		case PCI_DEVICE_ID_PC300_TE_M_1:
+			card->hw.nchan = 1;
+			break;
+
+		case PCI_DEVICE_ID_PC300_RX_2:
+		case PCI_DEVICE_ID_PC300_TE_2:
+		case PCI_DEVICE_ID_PC300_TE_M_2:
+		default:
+			card->hw.nchan = PC300_MAXCHAN;
+			break;
+	}
+#ifdef PC300_DEBUG_PCI
+	printk("cpc (bus=0x0%x,pci_id=0x%x,", pdev->bus->number, pdev->devfn);
+	printk("rev_id=%d) IRQ%d\n", cpc_rev_id, card->hw.irq);
+	printk("cpc:found  ramaddr=0x%08lx plxaddr=0x%08lx "
+	       "ctladdr=0x%08lx falcaddr=0x%08lx\n",
+	       card->hw.ramphys, card->hw.plxphys, card->hw.scaphys,
+	       card->hw.falcphys);
+#endif
+	/* Although we don't use this I/O region, we should
+	 * request it from the kernel anyway, to avoid problems
+	 * with other drivers accessing it. */
+	if (!request_region(card->hw.iophys, card->hw.iosize, "PLX Registers")) {
+		/* In case we can't allocate it, warn user */
+		printk("WARNING: couldn't allocate I/O region for PC300 board "
+		       "at 0x%08x!\n", card->hw.ramphys);
+	}
+
+	if (card->hw.plxphys) {
+		pci_write_config_dword(pdev, PCI_BASE_ADDRESS_0, card->hw.plxphys);
+	} else {
+		eeprom_outdated = 1;
+		card->hw.plxphys = pci_resource_start(pdev, 0);
+		card->hw.plxsize = pci_resource_len(pdev, 0);
+	}
+
+	if (!request_mem_region(card->hw.plxphys, card->hw.plxsize,
+				"PLX Registers")) {
+		printk("PC300 found at RAM 0x%08x, "
+		       "but could not allocate PLX mem region.\n",
+		       card->hw.ramphys);
+		err = -ENODEV;
+		goto err_release_io;
+	}
+	if (!request_mem_region(card->hw.ramphys, card->hw.alloc_ramsize,
+				"On-board RAM")) {
+		printk("PC300 found at RAM 0x%08x, "
+		       "but could not allocate RAM mem region.\n",
+		       card->hw.ramphys);
+		err = -ENODEV;
+		goto err_release_plx;
+	}
+	if (!request_mem_region(card->hw.scaphys, card->hw.scasize,
+				"SCA-II Registers")) {
+		printk("PC300 found at RAM 0x%08x, "
+		       "but could not allocate SCA mem region.\n",
+		       card->hw.ramphys);
+		err = -ENODEV;
+		goto err_release_ram;
+	}
+
+	if ((err = pci_enable_device(pdev)) != 0)
+		goto err_release_sca;
+
+	card->hw.plxbase = ioremap(card->hw.plxphys, card->hw.plxsize);
+	card->hw.rambase = ioremap(card->hw.ramphys, card->hw.alloc_ramsize);
+	card->hw.scabase = ioremap(card->hw.scaphys, card->hw.scasize);
+	switch (device_id) {
+		case PCI_DEVICE_ID_PC300_TE_1:
+		case PCI_DEVICE_ID_PC300_TE_2:
+		case PCI_DEVICE_ID_PC300_TE_M_1:
+		case PCI_DEVICE_ID_PC300_TE_M_2:
+			request_mem_region(card->hw.falcphys, card->hw.falcsize,
+					   "FALC Registers");
+			card->hw.falcbase = ioremap(card->hw.falcphys, card->hw.falcsize);
+			break;
+
+		case PCI_DEVICE_ID_PC300_RX_1:
+		case PCI_DEVICE_ID_PC300_RX_2:
+		default:
+			card->hw.falcbase = NULL;
+			break;
+	}
+
+#ifdef PC300_DEBUG_PCI
+	printk("cpc: relocate ramaddr=0x%08lx plxaddr=0x%08lx "
+	       "ctladdr=0x%08lx falcaddr=0x%08lx\n",
+	       card->hw.rambase, card->hw.plxbase, card->hw.scabase,
+	       card->hw.falcbase);
+#endif
+
+	/* Set PCI drv pointer to the card structure */
+	pci_set_drvdata(pdev, card);
+
+	/* Set board type */
+	switch (device_id) {
+		case PCI_DEVICE_ID_PC300_TE_1:
+		case PCI_DEVICE_ID_PC300_TE_2:
+		case PCI_DEVICE_ID_PC300_TE_M_1:
+		case PCI_DEVICE_ID_PC300_TE_M_2:
+			card->hw.type = PC300_TE;
+
+			if ((device_id == PCI_DEVICE_ID_PC300_TE_M_1) ||
+			    (device_id == PCI_DEVICE_ID_PC300_TE_M_2)) {
+				card->hw.bus = PC300_PMC;
+				/* Set PLX register offsets */
+				card->hw.gpioc_reg = 0x54;
+				card->hw.intctl_reg = 0x4c;
+			} else {
+				card->hw.bus = PC300_PCI;
+				/* Set PLX register offsets */
+				card->hw.gpioc_reg = 0x50;
+				card->hw.intctl_reg = 0x4c;
+			}
+			break;
+
+		case PCI_DEVICE_ID_PC300_RX_1:
+		case PCI_DEVICE_ID_PC300_RX_2:
+		default:
+			card->hw.bus = PC300_PCI;
+			/* Set PLX register offsets */
+			card->hw.gpioc_reg = 0x50;
+			card->hw.intctl_reg = 0x4c;
+
+			if ((cpc_readl(card->hw.plxbase + card->hw.gpioc_reg) & PC300_CTYPE_MASK)) {
+				card->hw.type = PC300_X21;
+			} else {
+				card->hw.type = PC300_RSV;
+			}
+			break;
+	}
+
+	/* Allocate IRQ */
+	if (request_irq(card->hw.irq, cpc_intr, SA_SHIRQ, "Cyclades-PC300", card)) {
+		printk ("PC300 found at RAM 0x%08x, but could not allocate IRQ%d.\n",
+			 card->hw.ramphys, card->hw.irq);
+		goto err_io_unmap;
+	}
+
+	cpc_init_card(card);
+
+	if (eeprom_outdated)
+		printk("WARNING: PC300 with outdated EEPROM.\n");
+	return 0;
+
+err_io_unmap:
+	iounmap(card->hw.plxbase);
+	iounmap(card->hw.scabase);
+	iounmap(card->hw.rambase);
+	if (card->hw.type == PC300_TE) {
+		iounmap(card->hw.falcbase);
+		release_mem_region(card->hw.falcphys, card->hw.falcsize);
+	}
+err_release_sca:
+	release_mem_region(card->hw.scaphys, card->hw.scasize);
+err_release_ram:
+	release_mem_region(card->hw.ramphys, card->hw.alloc_ramsize);
+err_release_plx:
+	release_mem_region(card->hw.plxphys, card->hw.plxsize);
+err_release_io:
+	release_region(card->hw.iophys, card->hw.iosize);
+	kfree(card);
+	return -ENODEV;
+}
+
+static void __devexit cpc_remove_one(struct pci_dev *pdev)
+{
+	pc300_t *card = pci_get_drvdata(pdev);
+
+	if (card->hw.rambase != 0) {
+		int i;
+
+		/* Disable interrupts on the PCI bridge */
+		cpc_writew(card->hw.plxbase + card->hw.intctl_reg,
+			   cpc_readw(card->hw.plxbase + card->hw.intctl_reg) & ~(0x0040));
+
+		for (i = 0; i < card->hw.nchan; i++) {
+			unregister_hdlc_device(card->chan[i].d.dev);
+		}
+		iounmap(card->hw.plxbase);
+		iounmap(card->hw.scabase);
+		iounmap(card->hw.rambase);
+		release_mem_region(card->hw.plxphys, card->hw.plxsize);
+		release_mem_region(card->hw.ramphys, card->hw.alloc_ramsize);
+		release_mem_region(card->hw.scaphys, card->hw.scasize);
+		release_region(card->hw.iophys, card->hw.iosize);
+		if (card->hw.type == PC300_TE) {
+			iounmap(card->hw.falcbase);
+			release_mem_region(card->hw.falcphys, card->hw.falcsize);
+		}
+		for (i = 0; i < card->hw.nchan; i++)
+			if (card->chan[i].d.dev)
+				free_netdev(card->chan[i].d.dev);
+		if (card->hw.irq)
+			free_irq(card->hw.irq, card);
+		kfree(card);
+	}
+}
+
+static struct pci_driver cpc_driver = {
+	.name           = "pc300",
+	.id_table       = cpc_pci_dev_id,
+	.probe          = cpc_init_one,
+	.remove         = __devexit_p(cpc_remove_one),
+};
+
+static int __init cpc_init(void)
+{
+	return pci_module_init(&cpc_driver);
+}
+
+static void __exit cpc_cleanup_module(void)
+{
+	pci_unregister_driver(&cpc_driver);
+}
+
+module_init(cpc_init);
+module_exit(cpc_cleanup_module);
+
+MODULE_DESCRIPTION("Cyclades-PC300 cards driver");
+MODULE_AUTHOR(  "Author: Ivan Passos <ivan@cyclades.com>\r\n"
+                "Maintainer: PC300 Maintainer <pc300@cyclades.com");
+MODULE_LICENSE("GPL");
+
diff --git a/drivers/net/wan/pc300_tty.c b/drivers/net/wan/pc300_tty.c
new file mode 100644
index 0000000..29f84ad
--- /dev/null
+++ b/drivers/net/wan/pc300_tty.c
@@ -0,0 +1,1095 @@
+/*
+ * pc300_tty.c	Cyclades-PC300(tm) TTY Driver.
+ *
+ * Author:	Regina Kodato <reginak@cyclades.com>
+ *
+ * Copyright:	(c) 1999-2002 Cyclades Corp.
+ *
+ *	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.
+ *   
+ *  $Log: pc300_tty.c,v $
+ *  Revision 3.7  2002/03/07 14:17:09  henrique
+ *  License data fixed
+ *
+ *  Revision 3.6  2001/12/10 12:29:42  regina
+ *  Fix the MLPPP bug
+ *
+ *  Revision 3.5  2001/10/31 11:20:05  regina
+ *  automatic pppd starts
+ *
+ *  Revision 3.4  2001/08/06 12:01:51  regina
+ *  problem in DSR_DE bit
+ *
+ *  Revision 3.3  2001/07/26 22:58:41  regina
+ *  update EDA value
+ *
+ *  Revision 3.2  2001/07/12 13:11:20  regina
+ *  bug fix - DCD-OFF in pc300 tty driver
+ *
+ *	DMA transmission bug fix
+ *  
+ *  Revision 3.1  2001/06/22 13:13:02  regina
+ *  MLPPP implementation
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/pci.h>
+#include <linux/errno.h>
+#include <linux/string.h>
+#include <linux/init.h>
+#include <linux/netdevice.h>
+#include <linux/spinlock.h>
+#include <linux/slab.h>
+#include <linux/if.h>
+#include <linux/skbuff.h>
+/* TTY includes */
+#include <linux/tty.h>
+#include <linux/tty_flip.h>
+#include <linux/serial.h>
+
+#include <asm/io.h>
+#include <asm/uaccess.h>
+
+#include "pc300.h"
+
+/* defines and macros */
+/* TTY Global definitions */
+#define	CPC_TTY_NPORTS	8	/* maximum number of the sync tty connections */
+#define	CPC_TTY_MAJOR	CYCLADES_MAJOR	
+#define CPC_TTY_MINOR_START	240	/* minor of the first PC300 interface */
+
+#define CPC_TTY_MAX_MTU	2000	
+
+/* tty interface state */
+#define	CPC_TTY_ST_IDLE	0
+#define CPC_TTY_ST_INIT	1	/* configured with MLPPP and up */
+#define CPC_TTY_ST_OPEN	2	/* opened by application */
+
+#define	CPC_TTY_LOCK(card,flags)\
+	do {\
+		spin_lock_irqsave(&card->card_lock, flags);	\
+	} while (0)
+
+#define CPC_TTY_UNLOCK(card,flags)	\
+	do {\
+		spin_unlock_irqrestore(&card->card_lock, flags);	\
+	} while (0)
+
+//#define	CPC_TTY_DBG(format,a...)	printk(format,##a)
+#define	CPC_TTY_DBG(format,a...)
+
+/* data structures */
+typedef struct _st_cpc_rx_buf {
+	struct _st_cpc_rx_buf	*next;
+	int		size;
+	unsigned char	data[1];
+} st_cpc_rx_buf;
+
+struct st_cpc_rx_list {
+	st_cpc_rx_buf	*first;
+	st_cpc_rx_buf	*last;
+};
+
+typedef	struct _st_cpc_tty_area {
+	int		state;		/* state of the TTY interface */
+	int		num_open;	
+	unsigned int 	tty_minor;	/* minor this interface */
+	volatile struct st_cpc_rx_list buf_rx;	/* ptr. to reception buffer */
+	unsigned char*	buf_tx;		/* ptr. to transmission buffer */
+	pc300dev_t*	pc300dev;	/* ptr. to info struct in PC300 driver */
+	unsigned char	name[20];	/* interf. name + "-tty" */
+	struct tty_struct *tty;		
+	struct work_struct tty_tx_work; /* tx work - tx interrupt */
+	struct work_struct tty_rx_work; /* rx work - rx interrupt */
+	} st_cpc_tty_area;
+
+/* TTY data structures */
+static struct tty_driver serial_drv;
+
+/* local variables */
+st_cpc_tty_area	cpc_tty_area[CPC_TTY_NPORTS];
+
+int cpc_tty_cnt=0;	/* number of intrfaces configured with MLPPP */
+int cpc_tty_unreg_flag = 0;
+
+/* TTY functions prototype */
+static int cpc_tty_open(struct tty_struct *tty, struct file *flip);
+static void cpc_tty_close(struct tty_struct *tty, struct file *flip);
+static int cpc_tty_write(struct tty_struct *tty, const unsigned char *buf, int count);
+static int cpc_tty_write_room(struct tty_struct *tty);
+static int cpc_tty_chars_in_buffer(struct tty_struct *tty);
+static void cpc_tty_flush_buffer(struct tty_struct *tty);
+static void cpc_tty_hangup(struct tty_struct *tty);
+static void cpc_tty_rx_work(void *data);
+static void cpc_tty_tx_work(void *data);
+static int cpc_tty_send_to_card(pc300dev_t *dev,void *buf, int len);
+static void cpc_tty_trace(pc300dev_t *dev, char* buf, int len, char rxtx);
+static void cpc_tty_signal_off(pc300dev_t *pc300dev, unsigned char);
+static void cpc_tty_signal_on(pc300dev_t *pc300dev, unsigned char);
+
+int pc300_tiocmset(struct tty_struct *, struct file *,
+			unsigned int, unsigned int);
+int pc300_tiocmget(struct tty_struct *, struct file *);
+
+/* functions called by PC300 driver */
+void cpc_tty_init(pc300dev_t *dev);
+void cpc_tty_unregister_service(pc300dev_t *pc300dev);
+void cpc_tty_receive(pc300dev_t *pc300dev);
+void cpc_tty_trigger_poll(pc300dev_t *pc300dev);
+void cpc_tty_reset_var(void);
+
+/*
+ * PC300 TTY clear "signal"
+ */
+static void cpc_tty_signal_off(pc300dev_t *pc300dev, unsigned char signal)
+{
+	pc300ch_t *pc300chan = (pc300ch_t *)pc300dev->chan; 
+	pc300_t *card = (pc300_t *) pc300chan->card; 
+	int ch = pc300chan->channel; 
+	unsigned long flags; 
+
+	CPC_TTY_DBG("%s-tty: Clear signal %x\n",
+		pc300dev->dev->name, signal);
+	CPC_TTY_LOCK(card, flags); 
+	cpc_writeb(card->hw.scabase + M_REG(CTL,ch), 
+		cpc_readb(card->hw.scabase+M_REG(CTL,ch))& signal);
+	CPC_TTY_UNLOCK(card,flags); 
+}
+
+/*
+ * PC300 TTY set "signal" to ON
+ */
+static void cpc_tty_signal_on(pc300dev_t *pc300dev, unsigned char signal)
+{
+	pc300ch_t *pc300chan = (pc300ch_t *)pc300dev->chan; 
+	pc300_t *card = (pc300_t *) pc300chan->card; 
+	int ch = pc300chan->channel; 
+	unsigned long flags; 
+
+	CPC_TTY_DBG("%s-tty: Set signal %x\n",
+		pc300dev->dev->name, signal);
+	CPC_TTY_LOCK(card, flags); 
+	cpc_writeb(card->hw.scabase + M_REG(CTL,ch), 
+		cpc_readb(card->hw.scabase+M_REG(CTL,ch))& ~signal);
+	CPC_TTY_UNLOCK(card,flags); 
+}
+
+/*
+ * PC300 TTY initialization routine
+ *
+ * This routine is called by the PC300 driver during board configuration 
+ * (ioctl=SIOCSP300CONF). At this point the adapter is completely
+ * initialized.
+ * o verify kernel version (only 2.4.x)
+ * o register TTY driver
+ * o init cpc_tty_area struct
+ */
+void cpc_tty_init(pc300dev_t *pc300dev)
+{
+	unsigned long port;
+	int aux;
+	st_cpc_tty_area * cpc_tty;
+
+	/* hdlcX - X=interface number */
+	port = pc300dev->dev->name[4] - '0';
+	if (port >= CPC_TTY_NPORTS) {
+		printk("%s-tty: invalid interface selected (0-%i): %li",
+			pc300dev->dev->name,
+			CPC_TTY_NPORTS-1,port);
+		return;
+	}
+
+	if (cpc_tty_cnt == 0) { /* first TTY connection -> register driver */
+		CPC_TTY_DBG("%s-tty: driver init, major:%i, minor range:%i=%i\n",
+			pc300dev->dev->name,
+			CPC_TTY_MAJOR, CPC_TTY_MINOR_START,
+			CPC_TTY_MINOR_START+CPC_TTY_NPORTS);
+		/* initialize tty driver struct */
+		memset(&serial_drv,0,sizeof(struct tty_driver));
+		serial_drv.magic = TTY_DRIVER_MAGIC;
+		serial_drv.owner = THIS_MODULE;
+		serial_drv.driver_name = "pc300_tty";
+		serial_drv.name = "ttyCP";
+		serial_drv.major = CPC_TTY_MAJOR;
+		serial_drv.minor_start = CPC_TTY_MINOR_START;
+		serial_drv.num = CPC_TTY_NPORTS;
+		serial_drv.type = TTY_DRIVER_TYPE_SERIAL;
+		serial_drv.subtype = SERIAL_TYPE_NORMAL;
+
+		serial_drv.init_termios = tty_std_termios;
+		serial_drv.init_termios.c_cflag = B9600|CS8|CREAD|HUPCL|CLOCAL;
+		serial_drv.flags = TTY_DRIVER_REAL_RAW;
+
+		/* interface routines from the upper tty layer to the tty driver */
+		serial_drv.open = cpc_tty_open;
+		serial_drv.close = cpc_tty_close;
+		serial_drv.write = cpc_tty_write; 
+		serial_drv.write_room = cpc_tty_write_room; 
+		serial_drv.chars_in_buffer = cpc_tty_chars_in_buffer; 
+		serial_drv.tiocmset = pc300_tiocmset;
+		serial_drv.tiocmget = pc300_tiocmget;
+		serial_drv.flush_buffer = cpc_tty_flush_buffer; 
+		serial_drv.hangup = cpc_tty_hangup;
+
+		/* register the TTY driver */
+		if (tty_register_driver(&serial_drv)) { 
+			printk("%s-tty: Failed to register serial driver! ",
+				pc300dev->dev->name);
+		   	return;
+		} 
+
+		memset((void *)cpc_tty_area, 0,
+								sizeof(st_cpc_tty_area) * CPC_TTY_NPORTS);
+	}
+
+	cpc_tty = &cpc_tty_area[port];
+	
+	if (cpc_tty->state != CPC_TTY_ST_IDLE) {
+		CPC_TTY_DBG("%s-tty: TTY port %i, already in use.\n",
+				pc300dev->dev->name, port);
+		return;
+	}
+
+	cpc_tty_cnt++;
+	cpc_tty->state = CPC_TTY_ST_INIT; 
+	cpc_tty->num_open= 0;
+	cpc_tty->tty_minor = port + CPC_TTY_MINOR_START;
+	cpc_tty->pc300dev = pc300dev; 
+
+	INIT_WORK(&cpc_tty->tty_tx_work, cpc_tty_tx_work, (void *)cpc_tty);
+	INIT_WORK(&cpc_tty->tty_rx_work, cpc_tty_rx_work, (void *)port);
+	
+	cpc_tty->buf_rx.first = cpc_tty->buf_rx.last = NULL;
+
+	pc300dev->cpc_tty = (void *)cpc_tty; 
+	
+	aux = strlen(pc300dev->dev->name);
+	memcpy(cpc_tty->name, pc300dev->dev->name, aux);
+	memcpy(&cpc_tty->name[aux], "-tty", 5);
+	
+	cpc_open(pc300dev->dev);
+	cpc_tty_signal_off(pc300dev, CTL_DTR);
+
+	CPC_TTY_DBG("%s: Initializing TTY Sync Driver, tty major#%d minor#%i\n",
+			cpc_tty->name,CPC_TTY_MAJOR,cpc_tty->tty_minor); 
+	return; 
+} 
+
+/*
+ * PC300 TTY OPEN routine
+ *
+ * This routine is called by the tty driver to open the interface 
+ * o verify minor
+ * o allocate buffer to Rx and Tx
+ */
+static int cpc_tty_open(struct tty_struct *tty, struct file *flip)
+{
+	int port ;
+	st_cpc_tty_area *cpc_tty;
+
+	if (!tty) { 
+		return -ENODEV;
+	} 
+
+	port = tty->index;
+
+	if ((port < 0) || (port >= CPC_TTY_NPORTS)){ 
+		CPC_TTY_DBG("pc300_tty: open invalid port %d\n", port);
+		return -ENODEV;
+	} 
+
+	cpc_tty = &cpc_tty_area[port];
+	
+	if (cpc_tty->state == CPC_TTY_ST_IDLE){
+		CPC_TTY_DBG("%s: open - invalid interface, port=%d\n",
+					cpc_tty->name, tty->index);
+		return -ENODEV;
+	}
+
+	if (cpc_tty->num_open == 0) { /* first open of this tty */
+		if (!cpc_tty_area[port].buf_tx){
+			cpc_tty_area[port].buf_tx = kmalloc(CPC_TTY_MAX_MTU,GFP_KERNEL);
+			if (cpc_tty_area[port].buf_tx == 0){
+				CPC_TTY_DBG("%s: error in memory allocation\n",cpc_tty->name);
+				return -ENOMEM;
+			}
+		} 
+
+		if (cpc_tty_area[port].buf_rx.first) {
+			unsigned char * aux;
+			while (cpc_tty_area[port].buf_rx.first) {
+				aux = (unsigned char *)cpc_tty_area[port].buf_rx.first;
+				cpc_tty_area[port].buf_rx.first = cpc_tty_area[port].buf_rx.first->next;
+				kfree(aux);
+			}
+			cpc_tty_area[port].buf_rx.first = NULL;
+			cpc_tty_area[port].buf_rx.last = NULL;
+		}
+
+		cpc_tty_area[port].state = CPC_TTY_ST_OPEN;
+		cpc_tty_area[port].tty = tty;
+		tty->driver_data = &cpc_tty_area[port];
+
+		cpc_tty_signal_on(cpc_tty->pc300dev, CTL_DTR);
+	} 
+
+	cpc_tty->num_open++;
+
+	CPC_TTY_DBG("%s: opening TTY driver\n", cpc_tty->name);
+	
+	/* avisar driver PC300 */ 
+	return 0; 
+}
+
+/*
+ * PC300 TTY CLOSE routine
+ *
+ * This routine is called by the tty driver to close the interface 
+ * o call close channel in PC300 driver (cpc_closech)
+ * o free Rx and Tx buffers
+ */
+
+static void cpc_tty_close(struct tty_struct *tty, struct file *flip)
+{
+	st_cpc_tty_area    *cpc_tty;
+	unsigned long flags;
+	int res;
+
+	if (!tty || !tty->driver_data ) {
+		CPC_TTY_DBG("hdlx-tty: no TTY in close \n");
+		return;
+	}
+
+	cpc_tty = (st_cpc_tty_area *) tty->driver_data;
+
+	if ((cpc_tty->tty != tty)|| (cpc_tty->state != CPC_TTY_ST_OPEN)) {
+		CPC_TTY_DBG("%s: TTY is not opened\n",cpc_tty->name);
+		return;
+	}
+   	
+	if (!cpc_tty->num_open) {
+		CPC_TTY_DBG("%s: TTY is closed\n",cpc_tty->name);
+		return;
+	}
+
+	if (--cpc_tty->num_open > 0) {
+		CPC_TTY_DBG("%s: TTY closed\n",cpc_tty->name);
+		return;
+	}
+
+	cpc_tty_signal_off(cpc_tty->pc300dev, CTL_DTR);
+
+	CPC_TTY_LOCK(cpc_tty->pc300dev->chan->card, flags);  /* lock irq */ 
+	cpc_tty->tty = NULL;
+	cpc_tty->state = CPC_TTY_ST_INIT;
+	CPC_TTY_UNLOCK(cpc_tty->pc300dev->chan->card, flags); /* unlock irq */ 
+	
+	if (cpc_tty->buf_rx.first) {
+		unsigned char * aux;
+		while (cpc_tty->buf_rx.first) {
+			aux = (unsigned char *)cpc_tty->buf_rx.first;
+			cpc_tty->buf_rx.first = cpc_tty->buf_rx.first->next;
+			kfree(aux);
+		}
+		cpc_tty->buf_rx.first = NULL;
+		cpc_tty->buf_rx.last = NULL;
+	}
+	
+	if (cpc_tty->buf_tx) {
+		kfree(cpc_tty->buf_tx);
+		cpc_tty->buf_tx = NULL;
+	}
+
+	CPC_TTY_DBG("%s: TTY closed\n",cpc_tty->name);
+	
+	if (!serial_drv.refcount && cpc_tty_unreg_flag) {
+		cpc_tty_unreg_flag = 0;
+		CPC_TTY_DBG("%s: unregister the tty driver\n", cpc_tty->name);
+		if ((res=tty_unregister_driver(&serial_drv))) { 
+			CPC_TTY_DBG("%s: ERROR ->unregister the tty driver error=%d\n",
+							cpc_tty->name,res);
+		}
+	}
+	return; 
+} 
+
+/*
+ * PC300 TTY WRITE routine
+ *
+ * This routine is called by the tty driver to write a series of characters
+ * to the tty device. The characters may come from user or kernel space.
+ * o verify the DCD signal
+ * o send characters to board and start the transmission
+ */
+static int cpc_tty_write(struct tty_struct *tty, const unsigned char *buf, int count)
+{
+	st_cpc_tty_area    *cpc_tty; 
+	pc300ch_t *pc300chan; 
+	pc300_t *card; 
+	int ch; 
+	unsigned long flags; 
+	struct net_device_stats *stats; 
+
+	if (!tty || !tty->driver_data ) { 
+		CPC_TTY_DBG("hdlcX-tty: no TTY in write\n");
+		return -ENODEV;
+	} 
+
+	cpc_tty = (st_cpc_tty_area *) tty->driver_data; 
+
+	if ((cpc_tty->tty != tty) ||  (cpc_tty->state != CPC_TTY_ST_OPEN)) { 
+		CPC_TTY_DBG("%s: TTY is not opened\n", cpc_tty->name);
+		return -ENODEV; 
+	}
+
+	if (count > CPC_TTY_MAX_MTU) { 
+		CPC_TTY_DBG("%s: count is invalid\n",cpc_tty->name);
+		return -EINVAL;        /* frame too big */ 
+	}
+
+	CPC_TTY_DBG("%s: cpc_tty_write data len=%i\n",cpc_tty->name,count);
+	
+	pc300chan = (pc300ch_t *)((pc300dev_t*)cpc_tty->pc300dev)->chan; 
+	stats = hdlc_stats(((pc300dev_t*)cpc_tty->pc300dev)->dev);
+	card = (pc300_t *) pc300chan->card;
+	ch = pc300chan->channel; 
+
+	/* verify DCD signal*/ 
+	if (cpc_readb(card->hw.scabase + M_REG(ST3,ch)) & ST3_DCD) { 
+		/* DCD is OFF */ 
+		CPC_TTY_DBG("%s : DCD is OFF\n", cpc_tty->name);
+		stats->tx_errors++;
+		stats->tx_carrier_errors++;
+		CPC_TTY_LOCK(card, flags); 
+		cpc_writeb(card->hw.scabase + M_REG(CMD, ch), CMD_TX_BUF_CLR); 
+		
+		if (card->hw.type == PC300_TE) { 
+			cpc_writeb(card->hw.falcbase + card->hw.cpld_reg2, 
+				cpc_readb(card->hw.falcbase + card->hw.cpld_reg2) & 
+				~(CPLD_REG2_FALC_LED1 << (2 *ch))); 
+		}
+
+		CPC_TTY_UNLOCK(card, flags); 
+
+		return -EINVAL; 
+	}
+
+	if (cpc_tty_send_to_card(cpc_tty->pc300dev, (void*)buf, count)) { 
+	   /* failed to send */
+	   CPC_TTY_DBG("%s: trasmition error\n", cpc_tty->name);
+	   return 0;
+	}
+	return count; 
+} 
+
+/*
+ * PC300 TTY Write Room routine
+ * 
+ * This routine returns the numbers of characteres the tty driver will accept
+ * for queuing to be written. 
+ * o return MTU
+ */
+static int cpc_tty_write_room(struct tty_struct *tty)
+{
+	st_cpc_tty_area    *cpc_tty; 
+
+	if (!tty || !tty->driver_data ) { 
+		CPC_TTY_DBG("hdlcX-tty: no TTY to write room\n");
+		return -ENODEV;
+	}
+
+	cpc_tty = (st_cpc_tty_area *) tty->driver_data; 
+
+	if ((cpc_tty->tty != tty) ||  (cpc_tty->state != CPC_TTY_ST_OPEN)) { 
+		CPC_TTY_DBG("%s: TTY is not opened\n",cpc_tty->name);
+		return -ENODEV; 
+	}
+   	
+	CPC_TTY_DBG("%s: write room\n",cpc_tty->name);
+	
+	return CPC_TTY_MAX_MTU;
+} 
+
+/*
+ * PC300 TTY chars in buffer routine
+ * 
+ * This routine returns the chars number in the transmission buffer 
+ * o returns 0
+ */
+static int cpc_tty_chars_in_buffer(struct tty_struct *tty)
+{
+	st_cpc_tty_area    *cpc_tty; 
+
+	if (!tty || !tty->driver_data ) {
+		CPC_TTY_DBG("hdlcX-tty: no TTY to chars in buffer\n");
+		return -ENODEV; 
+	}
+
+	cpc_tty = (st_cpc_tty_area *) tty->driver_data; 
+
+	if ((cpc_tty->tty != tty) || (cpc_tty->state != CPC_TTY_ST_OPEN)) { 
+		CPC_TTY_DBG("%s: TTY is not opened\n",cpc_tty->name);
+		return -ENODEV; 
+	}
+   
+	return(0); 
+} 
+
+int pc300_tiocmset(struct tty_struct *tty, struct file *file,
+			unsigned int set, unsigned int clear)
+{
+	st_cpc_tty_area    *cpc_tty; 
+
+	CPC_TTY_DBG("%s: set:%x clear:%x\n", __FUNCTION__, set, clear);
+
+	if (!tty || !tty->driver_data ) {
+	   	CPC_TTY_DBG("hdlcX-tty: no TTY to chars in buffer\n");	
+		return -ENODEV; 
+	}
+
+	cpc_tty = (st_cpc_tty_area *) tty->driver_data; 
+
+	if (set & TIOCM_RTS)
+		cpc_tty_signal_on(cpc_tty->pc300dev, CTL_RTS);
+	if (set & TIOCM_DTR)
+		cpc_tty_signal_on(cpc_tty->pc300dev, CTL_DTR);
+
+	if (clear & TIOCM_RTS)
+		cpc_tty_signal_off(cpc_tty->pc300dev, CTL_RTS);
+	if (clear & TIOCM_DTR)
+		cpc_tty_signal_off(cpc_tty->pc300dev, CTL_DTR);
+
+	return 0;
+}
+
+int pc300_tiocmget(struct tty_struct *tty, struct file *file)
+{
+	unsigned int result;
+	unsigned char status;
+	unsigned long flags;
+	st_cpc_tty_area  *cpc_tty = (st_cpc_tty_area *) tty->driver_data;
+	pc300dev_t *pc300dev = cpc_tty->pc300dev;
+	pc300ch_t *pc300chan = (pc300ch_t *)pc300dev->chan;
+	pc300_t *card = (pc300_t *) pc300chan->card;
+	int ch = pc300chan->channel;
+
+	cpc_tty = (st_cpc_tty_area *) tty->driver_data;
+
+	CPC_TTY_DBG("%s-tty: tiocmget\n",
+		((struct net_device*)(pc300dev->hdlc))->name);
+
+	CPC_TTY_LOCK(card, flags);
+	status = cpc_readb(card->hw.scabase+M_REG(CTL,ch));
+	CPC_TTY_UNLOCK(card,flags);
+
+	result = ((status & CTL_DTR) ? TIOCM_DTR : 0) |
+		 ((status & CTL_RTS) ? TIOCM_RTS : 0);
+
+	return result;
+}
+
+/*
+ * PC300 TTY Flush Buffer routine
+ *
+ * This routine resets the transmission buffer 
+ */
+static void cpc_tty_flush_buffer(struct tty_struct *tty)
+{ 
+	st_cpc_tty_area    *cpc_tty; 
+	
+	if (!tty || !tty->driver_data ) {
+	   	CPC_TTY_DBG("hdlcX-tty: no TTY to flush buffer\n");	
+		return; 
+	}
+
+	cpc_tty = (st_cpc_tty_area *) tty->driver_data; 
+
+	if ((cpc_tty->tty != tty) ||  (cpc_tty->state != CPC_TTY_ST_OPEN)) { 
+		CPC_TTY_DBG("%s: TTY is not opened\n",cpc_tty->name);
+		return; 
+	}
+
+	CPC_TTY_DBG("%s: call wake_up_interruptible\n",cpc_tty->name);
+
+	tty_wakeup(tty);	
+	return; 
+} 
+
+/*
+ * PC300 TTY Hangup routine
+ *
+ * This routine is called by the tty driver to hangup the interface 
+ * o clear DTR signal
+ */
+
+static void cpc_tty_hangup(struct tty_struct *tty)
+{ 
+	st_cpc_tty_area    *cpc_tty; 
+	int res;
+
+	if (!tty || !tty->driver_data ) {
+		CPC_TTY_DBG("hdlcX-tty: no TTY to hangup\n");	
+		return ; 
+	}
+
+	cpc_tty = (st_cpc_tty_area *) tty->driver_data; 
+
+	if ((cpc_tty->tty != tty) || (cpc_tty->state != CPC_TTY_ST_OPEN)) {
+		CPC_TTY_DBG("%s: TTY is not opened\n",cpc_tty->name);
+		return ;
+	}
+	if (!serial_drv.refcount && cpc_tty_unreg_flag) {
+		cpc_tty_unreg_flag = 0;
+		CPC_TTY_DBG("%s: unregister the tty driver\n", cpc_tty->name);
+		if ((res=tty_unregister_driver(&serial_drv))) { 
+			CPC_TTY_DBG("%s: ERROR ->unregister the tty driver error=%d\n",
+							cpc_tty->name,res);
+		}
+	}
+	cpc_tty_signal_off(cpc_tty->pc300dev, CTL_DTR);
+}
+
+/*
+ * PC300 TTY RX work routine
+ * This routine treats RX work
+ * o verify read buffer
+ * o call the line disc. read
+ * o free memory
+ */
+static void cpc_tty_rx_work(void * data)
+{
+	unsigned long port;
+	int i, j;
+	st_cpc_tty_area *cpc_tty; 
+	volatile st_cpc_rx_buf * buf;
+	char flags=0,flg_rx=1; 
+	struct tty_ldisc *ld;
+
+	if (cpc_tty_cnt == 0) return;
+
+	
+	for (i=0; (i < 4) && flg_rx ; i++) {
+		flg_rx = 0;
+		port = (unsigned long)data;
+		for (j=0; j < CPC_TTY_NPORTS; j++) {
+			cpc_tty = &cpc_tty_area[port];
+		
+			if ((buf=cpc_tty->buf_rx.first) != 0) {
+				if(cpc_tty->tty) {
+					ld = tty_ldisc_ref(cpc_tty->tty);
+					if(ld) {
+						if (ld->receive_buf) {
+							CPC_TTY_DBG("%s: call line disc. receive_buf\n",cpc_tty->name);
+							ld->receive_buf(cpc_tty->tty, (char *)(buf->data), &flags, buf->size);
+						}
+						tty_ldisc_deref(ld);
+					}
+				}	
+				cpc_tty->buf_rx.first = cpc_tty->buf_rx.first->next;
+				kfree((unsigned char *)buf);
+				buf = cpc_tty->buf_rx.first;
+				flg_rx = 1;
+			}
+			if (++port == CPC_TTY_NPORTS) port = 0;
+		}
+	}
+} 
+
+/*
+ * PC300 TTY RX work routine
+ *
+ * This routine treats RX interrupt. 
+ * o read all frames in card
+ * o verify the frame size
+ * o read the frame in rx buffer
+ */
+static void cpc_tty_rx_disc_frame(pc300ch_t *pc300chan)
+{
+	volatile pcsca_bd_t __iomem * ptdescr; 
+	volatile unsigned char status; 
+	pc300_t *card = (pc300_t *)pc300chan->card; 
+	int ch = pc300chan->channel; 
+
+	/* dma buf read */ 
+	ptdescr = (pcsca_bd_t __iomem *)(card->hw.rambase + 
+				RX_BD_ADDR(ch, pc300chan->rx_first_bd)); 
+	while (pc300chan->rx_first_bd != pc300chan->rx_last_bd) { 
+		status = cpc_readb(&ptdescr->status); 
+		cpc_writeb(&ptdescr->status, 0); 
+		cpc_writeb(&ptdescr->len, 0); 
+		pc300chan->rx_first_bd = (pc300chan->rx_first_bd + 1) & 
+					(N_DMA_RX_BUF - 1); 
+		if (status & DST_EOM) { 
+			break; /* end of message */
+		}
+		ptdescr = (pcsca_bd_t __iomem *)(card->hw.rambase + cpc_readl(&ptdescr->next)); 
+	}
+}
+
+void cpc_tty_receive(pc300dev_t *pc300dev)
+{
+	st_cpc_tty_area    *cpc_tty; 
+	pc300ch_t *pc300chan = (pc300ch_t *)pc300dev->chan; 
+	pc300_t *card = (pc300_t *)pc300chan->card; 
+	int ch = pc300chan->channel; 
+	volatile pcsca_bd_t  __iomem * ptdescr; 
+	struct net_device_stats *stats = hdlc_stats(pc300dev->dev);
+	int rx_len, rx_aux; 
+	volatile unsigned char status; 
+	unsigned short first_bd = pc300chan->rx_first_bd;
+	st_cpc_rx_buf	*new=NULL;
+	unsigned char dsr_rx;
+
+	if (pc300dev->cpc_tty == NULL) { 
+		return; 
+	}
+
+	dsr_rx = cpc_readb(card->hw.scabase + DSR_RX(ch));
+
+	cpc_tty = (st_cpc_tty_area *)pc300dev->cpc_tty; 
+
+	while (1) { 
+		rx_len = 0;
+		ptdescr = (pcsca_bd_t  __iomem *)(card->hw.rambase + RX_BD_ADDR(ch, first_bd));
+		while ((status = cpc_readb(&ptdescr->status)) & DST_OSB) {
+			rx_len += cpc_readw(&ptdescr->len);
+			first_bd = (first_bd + 1) & (N_DMA_RX_BUF - 1);
+			if (status & DST_EOM) {
+				break;
+			}
+			ptdescr=(pcsca_bd_t __iomem *)(card->hw.rambase+cpc_readl(&ptdescr->next));
+		}
+			
+		if (!rx_len) { 
+			if (dsr_rx & DSR_BOF) {
+				/* update EDA */ 
+				cpc_writel(card->hw.scabase + DRX_REG(EDAL, ch), 
+						RX_BD_ADDR(ch, pc300chan->rx_last_bd)); 
+			}
+			if (new) {
+				kfree(new);
+				new = NULL;
+			}
+			return; 
+		}
+		
+		if (rx_len > CPC_TTY_MAX_MTU) { 
+			/* Free RX descriptors */ 
+			CPC_TTY_DBG("%s: frame size is invalid.\n",cpc_tty->name);
+			stats->rx_errors++; 
+			stats->rx_frame_errors++; 
+			cpc_tty_rx_disc_frame(pc300chan);
+			continue;
+		} 
+		
+		new = (st_cpc_rx_buf *) kmalloc(rx_len + sizeof(st_cpc_rx_buf), GFP_ATOMIC);
+		if (new == 0) {
+			cpc_tty_rx_disc_frame(pc300chan);
+			continue;
+		}
+		
+		/* dma buf read */ 
+		ptdescr = (pcsca_bd_t __iomem *)(card->hw.rambase + 
+				RX_BD_ADDR(ch, pc300chan->rx_first_bd)); 
+
+		rx_len = 0;	/* counter frame size */
+		
+		while ((status = cpc_readb(&ptdescr->status)) & DST_OSB) {
+			rx_aux = cpc_readw(&ptdescr->len);
+			if ((status & (DST_OVR | DST_CRC | DST_RBIT |  DST_SHRT | DST_ABT))
+				|| (rx_aux > BD_DEF_LEN)) {
+				CPC_TTY_DBG("%s: reception error\n", cpc_tty->name);
+				stats->rx_errors++; 
+				if (status & DST_OVR) { 
+					stats->rx_fifo_errors++; 
+				}
+				if (status & DST_CRC) { 
+					stats->rx_crc_errors++; 
+				}
+				if ((status & (DST_RBIT | DST_SHRT | DST_ABT)) ||
+					(rx_aux > BD_DEF_LEN))	{ 
+					stats->rx_frame_errors++; 
+				} 
+				/* discard remainig descriptors used by the bad frame */ 
+				CPC_TTY_DBG("%s: reception error - discard descriptors",
+						cpc_tty->name);
+				cpc_tty_rx_disc_frame(pc300chan);
+				rx_len = 0;
+				kfree(new);
+				new = NULL;
+				break; /* read next frame - while(1) */
+			}
+
+			if (cpc_tty->state != CPC_TTY_ST_OPEN) {
+				/* Free RX descriptors */ 
+				cpc_tty_rx_disc_frame(pc300chan);
+				stats->rx_dropped++; 
+				rx_len = 0; 
+				kfree(new);
+				new = NULL;
+				break; /* read next frame - while(1) */
+			}
+
+			/* read the segment of the frame */
+			if (rx_aux != 0) {
+				memcpy_fromio((new->data + rx_len), 
+					(void __iomem *)(card->hw.rambase + 
+					 cpc_readl(&ptdescr->ptbuf)), rx_aux);
+				rx_len += rx_aux; 
+			}
+			cpc_writeb(&ptdescr->status,0); 
+			cpc_writeb(&ptdescr->len, 0); 
+			pc300chan->rx_first_bd = (pc300chan->rx_first_bd + 1) & 
+					(N_DMA_RX_BUF -1); 
+			if (status & DST_EOM)break;
+			
+			ptdescr = (pcsca_bd_t __iomem *) (card->hw.rambase + 
+					cpc_readl(&ptdescr->next)); 
+		}
+		/* update pointer */ 
+		pc300chan->rx_last_bd = (pc300chan->rx_first_bd - 1) & 
+					(N_DMA_RX_BUF - 1) ; 
+		if (!(dsr_rx & DSR_BOF)) {
+			/* update EDA */ 
+			cpc_writel(card->hw.scabase + DRX_REG(EDAL, ch), 
+					RX_BD_ADDR(ch, pc300chan->rx_last_bd)); 
+		}
+		if (rx_len != 0) { 
+			stats->rx_bytes += rx_len; 
+		
+			if (pc300dev->trace_on) { 
+				cpc_tty_trace(pc300dev, new->data,rx_len, 'R'); 
+			} 
+			new->size = rx_len;
+			new->next = NULL;
+			if (cpc_tty->buf_rx.first == 0) {
+				cpc_tty->buf_rx.first = new;
+				cpc_tty->buf_rx.last = new;
+			} else {
+				cpc_tty->buf_rx.last->next = new;
+				cpc_tty->buf_rx.last = new;
+			}
+			schedule_work(&(cpc_tty->tty_rx_work));
+			stats->rx_packets++;
+		}
+	} 
+} 
+
+/*
+ * PC300 TTY TX work routine
+ * 
+ * This routine treats TX interrupt. 
+ * o if need call line discipline wakeup
+ * o call wake_up_interruptible
+ */
+static void cpc_tty_tx_work(void *data)
+{
+	st_cpc_tty_area *cpc_tty = (st_cpc_tty_area *) data; 
+	struct tty_struct *tty; 
+
+	CPC_TTY_DBG("%s: cpc_tty_tx_work init\n",cpc_tty->name);
+	
+	if ((tty = cpc_tty->tty) == 0) { 
+		CPC_TTY_DBG("%s: the interface is not opened\n",cpc_tty->name);
+		return; 
+	}
+	tty_wakeup(tty);
+}
+
+/*
+ * PC300 TTY send to card routine
+ * 
+ * This routine send data to card. 
+ * o clear descriptors
+ * o write data to DMA buffers
+ * o start the transmission
+ */
+static int cpc_tty_send_to_card(pc300dev_t *dev,void* buf, int len)
+{
+	pc300ch_t *chan = (pc300ch_t *)dev->chan; 
+	pc300_t *card = (pc300_t *)chan->card; 
+	int ch = chan->channel; 
+	struct net_device_stats *stats = hdlc_stats(dev->dev);
+	unsigned long flags; 
+	volatile pcsca_bd_t __iomem *ptdescr; 
+	int i, nchar;
+	int tosend = len;
+	int nbuf = ((len - 1)/BD_DEF_LEN) + 1;
+	unsigned char *pdata=buf;
+
+	CPC_TTY_DBG("%s:cpc_tty_send_to_cars len=%i", 
+			(st_cpc_tty_area *)dev->cpc_tty->name,len);	
+
+	if (nbuf >= card->chan[ch].nfree_tx_bd) {
+		return 1;
+	}
+	
+	/* write buffer to DMA buffers */ 
+	CPC_TTY_DBG("%s: call dma_buf_write\n",
+			(st_cpc_tty_area *)dev->cpc_tty->name);	
+	for (i = 0 ; i < nbuf ; i++) {
+		ptdescr = (pcsca_bd_t __iomem *)(card->hw.rambase + 
+			TX_BD_ADDR(ch, card->chan[ch].tx_next_bd));
+		nchar = (BD_DEF_LEN > tosend) ? tosend : BD_DEF_LEN;
+		if (cpc_readb(&ptdescr->status) & DST_OSB) {
+			memcpy_toio((void __iomem *)(card->hw.rambase + 
+				cpc_readl(&ptdescr->ptbuf)), 
+				&pdata[len - tosend], 
+				nchar);
+			card->chan[ch].nfree_tx_bd--;
+			if ((i + 1) == nbuf) {
+				/* This must be the last BD to be used */
+				cpc_writeb(&ptdescr->status, DST_EOM);
+			} else {
+				cpc_writeb(&ptdescr->status, 0);
+			}
+			cpc_writew(&ptdescr->len, nchar);
+		} else {
+			CPC_TTY_DBG("%s: error in dma_buf_write\n",
+					(st_cpc_tty_area *)dev->cpc_tty->name);	
+			stats->tx_dropped++;
+			return 1; 
+		}
+		tosend -= nchar;
+		card->chan[ch].tx_next_bd = 
+			(card->chan[ch].tx_next_bd + 1) & (N_DMA_TX_BUF - 1);
+	}
+
+	if (dev->trace_on) { 
+		cpc_tty_trace(dev, buf, len,'T'); 
+	}
+
+	/* start transmission */ 
+	CPC_TTY_DBG("%s: start transmission\n",
+		(st_cpc_tty_area *)dev->cpc_tty->name);	
+	
+	CPC_TTY_LOCK(card, flags); 
+	cpc_writeb(card->hw.scabase + DTX_REG(EDAL, ch), 
+			TX_BD_ADDR(ch, chan->tx_next_bd)); 
+	cpc_writeb(card->hw.scabase + M_REG(CMD, ch), CMD_TX_ENA); 
+	cpc_writeb(card->hw.scabase + DSR_TX(ch), DSR_DE); 
+
+	if (card->hw.type == PC300_TE) { 
+		cpc_writeb(card->hw.falcbase + card->hw.cpld_reg2, 
+			cpc_readb(card->hw.falcbase + card->hw.cpld_reg2) |
+			(CPLD_REG2_FALC_LED1 << (2 * ch))); 
+	}
+	CPC_TTY_UNLOCK(card, flags); 
+	return 0; 
+} 
+
+/*
+ *	PC300 TTY trace routine
+ *
+ *  This routine send trace of connection to application. 
+ *  o clear descriptors
+ *  o write data to DMA buffers
+ *  o start the transmission
+ */
+
+static void cpc_tty_trace(pc300dev_t *dev, char* buf, int len, char rxtx)
+{
+	struct sk_buff *skb; 
+
+	if ((skb = dev_alloc_skb(10 + len)) == NULL) { 
+		/* out of memory */ 
+		CPC_TTY_DBG("%s: tty_trace - out of memory\n", dev->dev->name);
+		return; 
+	}
+
+	skb_put (skb, 10 + len); 
+	skb->dev = dev->dev; 
+	skb->protocol = htons(ETH_P_CUST); 
+	skb->mac.raw = skb->data; 
+	skb->pkt_type = PACKET_HOST; 
+	skb->len = 10 + len; 
+
+	memcpy(skb->data,dev->dev->name,5);
+	skb->data[5] = '['; 
+	skb->data[6] = rxtx; 
+	skb->data[7] = ']'; 
+	skb->data[8] = ':'; 
+	skb->data[9] = ' '; 
+	memcpy(&skb->data[10], buf, len); 
+	netif_rx(skb); 
+} 	
+
+/*
+ *	PC300 TTY unregister service routine
+ *
+ *	This routine unregister one interface. 
+ */
+void cpc_tty_unregister_service(pc300dev_t *pc300dev)
+{
+	st_cpc_tty_area *cpc_tty; 
+	ulong flags;
+	int res;
+
+	if ((cpc_tty= (st_cpc_tty_area *) pc300dev->cpc_tty) == 0) { 
+		CPC_TTY_DBG("%s: interface is not TTY\n", pc300dev->dev->name);
+		return; 
+	}
+	CPC_TTY_DBG("%s: cpc_tty_unregister_service", cpc_tty->name);
+
+	if (cpc_tty->pc300dev != pc300dev) { 
+		CPC_TTY_DBG("%s: invalid tty ptr=%s\n", 
+		pc300dev->dev->name, cpc_tty->name);
+		return; 
+	}
+
+	if (--cpc_tty_cnt == 0) { 
+		if (serial_drv.refcount) {
+			CPC_TTY_DBG("%s: unregister is not possible, refcount=%d",
+							cpc_tty->name, serial_drv.refcount);
+			cpc_tty_cnt++;
+			cpc_tty_unreg_flag = 1;
+			return;
+		} else { 
+			CPC_TTY_DBG("%s: unregister the tty driver\n", cpc_tty->name);
+			if ((res=tty_unregister_driver(&serial_drv))) { 
+				CPC_TTY_DBG("%s: ERROR ->unregister the tty driver error=%d\n",
+								cpc_tty->name,res);
+			}
+		}
+	}
+	CPC_TTY_LOCK(pc300dev->chan->card,flags);
+	cpc_tty->tty = NULL; 
+	CPC_TTY_UNLOCK(pc300dev->chan->card, flags);
+	cpc_tty->tty_minor = 0; 
+	cpc_tty->state = CPC_TTY_ST_IDLE; 
+} 
+
+/*
+ * PC300 TTY trigger poll routine
+ * This routine is called by pc300driver to treats Tx interrupt. 
+ */
+void cpc_tty_trigger_poll(pc300dev_t *pc300dev)
+{
+	st_cpc_tty_area *cpc_tty = (st_cpc_tty_area *)pc300dev->cpc_tty; 
+	if (!cpc_tty) {
+		return;
+	}
+	schedule_work(&(cpc_tty->tty_tx_work)); 
+} 
+
+/*
+ * PC300 TTY reset var routine
+ * This routine is called by pc300driver to init the TTY area. 
+ */
+
+void cpc_tty_reset_var(void)
+{
+	int i ; 
+
+	CPC_TTY_DBG("hdlcX-tty: reset variables\n");
+	/* reset  the tty_driver structure - serial_drv */ 
+	memset(&serial_drv, 0, sizeof(struct tty_driver));
+	for (i=0; i < CPC_TTY_NPORTS; i++){
+		memset(&cpc_tty_area[i],0, sizeof(st_cpc_tty_area)); 
+	}
+}
diff --git a/drivers/net/wan/pci200syn.c b/drivers/net/wan/pci200syn.c
new file mode 100644
index 0000000..8dea07b
--- /dev/null
+++ b/drivers/net/wan/pci200syn.c
@@ -0,0 +1,488 @@
+/*
+ * Goramo PCI200SYN synchronous serial card driver for Linux
+ *
+ * Copyright (C) 2002-2003 Krzysztof Halasa <khc@pm.waw.pl>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of version 2 of the GNU General Public License
+ * as published by the Free Software Foundation.
+ *
+ * For information see http://hq.pm.waw.pl/hdlc/
+ *
+ * Sources of information:
+ *    Hitachi HD64572 SCA-II User's Manual
+ *    PLX Technology Inc. PCI9052 Data Book
+ */
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include <linux/sched.h>
+#include <linux/types.h>
+#include <linux/fcntl.h>
+#include <linux/in.h>
+#include <linux/string.h>
+#include <linux/errno.h>
+#include <linux/init.h>
+#include <linux/ioport.h>
+#include <linux/moduleparam.h>
+#include <linux/netdevice.h>
+#include <linux/hdlc.h>
+#include <linux/pci.h>
+#include <asm/delay.h>
+#include <asm/io.h>
+
+#include "hd64572.h"
+
+static const char* version = "Goramo PCI200SYN driver version: 1.16";
+static const char* devname = "PCI200SYN";
+
+#undef DEBUG_PKT
+#define DEBUG_RINGS
+
+#define PCI200SYN_PLX_SIZE	0x80	/* PLX control window size (128b) */
+#define PCI200SYN_SCA_SIZE	0x400	/* SCA window size (1Kb) */
+#define ALL_PAGES_ALWAYS_MAPPED
+#define NEED_DETECT_RAM
+#define NEED_SCA_MSCI_INTR
+#define MAX_TX_BUFFERS		10
+
+static int pci_clock_freq = 33000000;
+#define CLOCK_BASE pci_clock_freq
+
+#define PCI_VENDOR_ID_GORAMO	0x10B5	/* uses PLX:9050 ID - this card	*/
+#define PCI_DEVICE_ID_PCI200SYN	0x9050	/* doesn't have its own ID	*/
+
+
+/*
+ *      PLX PCI9052 local configuration and shared runtime registers.
+ *      This structure can be used to access 9052 registers (memory mapped).
+ */
+typedef struct {
+	u32 loc_addr_range[4];	/* 00-0Ch : Local Address Ranges */
+	u32 loc_rom_range;	/* 10h : Local ROM Range */
+	u32 loc_addr_base[4];	/* 14-20h : Local Address Base Addrs */
+	u32 loc_rom_base;	/* 24h : Local ROM Base */
+	u32 loc_bus_descr[4];	/* 28-34h : Local Bus Descriptors */
+	u32 rom_bus_descr;	/* 38h : ROM Bus Descriptor */
+	u32 cs_base[4];		/* 3C-48h : Chip Select Base Addrs */
+	u32 intr_ctrl_stat;	/* 4Ch : Interrupt Control/Status */
+	u32 init_ctrl;		/* 50h : EEPROM ctrl, Init Ctrl, etc */
+}plx9052;
+
+
+
+typedef struct port_s {
+	struct net_device *dev;
+	struct card_s *card;
+	spinlock_t lock;	/* TX lock */
+	sync_serial_settings settings;
+	int rxpart;		/* partial frame received, next frame invalid*/
+	unsigned short encoding;
+	unsigned short parity;
+	u16 rxin;		/* rx ring buffer 'in' pointer */
+	u16 txin;		/* tx ring buffer 'in' and 'last' pointers */
+	u16 txlast;
+	u8 rxs, txs, tmc;	/* SCA registers */
+	u8 phy_node;		/* physical port # - 0 or 1 */
+}port_t;
+
+
+
+typedef struct card_s {
+	u8 __iomem *rambase;	/* buffer memory base (virtual) */
+	u8 __iomem *scabase;	/* SCA memory base (virtual) */
+	plx9052 __iomem *plxbase;/* PLX registers memory base (virtual) */
+	u16 rx_ring_buffers;	/* number of buffers in a ring */
+	u16 tx_ring_buffers;
+	u16 buff_offset;	/* offset of first buffer of first channel */
+	u8 irq;			/* interrupt request level */
+
+	port_t ports[2];
+}card_t;
+
+
+#define sca_in(reg, card)	     readb(card->scabase + (reg))
+#define sca_out(value, reg, card)    writeb(value, card->scabase + (reg))
+#define sca_inw(reg, card)	     readw(card->scabase + (reg))
+#define sca_outw(value, reg, card)   writew(value, card->scabase + (reg))
+#define sca_inl(reg, card)	     readl(card->scabase + (reg))
+#define sca_outl(value, reg, card)   writel(value, card->scabase + (reg))
+
+#define port_to_card(port)	     (port->card)
+#define log_node(port)		     (port->phy_node)
+#define phy_node(port)		     (port->phy_node)
+#define winbase(card)		     (card->rambase)
+#define get_port(card, port)	     (&card->ports[port])
+#define sca_flush(card)		     (sca_in(IER0, card));
+
+static inline void new_memcpy_toio(char __iomem *dest, char *src, int length)
+{
+	int len;
+	do {
+		len = length > 256 ? 256 : length;
+		memcpy_toio(dest, src, len);
+		dest += len;
+		src += len;
+		length -= len;
+		readb(dest);
+	} while (len);
+}
+
+#undef memcpy_toio
+#define memcpy_toio new_memcpy_toio
+
+#include "hd6457x.c"
+
+
+static void pci200_set_iface(port_t *port)
+{
+	card_t *card = port->card;
+	u16 msci = get_msci(port);
+	u8 rxs = port->rxs & CLK_BRG_MASK;
+	u8 txs = port->txs & CLK_BRG_MASK;
+
+	sca_out(EXS_TES1, (phy_node(port) ? MSCI1_OFFSET : MSCI0_OFFSET) + EXS,
+		port_to_card(port));
+	switch(port->settings.clock_type) {
+	case CLOCK_INT:
+		rxs |= CLK_BRG; /* BRG output */
+		txs |= CLK_PIN_OUT | CLK_TX_RXCLK; /* RX clock */
+		break;
+
+	case CLOCK_TXINT:
+		rxs |= CLK_LINE; /* RXC input */
+		txs |= CLK_PIN_OUT | CLK_BRG; /* BRG output */
+		break;
+
+	case CLOCK_TXFROMRX:
+		rxs |= CLK_LINE; /* RXC input */
+		txs |= CLK_PIN_OUT | CLK_TX_RXCLK; /* RX clock */
+		break;
+
+	default:		/* EXTernal clock */
+		rxs |= CLK_LINE; /* RXC input */
+		txs |= CLK_PIN_OUT | CLK_LINE; /* TXC input */
+		break;
+	}
+
+	port->rxs = rxs;
+	port->txs = txs;
+	sca_out(rxs, msci + RXS, card);
+	sca_out(txs, msci + TXS, card);
+	sca_set_port(port);
+}
+
+
+
+static int pci200_open(struct net_device *dev)
+{
+	port_t *port = dev_to_port(dev);
+
+	int result = hdlc_open(dev);
+	if (result)
+		return result;
+
+	sca_open(dev);
+	pci200_set_iface(port);
+	sca_flush(port_to_card(port));
+	return 0;
+}
+
+
+
+static int pci200_close(struct net_device *dev)
+{
+	sca_close(dev);
+	sca_flush(port_to_card(dev_to_port(dev)));
+	hdlc_close(dev);
+	return 0;
+}
+
+
+
+static int pci200_ioctl(struct net_device *dev, struct ifreq *ifr, int cmd)
+{
+	const size_t size = sizeof(sync_serial_settings);
+	sync_serial_settings new_line;
+	sync_serial_settings __user *line = ifr->ifr_settings.ifs_ifsu.sync;
+	port_t *port = dev_to_port(dev);
+
+#ifdef DEBUG_RINGS
+	if (cmd == SIOCDEVPRIVATE) {
+		sca_dump_rings(dev);
+		return 0;
+	}
+#endif
+	if (cmd != SIOCWANDEV)
+		return hdlc_ioctl(dev, ifr, cmd);
+
+	switch(ifr->ifr_settings.type) {
+	case IF_GET_IFACE:
+		ifr->ifr_settings.type = IF_IFACE_V35;
+		if (ifr->ifr_settings.size < size) {
+			ifr->ifr_settings.size = size; /* data size wanted */
+			return -ENOBUFS;
+		}
+		if (copy_to_user(line, &port->settings, size))
+			return -EFAULT;
+		return 0;
+
+	case IF_IFACE_V35:
+	case IF_IFACE_SYNC_SERIAL:
+		if (!capable(CAP_NET_ADMIN))
+			return -EPERM;
+
+		if (copy_from_user(&new_line, line, size))
+			return -EFAULT;
+
+		if (new_line.clock_type != CLOCK_EXT &&
+		    new_line.clock_type != CLOCK_TXFROMRX &&
+		    new_line.clock_type != CLOCK_INT &&
+		    new_line.clock_type != CLOCK_TXINT)
+		return -EINVAL;	/* No such clock setting */
+
+		if (new_line.loopback != 0 && new_line.loopback != 1)
+			return -EINVAL;
+
+		memcpy(&port->settings, &new_line, size); /* Update settings */
+		pci200_set_iface(port);
+		sca_flush(port_to_card(port));
+		return 0;
+
+	default:
+		return hdlc_ioctl(dev, ifr, cmd);
+	}
+}
+
+
+
+static void pci200_pci_remove_one(struct pci_dev *pdev)
+{
+	int i;
+	card_t *card = pci_get_drvdata(pdev);
+
+	for(i = 0; i < 2; i++)
+		if (card->ports[i].card) {
+			struct net_device *dev = port_to_dev(&card->ports[i]);
+			unregister_hdlc_device(dev);
+		}
+
+	if (card->irq)
+		free_irq(card->irq, card);
+
+	if (card->rambase)
+		iounmap(card->rambase);
+	if (card->scabase)
+		iounmap(card->scabase);
+	if (card->plxbase)
+		iounmap(card->plxbase);
+
+	pci_release_regions(pdev);
+	pci_disable_device(pdev);
+	pci_set_drvdata(pdev, NULL);
+	if (card->ports[0].dev)
+		free_netdev(card->ports[0].dev);
+	if (card->ports[1].dev)
+		free_netdev(card->ports[1].dev);
+	kfree(card);
+}
+
+
+
+static int __devinit pci200_pci_init_one(struct pci_dev *pdev,
+					 const struct pci_device_id *ent)
+{
+	card_t *card;
+	u8 rev_id;
+	u32 __iomem *p;
+	int i;
+	u32 ramsize;
+	u32 ramphys;		/* buffer memory base */
+	u32 scaphys;		/* SCA memory base */
+	u32 plxphys;		/* PLX registers memory base */
+
+#ifndef MODULE
+	static int printed_version;
+	if (!printed_version++)
+		printk(KERN_INFO "%s\n", version);
+#endif
+
+	i = pci_enable_device(pdev);
+	if (i)
+		return i;
+
+	i = pci_request_regions(pdev, "PCI200SYN");
+	if (i) {
+		pci_disable_device(pdev);
+		return i;
+	}
+
+	card = kmalloc(sizeof(card_t), GFP_KERNEL);
+	if (card == NULL) {
+		printk(KERN_ERR "pci200syn: unable to allocate memory\n");
+		pci_release_regions(pdev);
+		pci_disable_device(pdev);
+		return -ENOBUFS;
+	}
+	memset(card, 0, sizeof(card_t));
+	pci_set_drvdata(pdev, card);
+	card->ports[0].dev = alloc_hdlcdev(&card->ports[0]);
+	card->ports[1].dev = alloc_hdlcdev(&card->ports[1]);
+	if (!card->ports[0].dev || !card->ports[1].dev) {
+		printk(KERN_ERR "pci200syn: unable to allocate memory\n");
+		pci200_pci_remove_one(pdev);
+		return -ENOMEM;
+	}
+
+	pci_read_config_byte(pdev, PCI_REVISION_ID, &rev_id);
+	if (pci_resource_len(pdev, 0) != PCI200SYN_PLX_SIZE ||
+	    pci_resource_len(pdev, 2) != PCI200SYN_SCA_SIZE ||
+	    pci_resource_len(pdev, 3) < 16384) {
+		printk(KERN_ERR "pci200syn: invalid card EEPROM parameters\n");
+		pci200_pci_remove_one(pdev);
+		return -EFAULT;
+	}
+
+	plxphys = pci_resource_start(pdev,0) & PCI_BASE_ADDRESS_MEM_MASK;
+	card->plxbase = ioremap(plxphys, PCI200SYN_PLX_SIZE);
+
+	scaphys = pci_resource_start(pdev,2) & PCI_BASE_ADDRESS_MEM_MASK;
+	card->scabase = ioremap(scaphys, PCI200SYN_SCA_SIZE);
+
+	ramphys = pci_resource_start(pdev,3) & PCI_BASE_ADDRESS_MEM_MASK;
+	card->rambase = ioremap(ramphys, pci_resource_len(pdev,3));
+
+	if (card->plxbase == NULL ||
+	    card->scabase == NULL ||
+	    card->rambase == NULL) {
+		printk(KERN_ERR "pci200syn: ioremap() failed\n");
+		pci200_pci_remove_one(pdev);
+	}
+
+	/* Reset PLX */
+	p = &card->plxbase->init_ctrl;
+	writel(readl(p) | 0x40000000, p);
+	readl(p);		/* Flush the write - do not use sca_flush */
+	udelay(1);
+
+	writel(readl(p) & ~0x40000000, p);
+	readl(p);		/* Flush the write - do not use sca_flush */
+	udelay(1);
+
+	ramsize = sca_detect_ram(card, card->rambase,
+				 pci_resource_len(pdev, 3));
+
+	/* number of TX + RX buffers for one port - this is dual port card */
+	i = ramsize / (2 * (sizeof(pkt_desc) + HDLC_MAX_MRU));
+	card->tx_ring_buffers = min(i / 2, MAX_TX_BUFFERS);
+	card->rx_ring_buffers = i - card->tx_ring_buffers;
+
+	card->buff_offset = 2 * sizeof(pkt_desc) * (card->tx_ring_buffers +
+						    card->rx_ring_buffers);
+
+	printk(KERN_INFO "pci200syn: %u KB RAM at 0x%x, IRQ%u, using %u TX +"
+	       " %u RX packets rings\n", ramsize / 1024, ramphys,
+	       pdev->irq, card->tx_ring_buffers, card->rx_ring_buffers);
+
+	if (card->tx_ring_buffers < 1) {
+		printk(KERN_ERR "pci200syn: RAM test failed\n");
+		pci200_pci_remove_one(pdev);
+		return -EFAULT;
+	}
+
+	/* Enable interrupts on the PCI bridge */
+	p = &card->plxbase->intr_ctrl_stat;
+	writew(readw(p) | 0x0040, p);
+
+	/* Allocate IRQ */
+	if(request_irq(pdev->irq, sca_intr, SA_SHIRQ, devname, card)) {
+		printk(KERN_WARNING "pci200syn: could not allocate IRQ%d.\n",
+		       pdev->irq);
+		pci200_pci_remove_one(pdev);
+		return -EBUSY;
+	}
+	card->irq = pdev->irq;
+
+	sca_init(card, 0);
+
+	for(i = 0; i < 2; i++) {
+		port_t *port = &card->ports[i];
+		struct net_device *dev = port_to_dev(port);
+		hdlc_device *hdlc = dev_to_hdlc(dev);
+		port->phy_node = i;
+
+		spin_lock_init(&port->lock);
+		SET_MODULE_OWNER(dev);
+		dev->irq = card->irq;
+		dev->mem_start = ramphys;
+		dev->mem_end = ramphys + ramsize - 1;
+		dev->tx_queue_len = 50;
+		dev->do_ioctl = pci200_ioctl;
+		dev->open = pci200_open;
+		dev->stop = pci200_close;
+		hdlc->attach = sca_attach;
+		hdlc->xmit = sca_xmit;
+		port->settings.clock_type = CLOCK_EXT;
+		port->card = card;
+		if(register_hdlc_device(dev)) {
+			printk(KERN_ERR "pci200syn: unable to register hdlc "
+			       "device\n");
+			port->card = NULL;
+			pci200_pci_remove_one(pdev);
+			return -ENOBUFS;
+		}
+		sca_init_sync_port(port);	/* Set up SCA memory */
+
+		printk(KERN_INFO "%s: PCI200SYN node %d\n",
+		       dev->name, port->phy_node);
+	}
+
+	sca_flush(card);
+	return 0;
+}
+
+
+
+static struct pci_device_id pci200_pci_tbl[] __devinitdata = {
+	{ PCI_VENDOR_ID_GORAMO, PCI_DEVICE_ID_PCI200SYN, PCI_ANY_ID,
+	  PCI_ANY_ID, 0, 0, 0 },
+	{ 0, }
+};
+
+
+static struct pci_driver pci200_pci_driver = {
+	.name		= "PCI200SYN",
+	.id_table	= pci200_pci_tbl,
+	.probe		= pci200_pci_init_one,
+	.remove		= pci200_pci_remove_one,
+};
+
+
+static int __init pci200_init_module(void)
+{
+#ifdef MODULE
+	printk(KERN_INFO "%s\n", version);
+#endif
+	if (pci_clock_freq < 1000000 || pci_clock_freq > 80000000) {
+		printk(KERN_ERR "pci200syn: Invalid PCI clock frequency\n");
+		return -EINVAL;
+	}
+	return pci_module_init(&pci200_pci_driver);
+}
+
+
+
+static void __exit pci200_cleanup_module(void)
+{
+	pci_unregister_driver(&pci200_pci_driver);
+}
+
+MODULE_AUTHOR("Krzysztof Halasa <khc@pm.waw.pl>");
+MODULE_DESCRIPTION("Goramo PCI200SYN serial port driver");
+MODULE_LICENSE("GPL v2");
+MODULE_DEVICE_TABLE(pci, pci200_pci_tbl);
+module_param(pci_clock_freq, int, 0444);
+MODULE_PARM_DESC(pci_clock_freq, "System PCI clock frequency in Hz");
+module_init(pci200_init_module);
+module_exit(pci200_cleanup_module);
diff --git a/drivers/net/wan/sbni.c b/drivers/net/wan/sbni.c
new file mode 100644
index 0000000..db2c798
--- /dev/null
+++ b/drivers/net/wan/sbni.c
@@ -0,0 +1,1735 @@
+/* sbni.c:  Granch SBNI12 leased line adapters driver for linux
+ *
+ *	Written 2001 by Denis I.Timofeev (timofeev@granch.ru)
+ *
+ *	Previous versions were written by Yaroslav Polyakov,
+ *	Alexey Zverev and Max Khon.
+ *
+ *	Driver supports SBNI12-02,-04,-05,-10,-11 cards, single and
+ *	double-channel, PCI and ISA modifications.
+ *	More info and useful utilities to work with SBNI12 cards you can find
+ *	at http://www.granch.com (English) or http://www.granch.ru (Russian)
+ *
+ *	This software may be used and distributed according to the terms
+ *	of the GNU General Public License.
+ *
+ *
+ *  5.0.1	Jun 22 2001
+ *	  - Fixed bug in probe
+ *  5.0.0	Jun 06 2001
+ *	  - Driver was completely redesigned by Denis I.Timofeev,
+ *	  - now PCI/Dual, ISA/Dual (with single interrupt line) models are
+ *	  - supported
+ *  3.3.0	Thu Feb 24 21:30:28 NOVT 2000 
+ *        - PCI cards support
+ *  3.2.0	Mon Dec 13 22:26:53 NOVT 1999
+ * 	  - Completely rebuilt all the packet storage system
+ * 	  -    to work in Ethernet-like style.
+ *  3.1.1	just fixed some bugs (5 aug 1999)
+ *  3.1.0	added balancing feature	(26 apr 1999)
+ *  3.0.1	just fixed some bugs (14 apr 1999).
+ *  3.0.0	Initial Revision, Yaroslav Polyakov (24 Feb 1999)
+ *        - added pre-calculation for CRC, fixed bug with "len-2" frames, 
+ *        - removed outbound fragmentation (MTU=1000), written CRC-calculation 
+ *        - on asm, added work with hard_headers and now we have our own cache 
+ *        - for them, optionally supported word-interchange on some chipsets,
+ * 
+ *	Known problem: this driver wasn't tested on multiprocessor machine.
+ */
+
+#include <linux/config.h>
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/ptrace.h>
+#include <linux/fcntl.h>
+#include <linux/ioport.h>
+#include <linux/interrupt.h>
+#include <linux/slab.h>
+#include <linux/string.h>
+#include <linux/errno.h>
+#include <linux/netdevice.h>
+#include <linux/etherdevice.h>
+#include <linux/pci.h>
+#include <linux/skbuff.h>
+#include <linux/timer.h>
+#include <linux/init.h>
+#include <linux/delay.h>
+
+#include <net/arp.h>
+
+#include <asm/io.h>
+#include <asm/types.h>
+#include <asm/byteorder.h>
+#include <asm/irq.h>
+#include <asm/uaccess.h>
+
+#include "sbni.h"
+
+/* device private data */
+
+struct net_local {
+	struct net_device_stats	stats;
+	struct timer_list	watchdog;
+
+	spinlock_t	lock;
+	struct sk_buff  *rx_buf_p;		/* receive buffer ptr */
+	struct sk_buff  *tx_buf_p;		/* transmit buffer ptr */
+	
+	unsigned int	framelen;		/* current frame length */
+	unsigned int	maxframe;		/* maximum valid frame length */
+	unsigned int	state;
+	unsigned int	inppos, outpos;		/* positions in rx/tx buffers */
+
+	/* transmitting frame number - from frames qty to 1 */
+	unsigned int	tx_frameno;
+
+	/* expected number of next receiving frame */
+	unsigned int	wait_frameno;
+
+	/* count of failed attempts to frame send - 32 attempts do before
+	   error - while receiver tunes on opposite side of wire */
+	unsigned int	trans_errors;
+
+	/* idle time; send pong when limit exceeded */
+	unsigned int	timer_ticks;
+
+	/* fields used for receive level autoselection */
+	int	delta_rxl;
+	unsigned int	cur_rxl_index, timeout_rxl;
+	unsigned long	cur_rxl_rcvd, prev_rxl_rcvd;
+
+	struct sbni_csr1	csr1;		/* current value of CSR1 */
+	struct sbni_in_stats	in_stats; 	/* internal statistics */ 
+
+	struct net_device		*second;	/* for ISA/dual cards */
+
+#ifdef CONFIG_SBNI_MULTILINE
+	struct net_device		*master;
+	struct net_device		*link;
+#endif
+};
+
+
+static int  sbni_card_probe( unsigned long );
+static int  sbni_pci_probe( struct net_device  * );
+static struct net_device  *sbni_probe1(struct net_device *, unsigned long, int);
+static int  sbni_open( struct net_device * );
+static int  sbni_close( struct net_device * );
+static int  sbni_start_xmit( struct sk_buff *, struct net_device * );
+static int  sbni_ioctl( struct net_device *, struct ifreq *, int );
+static struct net_device_stats  *sbni_get_stats( struct net_device * );
+static void  set_multicast_list( struct net_device * );
+
+static irqreturn_t sbni_interrupt( int, void *, struct pt_regs * );
+static void  handle_channel( struct net_device * );
+static int   recv_frame( struct net_device * );
+static void  send_frame( struct net_device * );
+static int   upload_data( struct net_device *,
+			  unsigned, unsigned, unsigned, u32 );
+static void  download_data( struct net_device *, u32 * );
+static void  sbni_watchdog( unsigned long );
+static void  interpret_ack( struct net_device *, unsigned );
+static int   append_frame_to_pkt( struct net_device *, unsigned, u32 );
+static void  indicate_pkt( struct net_device * );
+static void  card_start( struct net_device * );
+static void  prepare_to_send( struct sk_buff *, struct net_device * );
+static void  drop_xmit_queue( struct net_device * );
+static void  send_frame_header( struct net_device *, u32 * );
+static int   skip_tail( unsigned int, unsigned int, u32 );
+static int   check_fhdr( u32, u32 *, u32 *, u32 *, u32 *, u32 * );
+static void  change_level( struct net_device * );
+static void  timeout_change_level( struct net_device * );
+static u32   calc_crc32( u32, u8 *, u32 );
+static struct sk_buff *  get_rx_buf( struct net_device * );
+static int  sbni_init( struct net_device * );
+
+#ifdef CONFIG_SBNI_MULTILINE
+static int  enslave( struct net_device *, struct net_device * );
+static int  emancipate( struct net_device * );
+#endif
+
+#ifdef __i386__
+#define ASM_CRC 1
+#endif
+
+static const char  version[] =
+	"Granch SBNI12 driver ver 5.0.1  Jun 22 2001  Denis I.Timofeev.\n";
+
+static int  skip_pci_probe	__initdata = 0;
+static int  scandone	__initdata = 0;
+static int  num		__initdata = 0;
+
+static unsigned char  rxl_tab[];
+static u32  crc32tab[];
+
+/* A list of all installed devices, for removing the driver module. */
+static struct net_device  *sbni_cards[ SBNI_MAX_NUM_CARDS ];
+
+/* Lists of device's parameters */
+static u32	io[   SBNI_MAX_NUM_CARDS ] __initdata =
+	{ [0 ... SBNI_MAX_NUM_CARDS-1] = -1 };
+static u32	irq[  SBNI_MAX_NUM_CARDS ] __initdata;
+static u32	baud[ SBNI_MAX_NUM_CARDS ] __initdata;
+static u32	rxl[  SBNI_MAX_NUM_CARDS ] __initdata =
+	{ [0 ... SBNI_MAX_NUM_CARDS-1] = -1 };
+static u32	mac[  SBNI_MAX_NUM_CARDS ] __initdata;
+
+#ifndef MODULE
+typedef u32  iarr[];
+static iarr __initdata *dest[5] = { &io, &irq, &baud, &rxl, &mac };
+#endif
+
+/* A zero-terminated list of I/O addresses to be probed on ISA bus */
+static unsigned int  netcard_portlist[ ] __initdata = { 
+	0x210, 0x214, 0x220, 0x224, 0x230, 0x234, 0x240, 0x244, 0x250, 0x254,
+	0x260, 0x264, 0x270, 0x274, 0x280, 0x284, 0x290, 0x294, 0x2a0, 0x2a4,
+	0x2b0, 0x2b4, 0x2c0, 0x2c4, 0x2d0, 0x2d4, 0x2e0, 0x2e4, 0x2f0, 0x2f4,
+	0 };
+
+
+/*
+ * Look for SBNI card which addr stored in dev->base_addr, if nonzero.
+ * Otherwise, look through PCI bus. If none PCI-card was found, scan ISA.
+ */
+
+static inline int __init
+sbni_isa_probe( struct net_device  *dev )
+{
+	if( dev->base_addr > 0x1ff
+	    &&  request_region( dev->base_addr, SBNI_IO_EXTENT, dev->name )
+	    &&  sbni_probe1( dev, dev->base_addr, dev->irq ) )
+
+		return  0;
+	else {
+		printk( KERN_ERR "sbni: base address 0x%lx is busy, or adapter "
+			"is malfunctional!\n", dev->base_addr );
+		return  -ENODEV;
+	}
+}
+
+static void __init sbni_devsetup(struct net_device *dev)
+{
+	ether_setup( dev );
+	dev->open		= &sbni_open;
+	dev->stop		= &sbni_close;
+	dev->hard_start_xmit	= &sbni_start_xmit;
+	dev->get_stats		= &sbni_get_stats;
+	dev->set_multicast_list	= &set_multicast_list;
+	dev->do_ioctl		= &sbni_ioctl;
+
+	SET_MODULE_OWNER( dev );
+}
+
+int __init sbni_probe(int unit)
+{
+	struct net_device *dev;
+	static unsigned  version_printed __initdata = 0;
+	int err;
+
+	dev = alloc_netdev(sizeof(struct net_local), "sbni", sbni_devsetup);
+	if (!dev)
+		return -ENOMEM;
+
+	sprintf(dev->name, "sbni%d", unit);
+	netdev_boot_setup_check(dev);
+
+	err = sbni_init(dev);
+	if (err) {
+		free_netdev(dev);
+		return err;
+	}
+
+	err = register_netdev(dev);
+	if (err) {
+		release_region( dev->base_addr, SBNI_IO_EXTENT );
+		free_netdev(dev);
+		return err;
+	}
+	if( version_printed++ == 0 )
+		printk( KERN_INFO "%s", version );
+	return 0;
+}
+
+static int __init sbni_init(struct net_device *dev)
+{
+	int  i;
+	if( dev->base_addr )
+		return  sbni_isa_probe( dev );
+	/* otherwise we have to perform search our adapter */
+
+	if( io[ num ] != -1 )
+		dev->base_addr	= io[ num ],
+		dev->irq	= irq[ num ];
+	else if( scandone  ||  io[ 0 ] != -1 )
+		return  -ENODEV;
+
+	/* if io[ num ] contains non-zero address, then that is on ISA bus */
+	if( dev->base_addr )
+		return  sbni_isa_probe( dev );
+
+	/* ...otherwise - scan PCI first */
+	if( !skip_pci_probe  &&  !sbni_pci_probe( dev ) )
+		return  0;
+
+	if( io[ num ] == -1 ) {
+		/* Auto-scan will be stopped when first ISA card were found */
+		scandone = 1;
+		if( num > 0 )
+			return  -ENODEV;
+	}
+
+	for( i = 0;  netcard_portlist[ i ];  ++i ) {
+		int  ioaddr = netcard_portlist[ i ];
+		if( request_region( ioaddr, SBNI_IO_EXTENT, dev->name )
+		    &&  sbni_probe1( dev, ioaddr, 0 ))
+			return 0;
+	}
+
+	return  -ENODEV;
+}
+
+
+int __init
+sbni_pci_probe( struct net_device  *dev )
+{
+	struct pci_dev  *pdev = NULL;
+
+	while( (pdev = pci_get_class( PCI_CLASS_NETWORK_OTHER << 8, pdev ))
+	       != NULL ) {
+		int  pci_irq_line;
+		unsigned long  pci_ioaddr;
+		u16  subsys;
+
+		if( pdev->vendor != SBNI_PCI_VENDOR
+		    &&  pdev->device != SBNI_PCI_DEVICE )
+				continue;
+
+		pci_ioaddr = pci_resource_start( pdev, 0 );
+		pci_irq_line = pdev->irq;
+
+		/* Avoid already found cards from previous calls */
+		if( !request_region( pci_ioaddr, SBNI_IO_EXTENT, dev->name ) ) {
+			pci_read_config_word( pdev, PCI_SUBSYSTEM_ID, &subsys );
+
+			if (subsys != 2)
+				continue;
+
+			/* Dual adapter is present */
+			if (!request_region(pci_ioaddr += 4, SBNI_IO_EXTENT,
+							dev->name ) )
+				continue;
+		}
+
+		if( pci_irq_line <= 0  ||  pci_irq_line >= NR_IRQS )
+			printk( KERN_WARNING "  WARNING: The PCI BIOS assigned "
+				"this PCI card to IRQ %d, which is unlikely "
+				"to work!.\n"
+				KERN_WARNING " You should use the PCI BIOS "
+				"setup to assign a valid IRQ line.\n",
+				pci_irq_line );
+
+		/* avoiding re-enable dual adapters */
+		if( (pci_ioaddr & 7) == 0  &&  pci_enable_device( pdev ) ) {
+			release_region( pci_ioaddr, SBNI_IO_EXTENT );
+			pci_dev_put( pdev );
+			return  -EIO;
+		}
+		if( sbni_probe1( dev, pci_ioaddr, pci_irq_line ) ) {
+			SET_NETDEV_DEV(dev, &pdev->dev);
+			/* not the best thing to do, but this is all messed up 
+			   for hotplug systems anyway... */
+			pci_dev_put( pdev );
+			return  0;
+		}
+	}
+	return  -ENODEV;
+}
+
+
+static struct net_device * __init
+sbni_probe1( struct net_device  *dev,  unsigned long  ioaddr,  int  irq )
+{
+	struct net_local  *nl;
+
+	if( sbni_card_probe( ioaddr ) ) {
+		release_region( ioaddr, SBNI_IO_EXTENT );
+		return NULL;
+	}
+
+	outb( 0, ioaddr + CSR0 );
+
+	if( irq < 2 ) {
+		unsigned long irq_mask;
+
+		irq_mask = probe_irq_on();
+		outb( EN_INT | TR_REQ, ioaddr + CSR0 );
+		outb( PR_RES, ioaddr + CSR1 );
+		mdelay(50);
+		irq = probe_irq_off(irq_mask);
+		outb( 0, ioaddr + CSR0 );
+
+		if( !irq ) {
+			printk( KERN_ERR "%s: can't detect device irq!\n",
+				dev->name );
+			release_region( ioaddr, SBNI_IO_EXTENT );
+			return NULL;
+		}
+	} else if( irq == 2 )
+		irq = 9;
+
+	dev->irq = irq;
+	dev->base_addr = ioaddr;
+
+	/* Allocate dev->priv and fill in sbni-specific dev fields. */
+	nl = dev->priv;
+	if( !nl ) {
+		printk( KERN_ERR "%s: unable to get memory!\n", dev->name );
+		release_region( ioaddr, SBNI_IO_EXTENT );
+		return NULL;
+	}
+
+	dev->priv = nl;
+	memset( nl, 0, sizeof(struct net_local) );
+	spin_lock_init( &nl->lock );
+
+	/* store MAC address (generate if that isn't known) */
+	*(u16 *)dev->dev_addr = htons( 0x00ff );
+	*(u32 *)(dev->dev_addr + 2) = htonl( 0x01000000 |
+		( (mac[num]  ?  mac[num]  :  (u32)((long)dev->priv)) & 0x00ffffff) );
+
+	/* store link settings (speed, receive level ) */
+	nl->maxframe  = DEFAULT_FRAME_LEN;
+	nl->csr1.rate = baud[ num ];
+
+	if( (nl->cur_rxl_index = rxl[ num ]) == -1 )
+		/* autotune rxl */
+		nl->cur_rxl_index = DEF_RXL,
+		nl->delta_rxl = DEF_RXL_DELTA;
+	else
+		nl->delta_rxl = 0;
+	nl->csr1.rxl  = rxl_tab[ nl->cur_rxl_index ];
+	if( inb( ioaddr + CSR0 ) & 0x01 )
+		nl->state |= FL_SLOW_MODE;
+
+	printk( KERN_NOTICE "%s: ioaddr %#lx, irq %d, "
+		"MAC: 00:ff:01:%02x:%02x:%02x\n", 
+		dev->name, dev->base_addr, dev->irq,
+		((u8 *) dev->dev_addr) [3],
+		((u8 *) dev->dev_addr) [4],
+		((u8 *) dev->dev_addr) [5] );
+
+	printk( KERN_NOTICE "%s: speed %d, receive level ", dev->name,
+		( (nl->state & FL_SLOW_MODE)  ?  500000 : 2000000)
+		/ (1 << nl->csr1.rate) );
+
+	if( nl->delta_rxl == 0 )
+		printk( "0x%x (fixed)\n", nl->cur_rxl_index ); 
+	else
+		printk( "(auto)\n");
+
+#ifdef CONFIG_SBNI_MULTILINE
+	nl->master = dev;
+	nl->link   = NULL;
+#endif
+   
+	sbni_cards[ num++ ] = dev;
+	return  dev;
+}
+
+/* -------------------------------------------------------------------------- */
+
+#ifdef CONFIG_SBNI_MULTILINE
+
+static int
+sbni_start_xmit( struct sk_buff  *skb,  struct net_device  *dev )
+{
+	struct net_device  *p;
+
+	netif_stop_queue( dev );
+
+	/* Looking for idle device in the list */
+	for( p = dev;  p; ) {
+		struct net_local  *nl = (struct net_local *) p->priv;
+		spin_lock( &nl->lock );
+		if( nl->tx_buf_p  ||  (nl->state & FL_LINE_DOWN) ) {
+			p = nl->link;
+			spin_unlock( &nl->lock );
+		} else {
+			/* Idle dev is found */
+			prepare_to_send( skb, p );
+			spin_unlock( &nl->lock );
+			netif_start_queue( dev );
+			return  0;
+		}
+	}
+
+	return  1;
+}
+
+#else	/* CONFIG_SBNI_MULTILINE */
+
+static int
+sbni_start_xmit( struct sk_buff  *skb,  struct net_device  *dev )
+{
+	struct net_local  *nl  = (struct net_local *) dev->priv;
+
+	netif_stop_queue( dev );
+	spin_lock( &nl->lock );
+
+	prepare_to_send( skb, dev );
+
+	spin_unlock( &nl->lock );
+	return  0;
+}
+
+#endif	/* CONFIG_SBNI_MULTILINE */
+
+/* -------------------------------------------------------------------------- */
+
+/* interrupt handler */
+
+/*
+ * 	SBNI12D-10, -11/ISA boards within "common interrupt" mode could not
+ * be looked as two independent single-channel devices. Every channel seems
+ * as Ethernet interface but interrupt handler must be common. Really, first
+ * channel ("master") driver only registers the handler. In its struct net_local
+ * it has got pointer to "slave" channel's struct net_local and handles that's
+ * interrupts too.
+ *	dev of successfully attached ISA SBNI boards is linked to list.
+ * While next board driver is initialized, it scans this list. If one
+ * has found dev with same irq and ioaddr different by 4 then it assumes
+ * this board to be "master".
+ */ 
+
+static irqreturn_t
+sbni_interrupt( int  irq,  void  *dev_id,  struct pt_regs  *regs )
+{
+	struct net_device	  *dev = (struct net_device *) dev_id;
+	struct net_local  *nl  = (struct net_local *) dev->priv;
+	int	repeat;
+
+	spin_lock( &nl->lock );
+	if( nl->second )
+		spin_lock( &((struct net_local *) nl->second->priv)->lock );
+
+	do {
+		repeat = 0;
+		if( inb( dev->base_addr + CSR0 ) & (RC_RDY | TR_RDY) )
+			handle_channel( dev ),
+			repeat = 1;
+		if( nl->second  && 	/* second channel present */
+		    (inb( nl->second->base_addr+CSR0 ) & (RC_RDY | TR_RDY)) )
+			handle_channel( nl->second ),
+			repeat = 1;
+	} while( repeat );
+
+	if( nl->second )
+		spin_unlock( &((struct net_local *)nl->second->priv)->lock );
+	spin_unlock( &nl->lock );
+	return IRQ_HANDLED;
+}
+
+
+static void
+handle_channel( struct net_device  *dev )
+{
+	struct net_local	*nl    = (struct net_local *) dev->priv;
+	unsigned long		ioaddr = dev->base_addr;
+
+	int  req_ans;
+	unsigned char  csr0;
+
+#ifdef CONFIG_SBNI_MULTILINE
+	/* Lock the master device because we going to change its local data */
+	if( nl->state & FL_SLAVE )
+		spin_lock( &((struct net_local *) nl->master->priv)->lock );
+#endif
+
+	outb( (inb( ioaddr + CSR0 ) & ~EN_INT) | TR_REQ, ioaddr + CSR0 );
+
+	nl->timer_ticks = CHANGE_LEVEL_START_TICKS;
+	for(;;) {
+		csr0 = inb( ioaddr + CSR0 );
+		if( ( csr0 & (RC_RDY | TR_RDY) ) == 0 )
+			break;
+
+		req_ans = !(nl->state & FL_PREV_OK);
+
+		if( csr0 & RC_RDY )
+			req_ans = recv_frame( dev );
+
+		/*
+		 * TR_RDY always equals 1 here because we have owned the marker,
+		 * and we set TR_REQ when disabled interrupts
+		 */
+		csr0 = inb( ioaddr + CSR0 );
+		if( !(csr0 & TR_RDY)  ||  (csr0 & RC_RDY) )
+			printk( KERN_ERR "%s: internal error!\n", dev->name );
+
+		/* if state & FL_NEED_RESEND != 0 then tx_frameno != 0 */
+		if( req_ans  ||  nl->tx_frameno != 0 )
+			send_frame( dev );
+		else
+			/* send marker without any data */
+			outb( inb( ioaddr + CSR0 ) & ~TR_REQ, ioaddr + CSR0 );
+	}
+
+	outb( inb( ioaddr + CSR0 ) | EN_INT, ioaddr + CSR0 );
+
+#ifdef CONFIG_SBNI_MULTILINE
+	if( nl->state & FL_SLAVE )
+		spin_unlock( &((struct net_local *) nl->master->priv)->lock );
+#endif
+}
+
+
+/*
+ * Routine returns 1 if it need to acknoweledge received frame.
+ * Empty frame received without errors won't be acknoweledged.
+ */
+
+static int
+recv_frame( struct net_device  *dev )
+{
+	struct net_local  *nl   = (struct net_local *) dev->priv;
+	unsigned long  ioaddr	= dev->base_addr;
+
+	u32  crc = CRC32_INITIAL;
+
+	unsigned  framelen, frameno, ack;
+	unsigned  is_first, frame_ok;
+
+	if( check_fhdr( ioaddr, &framelen, &frameno, &ack, &is_first, &crc ) ) {
+		frame_ok = framelen > 4
+			?  upload_data( dev, framelen, frameno, is_first, crc )
+			:  skip_tail( ioaddr, framelen, crc );
+		if( frame_ok )
+			interpret_ack( dev, ack );
+	} else
+		frame_ok = 0;
+
+	outb( inb( ioaddr + CSR0 ) ^ CT_ZER, ioaddr + CSR0 );
+	if( frame_ok ) {
+		nl->state |= FL_PREV_OK;
+		if( framelen > 4 )
+			nl->in_stats.all_rx_number++;
+	} else
+		nl->state &= ~FL_PREV_OK,
+		change_level( dev ),
+		nl->in_stats.all_rx_number++,
+		nl->in_stats.bad_rx_number++;
+
+	return  !frame_ok  ||  framelen > 4;
+}
+
+
+static void
+send_frame( struct net_device  *dev )
+{
+	struct net_local  *nl    = (struct net_local *) dev->priv;
+
+	u32  crc = CRC32_INITIAL;
+
+	if( nl->state & FL_NEED_RESEND ) {
+
+		/* if frame was sended but not ACK'ed - resend it */
+		if( nl->trans_errors ) {
+			--nl->trans_errors;
+			if( nl->framelen != 0 )
+				nl->in_stats.resend_tx_number++;
+		} else {
+			/* cannot xmit with many attempts */
+#ifdef CONFIG_SBNI_MULTILINE
+			if( (nl->state & FL_SLAVE)  ||  nl->link )
+#endif
+			nl->state |= FL_LINE_DOWN;
+			drop_xmit_queue( dev );
+			goto  do_send;
+		}
+	} else
+		nl->trans_errors = TR_ERROR_COUNT;
+
+	send_frame_header( dev, &crc );
+	nl->state |= FL_NEED_RESEND;
+	/*
+	 * FL_NEED_RESEND will be cleared after ACK, but if empty
+	 * frame sended then in prepare_to_send next frame
+	 */
+
+
+	if( nl->framelen ) {
+		download_data( dev, &crc );
+		nl->in_stats.all_tx_number++;
+		nl->state |= FL_WAIT_ACK;
+	}
+
+	outsb( dev->base_addr + DAT, (u8 *)&crc, sizeof crc );
+
+do_send:
+	outb( inb( dev->base_addr + CSR0 ) & ~TR_REQ, dev->base_addr + CSR0 );
+
+	if( nl->tx_frameno )
+		/* next frame exists - we request card to send it */
+		outb( inb( dev->base_addr + CSR0 ) | TR_REQ,
+		      dev->base_addr + CSR0 );
+}
+
+
+/*
+ * Write the frame data into adapter's buffer memory, and calculate CRC.
+ * Do padding if necessary.
+ */
+
+static void
+download_data( struct net_device  *dev,  u32  *crc_p )
+{
+	struct net_local  *nl    = (struct net_local *) dev->priv;
+	struct sk_buff    *skb	 = nl->tx_buf_p;
+
+	unsigned  len = min_t(unsigned int, skb->len - nl->outpos, nl->framelen);
+
+	outsb( dev->base_addr + DAT, skb->data + nl->outpos, len );
+	*crc_p = calc_crc32( *crc_p, skb->data + nl->outpos, len );
+
+	/* if packet too short we should write some more bytes to pad */
+	for( len = nl->framelen - len;  len--; )
+		outb( 0, dev->base_addr + DAT ),
+		*crc_p = CRC32( 0, *crc_p );
+}
+
+
+static int
+upload_data( struct net_device  *dev,  unsigned  framelen,  unsigned  frameno,
+	     unsigned  is_first,  u32  crc )
+{
+	struct net_local  *nl = (struct net_local *) dev->priv;
+
+	int  frame_ok;
+
+	if( is_first )
+		nl->wait_frameno = frameno,
+		nl->inppos = 0;
+
+	if( nl->wait_frameno == frameno ) {
+
+		if( nl->inppos + framelen  <=  ETHER_MAX_LEN )
+			frame_ok = append_frame_to_pkt( dev, framelen, crc );
+
+		/*
+		 * if CRC is right but framelen incorrect then transmitter
+		 * error was occurred... drop entire packet
+		 */
+		else if( (frame_ok = skip_tail( dev->base_addr, framelen, crc ))
+			 != 0 )
+			nl->wait_frameno = 0,
+			nl->inppos = 0,
+#ifdef CONFIG_SBNI_MULTILINE
+			((struct net_local *) nl->master->priv)
+				->stats.rx_errors++,
+			((struct net_local *) nl->master->priv)
+				->stats.rx_missed_errors++;
+#else
+			nl->stats.rx_errors++,
+			nl->stats.rx_missed_errors++;
+#endif
+			/* now skip all frames until is_first != 0 */
+	} else
+		frame_ok = skip_tail( dev->base_addr, framelen, crc );
+
+	if( is_first  &&  !frame_ok )
+		/*
+		 * Frame has been broken, but we had already stored
+		 * is_first... Drop entire packet.
+		 */
+		nl->wait_frameno = 0,
+#ifdef CONFIG_SBNI_MULTILINE
+		((struct net_local *) nl->master->priv)->stats.rx_errors++,
+		((struct net_local *) nl->master->priv)->stats.rx_crc_errors++;
+#else
+		nl->stats.rx_errors++,
+		nl->stats.rx_crc_errors++;
+#endif
+
+	return  frame_ok;
+}
+
+
+static __inline void
+send_complete( struct net_local  *nl )
+{
+#ifdef CONFIG_SBNI_MULTILINE
+	((struct net_local *) nl->master->priv)->stats.tx_packets++;
+	((struct net_local *) nl->master->priv)->stats.tx_bytes
+		+= nl->tx_buf_p->len;
+#else
+	nl->stats.tx_packets++;
+	nl->stats.tx_bytes += nl->tx_buf_p->len;
+#endif
+	dev_kfree_skb_irq( nl->tx_buf_p );
+
+	nl->tx_buf_p = NULL;
+
+	nl->outpos = 0;
+	nl->state &= ~(FL_WAIT_ACK | FL_NEED_RESEND);
+	nl->framelen   = 0;
+}
+
+
+static void
+interpret_ack( struct net_device  *dev,  unsigned  ack )
+{
+	struct net_local  *nl = (struct net_local *) dev->priv;
+
+	if( ack == FRAME_SENT_OK ) {
+		nl->state &= ~FL_NEED_RESEND;
+
+		if( nl->state & FL_WAIT_ACK ) {
+			nl->outpos += nl->framelen;
+
+			if( --nl->tx_frameno )
+				nl->framelen = min_t(unsigned int,
+						   nl->maxframe,
+						   nl->tx_buf_p->len - nl->outpos);
+			else
+				send_complete( nl ),
+#ifdef CONFIG_SBNI_MULTILINE
+				netif_wake_queue( nl->master );
+#else
+				netif_wake_queue( dev );
+#endif
+		}
+	}
+
+	nl->state &= ~FL_WAIT_ACK;
+}
+
+
+/*
+ * Glue received frame with previous fragments of packet.
+ * Indicate packet when last frame would be accepted.
+ */
+
+static int
+append_frame_to_pkt( struct net_device  *dev,  unsigned  framelen,  u32  crc )
+{
+	struct net_local  *nl = (struct net_local *) dev->priv;
+
+	u8  *p;
+
+	if( nl->inppos + framelen  >  ETHER_MAX_LEN )
+		return  0;
+
+	if( !nl->rx_buf_p  &&  !(nl->rx_buf_p = get_rx_buf( dev )) )
+		return  0;
+
+	p = nl->rx_buf_p->data + nl->inppos;
+	insb( dev->base_addr + DAT, p, framelen );
+	if( calc_crc32( crc, p, framelen ) != CRC32_REMAINDER )
+		return  0;
+
+	nl->inppos += framelen - 4;
+	if( --nl->wait_frameno == 0 )		/* last frame received */
+		indicate_pkt( dev );
+
+	return  1;
+}
+
+
+/*
+ * Prepare to start output on adapter.
+ * Transmitter will be actually activated when marker is accepted.
+ */
+
+static void
+prepare_to_send( struct sk_buff  *skb,  struct net_device  *dev )
+{
+	struct net_local  *nl = (struct net_local *) dev->priv;
+
+	unsigned int  len;
+
+	/* nl->tx_buf_p == NULL here! */
+	if( nl->tx_buf_p )
+		printk( KERN_ERR "%s: memory leak!\n", dev->name );
+
+	nl->outpos = 0;
+	nl->state &= ~(FL_WAIT_ACK | FL_NEED_RESEND);
+
+	len = skb->len;
+	if( len < SBNI_MIN_LEN )
+		len = SBNI_MIN_LEN;
+
+	nl->tx_buf_p	= skb;
+	nl->tx_frameno	= (len + nl->maxframe - 1) / nl->maxframe;
+	nl->framelen	= len < nl->maxframe  ?  len  :  nl->maxframe;
+
+	outb( inb( dev->base_addr + CSR0 ) | TR_REQ,  dev->base_addr + CSR0 );
+#ifdef CONFIG_SBNI_MULTILINE
+	nl->master->trans_start = jiffies;
+#else
+	dev->trans_start = jiffies;
+#endif
+}
+
+
+static void
+drop_xmit_queue( struct net_device  *dev )
+{
+	struct net_local  *nl = (struct net_local *) dev->priv;
+
+	if( nl->tx_buf_p )
+		dev_kfree_skb_any( nl->tx_buf_p ),
+		nl->tx_buf_p = NULL,
+#ifdef CONFIG_SBNI_MULTILINE
+		((struct net_local *) nl->master->priv)
+			->stats.tx_errors++,
+		((struct net_local *) nl->master->priv)
+			->stats.tx_carrier_errors++;
+#else
+		nl->stats.tx_errors++,
+		nl->stats.tx_carrier_errors++;
+#endif
+
+	nl->tx_frameno	= 0;
+	nl->framelen	= 0;
+	nl->outpos	= 0;
+	nl->state &= ~(FL_WAIT_ACK | FL_NEED_RESEND);
+#ifdef CONFIG_SBNI_MULTILINE
+	netif_start_queue( nl->master );
+	nl->master->trans_start = jiffies;
+#else
+	netif_start_queue( dev );
+	dev->trans_start = jiffies;
+#endif
+}
+
+
+static void
+send_frame_header( struct net_device  *dev,  u32  *crc_p )
+{
+	struct net_local  *nl  = (struct net_local *) dev->priv;
+
+	u32  crc = *crc_p;
+	u32  len_field = nl->framelen + 6;	/* CRC + frameno + reserved */
+	u8   value;
+
+	if( nl->state & FL_NEED_RESEND )
+		len_field |= FRAME_RETRY;	/* non-first attempt... */
+
+	if( nl->outpos == 0 )
+		len_field |= FRAME_FIRST;
+
+	len_field |= (nl->state & FL_PREV_OK) ? FRAME_SENT_OK : FRAME_SENT_BAD;
+	outb( SBNI_SIG, dev->base_addr + DAT );
+
+	value = (u8) len_field;
+	outb( value, dev->base_addr + DAT );
+	crc = CRC32( value, crc );
+	value = (u8) (len_field >> 8);
+	outb( value, dev->base_addr + DAT );
+	crc = CRC32( value, crc );
+
+	outb( nl->tx_frameno, dev->base_addr + DAT );
+	crc = CRC32( nl->tx_frameno, crc );
+	outb( 0, dev->base_addr + DAT );
+	crc = CRC32( 0, crc );
+	*crc_p = crc;
+}
+
+
+/*
+ * if frame tail not needed (incorrect number or received twice),
+ * it won't store, but CRC will be calculated
+ */
+
+static int
+skip_tail( unsigned int  ioaddr,  unsigned int  tail_len,  u32 crc )
+{
+	while( tail_len-- )
+		crc = CRC32( inb( ioaddr + DAT ), crc );
+
+	return  crc == CRC32_REMAINDER;
+}
+
+
+/*
+ * Preliminary checks if frame header is correct, calculates its CRC
+ * and split it to simple fields
+ */
+
+static int
+check_fhdr( u32  ioaddr,  u32  *framelen,  u32  *frameno,  u32  *ack,
+	    u32  *is_first,  u32  *crc_p )
+{
+	u32  crc = *crc_p;
+	u8   value;
+
+	if( inb( ioaddr + DAT ) != SBNI_SIG )
+		return  0;
+
+	value = inb( ioaddr + DAT );
+	*framelen = (u32)value;
+	crc = CRC32( value, crc );
+	value = inb( ioaddr + DAT );
+	*framelen |= ((u32)value) << 8;
+	crc = CRC32( value, crc );
+
+	*ack = *framelen & FRAME_ACK_MASK;
+	*is_first = (*framelen & FRAME_FIRST) != 0;
+
+	if( (*framelen &= FRAME_LEN_MASK) < 6
+	    ||  *framelen > SBNI_MAX_FRAME - 3 )
+		return  0;
+
+	value = inb( ioaddr + DAT );
+	*frameno = (u32)value;
+	crc = CRC32( value, crc );
+
+	crc = CRC32( inb( ioaddr + DAT ), crc );	/* reserved byte */
+	*framelen -= 2;
+
+	*crc_p = crc;
+	return  1;
+}
+
+
+static struct sk_buff *
+get_rx_buf( struct net_device  *dev )
+{
+	/* +2 is to compensate for the alignment fixup below */
+	struct sk_buff  *skb = dev_alloc_skb( ETHER_MAX_LEN + 2 );
+	if( !skb )
+		return  NULL;
+
+#ifdef CONFIG_SBNI_MULTILINE
+	skb->dev = ((struct net_local *) dev->priv)->master;
+#else
+	skb->dev = dev;
+#endif
+	skb_reserve( skb, 2 );		/* Align IP on longword boundaries */
+	return  skb;
+}
+
+
+static void
+indicate_pkt( struct net_device  *dev )
+{
+	struct net_local  *nl  = (struct net_local *) dev->priv;
+	struct sk_buff    *skb = nl->rx_buf_p;
+
+	skb_put( skb, nl->inppos );
+
+#ifdef CONFIG_SBNI_MULTILINE
+	skb->protocol = eth_type_trans( skb, nl->master );
+	netif_rx( skb );
+	dev->last_rx = jiffies;
+	++((struct net_local *) nl->master->priv)->stats.rx_packets;
+	((struct net_local *) nl->master->priv)->stats.rx_bytes += nl->inppos;
+#else
+	skb->protocol = eth_type_trans( skb, dev );
+	netif_rx( skb );
+	dev->last_rx = jiffies;
+	++nl->stats.rx_packets;
+	nl->stats.rx_bytes += nl->inppos;
+#endif
+	nl->rx_buf_p = NULL;	/* protocol driver will clear this sk_buff */
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+/*
+ * Routine checks periodically wire activity and regenerates marker if
+ * connect was inactive for a long time.
+ */
+
+static void
+sbni_watchdog( unsigned long  arg )
+{
+	struct net_device  *dev = (struct net_device *) arg;
+	struct net_local   *nl  = (struct net_local *) dev->priv;
+	struct timer_list  *w   = &nl->watchdog; 
+	unsigned long	   flags;
+	unsigned char	   csr0;
+
+	spin_lock_irqsave( &nl->lock, flags );
+
+	csr0 = inb( dev->base_addr + CSR0 );
+	if( csr0 & RC_CHK ) {
+
+		if( nl->timer_ticks ) {
+			if( csr0 & (RC_RDY | BU_EMP) )
+				/* receiving not active */
+				nl->timer_ticks--;
+		} else {
+			nl->in_stats.timeout_number++;
+			if( nl->delta_rxl )
+				timeout_change_level( dev );
+
+			outb( *(u_char *)&nl->csr1 | PR_RES,
+			      dev->base_addr + CSR1 );
+			csr0 = inb( dev->base_addr + CSR0 );
+		}
+	} else
+		nl->state &= ~FL_LINE_DOWN;
+
+	outb( csr0 | RC_CHK, dev->base_addr + CSR0 ); 
+
+	init_timer( w );
+	w->expires	= jiffies + SBNI_TIMEOUT;
+	w->data		= arg;
+	w->function	= sbni_watchdog;
+	add_timer( w );
+
+	spin_unlock_irqrestore( &nl->lock, flags );
+}
+
+
+static unsigned char  rxl_tab[] = {
+	0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x08,
+	0x0a, 0x0c, 0x0f, 0x16, 0x18, 0x1a, 0x1c, 0x1f
+};
+
+#define SIZE_OF_TIMEOUT_RXL_TAB 4
+static unsigned char  timeout_rxl_tab[] = {
+	0x03, 0x05, 0x08, 0x0b
+};
+
+/* -------------------------------------------------------------------------- */
+
+static void
+card_start( struct net_device  *dev )
+{
+	struct net_local  *nl = (struct net_local *) dev->priv;
+
+	nl->timer_ticks = CHANGE_LEVEL_START_TICKS;
+	nl->state &= ~(FL_WAIT_ACK | FL_NEED_RESEND);
+	nl->state |= FL_PREV_OK;
+
+	nl->inppos = nl->outpos = 0;
+	nl->wait_frameno = 0;
+	nl->tx_frameno	 = 0;
+	nl->framelen	 = 0;
+
+	outb( *(u_char *)&nl->csr1 | PR_RES, dev->base_addr + CSR1 );
+	outb( EN_INT, dev->base_addr + CSR0 );
+}
+
+/* -------------------------------------------------------------------------- */
+
+/* Receive level auto-selection */
+
+static void
+change_level( struct net_device  *dev )
+{
+	struct net_local  *nl = (struct net_local *) dev->priv;
+
+	if( nl->delta_rxl == 0 )	/* do not auto-negotiate RxL */
+		return;
+
+	if( nl->cur_rxl_index == 0 )
+		nl->delta_rxl = 1;
+	else if( nl->cur_rxl_index == 15 )
+		nl->delta_rxl = -1;
+	else if( nl->cur_rxl_rcvd < nl->prev_rxl_rcvd )
+		nl->delta_rxl = -nl->delta_rxl;
+
+	nl->csr1.rxl = rxl_tab[ nl->cur_rxl_index += nl->delta_rxl ];
+	inb( dev->base_addr + CSR0 );	/* needs for PCI cards */
+	outb( *(u8 *)&nl->csr1, dev->base_addr + CSR1 );
+
+	nl->prev_rxl_rcvd = nl->cur_rxl_rcvd;
+	nl->cur_rxl_rcvd  = 0;
+}
+
+
+static void
+timeout_change_level( struct net_device  *dev )
+{
+	struct net_local  *nl = (struct net_local *) dev->priv;
+
+	nl->cur_rxl_index = timeout_rxl_tab[ nl->timeout_rxl ];
+	if( ++nl->timeout_rxl >= 4 )
+		nl->timeout_rxl = 0;
+
+	nl->csr1.rxl = rxl_tab[ nl->cur_rxl_index ];
+	inb( dev->base_addr + CSR0 );
+	outb( *(unsigned char *)&nl->csr1, dev->base_addr + CSR1 );
+
+	nl->prev_rxl_rcvd = nl->cur_rxl_rcvd;
+	nl->cur_rxl_rcvd  = 0;
+}
+
+/* -------------------------------------------------------------------------- */
+
+/*
+ *	Open/initialize the board. 
+ */
+
+static int
+sbni_open( struct net_device  *dev )
+{
+	struct net_local	*nl = (struct net_local *) dev->priv;
+	struct timer_list	*w  = &nl->watchdog;
+
+	/*
+	 * For double ISA adapters within "common irq" mode, we have to
+	 * determine whether primary or secondary channel is initialized,
+	 * and set the irq handler only in first case.
+	 */
+	if( dev->base_addr < 0x400 ) {		/* ISA only */
+		struct net_device  **p = sbni_cards;
+		for( ;  *p  &&  p < sbni_cards + SBNI_MAX_NUM_CARDS;  ++p )
+			if( (*p)->irq == dev->irq
+			    &&  ((*p)->base_addr == dev->base_addr + 4
+				 ||  (*p)->base_addr == dev->base_addr - 4)
+			    &&  (*p)->flags & IFF_UP ) {
+
+				((struct net_local *) ((*p)->priv))
+					->second = dev;
+				printk( KERN_NOTICE "%s: using shared irq "
+					"with %s\n", dev->name, (*p)->name );
+				nl->state |= FL_SECONDARY;
+				goto  handler_attached;
+			}
+	}
+
+	if( request_irq(dev->irq, sbni_interrupt, SA_SHIRQ, dev->name, dev) ) {
+		printk( KERN_ERR "%s: unable to get IRQ %d.\n",
+			dev->name, dev->irq );
+		return  -EAGAIN;
+	}
+
+handler_attached:
+
+	spin_lock( &nl->lock );
+	memset( &nl->stats, 0, sizeof(struct net_device_stats) );
+	memset( &nl->in_stats, 0, sizeof(struct sbni_in_stats) );
+
+	card_start( dev );
+
+	netif_start_queue( dev );
+
+	/* set timer watchdog */
+	init_timer( w );
+	w->expires	= jiffies + SBNI_TIMEOUT;
+	w->data		= (unsigned long) dev;
+	w->function	= sbni_watchdog;
+	add_timer( w );
+   
+	spin_unlock( &nl->lock );
+	return 0;
+}
+
+
+static int
+sbni_close( struct net_device  *dev )
+{
+	struct net_local  *nl = (struct net_local *) dev->priv;
+
+	if( nl->second  &&  nl->second->flags & IFF_UP ) {
+		printk( KERN_NOTICE "Secondary channel (%s) is active!\n",
+			nl->second->name );
+		return  -EBUSY;
+	}
+
+#ifdef CONFIG_SBNI_MULTILINE
+	if( nl->state & FL_SLAVE )
+		emancipate( dev );
+	else
+		while( nl->link )	/* it's master device! */
+			emancipate( nl->link );
+#endif
+
+	spin_lock( &nl->lock );
+
+	nl->second = NULL;
+	drop_xmit_queue( dev );	
+	netif_stop_queue( dev );
+   
+	del_timer( &nl->watchdog );
+
+	outb( 0, dev->base_addr + CSR0 );
+
+	if( !(nl->state & FL_SECONDARY) )
+		free_irq( dev->irq, dev );
+	nl->state &= FL_SECONDARY;
+
+	spin_unlock( &nl->lock );
+	return 0;
+}
+
+
+/*
+	Valid combinations in CSR0 (for probing):
+
+	VALID_DECODER	0000,0011,1011,1010
+
+				    	; 0   ; -
+				TR_REQ	; 1   ; +
+			TR_RDY	    	; 2   ; -
+			TR_RDY	TR_REQ	; 3   ; +
+		BU_EMP		    	; 4   ; +
+		BU_EMP	     	TR_REQ	; 5   ; +
+		BU_EMP	TR_RDY	    	; 6   ; -
+		BU_EMP	TR_RDY	TR_REQ	; 7   ; +
+	RC_RDY 		     		; 8   ; +
+	RC_RDY			TR_REQ	; 9   ; +
+	RC_RDY		TR_RDY		; 10  ; -
+	RC_RDY		TR_RDY	TR_REQ	; 11  ; -
+	RC_RDY	BU_EMP			; 12  ; -
+	RC_RDY	BU_EMP		TR_REQ	; 13  ; -
+	RC_RDY	BU_EMP	TR_RDY		; 14  ; -
+	RC_RDY	BU_EMP	TR_RDY	TR_REQ	; 15  ; -
+*/
+
+#define VALID_DECODER (2 + 8 + 0x10 + 0x20 + 0x80 + 0x100 + 0x200)
+
+
+static int
+sbni_card_probe( unsigned long  ioaddr )
+{
+	unsigned char  csr0;
+
+	csr0 = inb( ioaddr + CSR0 );
+	if( csr0 != 0xff  &&  csr0 != 0x00 ) {
+		csr0 &= ~EN_INT;
+		if( csr0 & BU_EMP )
+			csr0 |= EN_INT;
+      
+		if( VALID_DECODER & (1 << (csr0 >> 4)) )
+			return  0;
+	}
+   
+	return  -ENODEV;
+}
+
+/* -------------------------------------------------------------------------- */
+
+static int
+sbni_ioctl( struct net_device  *dev,  struct ifreq  *ifr,  int  cmd )
+{
+	struct net_local  *nl = (struct net_local *) dev->priv; 
+	struct sbni_flags  flags;
+	int  error = 0;
+
+#ifdef CONFIG_SBNI_MULTILINE
+	struct net_device  *slave_dev;
+	char  slave_name[ 8 ];
+#endif
+  
+	switch( cmd ) {
+	case  SIOCDEVGETINSTATS :
+		if (copy_to_user( ifr->ifr_data, &nl->in_stats,
+					sizeof(struct sbni_in_stats) ))
+			error = -EFAULT;
+		break;
+
+	case  SIOCDEVRESINSTATS :
+		if( current->euid != 0 )	/* root only */
+			return  -EPERM;
+		memset( &nl->in_stats, 0, sizeof(struct sbni_in_stats) );
+		break;
+
+	case  SIOCDEVGHWSTATE :
+		flags.mac_addr	= *(u32 *)(dev->dev_addr + 3);
+		flags.rate	= nl->csr1.rate;
+		flags.slow_mode	= (nl->state & FL_SLOW_MODE) != 0;
+		flags.rxl	= nl->cur_rxl_index;
+		flags.fixed_rxl	= nl->delta_rxl == 0;
+
+		if (copy_to_user( ifr->ifr_data, &flags, sizeof flags ))
+			error = -EFAULT;
+		break;
+
+	case  SIOCDEVSHWSTATE :
+		if( current->euid != 0 )	/* root only */
+			return  -EPERM;
+
+		spin_lock( &nl->lock );
+		flags = *(struct sbni_flags*) &ifr->ifr_ifru;
+		if( flags.fixed_rxl )
+			nl->delta_rxl = 0,
+			nl->cur_rxl_index = flags.rxl;
+		else
+			nl->delta_rxl = DEF_RXL_DELTA,
+			nl->cur_rxl_index = DEF_RXL;
+
+		nl->csr1.rxl = rxl_tab[ nl->cur_rxl_index ];
+		nl->csr1.rate = flags.rate;
+		outb( *(u8 *)&nl->csr1 | PR_RES, dev->base_addr + CSR1 );
+		spin_unlock( &nl->lock );
+		break;
+
+#ifdef CONFIG_SBNI_MULTILINE
+
+	case  SIOCDEVENSLAVE :
+		if( current->euid != 0 )	/* root only */
+			return  -EPERM;
+
+		if (copy_from_user( slave_name, ifr->ifr_data, sizeof slave_name ))
+			return -EFAULT;
+		slave_dev = dev_get_by_name( slave_name );
+		if( !slave_dev  ||  !(slave_dev->flags & IFF_UP) ) {
+			printk( KERN_ERR "%s: trying to enslave non-active "
+				"device %s\n", dev->name, slave_name );
+			return  -EPERM;
+		}
+
+		return  enslave( dev, slave_dev );
+
+	case  SIOCDEVEMANSIPATE :
+		if( current->euid != 0 )	/* root only */
+			return  -EPERM;
+
+		return  emancipate( dev );
+
+#endif	/* CONFIG_SBNI_MULTILINE */
+
+	default :
+		return  -EOPNOTSUPP;
+	}
+
+	return  error;
+}
+
+
+#ifdef CONFIG_SBNI_MULTILINE
+
+static int
+enslave( struct net_device  *dev,  struct net_device  *slave_dev )
+{
+	struct net_local  *nl  = (struct net_local *) dev->priv;
+	struct net_local  *snl = (struct net_local *) slave_dev->priv;
+
+	if( nl->state & FL_SLAVE )	/* This isn't master or free device */
+		return  -EBUSY;
+
+	if( snl->state & FL_SLAVE )	/* That was already enslaved */
+		return  -EBUSY;
+
+	spin_lock( &nl->lock );
+	spin_lock( &snl->lock );
+
+	/* append to list */
+	snl->link = nl->link;
+	nl->link  = slave_dev;
+	snl->master = dev;
+	snl->state |= FL_SLAVE;
+
+	/* Summary statistics of MultiLine operation will be stored
+	   in master's counters */
+	memset( &snl->stats, 0, sizeof(struct net_device_stats) );
+	netif_stop_queue( slave_dev );
+	netif_wake_queue( dev );	/* Now we are able to transmit */
+
+	spin_unlock( &snl->lock );
+	spin_unlock( &nl->lock );
+	printk( KERN_NOTICE "%s: slave device (%s) attached.\n",
+		dev->name, slave_dev->name );
+	return  0;
+}
+
+
+static int
+emancipate( struct net_device  *dev )
+{
+	struct net_local   *snl = (struct net_local *) dev->priv;
+	struct net_device  *p   = snl->master;
+	struct net_local   *nl  = (struct net_local *) p->priv;
+
+	if( !(snl->state & FL_SLAVE) )
+		return  -EINVAL;
+
+	spin_lock( &nl->lock );
+	spin_lock( &snl->lock );
+	drop_xmit_queue( dev );
+
+	/* exclude from list */
+	for(;;) {	/* must be in list */
+		struct net_local  *t = (struct net_local *) p->priv;
+		if( t->link == dev ) {
+			t->link = snl->link;
+			break;
+		}
+		p = t->link;
+	}
+
+	snl->link = NULL;
+	snl->master = dev;
+	snl->state &= ~FL_SLAVE;
+
+	netif_start_queue( dev );
+
+	spin_unlock( &snl->lock );
+	spin_unlock( &nl->lock );
+
+	dev_put( dev );
+	return  0;
+}
+
+#endif
+
+
+static struct net_device_stats *
+sbni_get_stats( struct net_device  *dev )
+{
+	return  &((struct net_local *) dev->priv)->stats;
+}
+
+
+static void
+set_multicast_list( struct net_device  *dev )
+{
+	return;		/* sbni always operate in promiscuos mode */
+}
+
+
+#ifdef MODULE
+module_param_array(io, int, NULL, 0);
+module_param_array(irq, int, NULL, 0);
+module_param_array(baud, int, NULL, 0);
+module_param_array(rxl, int, NULL, 0);
+module_param_array(mac, int, NULL, 0);
+module_param(skip_pci_probe, bool, 0);
+
+MODULE_LICENSE("GPL");
+
+
+int
+init_module( void )
+{
+	struct net_device  *dev;
+	int err;
+
+	while( num < SBNI_MAX_NUM_CARDS ) {
+		dev = alloc_netdev(sizeof(struct net_local), 
+				   "sbni%d", sbni_devsetup);
+		if( !dev)
+			break;
+
+		sprintf( dev->name, "sbni%d", num );
+
+		err = sbni_init(dev);
+		if (err) {
+			free_netdev(dev);
+			break;
+		}
+
+		if( register_netdev( dev ) ) {
+			release_region( dev->base_addr, SBNI_IO_EXTENT );
+			free_netdev( dev );
+			break;
+		}
+	}
+
+	return  *sbni_cards  ?  0  :  -ENODEV;
+}
+
+void
+cleanup_module( void )
+{
+	struct net_device  *dev;
+	int  num;
+
+	for( num = 0;  num < SBNI_MAX_NUM_CARDS;  ++num )
+		if( (dev = sbni_cards[ num ]) != NULL ) {
+			unregister_netdev( dev );
+			release_region( dev->base_addr, SBNI_IO_EXTENT );
+			free_netdev( dev );
+		}
+}
+
+#else	/* MODULE */
+
+static int __init
+sbni_setup( char  *p )
+{
+	int  n, parm;
+
+	if( *p++ != '(' )
+		goto  bad_param;
+
+	for( n = 0, parm = 0;  *p  &&  n < 8; ) {
+		(*dest[ parm ])[ n ] = simple_strtol( p, &p, 0 );
+		if( !*p  ||  *p == ')' )
+			return 1;
+		if( *p == ';' )
+			++p, ++n, parm = 0;
+		else if( *p++ != ',' )
+			break;
+		else
+			if( ++parm >= 5 )
+				break;
+	}
+bad_param:
+	printk( KERN_ERR "Error in sbni kernel parameter!\n" );
+	return 0;
+}
+
+__setup( "sbni=", sbni_setup );
+
+#endif	/* MODULE */
+
+/* -------------------------------------------------------------------------- */
+
+#ifdef ASM_CRC
+
+static u32
+calc_crc32( u32  crc,  u8  *p,  u32  len )
+{
+	register u32  _crc;
+	_crc = crc;
+	
+	__asm__ __volatile__ (
+		"xorl	%%ebx, %%ebx\n"
+		"movl	%2, %%esi\n" 
+		"movl	%3, %%ecx\n" 
+		"movl	$crc32tab, %%edi\n"
+		"shrl	$2, %%ecx\n"
+		"jz	1f\n"
+
+		".align 4\n"
+	"0:\n"
+		"movb	%%al, %%bl\n"
+		"movl	(%%esi), %%edx\n"
+		"shrl	$8, %%eax\n"
+		"xorb	%%dl, %%bl\n"
+		"shrl	$8, %%edx\n"
+		"xorl	(%%edi,%%ebx,4), %%eax\n"
+
+		"movb	%%al, %%bl\n"
+		"shrl	$8, %%eax\n"
+		"xorb	%%dl, %%bl\n"
+		"shrl	$8, %%edx\n"
+		"xorl	(%%edi,%%ebx,4), %%eax\n"
+
+		"movb	%%al, %%bl\n"
+		"shrl	$8, %%eax\n"
+		"xorb	%%dl, %%bl\n"
+		"movb	%%dh, %%dl\n" 
+		"xorl	(%%edi,%%ebx,4), %%eax\n"
+
+		"movb	%%al, %%bl\n"
+		"shrl	$8, %%eax\n"
+		"xorb	%%dl, %%bl\n"
+		"addl	$4, %%esi\n"
+		"xorl	(%%edi,%%ebx,4), %%eax\n"
+
+		"decl	%%ecx\n"
+		"jnz	0b\n"
+
+	"1:\n"
+		"movl	%3, %%ecx\n"
+		"andl	$3, %%ecx\n"
+		"jz	2f\n"
+
+		"movb	%%al, %%bl\n"
+		"shrl	$8, %%eax\n"
+		"xorb	(%%esi), %%bl\n"
+		"xorl	(%%edi,%%ebx,4), %%eax\n"
+
+		"decl	%%ecx\n"
+		"jz	2f\n"
+
+		"movb	%%al, %%bl\n"
+		"shrl	$8, %%eax\n"
+		"xorb	1(%%esi), %%bl\n"
+		"xorl	(%%edi,%%ebx,4), %%eax\n"
+
+		"decl	%%ecx\n"
+		"jz	2f\n"
+
+		"movb	%%al, %%bl\n"
+		"shrl	$8, %%eax\n"
+		"xorb	2(%%esi), %%bl\n"
+		"xorl	(%%edi,%%ebx,4), %%eax\n"
+	"2:\n"
+		: "=a" (_crc)
+		: "0" (_crc), "g" (p), "g" (len)
+		: "bx", "cx", "dx", "si", "di"
+	);
+
+	return  _crc;
+}
+
+#else	/* ASM_CRC */
+
+static u32
+calc_crc32( u32  crc,  u8  *p,  u32  len )
+{
+	while( len-- )
+		crc = CRC32( *p++, crc );
+
+	return  crc;
+}
+
+#endif	/* ASM_CRC */
+
+
+static u32  crc32tab[] __attribute__ ((aligned(8))) = {
+	0xD202EF8D,  0xA505DF1B,  0x3C0C8EA1,  0x4B0BBE37,
+	0xD56F2B94,  0xA2681B02,  0x3B614AB8,  0x4C667A2E,
+	0xDCD967BF,  0xABDE5729,  0x32D70693,  0x45D03605,
+	0xDBB4A3A6,  0xACB39330,  0x35BAC28A,  0x42BDF21C,
+	0xCFB5FFE9,  0xB8B2CF7F,  0x21BB9EC5,  0x56BCAE53,
+	0xC8D83BF0,  0xBFDF0B66,  0x26D65ADC,  0x51D16A4A,
+	0xC16E77DB,  0xB669474D,  0x2F6016F7,  0x58672661,
+	0xC603B3C2,  0xB1048354,  0x280DD2EE,  0x5F0AE278,
+	0xE96CCF45,  0x9E6BFFD3,  0x0762AE69,  0x70659EFF,
+	0xEE010B5C,  0x99063BCA,  0x000F6A70,  0x77085AE6,
+	0xE7B74777,  0x90B077E1,  0x09B9265B,  0x7EBE16CD,
+	0xE0DA836E,  0x97DDB3F8,  0x0ED4E242,  0x79D3D2D4,
+	0xF4DBDF21,  0x83DCEFB7,  0x1AD5BE0D,  0x6DD28E9B,
+	0xF3B61B38,  0x84B12BAE,  0x1DB87A14,  0x6ABF4A82,
+	0xFA005713,  0x8D076785,  0x140E363F,  0x630906A9,
+	0xFD6D930A,  0x8A6AA39C,  0x1363F226,  0x6464C2B0,
+	0xA4DEAE1D,  0xD3D99E8B,  0x4AD0CF31,  0x3DD7FFA7,
+	0xA3B36A04,  0xD4B45A92,  0x4DBD0B28,  0x3ABA3BBE,
+	0xAA05262F,  0xDD0216B9,  0x440B4703,  0x330C7795,
+	0xAD68E236,  0xDA6FD2A0,  0x4366831A,  0x3461B38C,
+	0xB969BE79,  0xCE6E8EEF,  0x5767DF55,  0x2060EFC3,
+	0xBE047A60,  0xC9034AF6,  0x500A1B4C,  0x270D2BDA,
+	0xB7B2364B,  0xC0B506DD,  0x59BC5767,  0x2EBB67F1,
+	0xB0DFF252,  0xC7D8C2C4,  0x5ED1937E,  0x29D6A3E8,
+	0x9FB08ED5,  0xE8B7BE43,  0x71BEEFF9,  0x06B9DF6F,
+	0x98DD4ACC,  0xEFDA7A5A,  0x76D32BE0,  0x01D41B76,
+	0x916B06E7,  0xE66C3671,  0x7F6567CB,  0x0862575D,
+	0x9606C2FE,  0xE101F268,  0x7808A3D2,  0x0F0F9344,
+	0x82079EB1,  0xF500AE27,  0x6C09FF9D,  0x1B0ECF0B,
+	0x856A5AA8,  0xF26D6A3E,  0x6B643B84,  0x1C630B12,
+	0x8CDC1683,  0xFBDB2615,  0x62D277AF,  0x15D54739,
+	0x8BB1D29A,  0xFCB6E20C,  0x65BFB3B6,  0x12B88320,
+	0x3FBA6CAD,  0x48BD5C3B,  0xD1B40D81,  0xA6B33D17,
+	0x38D7A8B4,  0x4FD09822,  0xD6D9C998,  0xA1DEF90E,
+	0x3161E49F,  0x4666D409,  0xDF6F85B3,  0xA868B525,
+	0x360C2086,  0x410B1010,  0xD80241AA,  0xAF05713C,
+	0x220D7CC9,  0x550A4C5F,  0xCC031DE5,  0xBB042D73,
+	0x2560B8D0,  0x52678846,  0xCB6ED9FC,  0xBC69E96A,
+	0x2CD6F4FB,  0x5BD1C46D,  0xC2D895D7,  0xB5DFA541,
+	0x2BBB30E2,  0x5CBC0074,  0xC5B551CE,  0xB2B26158,
+	0x04D44C65,  0x73D37CF3,  0xEADA2D49,  0x9DDD1DDF,
+	0x03B9887C,  0x74BEB8EA,  0xEDB7E950,  0x9AB0D9C6,
+	0x0A0FC457,  0x7D08F4C1,  0xE401A57B,  0x930695ED,
+	0x0D62004E,  0x7A6530D8,  0xE36C6162,  0x946B51F4,
+	0x19635C01,  0x6E646C97,  0xF76D3D2D,  0x806A0DBB,
+	0x1E0E9818,  0x6909A88E,  0xF000F934,  0x8707C9A2,
+	0x17B8D433,  0x60BFE4A5,  0xF9B6B51F,  0x8EB18589,
+	0x10D5102A,  0x67D220BC,  0xFEDB7106,  0x89DC4190,
+	0x49662D3D,  0x3E611DAB,  0xA7684C11,  0xD06F7C87,
+	0x4E0BE924,  0x390CD9B2,  0xA0058808,  0xD702B89E,
+	0x47BDA50F,  0x30BA9599,  0xA9B3C423,  0xDEB4F4B5,
+	0x40D06116,  0x37D75180,  0xAEDE003A,  0xD9D930AC,
+	0x54D13D59,  0x23D60DCF,  0xBADF5C75,  0xCDD86CE3,
+	0x53BCF940,  0x24BBC9D6,  0xBDB2986C,  0xCAB5A8FA,
+	0x5A0AB56B,  0x2D0D85FD,  0xB404D447,  0xC303E4D1,
+	0x5D677172,  0x2A6041E4,  0xB369105E,  0xC46E20C8,
+	0x72080DF5,  0x050F3D63,  0x9C066CD9,  0xEB015C4F,
+	0x7565C9EC,  0x0262F97A,  0x9B6BA8C0,  0xEC6C9856,
+	0x7CD385C7,  0x0BD4B551,  0x92DDE4EB,  0xE5DAD47D,
+	0x7BBE41DE,  0x0CB97148,  0x95B020F2,  0xE2B71064,
+	0x6FBF1D91,  0x18B82D07,  0x81B17CBD,  0xF6B64C2B,
+	0x68D2D988,  0x1FD5E91E,  0x86DCB8A4,  0xF1DB8832,
+	0x616495A3,  0x1663A535,  0x8F6AF48F,  0xF86DC419,
+	0x660951BA,  0x110E612C,  0x88073096,  0xFF000000
+};
+
diff --git a/drivers/net/wan/sbni.h b/drivers/net/wan/sbni.h
new file mode 100644
index 0000000..27715e7
--- /dev/null
+++ b/drivers/net/wan/sbni.h
@@ -0,0 +1,141 @@
+/* sbni.h:  definitions for a Granch SBNI12 driver, version 5.0.0
+ * Written 2001 Denis I.Timofeev (timofeev@granch.ru)
+ * This file is distributed under the GNU GPL
+ */
+
+#ifndef SBNI_H
+#define SBNI_H
+
+#ifdef SBNI_DEBUG
+#define DP( A ) A
+#else
+#define DP( A )
+#endif
+
+
+/* We don't have official vendor id yet... */
+#define SBNI_PCI_VENDOR 	0x55 
+#define SBNI_PCI_DEVICE 	0x9f
+
+#define ISA_MODE 0x00
+#define PCI_MODE 0x01
+
+#define	SBNI_IO_EXTENT	4
+
+enum sbni_reg {
+	CSR0 = 0,
+	CSR1 = 1,
+	DAT  = 2
+};
+
+/* CSR0 mapping */
+enum {
+	BU_EMP = 0x02,
+	RC_CHK = 0x04,
+	CT_ZER = 0x08,
+	TR_REQ = 0x10,
+	TR_RDY = 0x20,
+	EN_INT = 0x40,
+	RC_RDY = 0x80
+};
+
+
+/* CSR1 mapping */
+#define PR_RES 0x80
+
+struct sbni_csr1 {
+	unsigned rxl	: 5;
+	unsigned rate	: 2;
+	unsigned 	: 1;
+};
+
+/* fields in frame header */
+#define FRAME_ACK_MASK  (unsigned short)0x7000
+#define FRAME_LEN_MASK  (unsigned short)0x03FF
+#define FRAME_FIRST     (unsigned short)0x8000
+#define FRAME_RETRY     (unsigned short)0x0800
+
+#define FRAME_SENT_BAD  (unsigned short)0x4000
+#define FRAME_SENT_OK   (unsigned short)0x3000
+
+
+/* state flags */
+enum {
+	FL_WAIT_ACK    = 0x01,
+	FL_NEED_RESEND = 0x02,
+	FL_PREV_OK     = 0x04,
+	FL_SLOW_MODE   = 0x08,
+	FL_SECONDARY   = 0x10,
+#ifdef CONFIG_SBNI_MULTILINE
+	FL_SLAVE       = 0x20,
+#endif
+	FL_LINE_DOWN   = 0x40
+};
+
+
+enum {
+	DEFAULT_IOBASEADDR = 0x210,
+	DEFAULT_INTERRUPTNUMBER = 5,
+	DEFAULT_RATE = 0,
+	DEFAULT_FRAME_LEN = 1012
+};
+
+#define DEF_RXL_DELTA	-1
+#define DEF_RXL		0xf
+
+#define SBNI_SIG 0x5a
+
+#define	SBNI_MIN_LEN	60	/* Shortest Ethernet frame without FCS */
+#define SBNI_MAX_FRAME	1023
+#define ETHER_MAX_LEN	1518
+
+#define SBNI_TIMEOUT	(HZ/10)
+
+#define TR_ERROR_COUNT	32
+#define CHANGE_LEVEL_START_TICKS 4
+
+#define SBNI_MAX_NUM_CARDS	16
+
+/* internal SBNI-specific statistics */
+struct sbni_in_stats {
+	u32	all_rx_number;
+	u32	bad_rx_number;
+	u32	timeout_number;
+	u32	all_tx_number;
+	u32	resend_tx_number;
+};
+
+/* SBNI ioctl params */
+#define SIOCDEVGETINSTATS 	SIOCDEVPRIVATE
+#define SIOCDEVRESINSTATS 	SIOCDEVPRIVATE+1
+#define SIOCDEVGHWSTATE   	SIOCDEVPRIVATE+2
+#define SIOCDEVSHWSTATE   	SIOCDEVPRIVATE+3
+#define SIOCDEVENSLAVE  	SIOCDEVPRIVATE+4
+#define SIOCDEVEMANSIPATE  	SIOCDEVPRIVATE+5
+
+
+/* data packet for SIOCDEVGHWSTATE/SIOCDEVSHWSTATE ioctl requests */
+struct sbni_flags {
+	u32	rxl		: 4;
+	u32	rate		: 2;
+	u32	fixed_rxl	: 1;
+	u32	slow_mode	: 1;
+	u32	mac_addr	: 24;
+};
+
+/*
+ * CRC-32 stuff
+ */
+#define CRC32(c,crc) (crc32tab[((size_t)(crc) ^ (c)) & 0xff] ^ (((crc) >> 8) & 0x00FFFFFF))
+      /* CRC generator 0xEDB88320 */
+      /* CRC remainder 0x2144DF1C */
+      /* CRC initial value 0x00000000 */
+#define CRC32_REMAINDER 0x2144DF1C
+#define CRC32_INITIAL 0x00000000
+
+#ifndef __initdata
+#define __initdata
+#endif
+
+#endif
+
diff --git a/drivers/net/wan/sdla.c b/drivers/net/wan/sdla.c
new file mode 100644
index 0000000..3ac9a45
--- /dev/null
+++ b/drivers/net/wan/sdla.c
@@ -0,0 +1,1676 @@
+/*
+ * SDLA		An implementation of a driver for the Sangoma S502/S508 series
+ *		multi-protocol PC interface card.  Initial offering is with 
+ *		the DLCI driver, providing Frame Relay support for linux.
+ *
+ *		Global definitions for the Frame relay interface.
+ *
+ * Version:	@(#)sdla.c   0.30	12 Sep 1996
+ *
+ * Credits:	Sangoma Technologies, for the use of 2 cards for an extended
+ *			period of time.
+ *		David Mandelstam <dm@sangoma.com> for getting me started on 
+ *			this project, and incentive to complete it.
+ *		Gene Kozen <74604.152@compuserve.com> for providing me with
+ *			important information about the cards.
+ *
+ * Author:	Mike McLagan <mike.mclagan@linux.org>
+ *
+ * Changes:
+ *		0.15	Mike McLagan	Improved error handling, packet dropping
+ *		0.20	Mike McLagan	New transmit/receive flags for config
+ *					If in FR mode, don't accept packets from
+ *					non DLCI devices.
+ *		0.25	Mike McLagan	Fixed problem with rejecting packets
+ *					from non DLCI devices.
+ *		0.30	Mike McLagan	Fixed kernel panic when used with modified
+ *					ifconfig
+ *
+ *		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.
+ */
+
+#include <linux/config.h> /* for CONFIG_DLCI_MAX */
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/types.h>
+#include <linux/fcntl.h>
+#include <linux/interrupt.h>
+#include <linux/ptrace.h>
+#include <linux/ioport.h>
+#include <linux/in.h>
+#include <linux/slab.h>
+#include <linux/string.h>
+#include <linux/timer.h>
+#include <linux/errno.h>
+#include <linux/init.h>
+#include <linux/netdevice.h>
+#include <linux/skbuff.h>
+#include <linux/if_arp.h>
+#include <linux/if_frad.h>
+#include <linux/sdla.h>
+#include <linux/bitops.h>
+
+#include <asm/system.h>
+#include <asm/io.h>
+#include <asm/dma.h>
+#include <asm/uaccess.h>
+
+static const char* version = "SDLA driver v0.30, 12 Sep 1996, mike.mclagan@linux.org";
+
+static unsigned int valid_port[] __initdata = { 0x250, 0x270, 0x280, 0x300, 0x350, 0x360, 0x380, 0x390};
+
+static unsigned int valid_mem[]  __initdata = {
+				    0xA0000, 0xA2000, 0xA4000, 0xA6000, 0xA8000, 0xAA000, 0xAC000, 0xAE000, 
+                                    0xB0000, 0xB2000, 0xB4000, 0xB6000, 0xB8000, 0xBA000, 0xBC000, 0xBE000,
+                                    0xC0000, 0xC2000, 0xC4000, 0xC6000, 0xC8000, 0xCA000, 0xCC000, 0xCE000,
+                                    0xD0000, 0xD2000, 0xD4000, 0xD6000, 0xD8000, 0xDA000, 0xDC000, 0xDE000,
+                                    0xE0000, 0xE2000, 0xE4000, 0xE6000, 0xE8000, 0xEA000, 0xEC000, 0xEE000}; 
+
+static DEFINE_SPINLOCK(sdla_lock);
+
+/*********************************************************
+ *
+ * these are the core routines that access the card itself 
+ *
+ *********************************************************/
+
+#define SDLA_WINDOW(dev,addr) outb((((addr) >> 13) & 0x1F), (dev)->base_addr + SDLA_REG_Z80_WINDOW)
+
+static void __sdla_read(struct net_device *dev, int addr, void *buf, short len)
+{
+	char          *temp;
+	const void    *base;
+	int           offset, bytes;
+
+	temp = buf;
+	while(len)
+	{	
+		offset = addr & SDLA_ADDR_MASK;
+		bytes = offset + len > SDLA_WINDOW_SIZE ? SDLA_WINDOW_SIZE - offset : len;
+		base = (const void *) (dev->mem_start + offset);
+
+		SDLA_WINDOW(dev, addr);
+		memcpy(temp, base, bytes);
+
+		addr += bytes;
+		temp += bytes;
+		len  -= bytes;
+	}  
+}
+
+static void sdla_read(struct net_device *dev, int addr, void *buf, short len)
+{
+	unsigned long flags;
+	spin_lock_irqsave(&sdla_lock, flags);
+	__sdla_read(dev, addr, buf, len);
+	spin_unlock_irqrestore(&sdla_lock, flags);
+}
+
+static void __sdla_write(struct net_device *dev, int addr, 
+			 const void *buf, short len)
+{
+	const char    *temp;
+	void 	      *base;
+	int           offset, bytes;
+
+	temp = buf;
+	while(len)
+	{
+		offset = addr & SDLA_ADDR_MASK;
+		bytes = offset + len > SDLA_WINDOW_SIZE ? SDLA_WINDOW_SIZE - offset : len;
+		base = (void *) (dev->mem_start + offset);
+
+		SDLA_WINDOW(dev, addr);
+		memcpy(base, temp, bytes);
+
+		addr += bytes;
+		temp += bytes;
+		len  -= bytes;
+	}
+}
+
+static void sdla_write(struct net_device *dev, int addr, 
+		       const void *buf, short len)
+{
+	unsigned long flags;
+
+	spin_lock_irqsave(&sdla_lock, flags);
+	__sdla_write(dev, addr, buf, len);
+	spin_unlock_irqrestore(&sdla_lock, flags);
+}
+
+
+static void sdla_clear(struct net_device *dev)
+{
+	unsigned long flags;
+	char          *base;
+	int           len, addr, bytes;
+
+	len = 65536;	
+	addr = 0;
+	bytes = SDLA_WINDOW_SIZE;
+	base = (void *) dev->mem_start;
+
+	spin_lock_irqsave(&sdla_lock, flags);
+	while(len)
+	{
+		SDLA_WINDOW(dev, addr);
+		memset(base, 0, bytes);
+
+		addr += bytes;
+		len  -= bytes;
+	}
+	spin_unlock_irqrestore(&sdla_lock, flags);
+
+}
+
+static char sdla_byte(struct net_device *dev, int addr)
+{
+	unsigned long flags;
+	char          byte, *temp;
+
+	temp = (void *) (dev->mem_start + (addr & SDLA_ADDR_MASK));
+
+	spin_lock_irqsave(&sdla_lock, flags);
+	SDLA_WINDOW(dev, addr);
+	byte = *temp;
+	spin_unlock_irqrestore(&sdla_lock, flags);
+
+	return(byte);
+}
+
+void sdla_stop(struct net_device *dev)
+{
+	struct frad_local *flp;
+
+	flp = dev->priv;
+	switch(flp->type)
+	{
+		case SDLA_S502A:
+			outb(SDLA_S502A_HALT, dev->base_addr + SDLA_REG_CONTROL);
+			flp->state = SDLA_HALT;
+			break;
+		case SDLA_S502E:
+			outb(SDLA_HALT, dev->base_addr + SDLA_REG_Z80_CONTROL);
+			outb(SDLA_S502E_ENABLE, dev->base_addr + SDLA_REG_CONTROL);
+			flp->state = SDLA_S502E_ENABLE;
+			break;
+		case SDLA_S507:
+			flp->state &= ~SDLA_CPUEN;
+			outb(flp->state, dev->base_addr + SDLA_REG_CONTROL);
+			break;
+		case SDLA_S508:
+			flp->state &= ~SDLA_CPUEN;
+			outb(flp->state, dev->base_addr + SDLA_REG_CONTROL);
+			break;
+	}
+}
+
+void sdla_start(struct net_device *dev)
+{
+	struct frad_local *flp;
+
+	flp = dev->priv;
+	switch(flp->type)
+	{
+		case SDLA_S502A:
+			outb(SDLA_S502A_NMI, dev->base_addr + SDLA_REG_CONTROL);
+			outb(SDLA_S502A_START, dev->base_addr + SDLA_REG_CONTROL);
+			flp->state = SDLA_S502A_START;
+			break;
+		case SDLA_S502E:
+			outb(SDLA_S502E_CPUEN, dev->base_addr + SDLA_REG_Z80_CONTROL);
+			outb(0x00, dev->base_addr + SDLA_REG_CONTROL);
+			flp->state = 0;
+			break;
+		case SDLA_S507:
+			flp->state |= SDLA_CPUEN;
+			outb(flp->state, dev->base_addr + SDLA_REG_CONTROL);
+			break;
+		case SDLA_S508:
+			flp->state |= SDLA_CPUEN;
+			outb(flp->state, dev->base_addr + SDLA_REG_CONTROL);
+			break;
+	}
+}
+
+/****************************************************
+ *
+ * this is used for the S502A/E cards to determine
+ * the speed of the onboard CPU.  Calibration is
+ * necessary for the Frame Relay code uploaded 
+ * later.  Incorrect results cause timing problems
+ * with link checks & status messages
+ *
+ ***************************************************/
+
+int sdla_z80_poll(struct net_device *dev, int z80_addr, int jiffs, char resp1, char resp2)
+{
+	unsigned long start, done, now;
+	char          resp, *temp;
+
+	start = now = jiffies;
+	done = jiffies + jiffs;
+
+	temp = (void *)dev->mem_start;
+	temp += z80_addr & SDLA_ADDR_MASK;
+	
+	resp = ~resp1;
+	while (time_before(jiffies, done) && (resp != resp1) && (!resp2 || (resp != resp2)))
+	{
+		if (jiffies != now)
+		{
+			SDLA_WINDOW(dev, z80_addr);
+			now = jiffies;
+			resp = *temp;
+		}
+	}
+	return(time_before(jiffies, done) ? jiffies - start : -1);
+}
+
+/* constants for Z80 CPU speed */
+#define Z80_READY 		'1'	/* Z80 is ready to begin */
+#define LOADER_READY 		'2'	/* driver is ready to begin */
+#define Z80_SCC_OK 		'3'	/* SCC is on board */
+#define Z80_SCC_BAD	 	'4'	/* SCC was not found */
+
+static int sdla_cpuspeed(struct net_device *dev, struct ifreq *ifr)
+{
+	int  jiffs;
+	char data;
+
+	sdla_start(dev);
+	if (sdla_z80_poll(dev, 0, 3*HZ, Z80_READY, 0) < 0)
+		return(-EIO);
+
+	data = LOADER_READY;
+	sdla_write(dev, 0, &data, 1);
+
+	if ((jiffs = sdla_z80_poll(dev, 0, 8*HZ, Z80_SCC_OK, Z80_SCC_BAD)) < 0)
+		return(-EIO);
+
+	sdla_stop(dev);
+	sdla_read(dev, 0, &data, 1);
+
+	if (data == Z80_SCC_BAD)
+	{
+		printk("%s: SCC bad\n", dev->name);
+		return(-EIO);
+	}
+
+	if (data != Z80_SCC_OK)
+		return(-EINVAL);
+
+	if (jiffs < 165)
+		ifr->ifr_mtu = SDLA_CPU_16M;
+	else if (jiffs < 220)
+		ifr->ifr_mtu = SDLA_CPU_10M;
+	else if (jiffs < 258)
+		ifr->ifr_mtu = SDLA_CPU_8M;
+	else if (jiffs < 357)
+		ifr->ifr_mtu = SDLA_CPU_7M;
+	else if (jiffs < 467)
+		ifr->ifr_mtu = SDLA_CPU_5M;
+	else
+		ifr->ifr_mtu = SDLA_CPU_3M;
+ 
+	return(0);
+}
+
+/************************************************
+ *
+ *  Direct interaction with the Frame Relay code 
+ *  starts here.
+ *
+ ************************************************/
+
+struct _dlci_stat 
+{
+	short dlci		__attribute__((packed));
+	char  flags		__attribute__((packed));
+};
+
+struct _frad_stat 
+{
+	char    flags;
+	struct _dlci_stat dlcis[SDLA_MAX_DLCI];
+};
+
+static void sdla_errors(struct net_device *dev, int cmd, int dlci, int ret, int len, void *data) 
+{
+	struct _dlci_stat *pstatus;
+	short             *pdlci;
+	int               i;
+	char              *state, line[30];
+
+	switch (ret)
+	{
+		case SDLA_RET_MODEM:
+			state = data;
+			if (*state & SDLA_MODEM_DCD_LOW)
+				printk(KERN_INFO "%s: Modem DCD unexpectedly low!\n", dev->name);
+			if (*state & SDLA_MODEM_CTS_LOW)
+				printk(KERN_INFO "%s: Modem CTS unexpectedly low!\n", dev->name);
+			/* I should probably do something about this! */
+			break;
+
+		case SDLA_RET_CHANNEL_OFF:
+			printk(KERN_INFO "%s: Channel became inoperative!\n", dev->name);
+			/* same here */
+			break;
+
+		case SDLA_RET_CHANNEL_ON:
+			printk(KERN_INFO "%s: Channel became operative!\n", dev->name);
+			/* same here */
+			break;
+
+		case SDLA_RET_DLCI_STATUS:
+			printk(KERN_INFO "%s: Status change reported by Access Node.\n", dev->name);
+			len /= sizeof(struct _dlci_stat);
+			for(pstatus = data, i=0;i < len;i++,pstatus++)
+			{
+				if (pstatus->flags & SDLA_DLCI_NEW)
+					state = "new";
+				else if (pstatus->flags & SDLA_DLCI_DELETED)
+					state = "deleted";
+				else if (pstatus->flags & SDLA_DLCI_ACTIVE)
+					state = "active";
+				else
+				{
+					sprintf(line, "unknown status: %02X", pstatus->flags);
+					state = line;
+				}
+				printk(KERN_INFO "%s: DLCI %i: %s.\n", dev->name, pstatus->dlci, state);
+				/* same here */
+			}
+			break;
+
+		case SDLA_RET_DLCI_UNKNOWN:
+			printk(KERN_INFO "%s: Received unknown DLCIs:", dev->name);
+			len /= sizeof(short);
+			for(pdlci = data,i=0;i < len;i++,pdlci++)
+				printk(" %i", *pdlci);
+			printk("\n");
+			break;
+
+		case SDLA_RET_TIMEOUT:
+			printk(KERN_ERR "%s: Command timed out!\n", dev->name);
+			break;
+
+		case SDLA_RET_BUF_OVERSIZE:
+			printk(KERN_INFO "%s: Bc/CIR overflow, acceptable size is %i\n", dev->name, len);
+			break;
+
+		case SDLA_RET_BUF_TOO_BIG:
+			printk(KERN_INFO "%s: Buffer size over specified max of %i\n", dev->name, len);
+			break;
+
+		case SDLA_RET_CHANNEL_INACTIVE:
+		case SDLA_RET_DLCI_INACTIVE:
+		case SDLA_RET_CIR_OVERFLOW:
+		case SDLA_RET_NO_BUFS:
+			if (cmd == SDLA_INFORMATION_WRITE)
+				break;
+
+		default: 
+			printk(KERN_DEBUG "%s: Cmd 0x%2.2X generated return code 0x%2.2X\n", dev->name, cmd, ret);
+			/* Further processing could be done here */
+			break;
+	}
+}
+
+static int sdla_cmd(struct net_device *dev, int cmd, short dlci, short flags, 
+                        void *inbuf, short inlen, void *outbuf, short *outlen)
+{
+	static struct _frad_stat status;
+	struct frad_local        *flp;
+	struct sdla_cmd          *cmd_buf;
+	unsigned long            pflags;
+	unsigned long		 jiffs;
+	int                      ret, waiting, len;
+	long                     window;
+
+	flp = dev->priv;
+	window = flp->type == SDLA_S508 ? SDLA_508_CMD_BUF : SDLA_502_CMD_BUF;
+	cmd_buf = (struct sdla_cmd *)(dev->mem_start + (window & SDLA_ADDR_MASK));
+	ret = 0;
+	len = 0;
+	jiffs = jiffies + HZ;  /* 1 second is plenty */
+
+	spin_lock_irqsave(&sdla_lock, pflags);
+	SDLA_WINDOW(dev, window);
+	cmd_buf->cmd = cmd;
+	cmd_buf->dlci = dlci;
+	cmd_buf->flags = flags;
+
+	if (inbuf)
+		memcpy(cmd_buf->data, inbuf, inlen);
+
+	cmd_buf->length = inlen;
+
+	cmd_buf->opp_flag = 1;
+	spin_unlock_irqrestore(&sdla_lock, pflags);
+
+	waiting = 1;
+	len = 0;
+	while (waiting && time_before_eq(jiffies, jiffs))
+	{
+		if (waiting++ % 3) 
+		{
+			spin_lock_irqsave(&sdla_lock, pflags);
+			SDLA_WINDOW(dev, window);
+			waiting = ((volatile int)(cmd_buf->opp_flag));
+			spin_unlock_irqrestore(&sdla_lock, pflags);
+		}
+	}
+	
+	if (!waiting)
+	{
+
+		spin_lock_irqsave(&sdla_lock, pflags);
+		SDLA_WINDOW(dev, window);
+		ret = cmd_buf->retval;
+		len = cmd_buf->length;
+		if (outbuf && outlen)
+		{
+			*outlen = *outlen >= len ? len : *outlen;
+
+			if (*outlen)
+				memcpy(outbuf, cmd_buf->data, *outlen);
+		}
+
+		/* This is a local copy that's used for error handling */
+		if (ret)
+			memcpy(&status, cmd_buf->data, len > sizeof(status) ? sizeof(status) : len);
+
+		spin_unlock_irqrestore(&sdla_lock, pflags);
+	}
+	else
+		ret = SDLA_RET_TIMEOUT;
+
+	if (ret != SDLA_RET_OK)
+	   	sdla_errors(dev, cmd, dlci, ret, len, &status);
+
+	return(ret);
+}
+
+/***********************************************
+ *
+ * these functions are called by the DLCI driver 
+ *
+ ***********************************************/
+
+static int sdla_reconfig(struct net_device *dev);
+
+int sdla_activate(struct net_device *slave, struct net_device *master)
+{
+	struct frad_local *flp;
+	int i;
+
+	flp = slave->priv;
+
+	for(i=0;i<CONFIG_DLCI_MAX;i++)
+		if (flp->master[i] == master)
+			break;
+
+	if (i == CONFIG_DLCI_MAX)
+		return(-ENODEV);
+
+	flp->dlci[i] = abs(flp->dlci[i]);
+
+	if (netif_running(slave) && (flp->config.station == FRAD_STATION_NODE))
+		sdla_cmd(slave, SDLA_ACTIVATE_DLCI, 0, 0, &flp->dlci[i], sizeof(short), NULL, NULL);
+
+	return(0);
+}
+
+int sdla_deactivate(struct net_device *slave, struct net_device *master)
+{
+	struct frad_local *flp;
+	int               i;
+
+	flp = slave->priv;
+
+	for(i=0;i<CONFIG_DLCI_MAX;i++)
+		if (flp->master[i] == master)
+			break;
+
+	if (i == CONFIG_DLCI_MAX)
+		return(-ENODEV);
+
+	flp->dlci[i] = -abs(flp->dlci[i]);
+
+	if (netif_running(slave) && (flp->config.station == FRAD_STATION_NODE))
+		sdla_cmd(slave, SDLA_DEACTIVATE_DLCI, 0, 0, &flp->dlci[i], sizeof(short), NULL, NULL);
+
+	return(0);
+}
+
+int sdla_assoc(struct net_device *slave, struct net_device *master)
+{
+	struct frad_local *flp;
+	int               i;
+
+	if (master->type != ARPHRD_DLCI)
+		return(-EINVAL);
+
+	flp = slave->priv;
+
+	for(i=0;i<CONFIG_DLCI_MAX;i++)
+	{
+		if (!flp->master[i])
+			break;
+		if (abs(flp->dlci[i]) == *(short *)(master->dev_addr))
+			return(-EADDRINUSE);
+	} 
+
+	if (i == CONFIG_DLCI_MAX)
+		return(-EMLINK);  /* #### Alan: Comments on this ?? */
+
+
+	flp->master[i] = master;
+	flp->dlci[i] = -*(short *)(master->dev_addr);
+	master->mtu = slave->mtu;
+
+	if (netif_running(slave)) {
+		if (flp->config.station == FRAD_STATION_CPE)
+			sdla_reconfig(slave);
+		else
+			sdla_cmd(slave, SDLA_ADD_DLCI, 0, 0, master->dev_addr, sizeof(short), NULL, NULL);
+	}
+
+	return(0);
+}
+
+int sdla_deassoc(struct net_device *slave, struct net_device *master)
+{
+	struct frad_local *flp;
+	int               i;
+
+	flp = slave->priv;
+
+	for(i=0;i<CONFIG_DLCI_MAX;i++)
+		if (flp->master[i] == master)
+			break;
+
+	if (i == CONFIG_DLCI_MAX)
+		return(-ENODEV);
+
+	flp->master[i] = NULL;
+	flp->dlci[i] = 0;
+
+
+	if (netif_running(slave)) {
+		if (flp->config.station == FRAD_STATION_CPE)
+			sdla_reconfig(slave);
+		else
+			sdla_cmd(slave, SDLA_DELETE_DLCI, 0, 0, master->dev_addr, sizeof(short), NULL, NULL);
+	}
+
+	return(0);
+}
+
+int sdla_dlci_conf(struct net_device *slave, struct net_device *master, int get)
+{
+	struct frad_local *flp;
+	struct dlci_local *dlp;
+	int               i;
+	short             len, ret;
+
+	flp = slave->priv;
+
+	for(i=0;i<CONFIG_DLCI_MAX;i++)
+		if (flp->master[i] == master)
+			break;
+
+	if (i == CONFIG_DLCI_MAX)
+		return(-ENODEV);
+
+	dlp = master->priv;
+
+	ret = SDLA_RET_OK;
+	len = sizeof(struct dlci_conf);
+	if (netif_running(slave)) {
+		if (get)
+			ret = sdla_cmd(slave, SDLA_READ_DLCI_CONFIGURATION, abs(flp->dlci[i]), 0,  
+			            NULL, 0, &dlp->config, &len);
+		else
+			ret = sdla_cmd(slave, SDLA_SET_DLCI_CONFIGURATION, abs(flp->dlci[i]), 0,  
+			            &dlp->config, sizeof(struct dlci_conf) - 4 * sizeof(short), NULL, NULL);
+	}
+
+	return(ret == SDLA_RET_OK ? 0 : -EIO);
+}
+
+/**************************
+ *
+ * now for the Linux driver 
+ *
+ **************************/
+
+/* NOTE: the DLCI driver deals with freeing the SKB!! */
+static int sdla_transmit(struct sk_buff *skb, struct net_device *dev)
+{
+	struct frad_local *flp;
+	int               ret, addr, accept, i;
+	short             size;
+	unsigned long     flags;
+	struct buf_entry  *pbuf;
+
+	flp = dev->priv;
+	ret = 0;
+	accept = 1;
+
+	netif_stop_queue(dev);
+
+	/*
+	 * stupid GateD insists on setting up the multicast router thru us
+	 * and we're ill equipped to handle a non Frame Relay packet at this
+	 * time!
+	 */
+
+	accept = 1;
+	switch (dev->type)
+	{
+		case ARPHRD_FRAD:
+			if (skb->dev->type != ARPHRD_DLCI)
+			{
+				printk(KERN_WARNING "%s: Non DLCI device, type %i, tried to send on FRAD module.\n", dev->name, skb->dev->type);
+				accept = 0;
+			}
+			break;
+		default:
+			printk(KERN_WARNING "%s: unknown firmware type 0x%4.4X\n", dev->name, dev->type);
+			accept = 0;
+			break;
+	}
+	if (accept)
+	{
+		/* this is frame specific, but till there's a PPP module, it's the default */
+		switch (flp->type)
+		{
+			case SDLA_S502A:
+			case SDLA_S502E:
+				ret = sdla_cmd(dev, SDLA_INFORMATION_WRITE, *(short *)(skb->dev->dev_addr), 0, skb->data, skb->len, NULL, NULL);
+				break;
+				case SDLA_S508:
+				size = sizeof(addr);
+				ret = sdla_cmd(dev, SDLA_INFORMATION_WRITE, *(short *)(skb->dev->dev_addr), 0, NULL, skb->len, &addr, &size);
+				if (ret == SDLA_RET_OK)
+				{
+
+					spin_lock_irqsave(&sdla_lock, flags);
+					SDLA_WINDOW(dev, addr);
+					pbuf = (void *)(((int) dev->mem_start) + (addr & SDLA_ADDR_MASK));
+					__sdla_write(dev, pbuf->buf_addr, skb->data, skb->len);
+					SDLA_WINDOW(dev, addr);
+					pbuf->opp_flag = 1;
+					spin_unlock_irqrestore(&sdla_lock, flags);
+				}
+				break;
+		}
+		switch (ret)
+		{
+			case SDLA_RET_OK:
+				flp->stats.tx_packets++;
+				ret = DLCI_RET_OK;
+				break;
+
+			case SDLA_RET_CIR_OVERFLOW:
+			case SDLA_RET_BUF_OVERSIZE:
+			case SDLA_RET_NO_BUFS:
+				flp->stats.tx_dropped++;
+				ret = DLCI_RET_DROP;
+				break;
+
+			default:
+				flp->stats.tx_errors++;
+				ret = DLCI_RET_ERR;
+				break;
+		}
+	}
+	netif_wake_queue(dev);
+	for(i=0;i<CONFIG_DLCI_MAX;i++)
+	{
+		if(flp->master[i]!=NULL)
+			netif_wake_queue(flp->master[i]);
+	}		
+	return(ret);
+}
+
+static void sdla_receive(struct net_device *dev)
+{
+	struct net_device	  *master;
+	struct frad_local *flp;
+	struct dlci_local *dlp;
+	struct sk_buff	 *skb;
+
+	struct sdla_cmd	*cmd;
+	struct buf_info	*pbufi;
+	struct buf_entry  *pbuf;
+
+	unsigned long	  flags;
+	int               i=0, received, success, addr, buf_base, buf_top;
+	short             dlci, len, len2, split;
+
+	flp = dev->priv;
+	success = 1;
+	received = addr = buf_top = buf_base = 0;
+	len = dlci = 0;
+	skb = NULL;
+	master = NULL;
+	cmd = NULL;
+	pbufi = NULL;
+	pbuf = NULL;
+
+	spin_lock_irqsave(&sdla_lock, flags);
+
+	switch (flp->type)
+	{
+		case SDLA_S502A:
+		case SDLA_S502E:
+			cmd = (void *) (dev->mem_start + (SDLA_502_RCV_BUF & SDLA_ADDR_MASK));
+			SDLA_WINDOW(dev, SDLA_502_RCV_BUF);
+			success = cmd->opp_flag;
+			if (!success)
+				break;
+
+			dlci = cmd->dlci;
+			len = cmd->length;
+			break;
+
+		case SDLA_S508:
+			pbufi = (void *) (dev->mem_start + (SDLA_508_RXBUF_INFO & SDLA_ADDR_MASK));
+			SDLA_WINDOW(dev, SDLA_508_RXBUF_INFO);
+			pbuf = (void *) (dev->mem_start + ((pbufi->rse_base + flp->buffer * sizeof(struct buf_entry)) & SDLA_ADDR_MASK));
+			success = pbuf->opp_flag;
+			if (!success)
+				break;
+
+			buf_top = pbufi->buf_top;
+			buf_base = pbufi->buf_base;
+			dlci = pbuf->dlci;
+			len = pbuf->length;
+			addr = pbuf->buf_addr;
+			break;
+	}
+
+	/* common code, find the DLCI and get the SKB */
+	if (success)
+	{
+		for (i=0;i<CONFIG_DLCI_MAX;i++)
+			if (flp->dlci[i] == dlci)
+				break;
+
+		if (i == CONFIG_DLCI_MAX)
+		{
+			printk(KERN_NOTICE "%s: Received packet from invalid DLCI %i, ignoring.", dev->name, dlci);
+			flp->stats.rx_errors++;
+			success = 0;
+		}
+	}
+
+	if (success)
+	{
+		master = flp->master[i];
+		skb = dev_alloc_skb(len + sizeof(struct frhdr));
+		if (skb == NULL) 
+		{
+			printk(KERN_NOTICE "%s: Memory squeeze, dropping packet.\n", dev->name);
+			flp->stats.rx_dropped++; 
+			success = 0;
+		}
+		else
+			skb_reserve(skb, sizeof(struct frhdr));
+	}
+
+	/* pick up the data */
+	switch (flp->type)
+	{
+		case SDLA_S502A:
+		case SDLA_S502E:
+			if (success)
+				__sdla_read(dev, SDLA_502_RCV_BUF + SDLA_502_DATA_OFS, skb_put(skb,len), len);
+
+			SDLA_WINDOW(dev, SDLA_502_RCV_BUF);
+			cmd->opp_flag = 0;
+			break;
+
+		case SDLA_S508:
+			if (success)
+			{
+				/* is this buffer split off the end of the internal ring buffer */
+				split = addr + len > buf_top + 1 ? len - (buf_top - addr + 1) : 0;
+				len2 = len - split;
+
+				__sdla_read(dev, addr, skb_put(skb, len2), len2);
+				if (split)
+					__sdla_read(dev, buf_base, skb_put(skb, split), split);
+			}
+
+			/* increment the buffer we're looking at */
+			SDLA_WINDOW(dev, SDLA_508_RXBUF_INFO);
+			flp->buffer = (flp->buffer + 1) % pbufi->rse_num;
+			pbuf->opp_flag = 0;
+			break;
+	}
+
+	if (success)
+	{
+		flp->stats.rx_packets++;
+		dlp = master->priv;
+		(*dlp->receive)(skb, master);
+	}
+
+	spin_unlock_irqrestore(&sdla_lock, flags);
+}
+
+static irqreturn_t sdla_isr(int irq, void *dev_id, struct pt_regs * regs)
+{
+	struct net_device     *dev;
+	struct frad_local *flp;
+	char              byte;
+
+	dev = dev_id;
+
+	if (dev == NULL)
+	{
+		printk(KERN_WARNING "sdla_isr(): irq %d for unknown device.\n", irq);
+		return IRQ_NONE;
+	}
+
+	flp = dev->priv;
+
+	if (!flp->initialized)
+	{
+		printk(KERN_WARNING "%s: irq %d for uninitialized device.\n", dev->name, irq);
+		return IRQ_NONE;
+	}
+
+	byte = sdla_byte(dev, flp->type == SDLA_S508 ? SDLA_508_IRQ_INTERFACE : SDLA_502_IRQ_INTERFACE);
+	switch (byte)
+	{
+		case SDLA_INTR_RX:
+			sdla_receive(dev);
+			break;
+
+		/* the command will get an error return, which is processed above */
+		case SDLA_INTR_MODEM:
+		case SDLA_INTR_STATUS:
+			sdla_cmd(dev, SDLA_READ_DLC_STATUS, 0, 0, NULL, 0, NULL, NULL);
+			break;
+
+		case SDLA_INTR_TX:
+		case SDLA_INTR_COMPLETE:
+		case SDLA_INTR_TIMER:
+			printk(KERN_WARNING "%s: invalid irq flag 0x%02X.\n", dev->name, byte);
+			break;
+	}
+
+	/* the S502E requires a manual acknowledgement of the interrupt */ 
+	if (flp->type == SDLA_S502E)
+	{
+		flp->state &= ~SDLA_S502E_INTACK;
+		outb(flp->state, dev->base_addr + SDLA_REG_CONTROL);
+		flp->state |= SDLA_S502E_INTACK;
+		outb(flp->state, dev->base_addr + SDLA_REG_CONTROL);
+	}
+
+	/* this clears the byte, informing the Z80 we're done */
+	byte = 0;
+	sdla_write(dev, flp->type == SDLA_S508 ? SDLA_508_IRQ_INTERFACE : SDLA_502_IRQ_INTERFACE, &byte, sizeof(byte));
+	return IRQ_HANDLED;
+}
+
+static void sdla_poll(unsigned long device)
+{
+	struct net_device	  *dev;
+	struct frad_local *flp;
+
+	dev = (struct net_device *) device;
+	flp = dev->priv;
+
+	if (sdla_byte(dev, SDLA_502_RCV_BUF))
+		sdla_receive(dev);
+
+	flp->timer.expires = 1;
+	add_timer(&flp->timer);
+}
+
+static int sdla_close(struct net_device *dev)
+{
+	struct frad_local *flp;
+	struct intr_info  intr;
+	int               len, i;
+	short             dlcis[CONFIG_DLCI_MAX];
+
+	flp = dev->priv;
+
+	len = 0;
+	for(i=0;i<CONFIG_DLCI_MAX;i++)
+		if (flp->dlci[i])
+			dlcis[len++] = abs(flp->dlci[i]);
+	len *= 2;
+
+	if (flp->config.station == FRAD_STATION_NODE)
+	{
+		for(i=0;i<CONFIG_DLCI_MAX;i++)
+			if (flp->dlci[i] > 0) 
+				sdla_cmd(dev, SDLA_DEACTIVATE_DLCI, 0, 0, dlcis, len, NULL, NULL);
+		sdla_cmd(dev, SDLA_DELETE_DLCI, 0, 0, &flp->dlci[i], sizeof(flp->dlci[i]), NULL, NULL);
+	}
+
+	memset(&intr, 0, sizeof(intr));
+	/* let's start up the reception */
+	switch(flp->type)
+	{
+		case SDLA_S502A:
+			del_timer(&flp->timer); 
+			break;
+
+		case SDLA_S502E:
+			sdla_cmd(dev, SDLA_SET_IRQ_TRIGGER, 0, 0, &intr, sizeof(char) + sizeof(short), NULL, NULL);
+			flp->state &= ~SDLA_S502E_INTACK;
+			outb(flp->state, dev->base_addr + SDLA_REG_CONTROL);
+			break;
+
+		case SDLA_S507:
+			break;
+
+		case SDLA_S508:
+			sdla_cmd(dev, SDLA_SET_IRQ_TRIGGER, 0, 0, &intr, sizeof(struct intr_info), NULL, NULL);
+			flp->state &= ~SDLA_S508_INTEN;
+			outb(flp->state, dev->base_addr + SDLA_REG_CONTROL);
+			break;
+	}
+
+	sdla_cmd(dev, SDLA_DISABLE_COMMUNICATIONS, 0, 0, NULL, 0, NULL, NULL);
+
+	netif_stop_queue(dev);
+	
+	return(0);
+}
+
+struct conf_data {
+	struct frad_conf config;
+	short            dlci[CONFIG_DLCI_MAX];
+};
+
+static int sdla_open(struct net_device *dev)
+{
+	struct frad_local *flp;
+	struct dlci_local *dlp;
+	struct conf_data  data;
+	struct intr_info  intr;
+	int               len, i;
+	char              byte;
+
+	flp = dev->priv;
+
+	if (!flp->initialized)
+		return(-EPERM);
+
+	if (!flp->configured)
+		return(-EPERM);
+
+	/* time to send in the configuration */
+	len = 0;
+	for(i=0;i<CONFIG_DLCI_MAX;i++)
+		if (flp->dlci[i])
+			data.dlci[len++] = abs(flp->dlci[i]);
+	len *= 2;
+
+	memcpy(&data.config, &flp->config, sizeof(struct frad_conf));
+	len += sizeof(struct frad_conf);
+
+	sdla_cmd(dev, SDLA_DISABLE_COMMUNICATIONS, 0, 0, NULL, 0, NULL, NULL);
+	sdla_cmd(dev, SDLA_SET_DLCI_CONFIGURATION, 0, 0, &data, len, NULL, NULL);
+
+	if (flp->type == SDLA_S508)
+		flp->buffer = 0;
+
+	sdla_cmd(dev, SDLA_ENABLE_COMMUNICATIONS, 0, 0, NULL, 0, NULL, NULL);
+
+	/* let's start up the reception */
+	memset(&intr, 0, sizeof(intr));
+	switch(flp->type)
+	{
+		case SDLA_S502A:
+			flp->timer.expires = 1;
+			add_timer(&flp->timer);
+			break;
+
+		case SDLA_S502E:
+			flp->state |= SDLA_S502E_ENABLE;
+			outb(flp->state, dev->base_addr + SDLA_REG_CONTROL);
+			flp->state |= SDLA_S502E_INTACK;
+			outb(flp->state, dev->base_addr + SDLA_REG_CONTROL);
+			byte = 0;
+			sdla_write(dev, SDLA_502_IRQ_INTERFACE, &byte, sizeof(byte));
+			intr.flags = SDLA_INTR_RX | SDLA_INTR_STATUS | SDLA_INTR_MODEM;
+			sdla_cmd(dev, SDLA_SET_IRQ_TRIGGER, 0, 0, &intr, sizeof(char) + sizeof(short), NULL, NULL);
+			break;
+
+		case SDLA_S507:
+			break;
+
+		case SDLA_S508:
+			flp->state |= SDLA_S508_INTEN;
+			outb(flp->state, dev->base_addr + SDLA_REG_CONTROL);
+			byte = 0;
+			sdla_write(dev, SDLA_508_IRQ_INTERFACE, &byte, sizeof(byte));
+			intr.flags = SDLA_INTR_RX | SDLA_INTR_STATUS | SDLA_INTR_MODEM;
+			intr.irq = dev->irq;
+			sdla_cmd(dev, SDLA_SET_IRQ_TRIGGER, 0, 0, &intr, sizeof(struct intr_info), NULL, NULL);
+			break;
+	}
+
+	if (flp->config.station == FRAD_STATION_CPE)
+	{
+		byte = SDLA_ICS_STATUS_ENQ;
+		sdla_cmd(dev, SDLA_ISSUE_IN_CHANNEL_SIGNAL, 0, 0, &byte, sizeof(byte), NULL, NULL);
+	}
+	else
+	{
+		sdla_cmd(dev, SDLA_ADD_DLCI, 0, 0, data.dlci, len - sizeof(struct frad_conf), NULL, NULL);
+		for(i=0;i<CONFIG_DLCI_MAX;i++)
+			if (flp->dlci[i] > 0)
+				sdla_cmd(dev, SDLA_ACTIVATE_DLCI, 0, 0, &flp->dlci[i], 2*sizeof(flp->dlci[i]), NULL, NULL);
+	}
+
+	/* configure any specific DLCI settings */
+	for(i=0;i<CONFIG_DLCI_MAX;i++)
+		if (flp->dlci[i])
+		{
+			dlp = flp->master[i]->priv;
+			if (dlp->configured)
+				sdla_cmd(dev, SDLA_SET_DLCI_CONFIGURATION, abs(flp->dlci[i]), 0, &dlp->config, sizeof(struct dlci_conf), NULL, NULL);
+		}
+
+	netif_start_queue(dev);
+	
+	return(0);
+}
+
+static int sdla_config(struct net_device *dev, struct frad_conf __user *conf, int get)
+{
+	struct frad_local *flp;
+	struct conf_data  data;
+	int               i;
+	short             size;
+
+	if (dev->type == 0xFFFF)
+		return(-EUNATCH);
+
+	flp = dev->priv;
+
+	if (!get)
+	{
+		if (netif_running(dev))
+			return(-EBUSY);
+
+		if(copy_from_user(&data.config, conf, sizeof(struct frad_conf)))
+			return -EFAULT;
+
+		if (data.config.station & ~FRAD_STATION_NODE)
+			return(-EINVAL);
+
+		if (data.config.flags & ~FRAD_VALID_FLAGS)
+			return(-EINVAL);
+
+		if ((data.config.kbaud < 0) || 
+			 ((data.config.kbaud > 128) && (flp->type != SDLA_S508)))
+			return(-EINVAL);
+
+		if (data.config.clocking & ~(FRAD_CLOCK_INT | SDLA_S508_PORT_RS232))
+			return(-EINVAL);
+
+		if ((data.config.mtu < 0) || (data.config.mtu > SDLA_MAX_MTU))
+			return(-EINVAL);
+
+		if ((data.config.T391 < 5) || (data.config.T391 > 30))
+			return(-EINVAL);
+
+		if ((data.config.T392 < 5) || (data.config.T392 > 30))
+			return(-EINVAL);
+
+		if ((data.config.N391 < 1) || (data.config.N391 > 255))
+			return(-EINVAL);
+
+		if ((data.config.N392 < 1) || (data.config.N392 > 10))
+			return(-EINVAL);
+
+		if ((data.config.N393 < 1) || (data.config.N393 > 10))
+			return(-EINVAL);
+
+		memcpy(&flp->config, &data.config, sizeof(struct frad_conf));
+		flp->config.flags |= SDLA_DIRECT_RECV;
+
+		if (flp->type == SDLA_S508)
+			flp->config.flags |= SDLA_TX70_RX30;
+
+		if (dev->mtu != flp->config.mtu)
+		{
+			/* this is required to change the MTU */
+			dev->mtu = flp->config.mtu;
+			for(i=0;i<CONFIG_DLCI_MAX;i++)
+				if (flp->master[i])
+					flp->master[i]->mtu = flp->config.mtu;
+		}
+
+		flp->config.mtu += sizeof(struct frhdr);
+
+		/* off to the races! */
+		if (!flp->configured)
+			sdla_start(dev);
+
+		flp->configured = 1;
+	}
+	else
+	{
+		/* no sense reading if the CPU isn't started */
+		if (netif_running(dev))
+		{
+			size = sizeof(data);
+			if (sdla_cmd(dev, SDLA_READ_DLCI_CONFIGURATION, 0, 0, NULL, 0, &data, &size) != SDLA_RET_OK)
+				return(-EIO);
+		}
+		else
+			if (flp->configured)
+				memcpy(&data.config, &flp->config, sizeof(struct frad_conf));
+			else
+				memset(&data.config, 0, sizeof(struct frad_conf));
+
+		memcpy(&flp->config, &data.config, sizeof(struct frad_conf));
+		data.config.flags &= FRAD_VALID_FLAGS;
+		data.config.mtu -= data.config.mtu > sizeof(struct frhdr) ? sizeof(struct frhdr) : data.config.mtu;
+		return copy_to_user(conf, &data.config, sizeof(struct frad_conf))?-EFAULT:0;
+	}
+
+	return(0);
+}
+
+static int sdla_xfer(struct net_device *dev, struct sdla_mem __user *info, int read)
+{
+	struct sdla_mem mem;
+	char	*temp;
+
+	if(copy_from_user(&mem, info, sizeof(mem)))
+		return -EFAULT;
+		
+	if (read)
+	{	
+		temp = kmalloc(mem.len, GFP_KERNEL);
+		if (!temp)
+			return(-ENOMEM);
+		memset(temp, 0, mem.len);
+		sdla_read(dev, mem.addr, temp, mem.len);
+		if(copy_to_user(mem.data, temp, mem.len))
+		{
+			kfree(temp);
+			return -EFAULT;
+		}
+		kfree(temp);
+	}
+	else
+	{
+		temp = kmalloc(mem.len, GFP_KERNEL);
+		if (!temp)
+			return(-ENOMEM);
+		if(copy_from_user(temp, mem.data, mem.len))
+		{
+			kfree(temp);
+			return -EFAULT;
+		}
+		sdla_write(dev, mem.addr, temp, mem.len);
+		kfree(temp);
+	}
+	return(0);
+}
+
+static int sdla_reconfig(struct net_device *dev)
+{
+	struct frad_local *flp;
+	struct conf_data  data;
+	int               i, len;
+
+	flp = dev->priv;
+
+	len = 0;
+	for(i=0;i<CONFIG_DLCI_MAX;i++)
+		if (flp->dlci[i])
+			data.dlci[len++] = flp->dlci[i];
+	len *= 2;
+
+	memcpy(&data, &flp->config, sizeof(struct frad_conf));
+	len += sizeof(struct frad_conf);
+
+	sdla_cmd(dev, SDLA_DISABLE_COMMUNICATIONS, 0, 0, NULL, 0, NULL, NULL);
+	sdla_cmd(dev, SDLA_SET_DLCI_CONFIGURATION, 0, 0, &data, len, NULL, NULL);
+	sdla_cmd(dev, SDLA_ENABLE_COMMUNICATIONS, 0, 0, NULL, 0, NULL, NULL);
+
+	return(0);
+}
+
+static int sdla_ioctl(struct net_device *dev, struct ifreq *ifr, int cmd)
+{
+	struct frad_local *flp;
+
+	if(!capable(CAP_NET_ADMIN))
+		return -EPERM;
+		
+	flp = dev->priv;
+
+	if (!flp->initialized)
+		return(-EINVAL);
+
+	switch (cmd)
+	{
+		case FRAD_GET_CONF:
+		case FRAD_SET_CONF:
+			return(sdla_config(dev, ifr->ifr_data, cmd == FRAD_GET_CONF));
+
+		case SDLA_IDENTIFY:
+			ifr->ifr_flags = flp->type;
+			break;
+
+		case SDLA_CPUSPEED:
+			return(sdla_cpuspeed(dev, ifr)); 
+
+/* ==========================================================
+NOTE:  This is rather a useless action right now, as the
+       current driver does not support protocols other than
+       FR.  However, Sangoma has modules for a number of
+       other protocols in the works.
+============================================================*/
+		case SDLA_PROTOCOL:
+			if (flp->configured)
+				return(-EALREADY);
+
+			switch (ifr->ifr_flags)
+			{
+				case ARPHRD_FRAD:
+					dev->type = ifr->ifr_flags;
+					break;
+				default:
+					return(-ENOPROTOOPT);
+			}
+			break;
+
+		case SDLA_CLEARMEM:
+			sdla_clear(dev);
+			break;
+
+		case SDLA_WRITEMEM:
+		case SDLA_READMEM:
+			if(!capable(CAP_SYS_RAWIO))
+				return -EPERM;
+			return(sdla_xfer(dev, ifr->ifr_data, cmd == SDLA_READMEM));
+
+		case SDLA_START:
+			sdla_start(dev);
+			break;
+
+		case SDLA_STOP:
+			sdla_stop(dev);
+			break;
+
+		default:
+			return(-EOPNOTSUPP);
+	}
+	return(0);
+}
+
+int sdla_change_mtu(struct net_device *dev, int new_mtu)
+{
+	struct frad_local *flp;
+
+	flp = dev->priv;
+
+	if (netif_running(dev))
+		return(-EBUSY);
+
+	/* for now, you can't change the MTU! */
+	return(-EOPNOTSUPP);
+}
+
+int sdla_set_config(struct net_device *dev, struct ifmap *map)
+{
+	struct frad_local *flp;
+	int               i;
+	char              byte;
+	unsigned base;
+	int err = -EINVAL;
+
+	flp = dev->priv;
+
+	if (flp->initialized)
+		return(-EINVAL);
+
+	for(i=0;i < sizeof(valid_port) / sizeof (int) ; i++)
+		if (valid_port[i] == map->base_addr)
+			break;   
+
+	if (i == sizeof(valid_port) / sizeof(int))
+		return(-EINVAL);
+
+	if (!request_region(map->base_addr, SDLA_IO_EXTENTS, dev->name)){
+		printk(KERN_WARNING "SDLA: io-port 0x%04lx in use \n", dev->base_addr);
+		return(-EINVAL);
+	}
+	base = map->base_addr;
+
+	/* test for card types, S502A, S502E, S507, S508                 */
+	/* these tests shut down the card completely, so clear the state */
+	flp->type = SDLA_UNKNOWN;
+	flp->state = 0;
+   
+	for(i=1;i<SDLA_IO_EXTENTS;i++)
+		if (inb(base + i) != 0xFF)
+			break;
+
+	if (i == SDLA_IO_EXTENTS) {   
+		outb(SDLA_HALT, base + SDLA_REG_Z80_CONTROL);
+		if ((inb(base + SDLA_S502_STS) & 0x0F) == 0x08) {
+			outb(SDLA_S502E_INTACK, base + SDLA_REG_CONTROL);
+			if ((inb(base + SDLA_S502_STS) & 0x0F) == 0x0C) {
+				outb(SDLA_HALT, base + SDLA_REG_CONTROL);
+				flp->type = SDLA_S502E;
+				goto got_type;
+			}
+		}
+	}
+
+	for(byte=inb(base),i=0;i<SDLA_IO_EXTENTS;i++)
+		if (inb(base + i) != byte)
+			break;
+
+	if (i == SDLA_IO_EXTENTS) {
+		outb(SDLA_HALT, base + SDLA_REG_CONTROL);
+		if ((inb(base + SDLA_S502_STS) & 0x7E) == 0x30) {
+			outb(SDLA_S507_ENABLE, base + SDLA_REG_CONTROL);
+			if ((inb(base + SDLA_S502_STS) & 0x7E) == 0x32) {
+				outb(SDLA_HALT, base + SDLA_REG_CONTROL);
+				flp->type = SDLA_S507;
+				goto got_type;
+			}
+		}
+	}
+
+	outb(SDLA_HALT, base + SDLA_REG_CONTROL);
+	if ((inb(base + SDLA_S508_STS) & 0x3F) == 0x00) {
+		outb(SDLA_S508_INTEN, base + SDLA_REG_CONTROL);
+		if ((inb(base + SDLA_S508_STS) & 0x3F) == 0x10) {
+			outb(SDLA_HALT, base + SDLA_REG_CONTROL);
+			flp->type = SDLA_S508;
+			goto got_type;
+		}
+	}
+
+	outb(SDLA_S502A_HALT, base + SDLA_REG_CONTROL);
+	if (inb(base + SDLA_S502_STS) == 0x40) {
+		outb(SDLA_S502A_START, base + SDLA_REG_CONTROL);
+		if (inb(base + SDLA_S502_STS) == 0x40) {
+			outb(SDLA_S502A_INTEN, base + SDLA_REG_CONTROL);
+			if (inb(base + SDLA_S502_STS) == 0x44) {
+				outb(SDLA_S502A_START, base + SDLA_REG_CONTROL);
+				flp->type = SDLA_S502A;
+				goto got_type;
+			}
+		}
+	}
+
+	printk(KERN_NOTICE "%s: Unknown card type\n", dev->name);
+	err = -ENODEV;
+	goto fail;
+
+got_type:
+	switch(base) {
+		case 0x270:
+		case 0x280:
+		case 0x380: 
+		case 0x390:
+			if (flp->type != SDLA_S508 && flp->type != SDLA_S507)
+				goto fail;
+	}
+
+	switch (map->irq) {
+		case 2:
+			if (flp->type != SDLA_S502E)
+				goto fail;
+			break;
+
+		case 10:
+		case 11:
+		case 12:
+		case 15:
+		case 4:
+			if (flp->type != SDLA_S508 && flp->type != SDLA_S507)
+				goto fail;
+			break;
+		case 3:
+		case 5:
+		case 7:
+			if (flp->type == SDLA_S502A)
+				goto fail;
+			break;
+
+		default:
+			goto fail;
+	}
+
+	err = -EAGAIN;
+	if (request_irq(dev->irq, &sdla_isr, 0, dev->name, dev)) 
+		goto fail;
+
+	if (flp->type == SDLA_S507) {
+		switch(dev->irq) {
+			case 3:
+				flp->state = SDLA_S507_IRQ3;
+				break;
+			case 4:
+				flp->state = SDLA_S507_IRQ4;
+				break;
+			case 5:
+				flp->state = SDLA_S507_IRQ5;
+				break;
+			case 7:
+				flp->state = SDLA_S507_IRQ7;
+				break;
+			case 10:
+				flp->state = SDLA_S507_IRQ10;
+				break;
+			case 11:
+				flp->state = SDLA_S507_IRQ11;
+				break;
+			case 12:
+				flp->state = SDLA_S507_IRQ12;
+				break;
+			case 15:
+				flp->state = SDLA_S507_IRQ15;
+				break;
+		}
+	}
+
+	for(i=0;i < sizeof(valid_mem) / sizeof (int) ; i++)
+		if (valid_mem[i] == map->mem_start)
+			break;   
+
+	err = -EINVAL;
+	if (i == sizeof(valid_mem) / sizeof(int))
+		goto fail2;
+
+	if (flp->type == SDLA_S502A && (map->mem_start & 0xF000) >> 12 == 0x0E)
+		goto fail2;
+
+	if (flp->type != SDLA_S507 && map->mem_start >> 16 == 0x0B)
+		goto fail2;
+
+	if (flp->type == SDLA_S507 && map->mem_start >> 16 == 0x0D)
+		goto fail2;
+
+	byte = flp->type != SDLA_S508 ? SDLA_8K_WINDOW : 0;
+	byte |= (map->mem_start & 0xF000) >> (12 + (flp->type == SDLA_S508 ? 1 : 0));
+	switch(flp->type) {
+		case SDLA_S502A:
+		case SDLA_S502E:
+			switch (map->mem_start >> 16) {
+				case 0x0A:
+					byte |= SDLA_S502_SEG_A;
+					break;
+				case 0x0C:
+					byte |= SDLA_S502_SEG_C;
+					break;
+				case 0x0D:
+					byte |= SDLA_S502_SEG_D;
+					break;
+				case 0x0E:
+					byte |= SDLA_S502_SEG_E;
+					break;
+			}
+			break;
+		case SDLA_S507:
+			switch (map->mem_start >> 16) {
+				case 0x0A:
+					byte |= SDLA_S507_SEG_A;
+					break;
+				case 0x0B:
+					byte |= SDLA_S507_SEG_B;
+					break;
+				case 0x0C:
+					byte |= SDLA_S507_SEG_C;
+					break;
+				case 0x0E:
+					byte |= SDLA_S507_SEG_E;
+					break;
+			}
+			break;
+		case SDLA_S508:
+			switch (map->mem_start >> 16) {
+				case 0x0A:
+					byte |= SDLA_S508_SEG_A;
+					break;
+				case 0x0C:
+					byte |= SDLA_S508_SEG_C;
+					break;
+				case 0x0D:
+					byte |= SDLA_S508_SEG_D;
+					break;
+				case 0x0E:
+					byte |= SDLA_S508_SEG_E;
+					break;
+			}
+			break;
+	}
+
+	/* set the memory bits, and enable access */
+	outb(byte, base + SDLA_REG_PC_WINDOW);
+
+	switch(flp->type)
+	{
+		case SDLA_S502E:
+			flp->state = SDLA_S502E_ENABLE;
+			break;
+		case SDLA_S507:
+			flp->state |= SDLA_MEMEN;
+			break;
+		case SDLA_S508:
+			flp->state = SDLA_MEMEN;
+			break;
+	}
+	outb(flp->state, base + SDLA_REG_CONTROL);
+
+	dev->irq = map->irq;
+	dev->base_addr = base;
+	dev->mem_start = map->mem_start;
+	dev->mem_end = dev->mem_start + 0x2000;
+	flp->initialized = 1;
+	return 0;
+
+fail2:
+	free_irq(map->irq, dev);
+fail:
+	release_region(base, SDLA_IO_EXTENTS);
+	return err;
+}
+ 
+static struct net_device_stats *sdla_stats(struct net_device *dev)
+{
+	struct frad_local *flp;
+	flp = dev->priv;
+
+	return(&flp->stats);
+}
+
+static void setup_sdla(struct net_device *dev)
+{
+	struct frad_local *flp = dev->priv;
+
+	netdev_boot_setup_check(dev);
+
+	SET_MODULE_OWNER(dev);
+	dev->flags		= 0;
+	dev->type		= 0xFFFF;
+	dev->hard_header_len	= 0;
+	dev->addr_len		= 0;
+	dev->mtu		= SDLA_MAX_MTU;
+
+	dev->open		= sdla_open;
+	dev->stop		= sdla_close;
+	dev->do_ioctl		= sdla_ioctl;
+	dev->set_config		= sdla_set_config;
+	dev->get_stats		= sdla_stats;
+	dev->hard_start_xmit	= sdla_transmit;
+	dev->change_mtu		= sdla_change_mtu;
+
+	flp->activate		= sdla_activate;
+	flp->deactivate		= sdla_deactivate;
+	flp->assoc		= sdla_assoc;
+	flp->deassoc		= sdla_deassoc;
+	flp->dlci_conf		= sdla_dlci_conf;
+
+	init_timer(&flp->timer);
+	flp->timer.expires	= 1;
+	flp->timer.data		= (unsigned long) dev;
+	flp->timer.function	= sdla_poll;
+}
+
+static struct net_device *sdla;
+
+static int __init init_sdla(void)
+{
+	int err;
+
+	printk("%s.\n", version);
+
+	sdla = alloc_netdev(sizeof(struct frad_local), "sdla0", setup_sdla);
+	if (!sdla) 
+		return -ENOMEM;
+
+	err = register_netdev(sdla);
+	if (err) 
+		free_netdev(sdla);
+
+	return err;
+}
+
+static void __exit exit_sdla(void)
+{
+	struct frad_local *flp = sdla->priv;
+
+	unregister_netdev(sdla);
+	if (flp->initialized) {
+		free_irq(sdla->irq, sdla);
+		release_region(sdla->base_addr, SDLA_IO_EXTENTS);
+	}
+	del_timer_sync(&flp->timer);
+	free_netdev(sdla);
+}
+
+MODULE_LICENSE("GPL");
+
+module_init(init_sdla);
+module_exit(exit_sdla);
diff --git a/drivers/net/wan/sdla_chdlc.c b/drivers/net/wan/sdla_chdlc.c
new file mode 100644
index 0000000..afbe002
--- /dev/null
+++ b/drivers/net/wan/sdla_chdlc.c
@@ -0,0 +1,4433 @@
+/*****************************************************************************
+* sdla_chdlc.c	WANPIPE(tm) Multiprotocol WAN Link Driver. Cisco HDLC module.
+*
+* Authors: 	Nenad Corbic <ncorbic@sangoma.com>
+*		Gideon Hack  
+*
+* Copyright:	(c) 1995-2001 Sangoma Technologies Inc.
+*
+*		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.
+* ============================================================================
+* Feb 28, 2001  Nenad Corbic	Updated if_tx_timeout() routine for 
+* 				2.4.X kernels.
+* Jan 25, 2001  Nenad Corbic	Added a TTY Sync serial driver over the
+* 				HDLC streaming protocol
+* 				Added a TTY Async serial driver over the
+* 				Async protocol.
+* Dec 15, 2000  Nenad Corbic    Updated for 2.4.X Kernel support
+* Nov 13, 2000  Nenad Corbic    Added true interface type encoding option.
+* 				Tcpdump doesn't support CHDLC inteface
+* 				types, to fix this "true type" option will set
+* 				the interface type to RAW IP mode.
+* Nov 07, 2000  Nenad Corbic	Added security features for UDP debugging:
+*                               Deny all and specify allowed requests.
+* Jun 20, 2000  Nenad Corbic	Fixed the API IP ERROR bug. Caused by the 
+*                               latest update.
+* May 09, 2000	Nenad Corbic	Option to bring down an interface
+*                               upon disconnect.
+* Mar 23, 2000  Nenad Corbic	Improved task queue, bh handling.
+* Mar 16, 2000	Nenad Corbic	Fixed the SLARP Dynamic IP addressing.
+* Mar 06, 2000  Nenad Corbic	Bug Fix: corrupted mbox recovery.
+* Feb 10, 2000  Gideon Hack     Added ASYNC support.
+* Feb 09, 2000  Nenad Corbic    Fixed two shutdown bugs in update() and
+*                               if_stats() functions.
+* Jan 24, 2000  Nenad Corbic    Fixed a startup wanpipe state racing,  
+*                               condition between if_open and isr. 
+* Jan 10, 2000  Nenad Corbic    Added new socket API support.
+* Dev 15, 1999  Nenad Corbic    Fixed up header files for 2.0.X kernels
+* Nov 20, 1999  Nenad Corbic 	Fixed zero length API bug.
+* Sep 30, 1999  Nenad Corbic    Fixed dynamic IP and route setup.
+* Sep 23, 1999  Nenad Corbic    Added SMP support, fixed tracing 
+* Sep 13, 1999  Nenad Corbic	Split up Port 0 and 1 into separate devices.
+* Jun 02, 1999  Gideon Hack     Added support for the S514 adapter.
+* Oct 30, 1998	Jaspreet Singh	Added Support for CHDLC API (HDLC STREAMING).
+* Oct 28, 1998	Jaspreet Singh	Added Support for Dual Port CHDLC.
+* Aug 07, 1998	David Fong	Initial version.
+*****************************************************************************/
+
+#include <linux/module.h>
+#include <linux/kernel.h>	/* printk(), and other useful stuff */
+#include <linux/stddef.h>	/* offsetof(), etc. */
+#include <linux/errno.h>	/* return codes */
+#include <linux/string.h>	/* inline memset(), etc. */
+#include <linux/slab.h>	/* kmalloc(), kfree() */
+#include <linux/wanrouter.h>	/* WAN router definitions */
+#include <linux/wanpipe.h>	/* WANPIPE common user API definitions */
+#include <linux/if_arp.h>	/* ARPHRD_* defines */
+
+
+#include <asm/uaccess.h>
+#include <linux/inetdevice.h>
+#include <linux/netdevice.h>
+
+#include <linux/in.h>		/* sockaddr_in */
+#include <linux/inet.h>	
+#include <linux/if.h>
+#include <asm/byteorder.h>	/* htons(), etc. */
+#include <linux/sdlapci.h>
+#include <asm/io.h>
+
+#include <linux/sdla_chdlc.h>		/* CHDLC firmware API definitions */
+#include <linux/sdla_asy.h>           	/* CHDLC (async) API definitions */
+
+#include <linux/if_wanpipe_common.h>    /* Socket Driver common area */
+#include <linux/if_wanpipe.h>		
+
+/* TTY Includes */
+#include <linux/tty.h>
+#include <linux/tty_flip.h>
+#include <linux/serial.h>
+
+
+/****** Defines & Macros ****************************************************/
+
+/* reasons for enabling the timer interrupt on the adapter */
+#define TMR_INT_ENABLED_UDP   		0x01
+#define TMR_INT_ENABLED_UPDATE		0x02
+#define TMR_INT_ENABLED_CONFIG		0x10
+
+#define MAX_IP_ERRORS	10
+
+#define TTY_CHDLC_MAX_MTU	2000
+#define	CHDLC_DFLT_DATA_LEN	1500		/* default MTU */
+#define CHDLC_HDR_LEN		1
+
+#define CHDLC_API 0x01
+
+#define PORT(x)   (x == 0 ? "PRIMARY" : "SECONDARY" )
+#define MAX_BH_BUFF	10
+
+//#define PRINT_DEBUG
+#ifdef PRINT_DEBUG
+#define dbg_printk(format, a...) printk(format, ## a)
+#else
+#define dbg_printk(format, a...)
+#endif  
+
+/******Data Structures*****************************************************/
+
+/* This structure is placed in the private data area of the device structure.
+ * The card structure used to occupy the private area but now the following 
+ * structure will incorporate the card structure along with CHDLC specific data
+ */
+
+typedef struct chdlc_private_area
+{
+	wanpipe_common_t common;
+	sdla_t		*card;
+	int 		TracingEnabled;		/* For enabling Tracing */
+	unsigned long 	curr_trace_addr;	/* Used for Tracing */
+	unsigned long 	start_trace_addr;
+	unsigned long 	end_trace_addr;
+	unsigned long 	base_addr_trace_buffer;
+	unsigned long 	end_addr_trace_buffer;
+	unsigned short 	number_trace_elements;
+	unsigned  	available_buffer_space;
+	unsigned long 	router_start_time;
+	unsigned char 	route_status;
+	unsigned char 	route_removed;
+	unsigned long 	tick_counter;		/* For 5s timeout counter */
+	unsigned long 	router_up_time;
+        u32             IP_address;		/* IP addressing */
+        u32             IP_netmask;
+	u32		ip_local;
+	u32		ip_remote;
+	u32 		ip_local_tmp;
+	u32		ip_remote_tmp;
+	u8		ip_error;
+	u8		config_chdlc;
+	u8 		config_chdlc_timeout;
+	unsigned char  mc;			/* Mulitcast support on/off */
+	unsigned short udp_pkt_lgth;		/* udp packet processing */
+	char udp_pkt_src;
+	char udp_pkt_data[MAX_LGTH_UDP_MGNT_PKT];
+	unsigned short timer_int_enabled;
+	char update_comms_stats;		/* updating comms stats */
+
+	bh_data_t *bh_head;	  	  /* Circular buffer for chdlc_bh */
+	unsigned long  tq_working;
+	volatile int  bh_write;
+	volatile int  bh_read;
+	atomic_t  bh_buff_used;
+	
+	unsigned char interface_down;
+
+	/* Polling work queue entry. Each interface
+         * has its own work queue entry, which is used
+         * to defer events from the interrupt */
+	struct work_struct poll_work;
+	struct timer_list poll_delay_timer;
+
+	u8 gateway;
+	u8 true_if_encoding;
+	//FIXME: add driver stats as per frame relay!
+
+} chdlc_private_area_t;
+
+/* Route Status options */
+#define NO_ROUTE	0x00
+#define ADD_ROUTE	0x01
+#define ROUTE_ADDED	0x02
+#define REMOVE_ROUTE	0x03
+
+
+/* variable for keeping track of enabling/disabling FT1 monitor status */
+static int rCount = 0;
+
+/* variable for tracking how many interfaces to open for WANPIPE on the
+   two ports */
+
+extern void disable_irq(unsigned int);
+extern void enable_irq(unsigned int);
+
+/****** Function Prototypes *************************************************/
+/* WAN link driver entry points. These are called by the WAN router module. */
+static int update(struct wan_device* wandev);
+static int new_if(struct wan_device* wandev, struct net_device* dev,
+		  wanif_conf_t* conf);
+
+/* Network device interface */
+static int if_init(struct net_device* dev);
+static int if_open(struct net_device* dev);
+static int if_close(struct net_device* dev);
+static int if_header(struct sk_buff* skb, struct net_device* dev,
+		     unsigned short type, void* daddr, void* saddr,
+		     unsigned len);
+
+static int if_rebuild_hdr (struct sk_buff *skb);
+static struct net_device_stats* if_stats(struct net_device* dev);
+  
+static int if_send(struct sk_buff* skb, struct net_device* dev);
+
+/* CHDLC Firmware interface functions */
+static int chdlc_configure 	(sdla_t* card, void* data);
+static int chdlc_comm_enable 	(sdla_t* card);
+static int chdlc_read_version 	(sdla_t* card, char* str);
+static int chdlc_set_intr_mode 	(sdla_t* card, unsigned mode);
+static int chdlc_send (sdla_t* card, void* data, unsigned len);
+static int chdlc_read_comm_err_stats (sdla_t* card);
+static int chdlc_read_op_stats (sdla_t* card);
+static int chdlc_error (sdla_t *card, int err, CHDLC_MAILBOX_STRUCT *mb);
+
+
+static int chdlc_disable_comm_shutdown (sdla_t *card);
+static void if_tx_timeout(struct net_device *dev);
+
+/* Miscellaneous CHDLC Functions */
+static int set_chdlc_config (sdla_t* card);
+static void init_chdlc_tx_rx_buff( sdla_t* card);
+static int process_chdlc_exception(sdla_t *card);
+static int process_global_exception(sdla_t *card);
+static int update_comms_stats(sdla_t* card,
+        chdlc_private_area_t* chdlc_priv_area);
+static int configure_ip (sdla_t* card);
+static int unconfigure_ip (sdla_t* card);
+static void process_route(sdla_t *card);
+static void port_set_state (sdla_t *card, int);
+static int config_chdlc (sdla_t *card);
+static void disable_comm (sdla_t *card);
+
+static void trigger_chdlc_poll(struct net_device *dev);
+static void chdlc_poll(struct net_device *dev);
+static void chdlc_poll_delay (unsigned long dev_ptr);
+
+
+/* Miscellaneous asynchronous interface Functions */
+static int set_asy_config (sdla_t* card);
+static int asy_comm_enable (sdla_t* card);
+
+/* Interrupt handlers */
+static void wpc_isr (sdla_t* card);
+static void rx_intr (sdla_t* card);
+static void timer_intr(sdla_t *);
+
+/* Bottom half handlers */
+static void chdlc_work(struct net_device *dev);
+static int chdlc_work_cleanup(struct net_device *dev);
+static int bh_enqueue(struct net_device *dev, struct sk_buff *skb);
+
+/* Miscellaneous functions */
+static int chk_bcast_mcast_addr(sdla_t* card, struct net_device* dev,
+				struct sk_buff *skb);
+static int reply_udp( unsigned char *data, unsigned int mbox_len );
+static int intr_test( sdla_t* card);
+static int udp_pkt_type( struct sk_buff *skb , sdla_t* card);
+static int store_udp_mgmt_pkt(char udp_pkt_src, sdla_t* card,
+                                struct sk_buff *skb, struct net_device* dev,
+                                chdlc_private_area_t* chdlc_priv_area);
+static int process_udp_mgmt_pkt(sdla_t* card, struct net_device* dev,  
+				chdlc_private_area_t* chdlc_priv_area);
+static unsigned short calc_checksum (char *, int);
+static void s508_lock (sdla_t *card, unsigned long *smp_flags);
+static void s508_unlock (sdla_t *card, unsigned long *smp_flags);
+
+
+static int  Intr_test_counter;
+
+/* TTY Global Definitions */
+
+#define NR_PORTS 4
+#define WAN_TTY_MAJOR 226
+#define WAN_TTY_MINOR 0
+
+#define WAN_CARD(port) (tty_card_map[port])
+#define MIN_PORT 0
+#define MAX_PORT NR_PORTS-1 
+
+#define CRC_LENGTH 2
+
+static int wanpipe_tty_init(sdla_t *card);
+static void wanpipe_tty_receive(sdla_t *, unsigned, unsigned int);
+static void wanpipe_tty_trigger_poll(sdla_t *card);
+
+static struct tty_driver serial_driver;
+static int tty_init_cnt=0;
+
+static struct serial_state rs_table[NR_PORTS];
+
+static char tty_driver_mode=WANOPT_TTY_SYNC;
+
+static char *opt_decode[] = {"NONE","CRTSCTS","XONXOFF-RX",
+	  	             "CRTSCTS XONXOFF-RX","XONXOFF-TX",
+		             "CRTSCTS XONXOFF-TX","CRTSCTS XONXOFF"};
+static char *p_decode[] = {"NONE","ODD","EVEN"};
+
+static void* tty_card_map[NR_PORTS] = {NULL,NULL,NULL,NULL};
+
+
+/****** Public Functions ****************************************************/
+
+/*============================================================================
+ * Cisco HDLC protocol initialization routine.
+ *
+ * This routine is called by the main WANPIPE module during setup.  At this
+ * point adapter is completely initialized and firmware is running.
+ *  o read firmware version (to make sure it's alive)
+ *  o configure adapter
+ *  o initialize protocol-specific fields of the adapter data space.
+ *
+ * Return:	0	o.k.
+ *		< 0	failure.
+ */
+int wpc_init (sdla_t* card, wandev_conf_t* conf)
+{
+	unsigned char port_num;
+	int err;
+	unsigned long max_permitted_baud = 0;
+	SHARED_MEMORY_INFO_STRUCT *flags;
+
+	union
+		{
+		char str[80];
+		} u;
+	volatile CHDLC_MAILBOX_STRUCT* mb;
+	CHDLC_MAILBOX_STRUCT* mb1;
+	unsigned long timeout;
+
+	/* Verify configuration ID */
+	if (conf->config_id != WANCONFIG_CHDLC) {
+		printk(KERN_INFO "%s: invalid configuration ID %u!\n",
+				  card->devname, conf->config_id);
+		return -EINVAL;
+	}
+
+	/* Find out which Port to use */
+	if ((conf->comm_port == WANOPT_PRI) || (conf->comm_port == WANOPT_SEC)){
+		if (card->next){
+
+			if (conf->comm_port != card->next->u.c.comm_port){
+				card->u.c.comm_port = conf->comm_port;
+			}else{
+				printk(KERN_INFO "%s: ERROR - %s port used!\n",
+        		        	card->wandev.name, PORT(conf->comm_port));
+				return -EINVAL;
+			}
+		}else{
+			card->u.c.comm_port = conf->comm_port;
+		}
+	}else{
+		printk(KERN_INFO "%s: ERROR - Invalid Port Selected!\n",
+                			card->wandev.name);
+		return -EINVAL;
+	}
+	
+
+	/* Initialize protocol-specific fields */
+	if(card->hw.type != SDLA_S514){
+
+		if (card->u.c.comm_port == WANOPT_PRI){	
+			card->mbox  = (void *) card->hw.dpmbase;
+		}else{
+			card->mbox  = (void *) card->hw.dpmbase + 
+				SEC_BASE_ADDR_MB_STRUCT - PRI_BASE_ADDR_MB_STRUCT;
+		}	
+	}else{ 
+		/* for a S514 adapter, set a pointer to the actual mailbox in the */
+		/* allocated virtual memory area */
+		if (card->u.c.comm_port == WANOPT_PRI){
+			card->mbox = (void *) card->hw.dpmbase + PRI_BASE_ADDR_MB_STRUCT;
+		}else{
+			card->mbox = (void *) card->hw.dpmbase + SEC_BASE_ADDR_MB_STRUCT;
+		}	
+	}
+
+	mb = mb1 = card->mbox;
+
+	if (!card->configured){
+
+		/* The board will place an 'I' in the return code to indicate that it is
+	   	ready to accept commands.  We expect this to be completed in less
+           	than 1 second. */
+
+		timeout = jiffies;
+		while (mb->return_code != 'I')	/* Wait 1s for board to initialize */
+			if ((jiffies - timeout) > 1*HZ) break;
+
+		if (mb->return_code != 'I') {
+			printk(KERN_INFO
+				"%s: Initialization not completed by adapter\n",
+				card->devname);
+			printk(KERN_INFO "Please contact Sangoma representative.\n");
+			return -EIO;
+		}
+	}
+
+	/* Read firmware version.  Note that when adapter initializes, it
+	 * clears the mailbox, so it may appear that the first command was
+	 * executed successfully when in fact it was merely erased. To work
+	 * around this, we execute the first command twice.
+	 */
+
+	if (chdlc_read_version(card, u.str))
+		return -EIO;
+
+	printk(KERN_INFO "%s: Running Cisco HDLC firmware v%s\n",
+		card->devname, u.str); 
+
+	card->isr			= &wpc_isr;
+	card->poll			= NULL;
+	card->exec			= NULL;
+	card->wandev.update		= &update;
+ 	card->wandev.new_if		= &new_if;
+	card->wandev.del_if		= NULL;
+	card->wandev.udp_port   	= conf->udp_port;
+	card->disable_comm		= &disable_comm;
+	card->wandev.new_if_cnt = 0;
+
+	/* reset the number of times the 'update()' proc has been called */
+	card->u.c.update_call_count = 0;
+	
+	card->wandev.ttl = conf->ttl;
+	card->wandev.interface = conf->interface; 
+
+	if ((card->u.c.comm_port == WANOPT_SEC && conf->interface == WANOPT_V35)&&
+	    card->hw.type != SDLA_S514){
+		printk(KERN_INFO "%s: ERROR - V35 Interface not supported on S508 %s port \n",
+			card->devname, PORT(card->u.c.comm_port));
+		return -EIO;
+	}
+
+	card->wandev.clocking = conf->clocking;
+
+	port_num = card->u.c.comm_port;
+
+	/* in API mode, we can configure for "receive only" buffering */
+	if(card->hw.type == SDLA_S514) {
+		card->u.c.receive_only = conf->receive_only;
+		if(conf->receive_only) {
+			printk(KERN_INFO
+				"%s: Configured for 'receive only' mode\n",
+                                card->devname);
+		}
+	}
+
+	/* Setup Port Bps */
+
+	if(card->wandev.clocking) {
+		if((port_num == WANOPT_PRI) || card->u.c.receive_only) {
+			/* For Primary Port 0 */
+               		max_permitted_baud =
+				(card->hw.type == SDLA_S514) ?
+				PRI_MAX_BAUD_RATE_S514 : 
+				PRI_MAX_BAUD_RATE_S508;
+
+		}else if(port_num == WANOPT_SEC) {
+			/* For Secondary Port 1 */
+                        max_permitted_baud =
+                               (card->hw.type == SDLA_S514) ?
+                                SEC_MAX_BAUD_RATE_S514 :
+                                SEC_MAX_BAUD_RATE_S508;
+                        }
+  
+			if(conf->bps > max_permitted_baud) {
+				conf->bps = max_permitted_baud;
+				printk(KERN_INFO "%s: Baud too high!\n",
+					card->wandev.name);
+ 				printk(KERN_INFO "%s: Baud rate set to %lu bps\n", 
+					card->wandev.name, max_permitted_baud);
+			}
+			card->wandev.bps = conf->bps;
+	}else{
+        	card->wandev.bps = 0;
+  	}
+
+	/* Setup the Port MTU */
+	if((port_num == WANOPT_PRI) || card->u.c.receive_only) {
+
+		/* For Primary Port 0 */
+		card->wandev.mtu =
+			(conf->mtu >= MIN_LGTH_CHDLC_DATA_CFG) ?
+			min_t(unsigned int, conf->mtu, PRI_MAX_NO_DATA_BYTES_IN_FRAME) :
+			CHDLC_DFLT_DATA_LEN;
+	} else if(port_num == WANOPT_SEC) { 
+		/* For Secondary Port 1 */
+		card->wandev.mtu =
+			(conf->mtu >= MIN_LGTH_CHDLC_DATA_CFG) ?
+			min_t(unsigned int, conf->mtu, SEC_MAX_NO_DATA_BYTES_IN_FRAME) :
+			CHDLC_DFLT_DATA_LEN;
+	}
+
+	/* Set up the interrupt status area */
+	/* Read the CHDLC Configuration and obtain: 
+	 *	Ptr to shared memory infor struct
+         * Use this pointer to calculate the value of card->u.c.flags !
+ 	 */
+	mb1->buffer_length = 0;
+	mb1->command = READ_CHDLC_CONFIGURATION;
+	err = sdla_exec(mb1) ? mb1->return_code : CMD_TIMEOUT;
+	if(err != COMMAND_OK) {
+                if(card->hw.type != SDLA_S514)
+                	enable_irq(card->hw.irq);
+
+		chdlc_error(card, err, mb1);
+		return -EIO;
+	}
+
+	if(card->hw.type == SDLA_S514){
+               	card->u.c.flags = (void *)(card->hw.dpmbase +
+               		(((CHDLC_CONFIGURATION_STRUCT *)mb1->data)->
+			ptr_shared_mem_info_struct));
+        }else{
+                card->u.c.flags = (void *)(card->hw.dpmbase +
+                        (((CHDLC_CONFIGURATION_STRUCT *)mb1->data)->
+			ptr_shared_mem_info_struct % SDLA_WINDOWSIZE));
+	}
+
+	flags = card->u.c.flags;
+	
+	/* This is for the ports link state */
+	card->wandev.state = WAN_DUALPORT;
+	card->u.c.state = WAN_DISCONNECTED;
+
+
+	if (!card->wandev.piggyback){	
+		int err;
+
+		/* Perform interrupt testing */
+		err = intr_test(card);
+
+		if(err || (Intr_test_counter < MAX_INTR_TEST_COUNTER)) { 
+			printk(KERN_INFO "%s: Interrupt test failed (%i)\n",
+					card->devname, Intr_test_counter);
+			printk(KERN_INFO "%s: Please choose another interrupt\n",
+					card->devname);
+			return -EIO;
+		}
+		
+		printk(KERN_INFO "%s: Interrupt test passed (%i)\n", 
+				card->devname, Intr_test_counter);
+		card->configured = 1;
+	}
+
+	if ((card->tty_opt=conf->tty) == WANOPT_YES){
+		int err;
+		card->tty_minor = conf->tty_minor;
+
+		/* On ASYNC connections internal clocking 
+		 * is mandatory */
+		if ((card->u.c.async_mode = conf->tty_mode)){
+			card->wandev.clocking = 1;
+		}
+		err=wanpipe_tty_init(card);
+		if (err){
+			return err;
+		}
+	}else{
+	
+
+		if (chdlc_set_intr_mode(card, APP_INT_ON_TIMER)){
+			printk (KERN_INFO "%s: "
+				"Failed to set interrupt triggers!\n",
+				card->devname);
+			return -EIO;	
+        	}
+	
+		/* Mask the Timer interrupt */
+		flags->interrupt_info_struct.interrupt_permission &= 
+			~APP_INT_ON_TIMER;
+	}
+
+	/* If we are using CHDLC in backup mode, this flag will
+	 * indicate not to look for IP addresses in config_chdlc()*/
+	card->u.c.backup = conf->backup;
+	
+	printk(KERN_INFO "\n");
+
+	return 0;
+}
+
+/******* WAN Device Driver Entry Points *************************************/
+
+/*============================================================================
+ * Update device status & statistics
+ * This procedure is called when updating the PROC file system and returns
+ * various communications statistics. These statistics are accumulated from 3 
+ * different locations:
+ * 	1) The 'if_stats' recorded for the device.
+ * 	2) Communication error statistics on the adapter.
+ *      3) CHDLC operational statistics on the adapter.
+ * The board level statistics are read during a timer interrupt. Note that we 
+ * read the error and operational statistics during consecitive timer ticks so
+ * as to minimize the time that we are inside the interrupt handler.
+ *
+ */
+static int update(struct wan_device* wandev)
+{
+	sdla_t* card = wandev->private;
+ 	struct net_device* dev;
+        volatile chdlc_private_area_t* chdlc_priv_area;
+        SHARED_MEMORY_INFO_STRUCT *flags;
+	unsigned long timeout;
+
+	/* sanity checks */
+	if((wandev == NULL) || (wandev->private == NULL))
+		return -EFAULT;
+	
+	if(wandev->state == WAN_UNCONFIGURED)
+		return -ENODEV;
+
+	/* more sanity checks */
+        if(!card->u.c.flags)
+                return -ENODEV;
+
+	if(test_bit(PERI_CRIT, (void*)&card->wandev.critical))
+                return -EAGAIN;
+
+	if((dev=card->wandev.dev) == NULL)
+		return -ENODEV;
+
+	if((chdlc_priv_area=dev->priv) == NULL)
+		return -ENODEV;
+
+      	flags = card->u.c.flags;
+       	if(chdlc_priv_area->update_comms_stats){
+		return -EAGAIN;
+	}
+			
+	/* we will need 2 timer interrupts to complete the */
+	/* reading of the statistics */
+	chdlc_priv_area->update_comms_stats = 2;
+       	flags->interrupt_info_struct.interrupt_permission |= APP_INT_ON_TIMER;
+	chdlc_priv_area->timer_int_enabled = TMR_INT_ENABLED_UPDATE;
+  
+	/* wait a maximum of 1 second for the statistics to be updated */ 
+        timeout = jiffies;
+        for(;;) {
+		if(chdlc_priv_area->update_comms_stats == 0)
+			break;
+                if ((jiffies - timeout) > (1 * HZ)){
+    			chdlc_priv_area->update_comms_stats = 0;
+ 			chdlc_priv_area->timer_int_enabled &=
+				~TMR_INT_ENABLED_UPDATE; 
+ 			return -EAGAIN;
+		}
+        }
+
+	return 0;
+}
+
+
+/*============================================================================
+ * Create new logical channel.
+ * This routine is called by the router when ROUTER_IFNEW IOCTL is being
+ * handled.
+ * o parse media- and hardware-specific configuration
+ * o make sure that a new channel can be created
+ * o allocate resources, if necessary
+ * o prepare network device structure for registaration.
+ *
+ * Return:	0	o.k.
+ *		< 0	failure (channel will not be created)
+ */
+static int new_if(struct wan_device* wandev, struct net_device* dev,
+		  wanif_conf_t* conf)
+{
+	sdla_t* card = wandev->private;
+	chdlc_private_area_t* chdlc_priv_area;
+
+
+	printk(KERN_INFO "%s: Configuring Interface: %s\n",
+			card->devname, conf->name);
+ 
+	if ((conf->name[0] == '\0') || (strlen(conf->name) > WAN_IFNAME_SZ)) {
+		printk(KERN_INFO "%s: Invalid interface name!\n",
+			card->devname);
+		return -EINVAL;
+	}
+		
+	/* allocate and initialize private data */
+	chdlc_priv_area = kmalloc(sizeof(chdlc_private_area_t), GFP_KERNEL);
+	
+	if(chdlc_priv_area == NULL) 
+		return -ENOMEM;
+
+	memset(chdlc_priv_area, 0, sizeof(chdlc_private_area_t));
+
+	chdlc_priv_area->card = card; 
+	chdlc_priv_area->common.sk = NULL;
+	chdlc_priv_area->common.func = NULL;	
+
+	/* initialize data */
+	strcpy(card->u.c.if_name, conf->name);
+
+	if(card->wandev.new_if_cnt > 0) {
+                kfree(chdlc_priv_area);
+		return -EEXIST;
+	}
+
+	card->wandev.new_if_cnt++;
+
+	chdlc_priv_area->TracingEnabled = 0;
+	chdlc_priv_area->route_status = NO_ROUTE;
+	chdlc_priv_area->route_removed = 0;
+
+	card->u.c.async_mode = conf->async_mode;
+	
+	/* setup for asynchronous mode */
+	if(conf->async_mode) {
+		printk(KERN_INFO "%s: Configuring for asynchronous mode\n",
+			wandev->name);
+
+		if(card->u.c.comm_port == WANOPT_PRI) {
+			printk(KERN_INFO
+				"%s:Asynchronous mode on secondary port only\n",
+					wandev->name);
+			kfree(chdlc_priv_area);
+			return -EINVAL;
+		}
+
+	       	if(strcmp(conf->usedby, "WANPIPE") == 0) {
+			printk(KERN_INFO
+                                "%s: Running in WANIPE Async Mode\n",                                        			wandev->name);
+			card->u.c.usedby = WANPIPE;
+		}else{
+			card->u.c.usedby = API;
+		}
+
+		if(!card->wandev.clocking) {
+			printk(KERN_INFO
+				"%s: Asynch. clocking must be 'Internal'\n",
+				wandev->name);
+			kfree(chdlc_priv_area);
+			return -EINVAL;
+		}
+
+		if((card->wandev.bps < MIN_ASY_BAUD_RATE) ||
+			(card->wandev.bps > MAX_ASY_BAUD_RATE)) {
+			printk(KERN_INFO "%s: Selected baud rate is invalid.\n",
+				wandev->name);
+			printk(KERN_INFO "Must be between %u and %u bps.\n",
+				MIN_ASY_BAUD_RATE, MAX_ASY_BAUD_RATE);
+			kfree(chdlc_priv_area);
+			return -EINVAL;
+		}
+
+		card->u.c.api_options = 0;
+                if (conf->asy_data_trans == WANOPT_YES) {
+                        card->u.c.api_options |= ASY_RX_DATA_TRANSPARENT;
+                }
+		
+		card->u.c.protocol_options = 0;
+		if (conf->rts_hs_for_receive == WANOPT_YES) {
+			card->u.c.protocol_options |= ASY_RTS_HS_FOR_RX;
+	        }
+                if (conf->xon_xoff_hs_for_receive == WANOPT_YES) {
+                        card->u.c.protocol_options |= ASY_XON_XOFF_HS_FOR_RX;
+                }
+                if (conf->xon_xoff_hs_for_transmit == WANOPT_YES) {
+                        card->u.c.protocol_options |= ASY_XON_XOFF_HS_FOR_TX;
+                }
+                if (conf->dcd_hs_for_transmit == WANOPT_YES) {
+                        card->u.c.protocol_options |= ASY_DCD_HS_FOR_TX;
+                }
+                if (conf->cts_hs_for_transmit == WANOPT_YES) {
+                        card->u.c.protocol_options |= ASY_CTS_HS_FOR_TX;
+                }
+
+		card->u.c.tx_bits_per_char = conf->tx_bits_per_char;
+                card->u.c.rx_bits_per_char = conf->rx_bits_per_char;
+                card->u.c.stop_bits = conf->stop_bits;
+		card->u.c.parity = conf->parity;
+		card->u.c.break_timer = conf->break_timer;
+		card->u.c.inter_char_timer = conf->inter_char_timer;
+		card->u.c.rx_complete_length = conf->rx_complete_length;
+		card->u.c.xon_char = conf->xon_char;
+
+	} else {	/* setup for synchronous mode */
+
+		card->u.c.protocol_options = 0;
+		if (conf->ignore_dcd == WANOPT_YES){
+			card->u.c.protocol_options |= IGNORE_DCD_FOR_LINK_STAT;
+		}
+		if (conf->ignore_cts == WANOPT_YES){
+			card->u.c.protocol_options |= IGNORE_CTS_FOR_LINK_STAT;
+		}
+
+		if (conf->ignore_keepalive == WANOPT_YES) {
+			card->u.c.protocol_options |=
+				IGNORE_KPALV_FOR_LINK_STAT;
+			card->u.c.kpalv_tx  = MIN_Tx_KPALV_TIMER; 
+			card->u.c.kpalv_rx  = MIN_Rx_KPALV_TIMER; 
+			card->u.c.kpalv_err = MIN_KPALV_ERR_TOL; 
+
+		} else {   /* Do not ignore keepalives */
+			card->u.c.kpalv_tx =
+				((conf->keepalive_tx_tmr - MIN_Tx_KPALV_TIMER)
+				>= 0) ?
+	   			min_t(unsigned int, conf->keepalive_tx_tmr,MAX_Tx_KPALV_TIMER) :
+				DEFAULT_Tx_KPALV_TIMER;
+
+			card->u.c.kpalv_rx =
+		   		((conf->keepalive_rx_tmr - MIN_Rx_KPALV_TIMER)
+				>= 0) ?
+	   			min_t(unsigned int, conf->keepalive_rx_tmr,MAX_Rx_KPALV_TIMER) :
+				DEFAULT_Rx_KPALV_TIMER;
+
+			card->u.c.kpalv_err =
+		   		((conf->keepalive_err_margin-MIN_KPALV_ERR_TOL)
+				>= 0) ?
+	   			min_t(unsigned int, conf->keepalive_err_margin,
+				MAX_KPALV_ERR_TOL) : 
+	   			DEFAULT_KPALV_ERR_TOL;
+		}
+
+		/* Setup slarp timer to control delay between slarps */
+		card->u.c.slarp_timer = 
+			((conf->slarp_timer - MIN_SLARP_REQ_TIMER) >= 0) ?
+			min_t(unsigned int, conf->slarp_timer, MAX_SLARP_REQ_TIMER) :
+			DEFAULT_SLARP_REQ_TIMER;
+
+		if (conf->hdlc_streaming == WANOPT_YES) {
+			printk(KERN_INFO "%s: Enabling HDLC STREAMING Mode\n",
+				wandev->name);
+			card->u.c.protocol_options = HDLC_STREAMING_MODE;
+		}
+
+		if ((chdlc_priv_area->true_if_encoding = conf->true_if_encoding) == WANOPT_YES){
+			printk(KERN_INFO 
+				"%s: Enabling, true interface type encoding.\n",
+				card->devname);
+		}
+		
+        	/* Setup wanpipe as a router (WANPIPE) or as an API */
+		if( strcmp(conf->usedby, "WANPIPE") == 0) {
+
+			printk(KERN_INFO "%s: Running in WANPIPE mode!\n",
+				wandev->name);
+			card->u.c.usedby = WANPIPE;
+
+			/* Option to bring down the interface when 
+        		 * the link goes down */
+			if (conf->if_down){
+				set_bit(DYN_OPT_ON,&chdlc_priv_area->interface_down);
+				printk(KERN_INFO 
+				 "%s: Dynamic interface configuration enabled\n",
+				   card->devname);
+			} 
+
+		} else if( strcmp(conf->usedby, "API") == 0) {
+			card->u.c.usedby = API;
+			printk(KERN_INFO "%s: Running in API mode !\n",
+				wandev->name);
+		}
+	}
+
+	/* Tells us that if this interface is a
+         * gateway or not */
+	if ((chdlc_priv_area->gateway = conf->gateway) == WANOPT_YES){
+		printk(KERN_INFO "%s: Interface %s is set as a gateway.\n",
+			card->devname,card->u.c.if_name);
+	}
+
+	/* Get Multicast Information */
+	chdlc_priv_area->mc = conf->mc;
+
+	/* prepare network device data space for registration */
+	strcpy(dev->name,card->u.c.if_name);
+
+	dev->init = &if_init;
+	dev->priv = chdlc_priv_area;
+
+	/* Initialize the polling work routine */
+	INIT_WORK(&chdlc_priv_area->poll_work, (void*)(void*)chdlc_poll, dev);
+
+	/* Initialize the polling delay timer */
+	init_timer(&chdlc_priv_area->poll_delay_timer);
+	chdlc_priv_area->poll_delay_timer.data = (unsigned long)dev;
+	chdlc_priv_area->poll_delay_timer.function = chdlc_poll_delay;
+	
+	printk(KERN_INFO "\n");
+
+	return 0;
+}
+
+
+/****** Network Device Interface ********************************************/
+
+/*============================================================================
+ * Initialize Linux network interface.
+ *
+ * This routine is called only once for each interface, during Linux network
+ * interface registration.  Returning anything but zero will fail interface
+ * registration.
+ */
+static int if_init(struct net_device* dev)
+{
+	chdlc_private_area_t* chdlc_priv_area = dev->priv;
+	sdla_t* card = chdlc_priv_area->card;
+	struct wan_device* wandev = &card->wandev;
+
+	/* Initialize device driver entry points */
+	dev->open		= &if_open;
+	dev->stop		= &if_close;
+	dev->hard_header	= &if_header;
+	dev->rebuild_header	= &if_rebuild_hdr;
+	dev->hard_start_xmit	= &if_send;
+	dev->get_stats		= &if_stats;
+	dev->tx_timeout		= &if_tx_timeout;
+	dev->watchdog_timeo	= TX_TIMEOUT;
+	
+	/* Initialize media-specific parameters */
+	dev->flags		|= IFF_POINTOPOINT;
+	dev->flags		|= IFF_NOARP;
+
+	/* Enable Mulitcasting if user selected */
+	if (chdlc_priv_area->mc == WANOPT_YES){
+		dev->flags 	|= IFF_MULTICAST;
+	}
+	
+	if (chdlc_priv_area->true_if_encoding){
+		dev->type	= ARPHRD_HDLC; /* This breaks the tcpdump */
+	}else{
+		dev->type	= ARPHRD_PPP;
+	}
+	
+	dev->mtu		= card->wandev.mtu;
+	/* for API usage, add the API header size to the requested MTU size */
+	if(card->u.c.usedby == API) {
+		dev->mtu += sizeof(api_tx_hdr_t);
+	}
+ 
+	dev->hard_header_len	= CHDLC_HDR_LEN;
+
+	/* Initialize hardware parameters */
+	dev->irq	= wandev->irq;
+	dev->dma	= wandev->dma;
+	dev->base_addr	= wandev->ioport;
+	dev->mem_start	= wandev->maddr;
+	dev->mem_end	= wandev->maddr + wandev->msize - 1;
+
+	/* Set transmit buffer queue length 
+	 * If too low packets will not be retransmitted 
+         * by stack.
+	 */
+        dev->tx_queue_len = 100;
+	SET_MODULE_OWNER(dev);
+   
+	return 0;
+}
+
+/*============================================================================
+ * Open network interface.
+ * o enable communications and interrupts.
+ * o prevent module from unloading by incrementing use count
+ *
+ * Return 0 if O.k. or errno.
+ */
+static int if_open(struct net_device* dev)
+{
+	chdlc_private_area_t* chdlc_priv_area = dev->priv;
+	sdla_t* card = chdlc_priv_area->card;
+	struct timeval tv;
+	int err = 0;
+
+	/* Only one open per interface is allowed */
+
+	if (netif_running(dev))
+		return -EBUSY;
+
+	/* Initialize the work queue entry */
+	chdlc_priv_area->tq_working=0;
+
+	INIT_WORK(&chdlc_priv_area->common.wanpipe_work,
+			(void *)(void *)chdlc_work, dev);
+
+	/* Allocate and initialize BH circular buffer */
+	/* Add 1 to MAX_BH_BUFF so we don't have test with (MAX_BH_BUFF-1) */
+	chdlc_priv_area->bh_head = kmalloc((sizeof(bh_data_t)*(MAX_BH_BUFF+1)),GFP_ATOMIC);
+	memset(chdlc_priv_area->bh_head,0,(sizeof(bh_data_t)*(MAX_BH_BUFF+1)));
+	atomic_set(&chdlc_priv_area->bh_buff_used, 0);
+ 
+	do_gettimeofday(&tv);
+	chdlc_priv_area->router_start_time = tv.tv_sec;
+
+	netif_start_queue(dev);
+
+	wanpipe_open(card);
+
+	/* TTY is configured during wanpipe_set_termios
+	 * call, not here */
+	if (card->tty_opt)
+		return err;
+	
+	set_bit(0,&chdlc_priv_area->config_chdlc);
+	chdlc_priv_area->config_chdlc_timeout=jiffies;
+
+	/* Start the CHDLC configuration after 1sec delay.
+	 * This will give the interface initilization time
+	 * to finish its configuration */
+	mod_timer(&chdlc_priv_area->poll_delay_timer, jiffies + HZ);
+	return err;
+}
+
+/*============================================================================
+ * Close network interface.
+ * o if this is the last close, then disable communications and interrupts.
+ * o reset flags.
+ */
+static int if_close(struct net_device* dev)
+{
+	chdlc_private_area_t* chdlc_priv_area = dev->priv;
+	sdla_t* card = chdlc_priv_area->card;
+
+	if (chdlc_priv_area->bh_head){
+		int i;
+		struct sk_buff *skb;
+	
+		for (i=0; i<(MAX_BH_BUFF+1); i++){
+			skb = ((bh_data_t *)&chdlc_priv_area->bh_head[i])->skb;
+			if (skb != NULL){
+                		dev_kfree_skb_any(skb);
+			}
+		}
+		kfree(chdlc_priv_area->bh_head);
+		chdlc_priv_area->bh_head=NULL;
+	}
+
+	netif_stop_queue(dev);
+	wanpipe_close(card);
+	del_timer(&chdlc_priv_area->poll_delay_timer);
+	return 0;
+}
+
+static void disable_comm (sdla_t *card)
+{
+	SHARED_MEMORY_INFO_STRUCT *flags = card->u.c.flags;
+	
+	if (card->u.c.comm_enabled){
+		chdlc_disable_comm_shutdown (card);
+	}else{
+		flags->interrupt_info_struct.interrupt_permission = 0;	
+	}
+
+	if (!tty_init_cnt)
+		return;
+
+	if (card->tty_opt){
+		struct serial_state * state;
+		if (!(--tty_init_cnt)){
+			int e1;
+			serial_driver.refcount=0;
+			
+			if ((e1 = tty_unregister_driver(&serial_driver)))
+				printk("SERIAL: failed to unregister serial driver (%d)\n",
+				       e1);
+			printk(KERN_INFO "%s: Unregistering TTY Driver, Major %i\n",
+					card->devname,WAN_TTY_MAJOR);
+		}
+		card->tty=NULL;
+		tty_card_map[card->tty_minor]=NULL;
+		state = &rs_table[card->tty_minor];
+		memset(state, 0, sizeof(*state));
+	}
+	return;
+}
+
+
+/*============================================================================
+ * Build media header.
+ *
+ * The trick here is to put packet type (Ethertype) into 'protocol' field of
+ * the socket buffer, so that we don't forget it.  If packet type is not
+ * supported, set skb->protocol to 0 and discard packet later.
+ *
+ * Return:	media header length.
+ */
+static int if_header(struct sk_buff* skb, struct net_device* dev,
+		     unsigned short type, void* daddr, void* saddr,
+		     unsigned len)
+{
+	skb->protocol = htons(type);
+
+	return CHDLC_HDR_LEN;
+}
+
+
+/*============================================================================
+ * Handle transmit timeout event from netif watchdog
+ */
+static void if_tx_timeout(struct net_device *dev)
+{
+    	chdlc_private_area_t* chan = dev->priv;
+	sdla_t *card = chan->card;
+	
+	/* If our device stays busy for at least 5 seconds then we will
+	 * kick start the device by making dev->tbusy = 0.  We expect
+	 * that our device never stays busy more than 5 seconds. So this                 
+	 * is only used as a last resort.
+	 */
+
+	++card->wandev.stats.collisions;
+
+	printk (KERN_INFO "%s: Transmit timed out on %s\n", card->devname,dev->name);
+	netif_wake_queue (dev);
+}
+
+
+
+/*============================================================================
+ * Re-build media header.
+ *
+ * Return:	1	physical address resolved.
+ *		0	physical address not resolved
+ */
+static int if_rebuild_hdr (struct sk_buff *skb)
+{
+	return 1;
+}
+
+
+/*============================================================================
+ * Send a packet on a network interface.
+ * o set tbusy flag (marks start of the transmission) to block a timer-based
+ *   transmit from overlapping.
+ * o check link state. If link is not up, then drop the packet.
+ * o execute adapter send command.
+ * o free socket buffer
+ *
+ * Return:	0	complete (socket buffer must be freed)
+ *		non-0	packet may be re-transmitted (tbusy must be set)
+ *
+ * Notes:
+ * 1. This routine is called either by the protocol stack or by the "net
+ *    bottom half" (with interrupts enabled).
+ * 2. Setting tbusy flag will inhibit further transmit requests from the
+ *    protocol stack and can be used for flow control with protocol layer.
+ */
+static int if_send(struct sk_buff* skb, struct net_device* dev)
+{
+	chdlc_private_area_t *chdlc_priv_area = dev->priv;
+	sdla_t *card = chdlc_priv_area->card;
+	SHARED_MEMORY_INFO_STRUCT *flags = card->u.c.flags;
+	INTERRUPT_INFORMATION_STRUCT *chdlc_int = &flags->interrupt_info_struct;
+	int udp_type = 0;
+	unsigned long smp_flags;
+	int err=0;
+
+	netif_stop_queue(dev);
+	
+	if (skb == NULL){
+		/* If we get here, some higher layer thinks we've missed an
+		 * tx-done interrupt.
+		 */
+		printk(KERN_INFO "%s: interface %s got kicked!\n",
+			card->devname, dev->name);
+
+		netif_wake_queue(dev);
+		return 0;
+	}
+
+   	if (ntohs(skb->protocol) != htons(PVC_PROT)){
+
+		/* check the udp packet type */
+		
+		udp_type = udp_pkt_type(skb, card);
+
+		if (udp_type == UDP_CPIPE_TYPE){
+                        if(store_udp_mgmt_pkt(UDP_PKT_FRM_STACK, card, skb, dev,
+                                chdlc_priv_area)){
+	                	chdlc_int->interrupt_permission |=
+					APP_INT_ON_TIMER;
+			}
+			netif_start_queue(dev);
+			return 0;
+		}
+
+		/* check to see if the source IP address is a broadcast or */
+		/* multicast IP address */
+                if(chk_bcast_mcast_addr(card, dev, skb)){
+			++card->wandev.stats.tx_dropped;
+			dev_kfree_skb_any(skb);
+			netif_start_queue(dev);
+			return 0;
+		}
+        }
+
+	/* Lock the 508 Card: SMP is supported */
+      	if(card->hw.type != SDLA_S514){
+		s508_lock(card,&smp_flags);
+	} 
+
+    	if(test_and_set_bit(SEND_CRIT, (void*)&card->wandev.critical)) {
+	
+		printk(KERN_INFO "%s: Critical in if_send: %lx\n",
+					card->wandev.name,card->wandev.critical);
+                ++card->wandev.stats.tx_dropped;
+		netif_start_queue(dev);
+		goto if_send_exit_crit;
+	}
+
+	if(card->u.c.state != WAN_CONNECTED){
+       		++card->wandev.stats.tx_dropped;
+		netif_start_queue(dev);
+		
+	}else if(!skb->protocol){
+        	++card->wandev.stats.tx_errors;
+		netif_start_queue(dev);
+		
+	}else {
+		void* data = skb->data;
+		unsigned len = skb->len;
+		unsigned char attr;
+
+		/* If it's an API packet pull off the API
+		 * header. Also check that the packet size
+		 * is larger than the API header
+	         */
+		if (card->u.c.usedby == API){
+			api_tx_hdr_t* api_tx_hdr;
+
+			/* discard the frame if we are configured for */
+			/* 'receive only' mode or if there is no data */
+			if (card->u.c.receive_only ||
+				(len <= sizeof(api_tx_hdr_t))) {
+				
+				++card->wandev.stats.tx_dropped;
+				netif_start_queue(dev);
+				goto if_send_exit_crit;
+			}
+				
+			api_tx_hdr = (api_tx_hdr_t *)data;
+			attr = api_tx_hdr->attr;
+			data += sizeof(api_tx_hdr_t);
+			len -= sizeof(api_tx_hdr_t);
+		}
+
+		if(chdlc_send(card, data, len)) {
+			netif_stop_queue(dev);
+		}else{
+			++card->wandev.stats.tx_packets;
+                        card->wandev.stats.tx_bytes += len;
+			
+			netif_start_queue(dev);
+			
+		 	dev->trans_start = jiffies;
+		}	
+	}
+
+if_send_exit_crit:
+	
+	if (!(err=netif_queue_stopped(dev))) {
+		dev_kfree_skb_any(skb);
+	}else{
+		chdlc_priv_area->tick_counter = jiffies;
+		chdlc_int->interrupt_permission |= APP_INT_ON_TX_FRAME;
+	}
+
+	clear_bit(SEND_CRIT, (void*)&card->wandev.critical);
+	if(card->hw.type != SDLA_S514){
+		s508_unlock(card,&smp_flags);
+	}
+	
+	return err;
+}
+
+
+/*============================================================================
+ * Check to see if the packet to be transmitted contains a broadcast or
+ * multicast source IP address.
+ */
+
+static int chk_bcast_mcast_addr(sdla_t *card, struct net_device* dev,
+				struct sk_buff *skb)
+{
+	u32 src_ip_addr;
+        u32 broadcast_ip_addr = 0;
+        struct in_device *in_dev;
+
+        /* read the IP source address from the outgoing packet */
+        src_ip_addr = *(u32 *)(skb->data + 12);
+
+	/* read the IP broadcast address for the device */
+        in_dev = dev->ip_ptr;
+        if(in_dev != NULL) {
+                struct in_ifaddr *ifa= in_dev->ifa_list;
+                if(ifa != NULL)
+                        broadcast_ip_addr = ifa->ifa_broadcast;
+                else
+                        return 0;
+        }
+ 
+        /* check if the IP Source Address is a Broadcast address */
+        if((dev->flags & IFF_BROADCAST) && (src_ip_addr == broadcast_ip_addr)) {
+                printk(KERN_INFO "%s: Broadcast Source Address silently discarded\n",
+				card->devname);
+                return 1;
+        } 
+
+        /* check if the IP Source Address is a Multicast address */
+        if((ntohl(src_ip_addr) >= 0xE0000001) &&
+		(ntohl(src_ip_addr) <= 0xFFFFFFFE)) {
+                printk(KERN_INFO "%s: Multicast Source Address silently discarded\n",
+				card->devname);
+                return 1;
+        }
+
+        return 0;
+}
+
+
+/*============================================================================
+ * Reply to UDP Management system.
+ * Return length of reply.
+ */
+static int reply_udp( unsigned char *data, unsigned int mbox_len )
+{
+
+	unsigned short len, udp_length, temp, ip_length;
+	unsigned long ip_temp;
+	int even_bound = 0;
+  	chdlc_udp_pkt_t *c_udp_pkt = (chdlc_udp_pkt_t *)data;
+	 
+	/* Set length of packet */
+	len = sizeof(ip_pkt_t)+ 
+	      sizeof(udp_pkt_t)+
+	      sizeof(wp_mgmt_t)+
+	      sizeof(cblock_t)+
+	      sizeof(trace_info_t)+ 
+	      mbox_len;
+
+	/* fill in UDP reply */
+	c_udp_pkt->wp_mgmt.request_reply = UDPMGMT_REPLY;
+   
+	/* fill in UDP length */
+	udp_length = sizeof(udp_pkt_t)+ 
+		     sizeof(wp_mgmt_t)+
+		     sizeof(cblock_t)+
+	             sizeof(trace_info_t)+
+		     mbox_len; 
+
+ 	/* put it on an even boundary */
+	if ( udp_length & 0x0001 ) {
+		udp_length += 1;
+		len += 1;
+		even_bound = 1;
+	}  
+
+	temp = (udp_length<<8)|(udp_length>>8);
+	c_udp_pkt->udp_pkt.udp_length = temp;
+		 
+	/* swap UDP ports */
+	temp = c_udp_pkt->udp_pkt.udp_src_port;
+	c_udp_pkt->udp_pkt.udp_src_port = 
+			c_udp_pkt->udp_pkt.udp_dst_port; 
+	c_udp_pkt->udp_pkt.udp_dst_port = temp;
+
+	/* add UDP pseudo header */
+	temp = 0x1100;
+	*((unsigned short *)(c_udp_pkt->data+mbox_len+even_bound)) = temp;	
+	temp = (udp_length<<8)|(udp_length>>8);
+	*((unsigned short *)(c_udp_pkt->data+mbox_len+even_bound+2)) = temp;
+
+		 
+	/* calculate UDP checksum */
+	c_udp_pkt->udp_pkt.udp_checksum = 0;
+	c_udp_pkt->udp_pkt.udp_checksum = calc_checksum(&data[UDP_OFFSET],udp_length+UDP_OFFSET);
+
+	/* fill in IP length */
+	ip_length = len;
+	temp = (ip_length<<8)|(ip_length>>8);
+	c_udp_pkt->ip_pkt.total_length = temp;
+  
+	/* swap IP addresses */
+	ip_temp = c_udp_pkt->ip_pkt.ip_src_address;
+	c_udp_pkt->ip_pkt.ip_src_address = c_udp_pkt->ip_pkt.ip_dst_address;
+	c_udp_pkt->ip_pkt.ip_dst_address = ip_temp;
+
+	/* fill in IP checksum */
+	c_udp_pkt->ip_pkt.hdr_checksum = 0;
+	c_udp_pkt->ip_pkt.hdr_checksum = calc_checksum(data,sizeof(ip_pkt_t));
+
+	return len;
+
+} /* reply_udp */
+
+unsigned short calc_checksum (char *data, int len)
+{
+	unsigned short temp; 
+	unsigned long sum=0;
+	int i;
+
+	for( i = 0; i <len; i+=2 ) {
+		memcpy(&temp,&data[i],2);
+		sum += (unsigned long)temp;
+	}
+
+	while (sum >> 16 ) {
+		sum = (sum & 0xffffUL) + (sum >> 16);
+	}
+
+	temp = (unsigned short)sum;
+	temp = ~temp;
+
+	if( temp == 0 ) 
+		temp = 0xffff;
+
+	return temp;	
+}
+
+
+/*============================================================================
+ * Get ethernet-style interface statistics.
+ * Return a pointer to struct enet_statistics.
+ */
+static struct net_device_stats* if_stats(struct net_device* dev)
+{
+	sdla_t *my_card;
+	chdlc_private_area_t* chdlc_priv_area;
+
+	if ((chdlc_priv_area=dev->priv) == NULL)
+		return NULL;
+
+	my_card = chdlc_priv_area->card;
+	return &my_card->wandev.stats; 
+}
+
+
+/****** Cisco HDLC Firmware Interface Functions *******************************/
+
+/*============================================================================
+ * Read firmware code version.
+ *	Put code version as ASCII string in str. 
+ */
+static int chdlc_read_version (sdla_t* card, char* str)
+{
+	CHDLC_MAILBOX_STRUCT* mb = card->mbox;
+	int len;
+	char err;
+	mb->buffer_length = 0;
+	mb->command = READ_CHDLC_CODE_VERSION;
+	err = sdla_exec(mb) ? mb->return_code : CMD_TIMEOUT;
+
+	if(err != COMMAND_OK) {
+		chdlc_error(card,err,mb);
+	}
+	else if (str) {  /* is not null */
+		len = mb->buffer_length;
+		memcpy(str, mb->data, len);
+		str[len] = '\0';
+	}
+	return (err);
+}
+
+/*-----------------------------------------------------------------------------
+ *  Configure CHDLC firmware.
+ */
+static int chdlc_configure (sdla_t* card, void* data)
+{
+	int err;
+	CHDLC_MAILBOX_STRUCT *mailbox = card->mbox;
+	int data_length = sizeof(CHDLC_CONFIGURATION_STRUCT);
+	
+	mailbox->buffer_length = data_length;  
+	memcpy(mailbox->data, data, data_length);
+	mailbox->command = SET_CHDLC_CONFIGURATION;
+	err = sdla_exec(mailbox) ? mailbox->return_code : CMD_TIMEOUT;
+	
+	if (err != COMMAND_OK) chdlc_error (card, err, mailbox);
+                           
+	return err;
+}
+
+
+/*============================================================================
+ * Set interrupt mode -- HDLC Version.
+ */
+
+static int chdlc_set_intr_mode (sdla_t* card, unsigned mode)
+{
+	CHDLC_MAILBOX_STRUCT* mb = card->mbox;
+	CHDLC_INT_TRIGGERS_STRUCT* int_data =
+		 (CHDLC_INT_TRIGGERS_STRUCT *)mb->data;
+	int err;
+
+	int_data->CHDLC_interrupt_triggers 	= mode;
+	int_data->IRQ				= card->hw.irq;
+	int_data->interrupt_timer               = 1;
+   
+	mb->buffer_length = sizeof(CHDLC_INT_TRIGGERS_STRUCT);
+	mb->command = SET_CHDLC_INTERRUPT_TRIGGERS;
+	err = sdla_exec(mb) ? mb->return_code : CMD_TIMEOUT;
+	if (err != COMMAND_OK)
+		chdlc_error (card, err, mb);
+	return err;
+}
+
+
+/*===========================================================
+ * chdlc_disable_comm_shutdown
+ *
+ * Shutdown() disables the communications. We must
+ * have a sparate functions, because we must not
+ * call chdlc_error() hander since the private
+ * area has already been replaced */
+
+static int chdlc_disable_comm_shutdown (sdla_t *card)
+{
+	CHDLC_MAILBOX_STRUCT* mb = card->mbox;
+	CHDLC_INT_TRIGGERS_STRUCT* int_data =
+		 (CHDLC_INT_TRIGGERS_STRUCT *)mb->data;
+	int err;
+
+	/* Disable Interrutps */
+	int_data->CHDLC_interrupt_triggers 	= 0;
+	int_data->IRQ				= card->hw.irq;
+	int_data->interrupt_timer               = 1;
+   
+	mb->buffer_length = sizeof(CHDLC_INT_TRIGGERS_STRUCT);
+	mb->command = SET_CHDLC_INTERRUPT_TRIGGERS;
+	err = sdla_exec(mb) ? mb->return_code : CMD_TIMEOUT;
+
+	/* Disable Communications */
+
+	if (card->u.c.async_mode) {
+		mb->command = DISABLE_ASY_COMMUNICATIONS;
+	}else{
+		mb->command = DISABLE_CHDLC_COMMUNICATIONS;
+	}
+	
+	mb->buffer_length = 0;
+	err = sdla_exec(mb) ? mb->return_code : CMD_TIMEOUT;
+	
+	card->u.c.comm_enabled = 0;
+	
+	return 0;
+}
+
+/*============================================================================
+ * Enable communications.
+ */
+
+static int chdlc_comm_enable (sdla_t* card)
+{
+	int err;
+	CHDLC_MAILBOX_STRUCT* mb = card->mbox;
+
+	mb->buffer_length = 0;
+	mb->command = ENABLE_CHDLC_COMMUNICATIONS;
+	err = sdla_exec(mb) ? mb->return_code : CMD_TIMEOUT;
+	if (err != COMMAND_OK)
+		chdlc_error(card, err, mb);
+	else
+		card->u.c.comm_enabled = 1;
+	
+	return err;
+}
+
+/*============================================================================
+ * Read communication error statistics.
+ */
+static int chdlc_read_comm_err_stats (sdla_t* card)
+{
+        int err;
+        CHDLC_MAILBOX_STRUCT* mb = card->mbox;
+
+        mb->buffer_length = 0;
+        mb->command = READ_COMMS_ERROR_STATS;
+        err = sdla_exec(mb) ? mb->return_code : CMD_TIMEOUT;
+        if (err != COMMAND_OK)
+                chdlc_error(card,err,mb);
+        return err;
+}
+
+
+/*============================================================================
+ * Read CHDLC operational statistics.
+ */
+static int chdlc_read_op_stats (sdla_t* card)
+{
+        int err;
+        CHDLC_MAILBOX_STRUCT* mb = card->mbox;
+
+        mb->buffer_length = 0;
+        mb->command = READ_CHDLC_OPERATIONAL_STATS;
+        err = sdla_exec(mb) ? mb->return_code : CMD_TIMEOUT;
+        if (err != COMMAND_OK)
+                chdlc_error(card,err,mb);
+        return err;
+}
+
+
+/*============================================================================
+ * Update communications error and general packet statistics.
+ */
+static int update_comms_stats(sdla_t* card,
+	chdlc_private_area_t* chdlc_priv_area)
+{
+        CHDLC_MAILBOX_STRUCT* mb = card->mbox;
+  	COMMS_ERROR_STATS_STRUCT* err_stats;
+        CHDLC_OPERATIONAL_STATS_STRUCT *op_stats;
+
+	/* on the first timer interrupt, read the comms error statistics */
+	if(chdlc_priv_area->update_comms_stats == 2) {
+		if(chdlc_read_comm_err_stats(card))
+			return 1;
+		err_stats = (COMMS_ERROR_STATS_STRUCT *)mb->data;
+		card->wandev.stats.rx_over_errors = 
+				err_stats->Rx_overrun_err_count;
+		card->wandev.stats.rx_crc_errors = 
+				err_stats->CRC_err_count;
+		card->wandev.stats.rx_frame_errors = 
+				err_stats->Rx_abort_count;
+		card->wandev.stats.rx_fifo_errors = 
+				err_stats->Rx_dis_pri_bfrs_full_count; 
+		card->wandev.stats.rx_missed_errors =
+				card->wandev.stats.rx_fifo_errors;
+		card->wandev.stats.tx_aborted_errors =
+				err_stats->sec_Tx_abort_count;
+	}
+
+        /* on the second timer interrupt, read the operational statistics */
+	else {
+        	if(chdlc_read_op_stats(card))
+                	return 1;
+		op_stats = (CHDLC_OPERATIONAL_STATS_STRUCT *)mb->data;
+		card->wandev.stats.rx_length_errors =
+			(op_stats->Rx_Data_discard_short_count +
+			op_stats->Rx_Data_discard_long_count);
+	}
+
+	return 0;
+}
+
+/*============================================================================
+ * Send packet.
+ *	Return:	0 - o.k.
+ *		1 - no transmit buffers available
+ */
+static int chdlc_send (sdla_t* card, void* data, unsigned len)
+{
+	CHDLC_DATA_TX_STATUS_EL_STRUCT *txbuf = card->u.c.txbuf;
+
+	if (txbuf->opp_flag)
+		return 1;
+	
+	sdla_poke(&card->hw, txbuf->ptr_data_bfr, data, len);
+
+	txbuf->frame_length = len;
+	txbuf->opp_flag = 1;		/* start transmission */
+	
+	/* Update transmit buffer control fields */
+	card->u.c.txbuf = ++txbuf;
+	
+	if ((void*)txbuf > card->u.c.txbuf_last)
+		card->u.c.txbuf = card->u.c.txbuf_base;
+	
+	return 0;
+}
+
+/****** Firmware Error Handler **********************************************/
+
+/*============================================================================
+ * Firmware error handler.
+ *	This routine is called whenever firmware command returns non-zero
+ *	return code.
+ *
+ * Return zero if previous command has to be cancelled.
+ */
+static int chdlc_error (sdla_t *card, int err, CHDLC_MAILBOX_STRUCT *mb)
+{
+	unsigned cmd = mb->command;
+
+	switch (err) {
+
+	case CMD_TIMEOUT:
+		printk(KERN_INFO "%s: command 0x%02X timed out!\n",
+			card->devname, cmd);
+		break;
+
+	case S514_BOTH_PORTS_SAME_CLK_MODE:
+		if(cmd == SET_CHDLC_CONFIGURATION) {
+			printk(KERN_INFO
+			 "%s: Configure both ports for the same clock source\n",
+				card->devname);
+			break;
+		}
+
+	default:
+		printk(KERN_INFO "%s: command 0x%02X returned 0x%02X!\n",
+			card->devname, cmd, err);
+	}
+
+	return 0;
+}
+
+
+/********** Bottom Half Handlers ********************************************/
+
+/* NOTE: There is no API, BH support for Kernels lower than 2.2.X.
+ *       DO NOT INSERT ANY CODE HERE, NOTICE THE 
+ *       PREPROCESSOR STATEMENT ABOVE, UNLESS YOU KNOW WHAT YOU ARE
+ *       DOING */
+
+static void chdlc_work(struct net_device * dev)
+{
+	chdlc_private_area_t* chan = dev->priv;
+	sdla_t *card = chan->card;
+	struct sk_buff *skb;
+
+	if (atomic_read(&chan->bh_buff_used) == 0){
+		clear_bit(0, &chan->tq_working);
+		return;
+	}
+
+	while (atomic_read(&chan->bh_buff_used)){
+
+		skb  = ((bh_data_t *)&chan->bh_head[chan->bh_read])->skb;
+
+		if (skb != NULL){
+
+			if (chan->common.sk == NULL || chan->common.func == NULL){
+				++card->wandev.stats.rx_dropped;
+				dev_kfree_skb_any(skb);
+				chdlc_work_cleanup(dev);
+				continue;
+			}
+
+			if (chan->common.func(skb,dev,chan->common.sk) != 0){
+				/* Sock full cannot send, queue us for another
+                                 * try */
+				atomic_set(&chan->common.receive_block,1);
+				return;
+			}else{
+				chdlc_work_cleanup(dev);
+			}
+		}else{
+			chdlc_work_cleanup(dev);
+		}
+	}	
+	clear_bit(0, &chan->tq_working);
+
+	return;
+}
+
+static int chdlc_work_cleanup(struct net_device *dev)
+{
+	chdlc_private_area_t* chan = dev->priv;
+
+	((bh_data_t *)&chan->bh_head[chan->bh_read])->skb = NULL;
+
+	if (chan->bh_read == MAX_BH_BUFF){
+		chan->bh_read=0;
+	}else{
+		++chan->bh_read;	
+	}
+
+	atomic_dec(&chan->bh_buff_used);
+	return 0;
+}
+
+
+
+static int bh_enqueue(struct net_device *dev, struct sk_buff *skb)
+{
+	/* Check for full */
+	chdlc_private_area_t* chan = dev->priv;
+	sdla_t *card = chan->card;
+
+	if (atomic_read(&chan->bh_buff_used) == (MAX_BH_BUFF+1)){
+		++card->wandev.stats.rx_dropped;
+		dev_kfree_skb_any(skb);
+		return 1; 
+	}
+
+	((bh_data_t *)&chan->bh_head[chan->bh_write])->skb = skb;
+
+	if (chan->bh_write == MAX_BH_BUFF){
+		chan->bh_write=0;
+	}else{
+		++chan->bh_write;
+	}
+
+	atomic_inc(&chan->bh_buff_used);
+
+	return 0;
+}
+
+/* END OF API BH Support */
+
+
+/****** Interrupt Handlers **************************************************/
+
+/*============================================================================
+ * Cisco HDLC interrupt service routine.
+ */
+static void wpc_isr (sdla_t* card)
+{
+	struct net_device* dev;
+	SHARED_MEMORY_INFO_STRUCT* flags = NULL;
+	int i;
+	sdla_t *my_card;
+
+
+	/* Check for which port the interrupt has been generated
+	 * Since Secondary Port is piggybacking on the Primary
+         * the check must be done here. 
+	 */
+
+	flags = card->u.c.flags;
+	if (!flags->interrupt_info_struct.interrupt_type){
+		/* Check for a second port (piggybacking) */
+		if ((my_card = card->next)){
+			flags = my_card->u.c.flags;
+			if (flags->interrupt_info_struct.interrupt_type){
+				card = my_card;
+				card->isr(card);
+				return;
+			}
+		}
+	}
+
+	flags = card->u.c.flags;
+	card->in_isr = 1;
+	dev = card->wandev.dev;
+	
+	/* If we get an interrupt with no network device, stop the interrupts
+	 * and issue an error */
+	if (!card->tty_opt && !dev && 
+	    flags->interrupt_info_struct.interrupt_type != 
+	    	COMMAND_COMPLETE_APP_INT_PEND){
+
+		goto isr_done;
+	}
+	
+	/* if critical due to peripheral operations
+	 * ie. update() or getstats() then reset the interrupt and
+	 * wait for the board to retrigger.
+	 */
+	if(test_bit(PERI_CRIT, (void*)&card->wandev.critical)) {
+		printk(KERN_INFO "ISR CRIT TO PERI\n");
+		goto isr_done;
+	}
+
+	/* On a 508 Card, if critical due to if_send 
+         * Major Error !!! */
+	if(card->hw.type != SDLA_S514) {
+		if(test_bit(SEND_CRIT, (void*)&card->wandev.critical)) {
+			printk(KERN_INFO "%s: Critical while in ISR: %lx\n",
+				card->devname, card->wandev.critical);
+			card->in_isr = 0;
+			flags->interrupt_info_struct.interrupt_type = 0;
+			return;
+		}
+	}
+
+	switch(flags->interrupt_info_struct.interrupt_type) {
+
+	case RX_APP_INT_PEND:	/* 0x01: receive interrupt */
+		rx_intr(card);
+		break;
+
+	case TX_APP_INT_PEND:	/* 0x02: transmit interrupt */
+		flags->interrupt_info_struct.interrupt_permission &=
+			 ~APP_INT_ON_TX_FRAME;
+
+		if (card->tty_opt){
+			wanpipe_tty_trigger_poll(card);
+			break;
+		}
+
+		if (dev && netif_queue_stopped(dev)){
+			if (card->u.c.usedby == API){
+				netif_start_queue(dev);
+				wakeup_sk_bh(dev);
+			}else{
+				netif_wake_queue(dev);
+			}
+		}
+		break;
+
+	case COMMAND_COMPLETE_APP_INT_PEND:/* 0x04: cmd cplt */
+		++ Intr_test_counter;
+		break;
+
+	case CHDLC_EXCEP_COND_APP_INT_PEND:	/* 0x20 */
+		process_chdlc_exception(card);
+		break;
+
+	case GLOBAL_EXCEP_COND_APP_INT_PEND:
+		process_global_exception(card);
+		break;
+
+	case TIMER_APP_INT_PEND:
+		timer_intr(card);
+		break;
+
+	default:
+		printk(KERN_INFO "%s: spurious interrupt 0x%02X!\n", 
+			card->devname,
+			flags->interrupt_info_struct.interrupt_type);
+		printk(KERN_INFO "Code name: ");
+		for(i = 0; i < 4; i ++)
+			printk(KERN_INFO "%c",
+				flags->global_info_struct.codename[i]); 
+		printk(KERN_INFO "\nCode version: ");
+	 	for(i = 0; i < 4; i ++)
+			printk(KERN_INFO "%c", 
+				flags->global_info_struct.codeversion[i]); 
+		printk(KERN_INFO "\n");	
+		break;
+	}
+
+isr_done:
+
+	card->in_isr = 0;
+	flags->interrupt_info_struct.interrupt_type = 0;
+	return;
+}
+
+/*============================================================================
+ * Receive interrupt handler.
+ */
+static void rx_intr (sdla_t* card)
+{
+	struct net_device *dev;
+	chdlc_private_area_t *chdlc_priv_area;
+	SHARED_MEMORY_INFO_STRUCT *flags = card->u.c.flags;
+	CHDLC_DATA_RX_STATUS_EL_STRUCT *rxbuf = card->u.c.rxmb;
+	struct sk_buff *skb;
+	unsigned len;
+	unsigned addr = rxbuf->ptr_data_bfr;
+	void *buf;
+	int i,udp_type;
+
+	if (rxbuf->opp_flag != 0x01) {
+		printk(KERN_INFO 
+			"%s: corrupted Rx buffer @ 0x%X, flag = 0x%02X!\n", 
+			card->devname, (unsigned)rxbuf, rxbuf->opp_flag);
+                printk(KERN_INFO "Code name: ");
+                for(i = 0; i < 4; i ++)
+                        printk(KERN_INFO "%c",
+                                flags->global_info_struct.codename[i]);
+                printk(KERN_INFO "\nCode version: ");
+                for(i = 0; i < 4; i ++)
+                        printk(KERN_INFO "%c",
+                                flags->global_info_struct.codeversion[i]);
+                printk(KERN_INFO "\n");
+
+
+		/* Bug Fix: Mar 6 2000
+                 * If we get a corrupted mailbox, it measn that driver 
+                 * is out of sync with the firmware. There is no recovery.
+                 * If we don't turn off all interrupts for this card
+                 * the machine will crash. 
+                 */
+		printk(KERN_INFO "%s: Critical router failure ...!!!\n", card->devname);
+		printk(KERN_INFO "Please contact Sangoma Technologies !\n");
+		chdlc_set_intr_mode(card,0);	
+		return;
+	}
+
+	len  = rxbuf->frame_length;
+
+	if (card->tty_opt){
+
+		if (rxbuf->error_flag){	
+			goto rx_exit;
+		}
+
+		if (len <= CRC_LENGTH){
+			goto rx_exit;
+		}
+		
+		if (!card->u.c.async_mode){
+			len -= CRC_LENGTH;
+		}
+
+		wanpipe_tty_receive(card,addr,len);
+		goto rx_exit;
+	}
+
+	dev = card->wandev.dev;
+
+	if (!dev){
+		goto rx_exit;
+	}
+
+	if (!netif_running(dev))
+		goto rx_exit;
+
+	chdlc_priv_area = dev->priv;
+
+	
+	/* Allocate socket buffer */
+	skb = dev_alloc_skb(len);
+
+	if (skb == NULL) {
+		printk(KERN_INFO "%s: no socket buffers available!\n",
+					card->devname);
+		++card->wandev.stats.rx_dropped;
+		goto rx_exit;
+	}
+
+	/* Copy data to the socket buffer */
+	if((addr + len) > card->u.c.rx_top + 1) {
+		unsigned tmp = card->u.c.rx_top - addr + 1;
+		buf = skb_put(skb, tmp);
+		sdla_peek(&card->hw, addr, buf, tmp);
+		addr = card->u.c.rx_base;
+		len -= tmp;
+	}
+		
+	buf = skb_put(skb, len);
+	sdla_peek(&card->hw, addr, buf, len);
+
+	skb->protocol = htons(ETH_P_IP);
+
+	card->wandev.stats.rx_packets ++;
+	card->wandev.stats.rx_bytes += skb->len;
+	udp_type = udp_pkt_type( skb, card );
+
+	if(udp_type == UDP_CPIPE_TYPE) {
+		if(store_udp_mgmt_pkt(UDP_PKT_FRM_NETWORK,
+   				      card, skb, dev, chdlc_priv_area)) {
+     		        flags->interrupt_info_struct.
+						interrupt_permission |= 
+							APP_INT_ON_TIMER; 
+		}
+	} else if(card->u.c.usedby == API) {
+
+		api_rx_hdr_t* api_rx_hdr;
+       		skb_push(skb, sizeof(api_rx_hdr_t));
+                api_rx_hdr = (api_rx_hdr_t*)&skb->data[0x00];
+		api_rx_hdr->error_flag = rxbuf->error_flag;
+     		api_rx_hdr->time_stamp = rxbuf->time_stamp;
+
+                skb->protocol = htons(PVC_PROT);
+     		skb->mac.raw  = skb->data;
+		skb->dev      = dev;
+               	skb->pkt_type = WAN_PACKET_DATA;
+
+		bh_enqueue(dev, skb);
+
+		if (!test_and_set_bit(0,&chdlc_priv_area->tq_working))
+			wanpipe_queue_work(&chdlc_priv_area->common.wanpipe_work);
+	}else{
+		/* FIXME: we should check to see if the received packet is a 
+                          multicast packet so that we can increment the multicast 
+                          statistic
+                          ++ chdlc_priv_area->if_stats.multicast;
+		*/
+               	/* Pass it up the protocol stack */
+	
+                skb->dev = dev;
+                skb->mac.raw  = skb->data;
+                netif_rx(skb);
+                dev->last_rx = jiffies;
+	}
+
+rx_exit:
+	/* Release buffer element and calculate a pointer to the next one */
+	rxbuf->opp_flag = 0x00;
+	card->u.c.rxmb = ++ rxbuf;
+	if((void*)rxbuf > card->u.c.rxbuf_last){
+		card->u.c.rxmb = card->u.c.rxbuf_base;
+	}
+}
+
+/*============================================================================
+ * Timer interrupt handler.
+ * The timer interrupt is used for two purposes:
+ *    1) Processing udp calls from 'cpipemon'.
+ *    2) Reading board-level statistics for updating the proc file system.
+ */
+void timer_intr(sdla_t *card)
+{
+        struct net_device* dev;
+        chdlc_private_area_t* chdlc_priv_area = NULL;
+        SHARED_MEMORY_INFO_STRUCT* flags = NULL;
+
+        if ((dev = card->wandev.dev)==NULL){
+		flags = card->u.c.flags;
+                flags->interrupt_info_struct.interrupt_permission &=
+                        ~APP_INT_ON_TIMER;
+		return;
+	}
+	
+        chdlc_priv_area = dev->priv;
+
+	if (chdlc_priv_area->timer_int_enabled & TMR_INT_ENABLED_CONFIG) {
+		if (!config_chdlc(card)){
+			chdlc_priv_area->timer_int_enabled &= ~TMR_INT_ENABLED_CONFIG;
+		}
+	}
+
+	/* process a udp call if pending */
+       	if(chdlc_priv_area->timer_int_enabled & TMR_INT_ENABLED_UDP) {
+               	process_udp_mgmt_pkt(card, dev,
+                       chdlc_priv_area);
+		chdlc_priv_area->timer_int_enabled &= ~TMR_INT_ENABLED_UDP;
+        }
+
+	/* read the communications statistics if required */
+	if(chdlc_priv_area->timer_int_enabled & TMR_INT_ENABLED_UPDATE) {
+		update_comms_stats(card, chdlc_priv_area);
+                if(!(-- chdlc_priv_area->update_comms_stats)) {
+			chdlc_priv_area->timer_int_enabled &= 
+				~TMR_INT_ENABLED_UPDATE;
+		}
+        }
+
+	/* only disable the timer interrupt if there are no udp or statistic */
+	/* updates pending */
+        if(!chdlc_priv_area->timer_int_enabled) {
+                flags = card->u.c.flags;
+                flags->interrupt_info_struct.interrupt_permission &=
+                        ~APP_INT_ON_TIMER;
+        }
+}
+
+/*------------------------------------------------------------------------------
+  Miscellaneous Functions
+	- set_chdlc_config() used to set configuration options on the board
+------------------------------------------------------------------------------*/
+
+static int set_chdlc_config(sdla_t* card)
+{
+	CHDLC_CONFIGURATION_STRUCT cfg;
+
+	memset(&cfg, 0, sizeof(CHDLC_CONFIGURATION_STRUCT));
+
+	if(card->wandev.clocking){
+		cfg.baud_rate = card->wandev.bps;
+	}
+		
+	cfg.line_config_options = (card->wandev.interface == WANOPT_RS232) ?
+		INTERFACE_LEVEL_RS232 : INTERFACE_LEVEL_V35;
+
+	cfg.modem_config_options	= 0;
+	cfg.modem_status_timer		= 100;
+
+	cfg.CHDLC_protocol_options	= card->u.c.protocol_options;
+
+	if (card->tty_opt){
+		cfg.CHDLC_API_options	= DISCARD_RX_ERROR_FRAMES;
+	}
+	
+	cfg.percent_data_buffer_for_Tx  = (card->u.c.receive_only) ? 0 : 50;
+	cfg.CHDLC_statistics_options	= (CHDLC_TX_DATA_BYTE_COUNT_STAT |
+		CHDLC_RX_DATA_BYTE_COUNT_STAT);
+	
+	if (card->tty_opt){
+		card->wandev.mtu = TTY_CHDLC_MAX_MTU;
+	}
+	cfg.max_CHDLC_data_field_length	= card->wandev.mtu;
+	cfg.transmit_keepalive_timer	= card->u.c.kpalv_tx;
+	cfg.receive_keepalive_timer	= card->u.c.kpalv_rx;
+	cfg.keepalive_error_tolerance	= card->u.c.kpalv_err;
+	cfg.SLARP_request_timer		= card->u.c.slarp_timer;
+
+	if (cfg.SLARP_request_timer) {
+		cfg.IP_address		= 0;
+		cfg.IP_netmask		= 0;
+		
+	}else if (card->wandev.dev){
+		struct net_device *dev = card->wandev.dev;
+		chdlc_private_area_t *chdlc_priv_area = dev->priv;
+		
+                struct in_device *in_dev = dev->ip_ptr;
+
+		if(in_dev != NULL) {
+			struct in_ifaddr *ifa = in_dev->ifa_list;
+
+			if (ifa != NULL ) {
+				cfg.IP_address	= ntohl(ifa->ifa_local);
+				cfg.IP_netmask	= ntohl(ifa->ifa_mask); 
+				chdlc_priv_area->IP_address = ntohl(ifa->ifa_local);
+				chdlc_priv_area->IP_netmask = ntohl(ifa->ifa_mask); 
+			}
+		}
+
+		/* FIXME: We must re-think this message in next release
+		if((cfg.IP_address & 0x000000FF) > 2) {
+			printk(KERN_WARNING "\n");
+	                printk(KERN_WARNING "  WARNING:%s configured with an\n",
+				card->devname);
+			printk(KERN_WARNING "  invalid local IP address.\n");
+                        printk(KERN_WARNING "  Slarp pragmatics will fail.\n");
+                        printk(KERN_WARNING "  IP address should be of the\n");
+			printk(KERN_WARNING "  format A.B.C.1 or A.B.C.2.\n");
+		}
+		*/		
+	}
+	
+	return chdlc_configure(card, &cfg);
+}
+
+
+/*-----------------------------------------------------------------------------
+   set_asy_config() used to set asynchronous configuration options on the board
+------------------------------------------------------------------------------*/
+
+static int set_asy_config(sdla_t* card)
+{
+
+        ASY_CONFIGURATION_STRUCT cfg;
+ 	CHDLC_MAILBOX_STRUCT *mailbox = card->mbox;
+	int err;
+
+	memset(&cfg, 0, sizeof(ASY_CONFIGURATION_STRUCT));
+
+	if(card->wandev.clocking)
+		cfg.baud_rate = card->wandev.bps;
+
+	cfg.line_config_options = (card->wandev.interface == WANOPT_RS232) ?
+		INTERFACE_LEVEL_RS232 : INTERFACE_LEVEL_V35;
+
+	cfg.modem_config_options	= 0;
+	cfg.asy_API_options 		= card->u.c.api_options;
+	cfg.asy_protocol_options	= card->u.c.protocol_options;
+	cfg.Tx_bits_per_char		= card->u.c.tx_bits_per_char;
+	cfg.Rx_bits_per_char		= card->u.c.rx_bits_per_char;
+	cfg.stop_bits			= card->u.c.stop_bits;
+	cfg.parity			= card->u.c.parity;
+	cfg.break_timer			= card->u.c.break_timer;
+	cfg.asy_Rx_inter_char_timer	= card->u.c.inter_char_timer; 
+	cfg.asy_Rx_complete_length	= card->u.c.rx_complete_length; 
+	cfg.XON_char			= card->u.c.xon_char;
+	cfg.XOFF_char			= card->u.c.xoff_char;
+	cfg.asy_statistics_options	= (CHDLC_TX_DATA_BYTE_COUNT_STAT |
+		CHDLC_RX_DATA_BYTE_COUNT_STAT);
+
+	mailbox->buffer_length = sizeof(ASY_CONFIGURATION_STRUCT);
+	memcpy(mailbox->data, &cfg, mailbox->buffer_length);
+	mailbox->command = SET_ASY_CONFIGURATION;
+	err = sdla_exec(mailbox) ? mailbox->return_code : CMD_TIMEOUT;
+	if (err != COMMAND_OK) 
+		chdlc_error (card, err, mailbox);
+	return err;
+}
+
+/*============================================================================
+ * Enable asynchronous communications.
+ */
+
+static int asy_comm_enable (sdla_t* card)
+{
+
+	int err;
+	CHDLC_MAILBOX_STRUCT* mb = card->mbox;
+
+	mb->buffer_length = 0;
+	mb->command = ENABLE_ASY_COMMUNICATIONS;
+	err = sdla_exec(mb) ? mb->return_code : CMD_TIMEOUT;
+	if (err != COMMAND_OK && card->wandev.dev)
+		chdlc_error(card, err, mb);
+	
+	if (!err)
+		card->u.c.comm_enabled = 1;
+
+	return err;
+}
+
+/*============================================================================
+ * Process global exception condition
+ */
+static int process_global_exception(sdla_t *card)
+{
+	CHDLC_MAILBOX_STRUCT* mbox = card->mbox;
+	int err;
+
+	mbox->buffer_length = 0;
+	mbox->command = READ_GLOBAL_EXCEPTION_CONDITION;
+	err = sdla_exec(mbox) ? mbox->return_code : CMD_TIMEOUT;
+
+	if(err != CMD_TIMEOUT ){
+	
+		switch(mbox->return_code) {
+         
+	      	case EXCEP_MODEM_STATUS_CHANGE:
+
+			printk(KERN_INFO "%s: Modem status change\n",
+				card->devname);
+
+			switch(mbox->data[0] & (DCD_HIGH | CTS_HIGH)) {
+				case (DCD_HIGH):
+					printk(KERN_INFO "%s: DCD high, CTS low\n",card->devname);
+					break;
+				case (CTS_HIGH):
+                                        printk(KERN_INFO "%s: DCD low, CTS high\n",card->devname); 
+					break;
+                                case ((DCD_HIGH | CTS_HIGH)):
+                                        printk(KERN_INFO "%s: DCD high, CTS high\n",card->devname);
+                                        break;
+				default:
+                                        printk(KERN_INFO "%s: DCD low, CTS low\n",card->devname);
+                                        break;
+			}
+			break;
+
+                case EXCEP_TRC_DISABLED:
+                        printk(KERN_INFO "%s: Line trace disabled\n",
+				card->devname);
+                        break;
+
+		case EXCEP_IRQ_TIMEOUT:
+			printk(KERN_INFO "%s: IRQ timeout occurred\n",
+				card->devname); 
+			break;
+
+		case 0x17:
+			if (card->tty_opt){
+				if (card->tty && card->tty_open){ 
+					printk(KERN_INFO 
+						"%s: Modem Hangup Exception: Hanging Up!\n",
+						card->devname);
+					tty_hangup(card->tty);
+				}
+				break;
+			}
+
+			/* If TTY is not used just drop throught */
+			
+                default:
+                        printk(KERN_INFO "%s: Global exception %x\n",
+				card->devname, mbox->return_code);
+                        break;
+                }
+	}
+	return 0;
+}
+
+
+/*============================================================================
+ * Process chdlc exception condition
+ */
+static int process_chdlc_exception(sdla_t *card)
+{
+	CHDLC_MAILBOX_STRUCT* mb = card->mbox;
+	int err;
+
+	mb->buffer_length = 0;
+	mb->command = READ_CHDLC_EXCEPTION_CONDITION;
+	err = sdla_exec(mb) ? mb->return_code : CMD_TIMEOUT;
+	if(err != CMD_TIMEOUT) {
+	
+		switch (err) {
+
+		case EXCEP_LINK_ACTIVE:
+			port_set_state(card, WAN_CONNECTED);
+			trigger_chdlc_poll(card->wandev.dev);
+			break;
+
+		case EXCEP_LINK_INACTIVE_MODEM:
+			port_set_state(card, WAN_DISCONNECTED);
+			unconfigure_ip(card);
+			trigger_chdlc_poll(card->wandev.dev);
+			break;
+
+		case EXCEP_LINK_INACTIVE_KPALV:
+			port_set_state(card, WAN_DISCONNECTED);
+			printk(KERN_INFO "%s: Keepalive timer expired.\n",
+				 		card->devname);
+			unconfigure_ip(card);
+			trigger_chdlc_poll(card->wandev.dev);
+			break;
+
+		case EXCEP_IP_ADDRESS_DISCOVERED:
+			if (configure_ip(card)) 
+				return -1;
+			break;
+
+		case EXCEP_LOOPBACK_CONDITION:
+			printk(KERN_INFO "%s: Loopback Condition Detected.\n",
+						card->devname);
+			break;
+
+		case NO_CHDLC_EXCEP_COND_TO_REPORT:
+			printk(KERN_INFO "%s: No exceptions reported.\n",
+						card->devname);
+			break;
+		}
+
+	}
+	return 0;
+}
+
+
+/*============================================================================
+ * Configure IP from SLARP negotiation
+ * This adds dynamic routes when SLARP has provided valid addresses
+ */
+
+static int configure_ip (sdla_t* card)
+{
+	struct net_device *dev = card->wandev.dev;
+        chdlc_private_area_t *chdlc_priv_area;
+        char err;
+
+	if (!dev)
+		return 0;
+
+	chdlc_priv_area = dev->priv;
+	
+	
+        /* set to discover */
+        if(card->u.c.slarp_timer != 0x00) {
+		CHDLC_MAILBOX_STRUCT* mb = card->mbox;
+		CHDLC_CONFIGURATION_STRUCT *cfg;
+
+     		mb->buffer_length = 0;
+		mb->command = READ_CHDLC_CONFIGURATION;
+		err = sdla_exec(mb) ? mb->return_code : CMD_TIMEOUT;
+	
+		if(err != COMMAND_OK) {
+			chdlc_error(card,err,mb);
+			return -1;
+		}
+
+		cfg = (CHDLC_CONFIGURATION_STRUCT *)mb->data;
+                chdlc_priv_area->IP_address = cfg->IP_address;
+                chdlc_priv_area->IP_netmask = cfg->IP_netmask;
+
+		/* Set flag to add route */
+		chdlc_priv_area->route_status = ADD_ROUTE;
+
+		/* The idea here is to add the route in the poll routine.
+	   	This way, we aren't in interrupt context when adding routes */
+		trigger_chdlc_poll(dev);
+        }
+
+	return 0;
+}
+
+
+/*============================================================================
+ * Un-Configure IP negotiated by SLARP
+ * This removes dynamic routes when the link becomes inactive.
+ */
+
+static int unconfigure_ip (sdla_t* card)
+{
+	struct net_device *dev = card->wandev.dev;
+	chdlc_private_area_t *chdlc_priv_area;
+
+	if (!dev)
+		return 0;
+
+	chdlc_priv_area= dev->priv;
+	
+	if (chdlc_priv_area->route_status == ROUTE_ADDED) {
+
+		/* Note: If this function is called, the 
+                 * port state has been DISCONNECTED.  This state
+                 * change will trigger a poll_disconnected 
+                 * function, that will check for this condition. 
+		 */
+		chdlc_priv_area->route_status = REMOVE_ROUTE;
+
+	}
+	return 0;
+}
+
+/*============================================================================
+ * Routine to add/remove routes 
+ * Called like a polling routine when Routes are flagged to be added/removed.
+ */
+
+static void process_route (sdla_t *card)
+{
+        struct net_device *dev = card->wandev.dev;
+        unsigned char port_num;
+        chdlc_private_area_t *chdlc_priv_area = NULL;
+	u32 local_IP_addr = 0;
+	u32 remote_IP_addr = 0;
+	u32 IP_netmask, IP_addr;
+        int err = 0;
+	struct in_device *in_dev;
+	mm_segment_t fs;
+	struct ifreq if_info;
+        struct sockaddr_in *if_data1, *if_data2;
+	
+        chdlc_priv_area = dev->priv;
+        port_num = card->u.c.comm_port;
+
+	/* Bug Fix Mar 16 2000
+	 * AND the IP address to the Mask before checking
+         * the last two bits. */
+
+	if((chdlc_priv_area->route_status == ADD_ROUTE) &&
+		((chdlc_priv_area->IP_address & ~chdlc_priv_area->IP_netmask) > 2)) {
+
+		printk(KERN_INFO "%s: Dynamic route failure.\n",card->devname);
+
+                if(card->u.c.slarp_timer) {
+			u32 addr_net = htonl(chdlc_priv_area->IP_address);
+
+			printk(KERN_INFO "%s: Bad IP address %u.%u.%u.%u received\n",
+				card->devname,
+			       NIPQUAD(addr_net));
+                        printk(KERN_INFO "%s: from remote station.\n",
+				card->devname);
+
+                }else{ 
+			u32 addr_net = htonl(chdlc_priv_area->IP_address);
+
+                        printk(KERN_INFO "%s: Bad IP address %u.%u.%u.%u issued\n",
+			       card->devname,
+			       NIPQUAD(addr_net));
+                        printk(KERN_INFO "%s: to remote station. Local\n",
+				card->devname);
+			printk(KERN_INFO "%s: IP address must be A.B.C.1\n",
+				card->devname);
+			printk(KERN_INFO "%s: or A.B.C.2.\n",card->devname);
+		}
+
+		/* remove the route due to the IP address error condition */
+		chdlc_priv_area->route_status = REMOVE_ROUTE;
+		err = 1;
+   	}
+
+	/* If we are removing a route with bad IP addressing, then use the */
+	/* locally configured IP addresses */
+        if((chdlc_priv_area->route_status == REMOVE_ROUTE) && err) {
+
+ 	        /* do not remove a bad route that has already been removed */
+        	if(chdlc_priv_area->route_removed) {
+	                return;
+        	}
+
+                in_dev = dev->ip_ptr;
+
+                if(in_dev != NULL) {
+                        struct in_ifaddr *ifa = in_dev->ifa_list;
+                        if (ifa != NULL ) {
+                                local_IP_addr = ifa->ifa_local;
+                                IP_netmask  = ifa->ifa_mask;
+                        }
+                }
+	}else{ 
+       		/* According to Cisco HDLC, if the point-to-point address is
+		   A.B.C.1, then we are the opposite (A.B.C.2), and vice-versa.
+		*/
+		IP_netmask = ntohl(chdlc_priv_area->IP_netmask);
+	        remote_IP_addr = ntohl(chdlc_priv_area->IP_address);
+	
+
+		/* If Netmask is 255.255.255.255 the local address
+                 * calculation will fail. Default it back to 255.255.255.0 */
+		if (IP_netmask == 0xffffffff)
+			IP_netmask &= 0x00ffffff;
+
+		/* Bug Fix Mar 16 2000
+		 * AND the Remote IP address with IP netmask, instead
+                 * of static netmask of 255.255.255.0 */
+        	local_IP_addr = (remote_IP_addr & IP_netmask) +
+                	(~remote_IP_addr & ntohl(0x0003));
+
+	        if(!card->u.c.slarp_timer) {
+			IP_addr = local_IP_addr;
+			local_IP_addr = remote_IP_addr;
+			remote_IP_addr = IP_addr;
+       		}
+	}
+
+        fs = get_fs();                  /* Save file system  */
+        set_fs(get_ds());               /* Get user space block */
+
+        /* Setup a structure for adding/removing routes */
+        memset(&if_info, 0, sizeof(if_info));
+        strcpy(if_info.ifr_name, dev->name);
+
+	switch (chdlc_priv_area->route_status) {
+
+	case ADD_ROUTE:
+
+		if(!card->u.c.slarp_timer) {
+			if_data2 = (struct sockaddr_in *)&if_info.ifr_dstaddr;
+			if_data2->sin_addr.s_addr = remote_IP_addr;
+			if_data2->sin_family = AF_INET;
+			err = devinet_ioctl(SIOCSIFDSTADDR, &if_info);
+		} else { 
+			if_data1 = (struct sockaddr_in *)&if_info.ifr_addr;
+			if_data1->sin_addr.s_addr = local_IP_addr;
+			if_data1->sin_family = AF_INET;
+			if(!(err = devinet_ioctl(SIOCSIFADDR, &if_info))){
+				if_data2 = (struct sockaddr_in *)&if_info.ifr_dstaddr;
+				if_data2->sin_addr.s_addr = remote_IP_addr;
+				if_data2->sin_family = AF_INET;
+				err = devinet_ioctl(SIOCSIFDSTADDR, &if_info);
+			}
+		}
+
+               if(err) {
+			printk(KERN_INFO "%s: Add route %u.%u.%u.%u failed (%d)\n", 
+				card->devname, NIPQUAD(remote_IP_addr), err);
+		} else {
+			((chdlc_private_area_t *)dev->priv)->route_status = ROUTE_ADDED;
+			printk(KERN_INFO "%s: Dynamic route added.\n",
+				card->devname);
+			printk(KERN_INFO "%s:    Local IP addr : %u.%u.%u.%u\n",
+				card->devname, NIPQUAD(local_IP_addr));
+			printk(KERN_INFO "%s:    Remote IP addr: %u.%u.%u.%u\n",
+				card->devname, NIPQUAD(remote_IP_addr));
+			chdlc_priv_area->route_removed = 0;
+		}
+		break;
+
+
+	case REMOVE_ROUTE:
+	
+		/* Change the local ip address of the interface to 0.
+		 * This will also delete the destination route.
+		 */
+		if(!card->u.c.slarp_timer) {
+			if_data2 = (struct sockaddr_in *)&if_info.ifr_dstaddr;
+			if_data2->sin_addr.s_addr = 0;
+			if_data2->sin_family = AF_INET;
+			err = devinet_ioctl(SIOCSIFDSTADDR, &if_info);
+		} else {
+			if_data1 = (struct sockaddr_in *)&if_info.ifr_addr;
+			if_data1->sin_addr.s_addr = 0;
+			if_data1->sin_family = AF_INET;
+			err = devinet_ioctl(SIOCSIFADDR,&if_info);
+		
+		}
+		if(err) {
+			printk(KERN_INFO
+				"%s: Remove route %u.%u.%u.%u failed, (err %d)\n",
+					card->devname, NIPQUAD(remote_IP_addr),
+					err);
+		} else {
+			((chdlc_private_area_t *)dev->priv)->route_status =
+				NO_ROUTE;
+                        printk(KERN_INFO "%s: Dynamic route removed: %u.%u.%u.%u\n",
+                                        card->devname, NIPQUAD(local_IP_addr)); 
+			chdlc_priv_area->route_removed = 1;
+		}
+		break;
+	}
+
+        set_fs(fs);                     /* Restore file system */
+
+}
+
+
+/*=============================================================================
+ * Store a UDP management packet for later processing.
+ */
+
+static int store_udp_mgmt_pkt(char udp_pkt_src, sdla_t* card,
+			      struct sk_buff *skb, struct net_device* dev,
+			      chdlc_private_area_t* chdlc_priv_area)
+{
+	int udp_pkt_stored = 0;
+
+	if(!chdlc_priv_area->udp_pkt_lgth &&
+	  (skb->len <= MAX_LGTH_UDP_MGNT_PKT)) {
+        	chdlc_priv_area->udp_pkt_lgth = skb->len;
+		chdlc_priv_area->udp_pkt_src = udp_pkt_src;
+       		memcpy(chdlc_priv_area->udp_pkt_data, skb->data, skb->len);
+		chdlc_priv_area->timer_int_enabled = TMR_INT_ENABLED_UDP;
+		udp_pkt_stored = 1;
+	}
+
+	if(udp_pkt_src == UDP_PKT_FRM_STACK){
+		dev_kfree_skb_any(skb);
+	}else{
+                dev_kfree_skb_any(skb);
+	}
+		
+	return(udp_pkt_stored);
+}
+
+
+/*=============================================================================
+ * Process UDP management packet.
+ */
+
+static int process_udp_mgmt_pkt(sdla_t* card, struct net_device* dev,
+				chdlc_private_area_t* chdlc_priv_area ) 
+{
+	unsigned char *buf;
+	unsigned int frames, len;
+	struct sk_buff *new_skb;
+	unsigned short buffer_length, real_len;
+	unsigned long data_ptr;
+	unsigned data_length;
+	int udp_mgmt_req_valid = 1;
+	CHDLC_MAILBOX_STRUCT *mb = card->mbox;
+	SHARED_MEMORY_INFO_STRUCT *flags = card->u.c.flags;
+	chdlc_udp_pkt_t *chdlc_udp_pkt;
+	struct timeval tv;
+	int err;
+	char ut_char;
+
+	chdlc_udp_pkt = (chdlc_udp_pkt_t *) chdlc_priv_area->udp_pkt_data;
+
+	if(chdlc_priv_area->udp_pkt_src == UDP_PKT_FRM_NETWORK){
+
+		/* Only these commands are support for remote debugging.
+		 * All others are not */
+		switch(chdlc_udp_pkt->cblock.command) {
+
+			case READ_GLOBAL_STATISTICS:
+			case READ_MODEM_STATUS:  
+			case READ_CHDLC_LINK_STATUS:
+			case CPIPE_ROUTER_UP_TIME:
+			case READ_COMMS_ERROR_STATS:
+			case READ_CHDLC_OPERATIONAL_STATS:
+
+			/* These two commands are executed for
+			 * each request */
+			case READ_CHDLC_CONFIGURATION:
+			case READ_CHDLC_CODE_VERSION:
+				udp_mgmt_req_valid = 1;
+				break;
+			default:
+				udp_mgmt_req_valid = 0;
+				break;
+		} 
+	}
+	
+  	if(!udp_mgmt_req_valid) {
+
+		/* set length to 0 */
+		chdlc_udp_pkt->cblock.buffer_length = 0;
+
+    		/* set return code */
+		chdlc_udp_pkt->cblock.return_code = 0xCD;
+
+		if (net_ratelimit()){	
+			printk(KERN_INFO 
+			"%s: Warning, Illegal UDP command attempted from network: %x\n",
+			card->devname,chdlc_udp_pkt->cblock.command);
+		}
+
+   	} else {
+	   	unsigned long trace_status_cfg_addr = 0;
+		TRACE_STATUS_EL_CFG_STRUCT trace_cfg_struct;
+		TRACE_STATUS_ELEMENT_STRUCT trace_element_struct;
+
+		switch(chdlc_udp_pkt->cblock.command) {
+
+		case CPIPE_ENABLE_TRACING:
+		     if (!chdlc_priv_area->TracingEnabled) {
+
+			/* OPERATE_DATALINE_MONITOR */
+
+			mb->buffer_length = sizeof(LINE_TRACE_CONFIG_STRUCT);
+			mb->command = SET_TRACE_CONFIGURATION;
+
+    			((LINE_TRACE_CONFIG_STRUCT *)mb->data)->
+				trace_config = TRACE_ACTIVE;
+			/* Trace delay mode is not used because it slows
+			   down transfer and results in a standoff situation
+			   when there is a lot of data */
+
+			/* Configure the Trace based on user inputs */
+			((LINE_TRACE_CONFIG_STRUCT *)mb->data)->trace_config |= 
+					chdlc_udp_pkt->data[0];
+
+			((LINE_TRACE_CONFIG_STRUCT *)mb->data)->
+			   trace_deactivation_timer = 4000;
+
+
+			err = sdla_exec(mb) ? mb->return_code : CMD_TIMEOUT;
+			if (err != COMMAND_OK) {
+				chdlc_error(card,err,mb);
+				card->TracingEnabled = 0;
+				chdlc_udp_pkt->cblock.return_code = err;
+				mb->buffer_length = 0;
+				break;
+	    		} 
+
+			/* Get the base address of the trace element list */
+			mb->buffer_length = 0;
+			mb->command = READ_TRACE_CONFIGURATION;
+			err = sdla_exec(mb) ? mb->return_code : CMD_TIMEOUT;
+
+			if (err != COMMAND_OK) {
+				chdlc_error(card,err,mb);
+				chdlc_priv_area->TracingEnabled = 0;
+				chdlc_udp_pkt->cblock.return_code = err;
+				mb->buffer_length = 0;
+				break;
+	    		} 	
+
+	   		trace_status_cfg_addr =((LINE_TRACE_CONFIG_STRUCT *)
+				mb->data) -> ptr_trace_stat_el_cfg_struct;
+
+			sdla_peek(&card->hw, trace_status_cfg_addr,
+				 &trace_cfg_struct, sizeof(trace_cfg_struct));
+		    
+			chdlc_priv_area->start_trace_addr = trace_cfg_struct.
+				base_addr_trace_status_elements;
+
+			chdlc_priv_area->number_trace_elements = 
+					trace_cfg_struct.number_trace_status_elements;
+
+			chdlc_priv_area->end_trace_addr = (unsigned long)
+					((TRACE_STATUS_ELEMENT_STRUCT *)
+					 chdlc_priv_area->start_trace_addr + 
+					 (chdlc_priv_area->number_trace_elements - 1));
+
+			chdlc_priv_area->base_addr_trace_buffer = 
+					trace_cfg_struct.base_addr_trace_buffer;
+
+			chdlc_priv_area->end_addr_trace_buffer = 
+					trace_cfg_struct.end_addr_trace_buffer;
+
+		    	chdlc_priv_area->curr_trace_addr = 
+					trace_cfg_struct.next_trace_element_to_use;
+
+	    		chdlc_priv_area->available_buffer_space = 2000 - 
+								  sizeof(ip_pkt_t) -
+								  sizeof(udp_pkt_t) -
+							      	  sizeof(wp_mgmt_t) -
+								  sizeof(cblock_t) -
+							          sizeof(trace_info_t);	
+	       	     }
+		     chdlc_udp_pkt->cblock.return_code = COMMAND_OK;
+		     mb->buffer_length = 0;
+	       	     chdlc_priv_area->TracingEnabled = 1;
+	       	     break;
+	   
+
+		case CPIPE_DISABLE_TRACING:
+		     if (chdlc_priv_area->TracingEnabled) {
+
+			/* OPERATE_DATALINE_MONITOR */
+			mb->buffer_length = sizeof(LINE_TRACE_CONFIG_STRUCT);
+			mb->command = SET_TRACE_CONFIGURATION;
+    			((LINE_TRACE_CONFIG_STRUCT *)mb->data)->
+				trace_config = TRACE_INACTIVE;
+			err = sdla_exec(mb) ? mb->return_code : CMD_TIMEOUT;
+		     }		
+
+		     chdlc_priv_area->TracingEnabled = 0;
+		     chdlc_udp_pkt->cblock.return_code = COMMAND_OK;
+		     mb->buffer_length = 0;
+		     break;
+	   
+
+		case CPIPE_GET_TRACE_INFO:
+
+		     if (!chdlc_priv_area->TracingEnabled) {
+			chdlc_udp_pkt->cblock.return_code = 1;
+			mb->buffer_length = 0;
+			break;
+		     }
+
+  		     chdlc_udp_pkt->trace_info.ismoredata = 0x00;
+		     buffer_length = 0;	/* offset of packet already occupied */
+
+		     for (frames=0; frames < chdlc_priv_area->number_trace_elements; frames++){
+
+			trace_pkt_t *trace_pkt = (trace_pkt_t *)
+				&chdlc_udp_pkt->data[buffer_length];
+
+			sdla_peek(&card->hw, chdlc_priv_area->curr_trace_addr,
+			   	  (unsigned char *)&trace_element_struct,
+			   	  sizeof(TRACE_STATUS_ELEMENT_STRUCT));
+
+     			if (trace_element_struct.opp_flag == 0x00) {
+			 	break;
+			}
+
+			/* get pointer to real data */
+			data_ptr = trace_element_struct.ptr_data_bfr;
+
+			/* See if there is actual data on the trace buffer */
+			if (data_ptr){
+				data_length = trace_element_struct.trace_length;
+			}else{
+				data_length = 0;
+				chdlc_udp_pkt->trace_info.ismoredata = 0x01;
+			}
+	
+   			if( (chdlc_priv_area->available_buffer_space - buffer_length)
+				< ( sizeof(trace_pkt_t) + data_length) ) {
+
+                            /* indicate there are more frames on board & exit */
+				chdlc_udp_pkt->trace_info.ismoredata = 0x01;
+                               	break;
+                         }
+
+			trace_pkt->status = trace_element_struct.trace_type;
+
+			trace_pkt->time_stamp =
+				trace_element_struct.trace_time_stamp;
+
+			trace_pkt->real_length =
+				trace_element_struct.trace_length;
+
+			/* see if we can fit the frame into the user buffer */
+			real_len = trace_pkt->real_length;
+
+			if (data_ptr == 0) {
+			     	trace_pkt->data_avail = 0x00;
+			} else {
+				unsigned tmp = 0;
+
+				/* get the data from circular buffer
+				    must check for end of buffer */
+			        trace_pkt->data_avail = 0x01;
+
+				if ((data_ptr + real_len) >
+					     chdlc_priv_area->end_addr_trace_buffer + 1){
+
+				    	tmp = chdlc_priv_area->end_addr_trace_buffer - data_ptr + 1;
+				    	sdla_peek(&card->hw, data_ptr,
+					       	  trace_pkt->data,tmp);
+				    	data_ptr = chdlc_priv_area->base_addr_trace_buffer;
+				}
+	
+		        	sdla_peek(&card->hw, data_ptr,
+					  &trace_pkt->data[tmp], real_len - tmp);
+			}	
+
+			/* zero the opp flag to show we got the frame */
+			ut_char = 0x00;
+			sdla_poke(&card->hw, chdlc_priv_area->curr_trace_addr, &ut_char, 1);
+
+       			/* now move onto the next frame */
+       			chdlc_priv_area->curr_trace_addr += sizeof(TRACE_STATUS_ELEMENT_STRUCT);
+
+       			/* check if we went over the last address */
+			if ( chdlc_priv_area->curr_trace_addr > chdlc_priv_area->end_trace_addr ) {
+				chdlc_priv_area->curr_trace_addr = chdlc_priv_area->start_trace_addr;
+       			}
+
+            		if(trace_pkt->data_avail == 0x01) {
+				buffer_length += real_len - 1;
+			}
+	 
+	       	    	/* for the header */
+	            	buffer_length += sizeof(trace_pkt_t);
+
+		     }  /* For Loop */
+
+		     if (frames == chdlc_priv_area->number_trace_elements){
+			chdlc_udp_pkt->trace_info.ismoredata = 0x01;
+	             }
+ 		     chdlc_udp_pkt->trace_info.num_frames = frames;
+		 
+    		     mb->buffer_length = buffer_length;
+		     chdlc_udp_pkt->cblock.buffer_length = buffer_length; 
+		 
+		     chdlc_udp_pkt->cblock.return_code = COMMAND_OK; 
+		     
+		     break;
+
+
+		case CPIPE_FT1_READ_STATUS:
+			((unsigned char *)chdlc_udp_pkt->data )[0] =
+				flags->FT1_info_struct.parallel_port_A_input;
+
+			((unsigned char *)chdlc_udp_pkt->data )[1] =
+				flags->FT1_info_struct.parallel_port_B_input;
+				
+			chdlc_udp_pkt->cblock.return_code = COMMAND_OK;
+			chdlc_udp_pkt->cblock.buffer_length = 2;
+			mb->buffer_length = 2;
+			break;
+
+		case CPIPE_ROUTER_UP_TIME:
+			do_gettimeofday( &tv );
+			chdlc_priv_area->router_up_time = tv.tv_sec - 
+					chdlc_priv_area->router_start_time;
+			*(unsigned long *)&chdlc_udp_pkt->data = 
+					chdlc_priv_area->router_up_time;	
+			mb->buffer_length = sizeof(unsigned long);
+			chdlc_udp_pkt->cblock.buffer_length = sizeof(unsigned long);
+			chdlc_udp_pkt->cblock.return_code = COMMAND_OK;
+			break;
+
+   		case FT1_MONITOR_STATUS_CTRL:
+			/* Enable FT1 MONITOR STATUS */
+	        	if ((chdlc_udp_pkt->data[0] & ENABLE_READ_FT1_STATUS) ||  
+				(chdlc_udp_pkt->data[0] & ENABLE_READ_FT1_OP_STATS)) {
+			
+			     	if( rCount++ != 0 ) {
+					chdlc_udp_pkt->cblock.
+					return_code = COMMAND_OK;
+					mb->buffer_length = 1;
+		  			break;
+		    	     	}
+	      		}
+
+	      		/* Disable FT1 MONITOR STATUS */
+	      		if( chdlc_udp_pkt->data[0] == 0) {
+
+	      	   	     	if( --rCount != 0) {
+		  			chdlc_udp_pkt->cblock.
+					return_code = COMMAND_OK;
+					mb->buffer_length = 1;
+		  			break;
+	   	    	     	} 
+	      		} 	
+			goto dflt_1;
+
+		default:
+dflt_1:
+			/* it's a board command */
+			mb->command = chdlc_udp_pkt->cblock.command;
+			mb->buffer_length = chdlc_udp_pkt->cblock.buffer_length;
+			if (mb->buffer_length) {
+				memcpy(&mb->data, (unsigned char *) chdlc_udp_pkt->
+							data, mb->buffer_length);
+	      		} 
+			/* run the command on the board */
+			err = sdla_exec(mb) ? mb->return_code : CMD_TIMEOUT;
+			if (err != COMMAND_OK) {
+				break;
+			}
+
+			/* copy the result back to our buffer */
+	         	memcpy(&chdlc_udp_pkt->cblock, mb, sizeof(cblock_t)); 
+			
+			if (mb->buffer_length) {
+	         		memcpy(&chdlc_udp_pkt->data, &mb->data, 
+								mb->buffer_length); 
+	      		}
+
+		} /* end of switch */
+     	} /* end of else */
+
+     	/* Fill UDP TTL */
+	chdlc_udp_pkt->ip_pkt.ttl = card->wandev.ttl; 
+
+     	len = reply_udp(chdlc_priv_area->udp_pkt_data, mb->buffer_length);
+	
+
+     	if(chdlc_priv_area->udp_pkt_src == UDP_PKT_FRM_NETWORK){
+
+		/* Must check if we interrupted if_send() routine. The
+		 * tx buffers might be used. If so drop the packet */
+	   	if (!test_bit(SEND_CRIT,&card->wandev.critical)) {
+		
+			if(!chdlc_send(card, chdlc_priv_area->udp_pkt_data, len)) {
+				++ card->wandev.stats.tx_packets;
+				card->wandev.stats.tx_bytes += len;
+			}
+		}
+	} else {	
+	
+		/* Pass it up the stack
+    		   Allocate socket buffer */
+		if ((new_skb = dev_alloc_skb(len)) != NULL) {
+			/* copy data into new_skb */
+
+ 	    		buf = skb_put(new_skb, len);
+  	    		memcpy(buf, chdlc_priv_area->udp_pkt_data, len);
+
+            		/* Decapsulate pkt and pass it up the protocol stack */
+	    		new_skb->protocol = htons(ETH_P_IP);
+            		new_skb->dev = dev;
+	    		new_skb->mac.raw  = new_skb->data;
+	
+			netif_rx(new_skb);
+			dev->last_rx = jiffies;
+		} else {
+	    	
+			printk(KERN_INFO "%s: no socket buffers available!\n",
+					card->devname);
+  		}
+    	}
+ 
+	chdlc_priv_area->udp_pkt_lgth = 0;
+ 	
+	return 0;
+}
+
+/*============================================================================
+ * Initialize Receive and Transmit Buffers.
+ */
+
+static void init_chdlc_tx_rx_buff( sdla_t* card)
+{
+	CHDLC_MAILBOX_STRUCT* mb = card->mbox;
+	CHDLC_TX_STATUS_EL_CFG_STRUCT *tx_config;
+	CHDLC_RX_STATUS_EL_CFG_STRUCT *rx_config;
+	char err;
+	
+	mb->buffer_length = 0;
+	mb->command = READ_CHDLC_CONFIGURATION;
+	err = sdla_exec(mb) ? mb->return_code : CMD_TIMEOUT;
+
+	if(err != COMMAND_OK) {
+		if (card->wandev.dev){
+			chdlc_error(card,err,mb);
+		}
+		return;
+	}
+
+	if(card->hw.type == SDLA_S514) {
+		tx_config = (CHDLC_TX_STATUS_EL_CFG_STRUCT *)(card->hw.dpmbase +
+                (((CHDLC_CONFIGURATION_STRUCT *)mb->data)->
+                            ptr_CHDLC_Tx_stat_el_cfg_struct));
+        	rx_config = (CHDLC_RX_STATUS_EL_CFG_STRUCT *)(card->hw.dpmbase +
+                (((CHDLC_CONFIGURATION_STRUCT *)mb->data)->
+                            ptr_CHDLC_Rx_stat_el_cfg_struct));
+
+       		/* Setup Head and Tails for buffers */
+        	card->u.c.txbuf_base = (void *)(card->hw.dpmbase +
+                tx_config->base_addr_Tx_status_elements);
+        	card->u.c.txbuf_last = 
+		(CHDLC_DATA_TX_STATUS_EL_STRUCT *)  
+                card->u.c.txbuf_base +
+		(tx_config->number_Tx_status_elements - 1);
+
+        	card->u.c.rxbuf_base = (void *)(card->hw.dpmbase +
+                rx_config->base_addr_Rx_status_elements);
+        	card->u.c.rxbuf_last =
+		(CHDLC_DATA_RX_STATUS_EL_STRUCT *)
+                card->u.c.rxbuf_base +
+		(rx_config->number_Rx_status_elements - 1);
+
+ 		/* Set up next pointer to be used */
+        	card->u.c.txbuf = (void *)(card->hw.dpmbase +
+                tx_config->next_Tx_status_element_to_use);
+        	card->u.c.rxmb = (void *)(card->hw.dpmbase +
+                rx_config->next_Rx_status_element_to_use);
+	}
+        else {
+                tx_config = (CHDLC_TX_STATUS_EL_CFG_STRUCT *)(card->hw.dpmbase +
+			(((CHDLC_CONFIGURATION_STRUCT *)mb->data)->
+			ptr_CHDLC_Tx_stat_el_cfg_struct % SDLA_WINDOWSIZE));
+
+                rx_config = (CHDLC_RX_STATUS_EL_CFG_STRUCT *)(card->hw.dpmbase +
+			(((CHDLC_CONFIGURATION_STRUCT *)mb->data)->
+			ptr_CHDLC_Rx_stat_el_cfg_struct % SDLA_WINDOWSIZE));
+
+                /* Setup Head and Tails for buffers */
+                card->u.c.txbuf_base = (void *)(card->hw.dpmbase +
+		(tx_config->base_addr_Tx_status_elements % SDLA_WINDOWSIZE));
+                card->u.c.txbuf_last =
+		(CHDLC_DATA_TX_STATUS_EL_STRUCT *)card->u.c.txbuf_base
+		+ (tx_config->number_Tx_status_elements - 1);
+                card->u.c.rxbuf_base = (void *)(card->hw.dpmbase +
+		(rx_config->base_addr_Rx_status_elements % SDLA_WINDOWSIZE));
+                card->u.c.rxbuf_last = 
+		(CHDLC_DATA_RX_STATUS_EL_STRUCT *)card->u.c.rxbuf_base
+		+ (rx_config->number_Rx_status_elements - 1);
+
+                 /* Set up next pointer to be used */
+                card->u.c.txbuf = (void *)(card->hw.dpmbase +
+		(tx_config->next_Tx_status_element_to_use % SDLA_WINDOWSIZE));
+                card->u.c.rxmb = (void *)(card->hw.dpmbase +
+		(rx_config->next_Rx_status_element_to_use % SDLA_WINDOWSIZE));
+        }
+
+        /* Setup Actual Buffer Start and end addresses */
+        card->u.c.rx_base = rx_config->base_addr_Rx_buffer;
+        card->u.c.rx_top  = rx_config->end_addr_Rx_buffer;
+
+}
+
+/*=============================================================================
+ * Perform Interrupt Test by running READ_CHDLC_CODE_VERSION command MAX_INTR
+ * _TEST_COUNTER times.
+ */
+static int intr_test( sdla_t* card)
+{
+	CHDLC_MAILBOX_STRUCT* mb = card->mbox;
+	int err,i;
+
+	Intr_test_counter = 0;
+	
+	err = chdlc_set_intr_mode(card, APP_INT_ON_COMMAND_COMPLETE);
+
+	if (err == CMD_OK) { 
+		for (i = 0; i < MAX_INTR_TEST_COUNTER; i ++) {	
+			mb->buffer_length  = 0;
+			mb->command = READ_CHDLC_CODE_VERSION;
+			err = sdla_exec(mb) ? mb->return_code : CMD_TIMEOUT;
+			if (err != CMD_OK) 
+				chdlc_error(card, err, mb);
+		}
+	}
+	else {
+		return err;
+	}
+
+	err = chdlc_set_intr_mode(card, 0);
+
+	if (err != CMD_OK)
+		return err;
+
+	return 0;
+}
+
+/*==============================================================================
+ * Determine what type of UDP call it is. CPIPEAB ?
+ */
+static int udp_pkt_type(struct sk_buff *skb, sdla_t* card)
+{
+	 chdlc_udp_pkt_t *chdlc_udp_pkt = (chdlc_udp_pkt_t *)skb->data;
+
+#ifdef _WAN_UDP_DEBUG
+		printk(KERN_INFO "SIG %s = %s\n\
+				  UPP %x = %x\n\
+				  PRT %x = %x\n\
+				  REQ %i = %i\n\
+				  36 th = %x 37th = %x\n",
+				  chdlc_udp_pkt->wp_mgmt.signature,
+				  UDPMGMT_SIGNATURE,
+				  chdlc_udp_pkt->udp_pkt.udp_dst_port,
+				  ntohs(card->wandev.udp_port),
+				  chdlc_udp_pkt->ip_pkt.protocol,
+				  UDPMGMT_UDP_PROTOCOL,
+				  chdlc_udp_pkt->wp_mgmt.request_reply,
+				  UDPMGMT_REQUEST,
+				  skb->data[36], skb->data[37]);
+#endif	
+		
+	if (!strncmp(chdlc_udp_pkt->wp_mgmt.signature,UDPMGMT_SIGNATURE,8) &&
+	   (chdlc_udp_pkt->udp_pkt.udp_dst_port == ntohs(card->wandev.udp_port)) &&
+	   (chdlc_udp_pkt->ip_pkt.protocol == UDPMGMT_UDP_PROTOCOL) &&
+	   (chdlc_udp_pkt->wp_mgmt.request_reply == UDPMGMT_REQUEST)) {
+
+		return UDP_CPIPE_TYPE;
+
+	}else{ 
+		return UDP_INVALID_TYPE;
+	}
+}
+
+/*============================================================================
+ * Set PORT state.
+ */
+static void port_set_state (sdla_t *card, int state)
+{
+        if (card->u.c.state != state)
+        {
+                switch (state)
+                {
+                case WAN_CONNECTED:
+                        printk (KERN_INFO "%s: Link connected!\n",
+                                card->devname);
+                      	break;
+
+                case WAN_CONNECTING:
+                        printk (KERN_INFO "%s: Link connecting...\n",
+                                card->devname);
+                        break;
+
+                case WAN_DISCONNECTED:
+                        printk (KERN_INFO "%s: Link disconnected!\n",
+                                card->devname);
+                        break;
+                }
+
+                card->wandev.state = card->u.c.state = state;
+		if (card->wandev.dev){
+			struct net_device *dev = card->wandev.dev;
+			chdlc_private_area_t *chdlc_priv_area = dev->priv;
+			chdlc_priv_area->common.state = state;
+		}
+        }
+}
+
+/*===========================================================================
+ * config_chdlc
+ *
+ *	Configure the chdlc protocol and enable communications.		
+ *
+ *   	The if_open() function binds this function to the poll routine.
+ *      Therefore, this function will run every time the chdlc interface
+ *      is brought up. We cannot run this function from the if_open 
+ *      because if_open does not have access to the remote IP address.
+ *      
+ *	If the communications are not enabled, proceed to configure
+ *      the card and enable communications.
+ *
+ *      If the communications are enabled, it means that the interface
+ *      was shutdown by ether the user or driver. In this case, we 
+ *      have to check that the IP addresses have not changed.  If
+ *      the IP addresses have changed, we have to reconfigure the firmware
+ *      and update the changed IP addresses.  Otherwise, just exit.
+ *
+ */
+
+static int config_chdlc (sdla_t *card)
+{
+	struct net_device *dev = card->wandev.dev;
+	chdlc_private_area_t *chdlc_priv_area = dev->priv;
+	SHARED_MEMORY_INFO_STRUCT *flags = card->u.c.flags;
+
+	if (card->u.c.comm_enabled){
+
+		/* Jun 20. 2000: NC
+		 * IP addresses are not used in the API mode */
+		
+		if ((chdlc_priv_area->ip_local_tmp != chdlc_priv_area->ip_local ||
+		     chdlc_priv_area->ip_remote_tmp != chdlc_priv_area->ip_remote) && 
+		     card->u.c.usedby == WANPIPE) {
+			
+			/* The IP addersses have changed, we must
+                         * stop the communications and reconfigure
+                         * the card. Reason: the firmware must know
+                         * the local and remote IP addresses. */
+			disable_comm(card);
+			port_set_state(card, WAN_DISCONNECTED);
+			printk(KERN_INFO 
+				"%s: IP addresses changed!\n",
+					card->devname);
+			printk(KERN_INFO 
+				"%s: Restarting communications ...\n",
+					card->devname);
+		}else{ 
+			/* IP addresses are the same and the link is up, 
+                         * we don't have to do anything here. Therefore, exit */
+			return 0;
+		}
+	}
+
+	chdlc_priv_area->ip_local = chdlc_priv_area->ip_local_tmp;
+	chdlc_priv_area->ip_remote = chdlc_priv_area->ip_remote_tmp;
+
+
+	/* Setup the Board for asynchronous mode */
+	if (card->u.c.async_mode){
+		
+		if (set_asy_config(card)) {
+			printk (KERN_INFO "%s: Failed CHDLC Async configuration!\n",
+				card->devname);
+			return 0;
+		}
+	}else{
+		/* Setup the Board for CHDLC */
+		if (set_chdlc_config(card)) {
+			printk (KERN_INFO "%s: Failed CHDLC configuration!\n",
+				card->devname);
+			return 0;
+		}
+	}
+
+	/* Set interrupt mode and mask */
+        if (chdlc_set_intr_mode(card, APP_INT_ON_RX_FRAME |
+                		APP_INT_ON_GLOBAL_EXCEP_COND |
+                		APP_INT_ON_TX_FRAME |
+                		APP_INT_ON_CHDLC_EXCEP_COND | APP_INT_ON_TIMER)){
+		printk (KERN_INFO "%s: Failed to set interrupt triggers!\n",
+				card->devname);
+		return 0;	
+        }
+	
+
+	/* Mask the Transmit and Timer interrupt */
+	flags->interrupt_info_struct.interrupt_permission &= 
+		~(APP_INT_ON_TX_FRAME | APP_INT_ON_TIMER);
+
+	/* In TTY mode, receive interrupt will be enabled during
+	 * wanpipe_tty_open() operation */
+	if (card->tty_opt){
+		flags->interrupt_info_struct.interrupt_permission &= ~APP_INT_ON_RX_FRAME;
+	}
+
+	/* Enable communications */
+ 	if (card->u.c.async_mode){
+		if (asy_comm_enable(card) != 0) {
+			printk(KERN_INFO "%s: Failed to enable async commnunication!\n",
+					card->devname);
+			flags->interrupt_info_struct.interrupt_permission = 0;
+			card->u.c.comm_enabled=0;
+			chdlc_set_intr_mode(card,0);
+			return 0;
+		}
+        }else{ 
+		if (chdlc_comm_enable(card) != 0) {
+			printk(KERN_INFO "%s: Failed to enable chdlc communications!\n",
+					card->devname);
+			flags->interrupt_info_struct.interrupt_permission = 0;
+			card->u.c.comm_enabled=0;
+			chdlc_set_intr_mode(card,0);
+			return 0;
+		}
+	}
+
+	/* Initialize Rx/Tx buffer control fields */
+	init_chdlc_tx_rx_buff(card);
+	port_set_state(card, WAN_CONNECTING);
+	return 0; 
+}
+
+
+/*============================================================
+ * chdlc_poll
+ *	
+ * Rationale:
+ * 	We cannot manipulate the routing tables, or
+ *      ip addresses withing the interrupt. Therefore
+ *      we must perform such actons outside an interrupt 
+ *      at a later time. 
+ *
+ * Description:	
+ *	CHDLC polling routine, responsible for 
+ *     	shutting down interfaces upon disconnect
+ *     	and adding/removing routes. 
+ *      
+ * Usage:        
+ * 	This function is executed for each CHDLC  
+ * 	interface through a tq_schedule bottom half.
+ *      
+ *      trigger_chdlc_poll() function is used to kick
+ *      the chldc_poll routine.  
+ */
+
+static void chdlc_poll(struct net_device *dev)
+{
+	chdlc_private_area_t *chdlc_priv_area;
+	sdla_t *card;
+	u8 check_gateway=0;	
+	SHARED_MEMORY_INFO_STRUCT* flags;
+
+	
+	if (!dev || (chdlc_priv_area=dev->priv) == NULL)
+		return;
+
+	card = chdlc_priv_area->card;
+	flags = card->u.c.flags;
+	
+	/* (Re)Configuraiton is in progress, stop what you are 
+	 * doing and get out */
+	if (test_bit(PERI_CRIT,&card->wandev.critical)){
+		clear_bit(POLL_CRIT,&card->wandev.critical);
+		return;
+	}
+	
+	/* if_open() function has triggered the polling routine
+	 * to determine the configured IP addresses.  Once the
+	 * addresses are found, trigger the chdlc configuration */
+	if (test_bit(0,&chdlc_priv_area->config_chdlc)){
+
+		chdlc_priv_area->ip_local_tmp  = get_ip_address(dev,WAN_LOCAL_IP);
+		chdlc_priv_area->ip_remote_tmp = get_ip_address(dev,WAN_POINTOPOINT_IP);
+	
+	       /* Jun 20. 2000 Bug Fix
+	 	* Only perform this check in WANPIPE mode, since
+	 	* IP addresses are not used in the API mode. */
+	
+		if (chdlc_priv_area->ip_local_tmp == chdlc_priv_area->ip_remote_tmp && 
+		    card->u.c.slarp_timer == 0x00 && 
+		    !card->u.c.backup && 
+		    card->u.c.usedby == WANPIPE){
+
+			if (++chdlc_priv_area->ip_error > MAX_IP_ERRORS){
+				printk(KERN_INFO "\n%s: --- WARNING ---\n",
+						card->devname);
+				printk(KERN_INFO 
+				"%s: The local IP address is the same as the\n",
+						card->devname);
+				printk(KERN_INFO 
+				"%s: Point-to-Point IP address.\n",
+						card->devname);
+				printk(KERN_INFO "%s: --- WARNING ---\n\n",
+						card->devname);
+			}else{
+				clear_bit(POLL_CRIT,&card->wandev.critical);
+				chdlc_priv_area->poll_delay_timer.expires = jiffies+HZ;
+				add_timer(&chdlc_priv_area->poll_delay_timer);
+				return;
+			}
+		}
+
+		clear_bit(0,&chdlc_priv_area->config_chdlc);
+		clear_bit(POLL_CRIT,&card->wandev.critical);
+		
+		chdlc_priv_area->timer_int_enabled |= TMR_INT_ENABLED_CONFIG;
+		flags->interrupt_info_struct.interrupt_permission |= APP_INT_ON_TIMER;
+		return;
+	}
+	/* Dynamic interface implementation, as well as dynamic
+	 * routing.  */
+	
+	switch (card->u.c.state){
+
+	case WAN_DISCONNECTED:
+
+		/* If the dynamic interface configuration is on, and interface 
+		 * is up, then bring down the netowrk interface */
+		
+		if (test_bit(DYN_OPT_ON,&chdlc_priv_area->interface_down) && 
+		    !test_bit(DEV_DOWN,  &chdlc_priv_area->interface_down) &&		
+		    card->wandev.dev->flags & IFF_UP){	
+
+			printk(KERN_INFO "%s: Interface %s down.\n",
+				card->devname,card->wandev.dev->name);
+			change_dev_flags(card->wandev.dev,(card->wandev.dev->flags&~IFF_UP));
+			set_bit(DEV_DOWN,&chdlc_priv_area->interface_down);
+			chdlc_priv_area->route_status = NO_ROUTE;
+
+		}else{
+			/* We need to check if the local IP address is
+               	  	 * zero. If it is, we shouldn't try to remove it.
+                 	 */
+
+			if (card->wandev.dev->flags & IFF_UP && 
+		    	    get_ip_address(card->wandev.dev,WAN_LOCAL_IP) && 
+		    	    chdlc_priv_area->route_status != NO_ROUTE &&
+			    card->u.c.slarp_timer){
+
+				process_route(card);
+			}
+		}
+		break;
+
+	case WAN_CONNECTED:
+
+		/* In SMP machine this code can execute before the interface
+		 * comes up.  In this case, we must make sure that we do not
+		 * try to bring up the interface before dev_open() is finished */
+
+
+		/* DEV_DOWN will be set only when we bring down the interface
+		 * for the very first time. This way we know that it was us
+		 * that brought the interface down */
+		
+		if (test_bit(DYN_OPT_ON,&chdlc_priv_area->interface_down) &&
+		    test_bit(DEV_DOWN,  &chdlc_priv_area->interface_down) &&
+		    !(card->wandev.dev->flags & IFF_UP)){
+			
+			printk(KERN_INFO "%s: Interface %s up.\n",
+				card->devname,card->wandev.dev->name);
+			change_dev_flags(card->wandev.dev,(card->wandev.dev->flags|IFF_UP));
+			clear_bit(DEV_DOWN,&chdlc_priv_area->interface_down);
+			check_gateway=1;
+		}
+
+		if (chdlc_priv_area->route_status == ADD_ROUTE && 
+		    card->u.c.slarp_timer){ 
+
+			process_route(card);
+			check_gateway=1;
+		}
+
+		if (chdlc_priv_area->gateway && check_gateway)
+			add_gateway(card,dev);
+
+		break;
+	}	
+
+	clear_bit(POLL_CRIT,&card->wandev.critical);
+}
+
+/*============================================================
+ * trigger_chdlc_poll
+ *
+ * Description:
+ * 	Add a chdlc_poll() work entry into the keventd work queue
+ *      for a specific dlci/interface.  This will kick
+ *      the fr_poll() routine at a later time. 
+ *
+ * Usage:
+ * 	Interrupts use this to defer a taks to 
+ *      a polling routine.
+ *
+ */	
+static void trigger_chdlc_poll(struct net_device *dev)
+{
+	chdlc_private_area_t *chdlc_priv_area;
+	sdla_t *card;
+
+	if (!dev)
+		return;
+	
+	if ((chdlc_priv_area = dev->priv)==NULL)
+		return;
+
+	card = chdlc_priv_area->card;
+	
+	if (test_and_set_bit(POLL_CRIT,&card->wandev.critical)){
+		return;
+	}
+	if (test_bit(PERI_CRIT,&card->wandev.critical)){
+		return; 
+	}
+	schedule_work(&chdlc_priv_area->poll_work);
+}
+
+
+static void chdlc_poll_delay (unsigned long dev_ptr)
+{
+	struct net_device *dev = (struct net_device *)dev_ptr;
+	trigger_chdlc_poll(dev);
+}
+
+
+void s508_lock (sdla_t *card, unsigned long *smp_flags)
+{
+	spin_lock_irqsave(&card->wandev.lock, *smp_flags);
+        if (card->next){
+        	spin_lock(&card->next->wandev.lock);
+	}
+}
+
+void s508_unlock (sdla_t *card, unsigned long *smp_flags)
+{
+        if (card->next){
+        	spin_unlock(&card->next->wandev.lock);
+        }
+        spin_unlock_irqrestore(&card->wandev.lock, *smp_flags);
+}
+
+//*********** TTY SECTION ****************
+
+static void wanpipe_tty_trigger_tx_irq(sdla_t *card)
+{
+	SHARED_MEMORY_INFO_STRUCT *flags = card->u.c.flags;
+	INTERRUPT_INFORMATION_STRUCT *chdlc_int = &flags->interrupt_info_struct;
+	chdlc_int->interrupt_permission |= APP_INT_ON_TX_FRAME;
+}
+
+static void wanpipe_tty_trigger_poll(sdla_t *card)
+{
+	schedule_work(&card->tty_work);
+}
+
+static void tty_poll_work (void* data)
+{
+	sdla_t *card = (sdla_t*)data;
+	struct tty_struct *tty;
+
+	if ((tty=card->tty)==NULL)
+		return;
+	
+	tty_wakeup(tty);
+#if defined(SERIAL_HAVE_POLL_WAIT)
+	wake_up_interruptible(&tty->poll_wait);
+#endif	
+	return;
+}
+
+static void wanpipe_tty_close(struct tty_struct *tty, struct file * filp)
+{
+	sdla_t *card;
+	unsigned long smp_flags;
+	
+	if (!tty || !tty->driver_data){
+		return;
+	}
+	
+	card = (sdla_t*)tty->driver_data;
+	
+	if (!card)
+		return;
+
+	printk(KERN_INFO "%s: Closing TTY Driver!\n",
+			card->devname);
+
+	/* Sanity Check */
+	if (!card->tty_open)
+		return;
+	
+	wanpipe_close(card);
+	if (--card->tty_open == 0){
+
+		lock_adapter_irq(&card->wandev.lock,&smp_flags);	
+		card->tty=NULL;
+		chdlc_disable_comm_shutdown(card);
+		unlock_adapter_irq(&card->wandev.lock,&smp_flags);
+
+		if (card->tty_buf){
+			kfree(card->tty_buf);
+			card->tty_buf=NULL;			
+		}
+
+		if (card->tty_rx){
+			kfree(card->tty_rx);
+			card->tty_rx=NULL;
+		}
+	}
+	return;
+}
+static int wanpipe_tty_open(struct tty_struct *tty, struct file * filp)
+{
+	unsigned long smp_flags;
+	sdla_t *card;
+	
+	if (!tty){
+		return -ENODEV;
+	}
+	
+	if (!tty->driver_data){
+		int port;
+		port = tty->index;
+		if ((port < 0) || (port >= NR_PORTS)) 
+			return -ENODEV;
+		
+		tty->driver_data = WAN_CARD(port);
+		if (!tty->driver_data)
+			return -ENODEV;
+	}
+
+	card = (sdla_t*)tty->driver_data;
+
+	if (!card){
+		lock_adapter_irq(&card->wandev.lock,&smp_flags);	
+		card->tty=NULL;
+		unlock_adapter_irq(&card->wandev.lock,&smp_flags);
+		return -ENODEV;
+	}
+
+	printk(KERN_INFO "%s: Opening TTY Driver!\n",
+			card->devname);
+
+	if (card->tty_open == 0){
+		lock_adapter_irq(&card->wandev.lock,&smp_flags);	
+		card->tty=tty;
+		unlock_adapter_irq(&card->wandev.lock,&smp_flags);
+
+		if (!card->tty_buf){
+			card->tty_buf = kmalloc(TTY_CHDLC_MAX_MTU, GFP_KERNEL);
+			if (!card->tty_buf){
+				card->tty_buf=NULL;
+				card->tty=NULL;
+				return -ENOMEM;	
+			}
+		}
+
+		if (!card->tty_rx){
+			card->tty_rx = kmalloc(TTY_CHDLC_MAX_MTU, GFP_KERNEL);
+			if (!card->tty_rx){
+				/* Free the buffer above */
+				kfree(card->tty_buf);
+				card->tty_buf=NULL;
+				card->tty=NULL;
+				return -ENOMEM;	
+			}
+		}
+	}
+
+	++card->tty_open;
+	wanpipe_open(card);
+	return 0;
+}
+
+static int wanpipe_tty_write(struct tty_struct * tty, const unsigned char *buf, int count)
+{
+	unsigned long smp_flags=0;
+	sdla_t *card=NULL;
+
+	if (!tty){
+		dbg_printk(KERN_INFO "NO TTY in Write\n");
+		return -ENODEV;
+	}
+
+	card = (sdla_t *)tty->driver_data;
+			
+	if (!card){
+		dbg_printk(KERN_INFO "No Card in TTY Write\n");
+		return -ENODEV;
+	}	
+
+	if (count > card->wandev.mtu){
+		dbg_printk(KERN_INFO "Frame too big in Write %i Max: %i\n",
+				count,card->wandev.mtu);
+		return -EINVAL;
+	}
+	
+	if (card->wandev.state != WAN_CONNECTED){
+		dbg_printk(KERN_INFO "Card not connected in TTY Write\n");
+		return -EINVAL;
+	}
+
+	/* Lock the 508 Card: SMP is supported */
+      	if(card->hw.type != SDLA_S514){
+		s508_lock(card,&smp_flags);
+	} 
+	
+	if (test_and_set_bit(SEND_CRIT,(void*)&card->wandev.critical)){
+		printk(KERN_INFO "%s: Critical in TTY Write\n",
+				card->devname);
+		
+		/* Lock the 508 Card: SMP is supported */
+		if(card->hw.type != SDLA_S514)
+			s508_unlock(card,&smp_flags);
+		
+		return -EINVAL; 
+	}
+	
+ 	if (chdlc_send(card,(void*)buf,count)){
+		dbg_printk(KERN_INFO "%s: Failed to send, retry later: kernel!\n",
+				card->devname);
+		clear_bit(SEND_CRIT,(void*)&card->wandev.critical);
+
+		wanpipe_tty_trigger_tx_irq(card);
+		
+		if(card->hw.type != SDLA_S514)
+			s508_unlock(card,&smp_flags);
+		return 0;
+	}
+	dbg_printk(KERN_INFO "%s: Packet sent OK: %i\n",card->devname,count);
+	clear_bit(SEND_CRIT,(void*)&card->wandev.critical);
+	
+	if(card->hw.type != SDLA_S514)
+		s508_unlock(card,&smp_flags);
+
+	return count;
+}
+
+static void wanpipe_tty_receive(sdla_t *card, unsigned addr, unsigned int len)
+{
+	unsigned offset=0;
+	unsigned olen=len;
+	char fp=0;
+	struct tty_struct *tty;
+	int i;
+	struct tty_ldisc *ld;
+	
+	if (!card->tty_open){
+		dbg_printk(KERN_INFO "%s: TTY not open during receive\n",
+				card->devname);
+		return;
+	}
+	
+	if ((tty=card->tty) == NULL){
+		dbg_printk(KERN_INFO "%s: No TTY on receive\n",
+				card->devname);
+		return;
+	}
+	
+	if (!tty->driver_data){
+		dbg_printk(KERN_INFO "%s: No Driver Data, or Flip on receive\n",
+				card->devname);
+		return;
+	}
+	
+
+	if (card->u.c.async_mode){
+		if ((tty->flip.count+len) >= TTY_FLIPBUF_SIZE){
+			if (net_ratelimit()){
+				printk(KERN_INFO 
+					"%s: Received packet size too big: %i bytes, Max: %i!\n",
+					card->devname,len,TTY_FLIPBUF_SIZE);
+			}
+			return;
+		}
+
+		
+		if((addr + len) > card->u.c.rx_top + 1) {
+			offset = card->u.c.rx_top - addr + 1;
+			
+			sdla_peek(&card->hw, addr, tty->flip.char_buf_ptr, offset);
+			
+			addr = card->u.c.rx_base;
+			len -= offset;
+			
+			tty->flip.char_buf_ptr+=offset;
+			tty->flip.count+=offset;
+			for (i=0;i<offset;i++){
+				*tty->flip.flag_buf_ptr = 0;
+				tty->flip.flag_buf_ptr++;
+			}
+		}
+		
+		sdla_peek(&card->hw, addr, tty->flip.char_buf_ptr, len);
+			
+		tty->flip.char_buf_ptr+=len;
+		card->tty->flip.count+=len;
+		for (i=0;i<len;i++){
+			*tty->flip.flag_buf_ptr = 0;
+			tty->flip.flag_buf_ptr++;
+		}
+
+		tty->low_latency=1;
+		tty_flip_buffer_push(tty);
+	}else{
+		if (!card->tty_rx){	
+			if (net_ratelimit()){
+				printk(KERN_INFO 
+				"%s: Receive sync buffer not available!\n",
+				 card->devname);
+			}
+			return;
+		}
+	
+		if (len > TTY_CHDLC_MAX_MTU){
+			if (net_ratelimit()){
+				printk(KERN_INFO 
+				"%s: Received packet size too big: %i bytes, Max: %i!\n",
+					card->devname,len,TTY_FLIPBUF_SIZE);
+			}
+			return;
+		}
+
+		
+		if((addr + len) > card->u.c.rx_top + 1) {
+			offset = card->u.c.rx_top - addr + 1;
+			
+			sdla_peek(&card->hw, addr, card->tty_rx, offset);
+			
+			addr = card->u.c.rx_base;
+			len -= offset;
+		}
+		sdla_peek(&card->hw, addr, card->tty_rx+offset, len);
+		ld = tty_ldisc_ref(tty);
+		if (ld) {
+			if (ld->receive_buf)
+				ld->receive_buf(tty,card->tty_rx,&fp,olen);
+			tty_ldisc_deref(ld);
+		}else{
+			if (net_ratelimit()){
+				printk(KERN_INFO 
+					"%s: NO TTY Sync line discipline!\n",
+					card->devname);
+			}
+		}
+	}
+
+	dbg_printk(KERN_INFO "%s: Received Data %i\n",card->devname,olen);
+	return;
+}
+
+#if 0
+static int wanpipe_tty_ioctl(struct tty_struct *tty, struct file * file,
+		    unsigned int cmd, unsigned long arg)
+{
+	return -ENOIOCTLCMD;
+}
+#endif
+
+static void wanpipe_tty_stop(struct tty_struct *tty)
+{
+	return;
+}
+
+static void wanpipe_tty_start(struct tty_struct *tty)
+{
+	return;
+}
+
+static int config_tty (sdla_t *card)
+{
+	SHARED_MEMORY_INFO_STRUCT *flags = card->u.c.flags;
+
+	/* Setup the Board for asynchronous mode */
+	if (card->u.c.async_mode){
+		
+		if (set_asy_config(card)) {
+			printk (KERN_INFO "%s: Failed CHDLC Async configuration!\n",
+				card->devname);
+			return -EINVAL;
+		}
+	}else{
+		/* Setup the Board for CHDLC */
+		if (set_chdlc_config(card)) {
+			printk (KERN_INFO "%s: Failed CHDLC configuration!\n",
+				card->devname);
+			return -EINVAL;
+		}
+	}
+
+	/* Set interrupt mode and mask */
+        if (chdlc_set_intr_mode(card, APP_INT_ON_RX_FRAME |
+                		APP_INT_ON_GLOBAL_EXCEP_COND |
+                		APP_INT_ON_TX_FRAME |
+                		APP_INT_ON_CHDLC_EXCEP_COND | APP_INT_ON_TIMER)){
+		printk (KERN_INFO "%s: Failed to set interrupt triggers!\n",
+				card->devname);
+		return -EINVAL;	
+        }
+	
+
+	/* Mask the Transmit and Timer interrupt */
+	flags->interrupt_info_struct.interrupt_permission &= 
+		~(APP_INT_ON_TX_FRAME | APP_INT_ON_TIMER);
+
+	
+	/* Enable communications */
+ 	if (card->u.c.async_mode){
+		if (asy_comm_enable(card) != 0) {
+			printk(KERN_INFO "%s: Failed to enable async commnunication!\n",
+					card->devname);
+			flags->interrupt_info_struct.interrupt_permission = 0;
+			card->u.c.comm_enabled=0;
+			chdlc_set_intr_mode(card,0);
+			return -EINVAL;
+		}
+        }else{ 
+		if (chdlc_comm_enable(card) != 0) {
+			printk(KERN_INFO "%s: Failed to enable chdlc communications!\n",
+					card->devname);
+			flags->interrupt_info_struct.interrupt_permission = 0;
+			card->u.c.comm_enabled=0;
+			chdlc_set_intr_mode(card,0);
+			return -EINVAL;
+		}
+	}
+
+	/* Initialize Rx/Tx buffer control fields */
+	init_chdlc_tx_rx_buff(card);
+	port_set_state(card, WAN_CONNECTING);
+	return 0; 
+}
+
+
+static int change_speed(sdla_t *card, struct tty_struct *tty,
+			 struct termios *old_termios)
+{
+	int	baud, ret=0;
+	unsigned cflag; 
+	int	dbits,sbits,parity,handshaking;
+
+	cflag = tty->termios->c_cflag;
+
+	/* There is always one stop bit */
+	sbits=WANOPT_ONE;
+	
+	/* Parity is defaulted to NONE */
+	parity = WANOPT_NONE;
+
+	handshaking=0;
+	
+	/* byte size and parity */
+	switch (cflag & CSIZE) {
+	      case CS5: dbits = 5; break;
+	      case CS6: dbits = 6; break;
+	      case CS7: dbits = 7; break;
+	      case CS8: dbits = 8; break;
+	      /* Never happens, but GCC is too dumb to figure it out */
+	      default:  dbits = 8; break;
+	}
+	
+	/* One more stop bit should be supported, thus increment
+	 * the number of stop bits Max=2 */
+	if (cflag & CSTOPB) {
+		sbits = WANOPT_TWO;
+	}
+	if (cflag & PARENB) {
+		parity = WANOPT_EVEN;
+	}
+	if (cflag & PARODD){
+		parity = WANOPT_ODD;
+	}
+
+	/* Determine divisor based on baud rate */
+	baud = tty_get_baud_rate(tty);
+
+	if (!baud)
+		baud = 9600;	/* B0 transition handled in rs_set_termios */
+
+	if (cflag & CRTSCTS) {
+		handshaking|=ASY_RTS_HS_FOR_RX;
+	}
+	
+	if (I_IGNPAR(tty))
+		parity = WANOPT_NONE;
+
+	if (I_IXOFF(tty)){
+		handshaking|=ASY_XON_XOFF_HS_FOR_RX;
+		handshaking|=ASY_XON_XOFF_HS_FOR_TX;
+	}
+
+	if (I_IXON(tty)){
+		handshaking|=ASY_XON_XOFF_HS_FOR_RX;
+		handshaking|=ASY_XON_XOFF_HS_FOR_TX;
+	}
+
+	if (card->u.c.async_mode){
+		if (card->wandev.bps != baud)
+			ret=1;
+		card->wandev.bps = baud;
+	}
+
+	if (card->u.c.async_mode){
+		if (card->u.c.protocol_options != handshaking)
+			ret=1;
+		card->u.c.protocol_options = handshaking;
+
+		if (card->u.c.tx_bits_per_char != dbits)
+			ret=1;
+		card->u.c.tx_bits_per_char = dbits;
+
+		if (card->u.c.rx_bits_per_char != dbits)
+			ret=1;
+		card->u.c.rx_bits_per_char = dbits;
+		
+		if (card->u.c.stop_bits != sbits)
+			ret=1;
+		card->u.c.stop_bits = sbits;
+
+		if (card->u.c.parity != parity)
+			ret=1;
+		card->u.c.parity = parity;	
+
+		card->u.c.break_timer = 50;
+		card->u.c.inter_char_timer = 10;
+		card->u.c.rx_complete_length = 100;
+		card->u.c.xon_char = 0xFE;
+	}else{
+		card->u.c.protocol_options = HDLC_STREAMING_MODE;
+	}
+	
+	return ret;
+}
+
+	
+static void wanpipe_tty_set_termios(struct tty_struct *tty, struct termios *old_termios)
+{
+	sdla_t *card;
+	int err=1;
+
+	if (!tty){
+		return;
+	}
+
+	card = (sdla_t *)tty->driver_data;
+			
+	if (!card)
+		return;
+
+	if (change_speed(card, tty, old_termios) || !card->u.c.comm_enabled){
+		unsigned long smp_flags;
+		
+		if (card->u.c.comm_enabled){
+			lock_adapter_irq(&card->wandev.lock,&smp_flags);
+			chdlc_disable_comm_shutdown(card);
+			unlock_adapter_irq(&card->wandev.lock,&smp_flags);
+		}
+		lock_adapter_irq(&card->wandev.lock,&smp_flags);
+		err = config_tty(card);
+		unlock_adapter_irq(&card->wandev.lock,&smp_flags);
+		if (card->u.c.async_mode){
+			printk(KERN_INFO "%s: TTY Async Configuration:\n"
+				 "   Baud        =%i\n"
+				 "   Handshaking =%s\n"
+				 "   Tx Dbits    =%i\n"
+				 "   Rx Dbits    =%i\n"
+				 "   Parity      =%s\n"
+				 "   Stop Bits   =%i\n",
+				 card->devname,
+				 card->wandev.bps,
+				 opt_decode[card->u.c.protocol_options],
+				 card->u.c.tx_bits_per_char,
+				 card->u.c.rx_bits_per_char,
+				 p_decode[card->u.c.parity] ,
+				 card->u.c.stop_bits);
+		}else{
+			printk(KERN_INFO "%s: TTY Sync Configuration:\n"
+				 "   Baud        =%i\n"
+				 "   Protocol    =HDLC_STREAMING\n",
+				 card->devname,card->wandev.bps);
+		}
+		if (!err){
+			port_set_state(card,WAN_CONNECTED);
+		}else{
+			port_set_state(card,WAN_DISCONNECTED);
+		}
+	}
+	return;
+}
+
+static void wanpipe_tty_put_char(struct tty_struct *tty, unsigned char ch)
+{
+	sdla_t *card;
+	unsigned long smp_flags=0;
+
+	if (!tty){
+		return;
+	}
+	
+	card = (sdla_t *)tty->driver_data;
+			
+	if (!card)
+		return;
+
+	if (card->wandev.state != WAN_CONNECTED)
+		return;
+
+	if(card->hw.type != SDLA_S514)
+		s508_lock(card,&smp_flags);
+	
+	if (test_and_set_bit(SEND_CRIT,(void*)&card->wandev.critical)){
+		
+		wanpipe_tty_trigger_tx_irq(card);
+
+		if(card->hw.type != SDLA_S514)
+			s508_unlock(card,&smp_flags);
+		return;
+	}
+
+	if (chdlc_send(card,(void*)&ch,1)){
+		wanpipe_tty_trigger_tx_irq(card);
+		dbg_printk("%s: Failed to TX char!\n",card->devname);
+	}
+	
+	dbg_printk("%s: Char TX OK\n",card->devname);
+	
+	clear_bit(SEND_CRIT,(void*)&card->wandev.critical);
+	
+	if(card->hw.type != SDLA_S514)
+		s508_unlock(card,&smp_flags);
+	
+	return;
+}
+
+static void wanpipe_tty_flush_chars(struct tty_struct *tty)
+{
+	return;
+}
+
+static void wanpipe_tty_flush_buffer(struct tty_struct *tty)
+{
+	if (!tty)
+		return;
+	
+#if defined(SERIAL_HAVE_POLL_WAIT)
+	wake_up_interruptible(&tty->poll_wait);
+#endif
+	tty_wakeup(tty);
+	return;
+}
+
+/*
+ * This function is used to send a high-priority XON/XOFF character to
+ * the device
+ */
+static void wanpipe_tty_send_xchar(struct tty_struct *tty, char ch)
+{
+	return;
+}
+
+
+static int wanpipe_tty_chars_in_buffer(struct tty_struct *tty)
+{
+	return 0;
+}
+
+
+static int wanpipe_tty_write_room(struct tty_struct *tty)
+{
+	sdla_t *card;
+
+	printk(KERN_INFO "TTY Write Room\n");
+	
+	if (!tty){
+		return 0;
+	}
+
+	card = (sdla_t *)tty->driver_data;
+	if (!card)
+		return 0;
+
+	if (card->wandev.state != WAN_CONNECTED)
+		return 0;
+	
+	return SEC_MAX_NO_DATA_BYTES_IN_FRAME;
+}
+
+
+static int set_modem_status(sdla_t *card, unsigned char data)
+{
+	CHDLC_MAILBOX_STRUCT *mb = card->mbox;
+	int err;
+
+	mb->buffer_length=1;
+	mb->command=SET_MODEM_STATUS;
+	mb->data[0]=data;
+	err = sdla_exec(mb) ? mb->return_code : CMD_TIMEOUT;
+	if (err != COMMAND_OK) 
+		chdlc_error (card, err, mb);
+	
+	return err;
+}
+
+static void wanpipe_tty_hangup(struct tty_struct *tty)
+{
+	sdla_t *card;
+	unsigned long smp_flags;
+
+	printk(KERN_INFO "TTY Hangup!\n");
+	
+	if (!tty){
+		return;
+	}
+
+	card = (sdla_t *)tty->driver_data;
+	if (!card)
+		return;
+
+	lock_adapter_irq(&card->wandev.lock,&smp_flags);
+	set_modem_status(card,0);
+	unlock_adapter_irq(&card->wandev.lock,&smp_flags);
+	return;
+}
+
+static void wanpipe_tty_break(struct tty_struct *tty, int break_state)
+{
+	return;
+}
+
+static void wanpipe_tty_wait_until_sent(struct tty_struct *tty, int timeout)
+{
+	return;
+}
+
+static void wanpipe_tty_throttle(struct tty_struct * tty)
+{
+	return;
+}
+
+static void wanpipe_tty_unthrottle(struct tty_struct * tty)
+{
+	return;
+}
+
+int wanpipe_tty_read_proc(char *page, char **start, off_t off, int count,
+		 int *eof, void *data)
+{
+	return 0;
+}
+
+/*
+ * The serial driver boot-time initialization code!
+ */
+int wanpipe_tty_init(sdla_t *card)
+{
+	struct serial_state * state;
+	
+	/* Initialize the tty_driver structure */
+
+	if (card->tty_minor < 0 || card->tty_minor > NR_PORTS){
+		printk(KERN_INFO "%s: Illegal Minor TTY number (0-4): %i\n",
+				card->devname,card->tty_minor);
+		return -EINVAL;
+	}
+
+	if (WAN_CARD(card->tty_minor)){
+		printk(KERN_INFO "%s: TTY Minor %i, already in use\n",
+				card->devname,card->tty_minor);
+		return -EBUSY;
+	}
+
+	if (tty_init_cnt==0){
+		
+		printk(KERN_INFO "%s: TTY %s Driver Init: Major %i, Minor Range %i-%i\n",
+				card->devname,
+				card->u.c.async_mode ? "ASYNC" : "SYNC",
+				WAN_TTY_MAJOR,MIN_PORT,MAX_PORT);
+		
+		tty_driver_mode = card->u.c.async_mode;
+		
+		memset(&serial_driver, 0, sizeof(struct tty_driver));
+		serial_driver.magic = TTY_DRIVER_MAGIC;
+		serial_driver.owner = THIS_MODULE;
+		serial_driver.driver_name = "wanpipe_tty"; 
+		serial_driver.name = "ttyW";
+		serial_driver.major = WAN_TTY_MAJOR;
+		serial_driver.minor_start = WAN_TTY_MINOR;
+		serial_driver.num = NR_PORTS; 
+		serial_driver.type = TTY_DRIVER_TYPE_SERIAL;
+		serial_driver.subtype = SERIAL_TYPE_NORMAL;
+		
+		serial_driver.init_termios = tty_std_termios;
+		serial_driver.init_termios.c_cflag =
+			B9600 | CS8 | CREAD | HUPCL | CLOCAL;
+		serial_driver.flags = TTY_DRIVER_REAL_RAW;
+		
+		serial_driver.refcount = 1;	/* !@!@^#^&!! */
+
+		serial_driver.open = wanpipe_tty_open;
+		serial_driver.close = wanpipe_tty_close;
+		serial_driver.write = wanpipe_tty_write;
+		
+		serial_driver.put_char = wanpipe_tty_put_char;
+		serial_driver.flush_chars = wanpipe_tty_flush_chars;
+		serial_driver.write_room = wanpipe_tty_write_room;
+		serial_driver.chars_in_buffer = wanpipe_tty_chars_in_buffer;
+		serial_driver.flush_buffer = wanpipe_tty_flush_buffer;
+		//serial_driver.ioctl = wanpipe_tty_ioctl;
+		serial_driver.throttle = wanpipe_tty_throttle;
+		serial_driver.unthrottle = wanpipe_tty_unthrottle;
+		serial_driver.send_xchar = wanpipe_tty_send_xchar;
+		serial_driver.set_termios = wanpipe_tty_set_termios;
+		serial_driver.stop = wanpipe_tty_stop;
+		serial_driver.start = wanpipe_tty_start;
+		serial_driver.hangup = wanpipe_tty_hangup;
+		serial_driver.break_ctl = wanpipe_tty_break;
+		serial_driver.wait_until_sent = wanpipe_tty_wait_until_sent;
+		serial_driver.read_proc = wanpipe_tty_read_proc;
+		
+		if (tty_register_driver(&serial_driver)){
+			printk(KERN_INFO "%s: Failed to register serial driver!\n",
+					card->devname);
+		}
+	}
+
+
+	/* The subsequent ports must comply to the initial configuration */
+	if (tty_driver_mode != card->u.c.async_mode){
+		printk(KERN_INFO "%s: Error: TTY Driver operation mode mismatch!\n",
+				card->devname);
+		printk(KERN_INFO "%s: The TTY driver is configured for %s!\n",
+				card->devname, tty_driver_mode ? "ASYNC" : "SYNC");
+		return -EINVAL;
+	}
+	
+	tty_init_cnt++;
+	
+	printk(KERN_INFO "%s: Initializing TTY %s Driver Minor %i\n",
+			card->devname,
+			tty_driver_mode ? "ASYNC" : "SYNC",
+			card->tty_minor);
+	
+	tty_card_map[card->tty_minor] = card;
+	state = &rs_table[card->tty_minor];
+	
+	state->magic = SSTATE_MAGIC;
+	state->line = 0;
+	state->type = PORT_UNKNOWN;
+	state->custom_divisor = 0;
+	state->close_delay = 5*HZ/10;
+	state->closing_wait = 30*HZ;
+	state->icount.cts = state->icount.dsr = 
+		state->icount.rng = state->icount.dcd = 0;
+	state->icount.rx = state->icount.tx = 0;
+	state->icount.frame = state->icount.parity = 0;
+	state->icount.overrun = state->icount.brk = 0;
+	state->irq = card->wandev.irq; 
+
+	INIT_WORK(&card->tty_work, tty_poll_work, (void*)card);
+	return 0;
+}
+
+
+MODULE_LICENSE("GPL");
+
+/****** End ****************************************************************/
diff --git a/drivers/net/wan/sdla_fr.c b/drivers/net/wan/sdla_fr.c
new file mode 100644
index 0000000..2efccb0
--- /dev/null
+++ b/drivers/net/wan/sdla_fr.c
@@ -0,0 +1,5068 @@
+/*****************************************************************************
+* sdla_fr.c	WANPIPE(tm) Multiprotocol WAN Link Driver. Frame relay module.
+*
+* Author(s):	Nenad Corbic  <ncorbic@sangoma.com>
+*		Gideon Hack
+*
+* Copyright:	(c) 1995-2001 Sangoma Technologies Inc.
+*
+*		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.
+* ============================================================================
+* Nov 23, 2000  Nenad Corbic    o Added support for 2.4.X kernels
+* Nov 15, 2000  David Rokavarg  
+*               Nenad Corbic	o Added frame relay bridging support.
+* 				  Original code from Mark Wells and Kristian Hoffmann has
+* 				  been integrated into the frame relay driver.
+* Nov 13, 2000  Nenad Corbic    o Added true interface type encoding option.
+* 				  Tcpdump doesn't support Frame Relay inteface
+* 				  types, to fix this true type option will set
+* 				  the interface type to RAW IP mode.
+* Nov 07, 2000  Nenad Corbic	o Added security features for UDP debugging:
+*                                 Deny all and specify allowed requests.
+* Nov 06, 2000  Nenad Corbic	o Wanpipe interfaces conform to raw packet interfaces.  
+*                                 Moved the if_header into the if_send() routine.
+*                                 The if_header() was breaking the libpcap 
+*                                 support. i.e. support for tcpdump, ethereal ...
+* Oct 12. 2000  Nenad Corbic    o Added error message in fr_configure
+* Jul 31, 2000  Nenad Corbic	o Fixed the Router UP Time.
+* Apr 28, 2000  Nenad Corbic	o Added the option to shutdown an interface
+*                                 when the channel gets disconnected.
+* Apr 28, 2000  Nenad Corbic 	o Added M.Grants patch: disallow duplicate
+*                                 interface setups. 
+* Apr 25, 2000  Nenad Corbic	o Added M.Grants patch: dynamically add/remove 
+*                                 new dlcis/interfaces.
+* Mar 23, 2000  Nenad Corbic 	o Improved task queue, bh handling.
+* Mar 16, 2000	Nenad Corbic	o Added Inverse ARP support
+* Mar 13, 2000  Nenad Corbic	o Added new socket API support.
+* Mar 06, 2000  Nenad Corbic	o Bug Fix: corrupted mbox recovery.
+* Feb 24, 2000  Nenad Corbic    o Fixed up FT1 UDP debugging problem.
+* Dev 15, 1999  Nenad Corbic    o Fixed up header files for 2.0.X kernels
+*
+* Nov 08, 1999  Nenad Corbic    o Combined all debug UDP calls into one function
+*                               o Removed the ARP support. This has to be done
+*                                 in the next version.
+*                               o Only a Node can implement NO signalling.
+*                                 Initialize DLCI during if_open() if NO 
+*				  signalling.
+*				o Took out IPX support, implement in next
+*                                 version
+* Sep 29, 1999  Nenad Corbic	o Added SMP support and changed the update
+*                                 function to use timer interrupt.
+*				o Fixed the CIR bug:  Set the value of BC
+*                                 to CIR when the CIR is enabled.
+*  				o Updated comments, statistics and tracing.
+* Jun 02, 1999	Gideon Hack	o Updated for S514 support.
+* Sep 18, 1998	Jaspreet Singh	o Updated for 2.2.X kernels.
+* Jul 31, 1998	Jaspreet Singh	o Removed wpf_poll routine.  The channel/DLCI 
+*				  status is received through an event interrupt.
+* Jul 08, 1998	David Fong	o Added inverse ARP support.
+* Mar 26, 1997	Jaspreet Singh	o Returning return codes for failed UDP cmds.
+* Jan 28, 1997	Jaspreet Singh  o Improved handling of inactive DLCIs.
+* Dec 30, 1997	Jaspreet Singh	o Replaced dev_tint() with mark_bh(NET_BH)
+* Dec 16, 1997	Jaspreet Singh	o Implemented Multiple IPX support.
+* Nov 26, 1997	Jaspreet Singh	o Improved load sharing with multiple boards
+*				o Added Cli() to protect enabling of interrupts
+*				  while polling is called.
+* Nov 24, 1997	Jaspreet Singh	o Added counters to avoid enabling of interrupts
+*				  when they have been disabled by another
+*				  interface or routine (eg. wpf_poll).
+* Nov 06, 1997	Jaspreet Singh	o Added INTR_TEST_MODE to avoid polling	
+*				  routine disable interrupts during interrupt
+*				  testing.
+* Oct 20, 1997  Jaspreet Singh  o Added hooks in for Router UP time.
+* Oct 16, 1997  Jaspreet Singh  o The critical flag is used to maintain flow
+*                                 control by avoiding RACE conditions.  The
+*                                 cli() and restore_flags() are taken out.
+*                                 The fr_channel structure is appended for 
+*                                 Driver Statistics.
+* Oct 15, 1997  Farhan Thawar    o updated if_send() and receive for IPX
+* Aug 29, 1997  Farhan Thawar    o Removed most of the cli() and sti()
+*                                o Abstracted the UDP management stuff
+*                                o Now use tbusy and critical more intelligently
+* Jul 21, 1997  Jaspreet Singh	 o Can configure T391, T392, N391, N392 & N393
+*				   through router.conf.
+*				 o Protected calls to sdla_peek() by adDing 
+*				   save_flags(), cli() and restore_flags().
+*				 o Added error message for Inactive DLCIs in
+*				   fr_event() and update_chan_state().
+*				 o Fixed freeing up of buffers using kfree() 
+*			           when packets are received.
+* Jul 07, 1997	Jaspreet Singh	 o Added configurable TTL for UDP packets 
+*				 o Added ability to discard multicast and 
+*				   broadcast source addressed packets
+* Jun 27, 1997	Jaspreet Singh	 o Added FT1 monitor capabilities 
+*				   New case (0x44) statement in if_send routine 
+*				   Added a global variable rCount to keep track
+*			 	   of FT1 status enabled on the board.
+* May 29, 1997	Jaspreet Singh	 o Fixed major Flow Control Problem
+*				   With multiple boards a problem was seen where
+*				   the second board always stopped transmitting
+*				   packet after running for a while. The code
+*				   got into a stage where the interrupts were
+*				   disabled and dev->tbusy was set to 1.
+*                  		   This caused the If_send() routine to get into
+*                                  the if clause for it(0,dev->tbusy) 
+*				   forever.
+*				   The code got into this stage due to an 
+*				   interrupt occurring within the if clause for 
+*				   set_bit(0,dev->tbusy).  Since an interrupt 
+*				   disables furhter transmit interrupt and 
+* 				   makes dev->tbusy = 0, this effect was undone 
+*                                  by making dev->tbusy = 1 in the if clause.
+*				   The Fix checks to see if Transmit interrupts
+*				   are disabled then do not make dev->tbusy = 1
+* 	   			   Introduced a global variable: int_occur and
+*				   added tx_int_enabled in the wan_device 
+*				   structure.	
+* May 21, 1997  Jaspreet Singh   o Fixed UDP Management for multiple
+*                                  boards.
+*
+* Apr 25, 1997  Farhan Thawar    o added UDP Management stuff
+*                                o fixed bug in if_send() and tx_intr() to
+*                                  sleep and wakeup all devices
+* Mar 11, 1997  Farhan Thawar   Version 3.1.1
+*                                o fixed (+1) bug in fr508_rx_intr()
+*                                o changed if_send() to return 0 if
+*                                  wandev.critical() is true
+*                                o free socket buffer in if_send() if
+*                                  returning 0 
+*                                o added tx_intr() routine
+* Jan 30, 1997	Gene Kozin	Version 3.1.0
+*				 o implemented exec() entry point
+*				 o fixed a bug causing driver configured as
+*				   a FR switch to be stuck in WAN_
+*				   mode
+* Jan 02, 1997	Gene Kozin	Initial version.
+*****************************************************************************/
+
+#include <linux/module.h>
+#include <linux/kernel.h>	/* printk(), and other useful stuff */
+#include <linux/stddef.h>	/* offsetof(), etc. */
+#include <linux/errno.h>	/* return codes */
+#include <linux/string.h>	/* inline memset(), etc. */
+#include <linux/slab.h>	/* kmalloc(), kfree() */
+#include <linux/wanrouter.h>	/* WAN router definitions */
+#include <linux/wanpipe.h>	/* WANPIPE common user API definitions */
+#include <linux/workqueue.h>
+#include <linux/if_arp.h>	/* ARPHRD_* defines */
+#include <asm/byteorder.h>	/* htons(), etc. */
+#include <asm/io.h>		/* for inb(), outb(), etc. */
+#include <linux/time.h>	 	/* for do_gettimeofday */	
+#include <linux/in.h>		/* sockaddr_in */
+#include <asm/errno.h>
+
+#include <linux/ip.h>
+#include <linux/if.h>
+
+#include <linux/if_wanpipe_common.h>	/* Wanpipe Socket */
+#include <linux/if_wanpipe.h>	
+
+#include <linux/sdla_fr.h>		/* frame relay firmware API definitions */
+
+#include <asm/uaccess.h>
+#include <linux/inetdevice.h>
+#include <linux/netdevice.h>
+
+#include <net/route.h>          	/* Dynamic Route Creation */
+#include <linux/etherdevice.h>		/* eth_type_trans() used for bridging */
+#include <linux/random.h>
+
+/****** Defines & Macros ****************************************************/
+
+#define	MAX_CMD_RETRY	10		/* max number of firmware retries */
+
+#define	FR_HEADER_LEN	8		/* max encapsulation header size */
+#define	FR_CHANNEL_MTU	1500		/* unfragmented logical channel MTU */
+
+/* Q.922 frame types */
+#define	Q922_UI		0x03		/* Unnumbered Info frame */
+#define	Q922_XID	0xAF		
+
+/* DLCI configured or not */
+#define DLCI_NOT_CONFIGURED	0x00
+#define DLCI_CONFIG_PENDING	0x01
+#define DLCI_CONFIGURED		0x02
+
+/* CIR enabled or not */
+#define CIR_ENABLED	0x00
+#define CIR_DISABLED	0x01
+
+#define FRAME_RELAY_API 1
+#define MAX_BH_BUFF	10
+
+/* For handle_IPXWAN() */
+#define CVHexToAscii(b) (((unsigned char)(b) > (unsigned char)9) ? ((unsigned char)'A' + ((unsigned char)(b) - (unsigned char)10)) : ((unsigned char)'0' + (unsigned char)(b)))
+ 
+/****** Data Structures *****************************************************/
+
+/* This is an extention of the 'struct device' we create for each network
+ * interface to keep the rest of channel-specific data.
+ */
+typedef struct fr_channel
+{
+	wanpipe_common_t common;
+	char name[WAN_IFNAME_SZ+1];	/* interface name, ASCIIZ */
+	unsigned dlci_configured  ;	/* check whether configured or not */
+	unsigned cir_status;		/* check whether CIR enabled or not */
+	unsigned dlci;			/* logical channel number */
+	unsigned cir;			/* committed information rate */
+	unsigned bc;			/* committed burst size */
+	unsigned be;			/* excess burst size */
+	unsigned mc;			/* multicast support on or off */
+	unsigned tx_int_status;		/* Transmit Interrupt Status */	
+	unsigned short pkt_length;	/* Packet Length */
+	unsigned long router_start_time;/* Router start time in seconds */
+	unsigned long tick_counter;	/* counter for transmit time out */
+	char dev_pending_devtint;	/* interface pending dev_tint() */
+	void *dlci_int_interface;	/* pointer to the DLCI Interface */ 
+	unsigned long IB_addr;		/* physical address of Interface Byte */
+	unsigned long state_tick;	/* time of the last state change */
+	unsigned char enable_IPX;	/* Enable/Disable the use of IPX */
+	unsigned long network_number;	/* Internal Network Number for IPX*/
+	sdla_t *card;			/* -> owner */
+	unsigned route_flag;		/* Add/Rem dest addr in route tables */
+	unsigned inarp;			/* Inverse Arp Request status */ 
+	long inarp_ready;		/* Ready to send requests */
+	int inarp_interval;		/* Time between InArp Requests */
+	unsigned long inarp_tick;	/* InArp jiffies tick counter */
+	long interface_down;		/* Bring interface down on disconnect */
+	struct net_device_stats ifstats;	/* interface statistics */
+	if_send_stat_t drvstats_if_send;
+        rx_intr_stat_t drvstats_rx_intr;
+        pipe_mgmt_stat_t drvstats_gen;
+	unsigned long router_up_time;
+
+	unsigned short transmit_length;
+	struct sk_buff *delay_skb;
+
+	bh_data_t *bh_head;	  	  /* Circular buffer for chdlc_bh */
+	unsigned long  tq_working;
+	volatile int  bh_write;
+	volatile int  bh_read;
+	atomic_t  bh_buff_used;
+
+	/* Polling task queue. Each interface
+         * has its own task queue, which is used
+         * to defer events from the interrupt */
+	struct work_struct fr_poll_work;
+	struct timer_list fr_arp_timer;
+
+	u32 ip_local;
+	u32 ip_remote;
+	long config_dlci;
+	long unconfig_dlci;
+
+	/* Whether this interface should be setup as a gateway.
+	 * Used by dynamic route setup code */
+	u8  gateway;
+
+	/* True interface type */
+	u8 true_if_encoding;
+	u8 fr_header[FR_HEADER_LEN];
+	char fr_header_len;
+
+} fr_channel_t;
+
+/* Route Flag options */
+#define NO_ROUTE	0x00
+#define ADD_ROUTE 	0x01
+#define ROUTE_ADDED	0x02
+#define REMOVE_ROUTE 	0x03
+#define ARP_REQ		0x04
+
+/* inarp options */
+#define INARP_NONE		0x00
+#define INARP_REQUEST		0x01
+#define INARP_CONFIGURED	0x02
+
+/* reasons for enabling the timer interrupt on the adapter */
+#define TMR_INT_ENABLED_UDP   	0x01
+#define TMR_INT_ENABLED_UPDATE 	0x02
+#define TMR_INT_ENABLED_ARP	0x04
+#define TMR_INT_ENABLED_UPDATE_STATE 	0x08
+#define TMR_INT_ENABLED_CONFIG	0x10
+#define TMR_INT_ENABLED_UNCONFIG	0x20
+
+
+typedef struct dlci_status
+{
+	unsigned short dlci	PACKED;
+	unsigned char state	PACKED;
+} dlci_status_t;
+
+typedef struct dlci_IB_mapping
+{
+	unsigned short dlci		PACKED;
+	unsigned long  addr_value	PACKED;
+} dlci_IB_mapping_t;
+
+/* This structure is used for DLCI list Tx interrupt mode.  It is used to
+   enable interrupt bit and set the packet length for transmission
+ */
+typedef struct fr_dlci_interface 
+{
+	unsigned char gen_interrupt	PACKED;
+	unsigned short packet_length	PACKED;
+	unsigned char reserved		PACKED;
+} fr_dlci_interface_t; 
+
+/* variable for keeping track of enabling/disabling FT1 monitor status */
+static int rCount = 0;
+
+extern void disable_irq(unsigned int);
+extern void enable_irq(unsigned int);
+
+/* variable for keeping track of number of interrupts generated during 
+ * interrupt test routine 
+ */
+static int Intr_test_counter;
+
+/****** Function Prototypes *************************************************/
+
+/* WAN link driver entry points. These are called by the WAN router module. */
+static int update(struct wan_device *wandev);
+static int new_if(struct wan_device *wandev, struct net_device *dev,
+		  wanif_conf_t *conf);
+static int del_if(struct wan_device *wandev, struct net_device *dev);
+static void disable_comm (sdla_t *card);
+
+/* WANPIPE-specific entry points */
+static int wpf_exec(struct sdla *card, void *u_cmd, void *u_data);
+
+/* Network device interface */
+static int if_init(struct net_device *dev);
+static int if_open(struct net_device *dev);
+static int if_close(struct net_device *dev);
+
+static void if_tx_timeout(struct net_device *dev);
+
+static int if_rebuild_hdr (struct sk_buff *skb);
+
+static int if_send(struct sk_buff *skb, struct net_device *dev);
+static int chk_bcast_mcast_addr(sdla_t *card, struct net_device* dev,
+                                struct sk_buff *skb);
+static struct net_device_stats *if_stats(struct net_device *dev);
+
+/* Interrupt handlers */
+static void fr_isr(sdla_t *card);
+static void rx_intr(sdla_t *card);
+static void tx_intr(sdla_t *card);
+static void timer_intr(sdla_t *card);
+static void spur_intr(sdla_t *card);
+
+/* Frame relay firmware interface functions */
+static int fr_read_version(sdla_t *card, char *str);
+static int fr_configure(sdla_t *card, fr_conf_t *conf);
+static int fr_dlci_configure(sdla_t *card, fr_dlc_conf_t *conf, unsigned dlci);
+static int fr_init_dlci (sdla_t *card, fr_channel_t *chan);
+static int fr_set_intr_mode (sdla_t *card, unsigned mode, unsigned mtu, unsigned short timeout);
+static int fr_comm_enable(sdla_t *card);
+static void fr_comm_disable(sdla_t *card);
+static int fr_get_err_stats(sdla_t *card);
+static int fr_get_stats(sdla_t *card);
+static int fr_add_dlci(sdla_t *card, int dlci);
+static int fr_activate_dlci(sdla_t *card, int dlci);
+static int fr_delete_dlci (sdla_t* card, int dlci);
+static int fr_issue_isf(sdla_t *card, int isf);
+static int fr_send(sdla_t *card, int dlci, unsigned char attr, int len,
+	void *buf);
+static int fr_send_data_header(sdla_t *card, int dlci, unsigned char attr, int len,
+	void *buf,unsigned char hdr_len);
+static unsigned int fr_send_hdr(sdla_t *card, int dlci, unsigned int offset);
+
+static int check_dlci_config (sdla_t *card, fr_channel_t *chan);
+static void initialize_rx_tx_buffers (sdla_t *card);
+
+
+/* Firmware asynchronous event handlers */
+static int fr_event(sdla_t *card, int event, fr_mbox_t *mbox);
+static int fr_modem_failure(sdla_t *card, fr_mbox_t *mbox);
+static int fr_dlci_change(sdla_t *card, fr_mbox_t *mbox);
+
+/* Miscellaneous functions */
+static int update_chan_state(struct net_device *dev);
+static void set_chan_state(struct net_device *dev, int state);
+static struct net_device *find_channel(sdla_t *card, unsigned dlci);
+static int is_tx_ready(sdla_t *card, fr_channel_t *chan);
+static unsigned int dec_to_uint(unsigned char *str, int len);
+static int reply_udp( unsigned char *data, unsigned int mbox_len );
+
+static int intr_test( sdla_t* card );
+static void init_chan_statistics( fr_channel_t* chan );
+static void init_global_statistics( sdla_t* card );
+static void read_DLCI_IB_mapping( sdla_t* card, fr_channel_t* chan );
+static int setup_for_delayed_transmit(struct net_device* dev,
+				      struct sk_buff *skb);
+
+struct net_device *move_dev_to_next(sdla_t *card, struct net_device *dev);
+static int check_tx_status(sdla_t *card, struct net_device *dev);
+
+/* Frame Relay Socket API */
+static void trigger_fr_bh (fr_channel_t *);
+static void fr_bh(struct net_device *dev);
+static int fr_bh_cleanup(struct net_device *dev);
+static int bh_enqueue(struct net_device *dev, struct sk_buff *skb);
+
+static void trigger_fr_poll(struct net_device *dev);
+static void fr_poll(struct net_device *dev);
+//static void add_gateway(struct net_device *dev);
+
+static void trigger_unconfig_fr(struct net_device *dev);
+static void unconfig_fr (sdla_t *);
+
+static void trigger_config_fr (sdla_t *);
+static void config_fr (sdla_t *);
+
+
+/* Inverse ARP and Dynamic routing functions */
+int process_ARP(arphdr_1490_t *ArpPacket, sdla_t *card, struct net_device *dev);
+int is_arp(void *buf);
+int send_inarp_request(sdla_t *card, struct net_device *dev);
+
+static void trigger_fr_arp(struct net_device *dev);
+static void fr_arp (unsigned long data);
+
+
+/* Udp management functions */
+static int process_udp_mgmt_pkt(sdla_t *card);
+static int udp_pkt_type( struct sk_buff *skb, sdla_t *card );
+static int store_udp_mgmt_pkt(int udp_type, char udp_pkt_src, sdla_t* card,
+                                struct sk_buff *skb, int dlci);
+
+/* IPX functions */
+static void switch_net_numbers(unsigned char *sendpacket,
+	unsigned long network_number, unsigned char incoming);
+
+static int handle_IPXWAN(unsigned char *sendpacket, char *devname,
+	unsigned char enable_IPX, unsigned long network_number);
+
+/* Lock Functions: SMP supported */
+void 	s508_s514_unlock(sdla_t *card, unsigned long *smp_flags);
+void 	s508_s514_lock(sdla_t *card, unsigned long *smp_flags);
+
+unsigned short calc_checksum (char *, int);
+static int setup_fr_header(struct sk_buff** skb,
+			   struct net_device* dev, char op_mode);
+
+
+/****** Public Functions ****************************************************/
+
+/*============================================================================
+ * Frame relay protocol initialization routine.
+ *
+ * This routine is called by the main WANPIPE module during setup.  At this
+ * point adapter is completely initialized and firmware is running.
+ *  o read firmware version (to make sure it's alive)
+ *  o configure adapter
+ *  o initialize protocol-specific fields of the adapter data space.
+ *
+ * Return:	0	o.k.
+ *		< 0	failure.
+ */
+int wpf_init(sdla_t *card, wandev_conf_t *conf)
+{
+
+	int err;
+	fr508_flags_t* flags;
+
+	union
+	{
+		char str[80];
+		fr_conf_t cfg;
+	} u;
+
+	fr_buf_info_t* buf_info;
+	int i;
+
+
+	printk(KERN_INFO "\n");
+
+	/* Verify configuration ID */
+	if (conf->config_id != WANCONFIG_FR) {
+		
+		printk(KERN_INFO "%s: invalid configuration ID %u!\n",
+			card->devname, conf->config_id);
+		return -EINVAL;
+	
+	}
+
+	/* Initialize protocol-specific fields of adapter data space */
+	switch (card->hw.fwid) {
+	
+		case SFID_FR508:
+			card->mbox  = (void*)(card->hw.dpmbase + 
+					FR508_MBOX_OFFS);
+			card->flags = (void*)(card->hw.dpmbase + 
+					FR508_FLAG_OFFS);
+			if(card->hw.type == SDLA_S514) {
+				card->mbox += FR_MB_VECTOR;
+                                card->flags += FR_MB_VECTOR;
+			}
+                        card->isr = &fr_isr;
+			break;
+
+		default:
+			return -EINVAL;
+	}
+
+	flags = card->flags;
+
+	/* Read firmware version.  Note that when adapter initializes, it
+	 * clears the mailbox, so it may appear that the first command was
+	 * executed successfully when in fact it was merely erased. To work
+	 * around this, we execute the first command twice.
+	 */
+
+	if (fr_read_version(card, NULL) || fr_read_version(card, u.str))
+		return -EIO;
+
+	printk(KERN_INFO "%s: running frame relay firmware v%s\n",
+		card->devname, u.str);
+
+	/* Adjust configuration */
+	conf->mtu += FR_HEADER_LEN;
+	conf->mtu = (conf->mtu >= MIN_LGTH_FR_DATA_CFG) ?
+			min_t(unsigned int, conf->mtu, FR_MAX_NO_DATA_BYTES_IN_FRAME) :
+                        FR_CHANNEL_MTU + FR_HEADER_LEN;
+     
+	conf->bps = min_t(unsigned int, conf->bps, 2048000);
+
+	/* Initialze the configuration structure sent to the board to zero */
+	memset(&u.cfg, 0, sizeof(u.cfg));
+
+	memset(card->u.f.dlci_to_dev_map, 0, sizeof(card->u.f.dlci_to_dev_map));
+ 	
+	/* Configure adapter firmware */
+
+	u.cfg.mtu	= conf->mtu;
+	u.cfg.kbps	= conf->bps / 1000;
+
+    	u.cfg.cir_fwd = u.cfg.cir_bwd = 16;
+        u.cfg.bc_fwd  = u.cfg.bc_bwd = 16;
+	
+	u.cfg.options	= 0x0000;
+	printk(KERN_INFO "%s: Global CIR enabled by Default\n", card->devname);
+	
+	switch (conf->u.fr.signalling) {
+
+		case WANOPT_FR_ANSI:
+			u.cfg.options = 0x0000; 
+			break;		
+	
+		case WANOPT_FR_Q933:	
+			u.cfg.options |= 0x0200; 
+			break;
+	
+		case WANOPT_FR_LMI:	
+			u.cfg.options |= 0x0400; 
+			break;
+
+		case WANOPT_NO:
+			u.cfg.options |= 0x0800; 
+			break;
+		default:
+			printk(KERN_INFO "%s: Illegal Signalling option\n",
+					card->wandev.name);
+			return -EINVAL;
+	}
+
+
+	card->wandev.signalling = conf->u.fr.signalling;
+
+	if (conf->station == WANOPT_CPE) {
+
+
+		if (conf->u.fr.signalling == WANOPT_NO){
+			printk(KERN_INFO 
+				"%s: ERROR - For NO signalling, station must be set to Node!",
+				 	 card->devname);
+			return -EINVAL;
+		}
+
+		u.cfg.station = 0;
+		u.cfg.options |= 0x8000;	/* auto config DLCI */
+		card->u.f.dlci_num  = 0;
+	
+	} else {
+
+		u.cfg.station = 1;	/* switch emulation mode */
+
+		/* For switch emulation we have to create a list of dlci(s)
+		 * that will be sent to be global SET_DLCI_CONFIGURATION 
+		 * command in fr_configure() routine. 
+		 */
+
+		card->u.f.dlci_num  = min_t(unsigned int, max_t(unsigned int, conf->u.fr.dlci_num, 1), 100);
+	
+		for ( i = 0; i < card->u.f.dlci_num; i++) {
+
+			card->u.f.node_dlci[i] = (unsigned short) 
+				conf->u.fr.dlci[i] ? conf->u.fr.dlci[i] : 16;
+	
+		}
+	}
+
+	if (conf->clocking == WANOPT_INTERNAL)
+		u.cfg.port |= 0x0001;
+
+	if (conf->interface == WANOPT_RS232)
+		u.cfg.port |= 0x0002;
+
+	if (conf->u.fr.t391)
+		u.cfg.t391 = min_t(unsigned int, conf->u.fr.t391, 30);
+	else
+		u.cfg.t391 = 5;
+
+	if (conf->u.fr.t392)
+		u.cfg.t392 = min_t(unsigned int, conf->u.fr.t392, 30);
+	else
+		u.cfg.t392 = 15;
+
+	if (conf->u.fr.n391)
+		u.cfg.n391 = min_t(unsigned int, conf->u.fr.n391, 255);
+	else
+		u.cfg.n391 = 2;
+
+	if (conf->u.fr.n392)
+		u.cfg.n392 = min_t(unsigned int, conf->u.fr.n392, 10);
+	else
+		u.cfg.n392 = 3;	
+
+	if (conf->u.fr.n393)
+		u.cfg.n393 = min_t(unsigned int, conf->u.fr.n393, 10);
+	else
+		u.cfg.n393 = 4;
+
+	if (fr_configure(card, &u.cfg))
+		return -EIO;
+
+	if (card->hw.type == SDLA_S514) {
+	
+                buf_info = (void*)(card->hw.dpmbase + FR_MB_VECTOR +
+			FR508_RXBC_OFFS);
+
+                card->rxmb = (void*)(buf_info->rse_next + card->hw.dpmbase);
+
+                card->u.f.rxmb_base =
+                        (void*)(buf_info->rse_base + card->hw.dpmbase); 
+
+                card->u.f.rxmb_last =
+                        (void*)(buf_info->rse_base +
+                        (buf_info->rse_num - 1) * sizeof(fr_rx_buf_ctl_t) +
+                        card->hw.dpmbase);
+	}else{	
+		buf_info = (void*)(card->hw.dpmbase + FR508_RXBC_OFFS);
+
+		card->rxmb = (void*)(buf_info->rse_next -
+			FR_MB_VECTOR + card->hw.dpmbase);
+		
+		card->u.f.rxmb_base =
+			(void*)(buf_info->rse_base -
+			FR_MB_VECTOR + card->hw.dpmbase);
+		
+		card->u.f.rxmb_last =
+			(void*)(buf_info->rse_base +
+			(buf_info->rse_num - 1) * sizeof(fr_rx_buf_ctl_t) -
+			FR_MB_VECTOR + card->hw.dpmbase);
+	}
+
+	card->u.f.rx_base = buf_info->buf_base;
+	card->u.f.rx_top  = buf_info->buf_top;
+
+	card->u.f.tx_interrupts_pending = 0;
+
+	card->wandev.mtu	= conf->mtu;
+	card->wandev.bps	= conf->bps;
+	card->wandev.interface	= conf->interface;
+	card->wandev.clocking	= conf->clocking;
+	card->wandev.station	= conf->station;
+	card->poll		= NULL; 
+	card->exec		= &wpf_exec;
+	card->wandev.update	= &update;
+	card->wandev.new_if	= &new_if;
+	card->wandev.del_if	= &del_if;
+	card->wandev.state	= WAN_DISCONNECTED;
+	card->wandev.ttl	= conf->ttl;
+        card->wandev.udp_port 	= conf->udp_port;       
+	card->disable_comm	= &disable_comm;	
+	card->u.f.arp_dev 	= NULL;
+
+	/* Intialize global statistics for a card */
+	init_global_statistics( card );
+
+        card->TracingEnabled          = 0;
+
+	/* Interrupt Test */
+	Intr_test_counter = 0;
+	card->intr_mode = INTR_TEST_MODE;
+	err = intr_test( card );
+
+	printk(KERN_INFO "%s: End of Interrupt Test rc=0x%x  count=%i\n",
+			card->devname,err,Intr_test_counter); 
+	
+	if (err || (Intr_test_counter < MAX_INTR_TEST_COUNTER)) {
+		printk(KERN_ERR "%s: Interrupt Test Failed, Counter: %i\n", 
+			card->devname, Intr_test_counter);
+		printk(KERN_ERR "Please choose another interrupt\n");
+		err = -EIO;
+		return err;
+	}
+
+	printk(KERN_INFO "%s: Interrupt Test Passed, Counter: %i\n",
+			card->devname, Intr_test_counter);
+
+
+	/* Apr 28 2000. Nenad Corbic
+	 * Enable commnunications here, not in if_open or new_if, since
+         * interfaces come down when the link is disconnected. 
+         */
+	 
+	/* If you enable comms and then set ints, you get a Tx int as you
+	 * perform the SET_INT_TRIGGERS command. So, we only set int
+	 * triggers and then adjust the interrupt mask (to disable Tx ints)
+	 * before enabling comms. 
+	 */	
+        if (fr_set_intr_mode(card, (FR_INTR_RXRDY | FR_INTR_TXRDY |
+		FR_INTR_DLC | FR_INTR_TIMER | FR_INTR_TX_MULT_DLCIs) ,
+		card->wandev.mtu, 0)) {
+		return -EIO;
+	}
+
+	flags->imask &= ~(FR_INTR_TXRDY | FR_INTR_TIMER);
+ 
+	if (fr_comm_enable(card)) {
+		return -EIO;
+	}	
+	wanpipe_set_state(card, WAN_CONNECTED);
+	spin_lock_init(&card->u.f.if_send_lock);
+	
+	printk(KERN_INFO "\n");
+
+        return 0;
+}
+
+/******* WAN Device Driver Entry Points *************************************/
+
+/*============================================================================
+ * Update device status & statistics.
+ */
+static int update(struct wan_device* wandev)
+{
+	volatile sdla_t* card;
+	unsigned long timeout;
+	fr508_flags_t* flags;
+
+	/* sanity checks */
+	if ((wandev == NULL) || (wandev->private == NULL))
+		return -EFAULT;
+
+	if (wandev->state == WAN_UNCONFIGURED)
+		return -ENODEV;
+
+	card = wandev->private;
+	flags = card->flags;
+
+
+	card->u.f.update_comms_stats = 1;
+	card->u.f.timer_int_enabled |= TMR_INT_ENABLED_UPDATE;
+	flags->imask |= FR_INTR_TIMER;
+       	timeout = jiffies;
+       	for(;;) {
+		if(card->u.f.update_comms_stats == 0)
+			break;
+                if ((jiffies - timeout) > (1 * HZ)){
+    			card->u.f.update_comms_stats = 0;
+ 			return -EAGAIN;
+		}
+        }
+
+	return 0;
+}
+
+/*============================================================================
+ * Create new logical channel.
+ * This routine is called by the router when ROUTER_IFNEW IOCTL is being
+ * handled.
+ * o parse media- and hardware-specific configuration
+ * o make sure that a new channel can be created
+ * o allocate resources, if necessary
+ * o prepare network device structure for registaration.
+ *
+ * Return:	0	o.k.
+ *		< 0	failure (channel will not be created)
+ */
+static int new_if(struct wan_device* wandev, struct net_device* dev,
+		  wanif_conf_t* conf)
+{
+	sdla_t* card = wandev->private;
+	fr_channel_t* chan;
+	int dlci = 0;
+	int err = 0;
+
+	
+	if ((conf->name[0] == '\0') || (strlen(conf->name) > WAN_IFNAME_SZ)) {
+		
+		printk(KERN_INFO "%s: Invalid interface name!\n",
+			card->devname);
+		return -EINVAL;
+	}
+
+	/* allocate and initialize private data */
+	chan = kmalloc(sizeof(fr_channel_t), GFP_KERNEL);
+
+	if (chan == NULL)
+		return -ENOMEM;
+
+	memset(chan, 0, sizeof(fr_channel_t));
+	strcpy(chan->name, conf->name);
+	chan->card = card;
+
+	/* verify media address */
+	if (is_digit(conf->addr[0])) {
+
+		dlci = dec_to_uint(conf->addr, 0);
+
+		if (dlci && (dlci <= HIGHEST_VALID_DLCI)) {
+		
+			chan->dlci = dlci;
+		
+		} else {
+		
+			printk(KERN_ERR
+				"%s: Invalid DLCI %u on interface %s!\n",
+				wandev->name, dlci, chan->name);
+			err = -EINVAL;
+		}
+
+	} else {
+		printk(KERN_ERR
+			"%s: Invalid media address on interface %s!\n",
+			wandev->name, chan->name);
+		err = -EINVAL;
+	}
+
+	if ((chan->true_if_encoding = conf->true_if_encoding) == WANOPT_YES){
+		printk(KERN_INFO 
+			"%s: Enabling, true interface type encoding.\n",
+			card->devname);
+	}
+	
+
+
+    /* Setup wanpipe as a router (WANPIPE) even if it is
+	 * a bridged DLCI, or as an API 
+	 */
+        if (strcmp(conf->usedby, "WANPIPE")  == 0  || 
+	    strcmp(conf->usedby, "BRIDGE")   == 0  ||
+	    strcmp(conf->usedby, "BRIDGE_N") == 0){
+		
+		if(strcmp(conf->usedby, "WANPIPE") == 0){
+			chan->common.usedby = WANPIPE;
+			
+	                printk(KERN_INFO "%s: Running in WANPIPE mode.\n", 
+					card->devname);
+			
+		}else if(strcmp(conf->usedby, "BRIDGE") == 0){
+			
+			chan->common.usedby = BRIDGE;
+			
+			printk(KERN_INFO "%s: Running in WANPIPE (BRIDGE) mode.\n", 
+					card->devname);
+		}else if( strcmp(conf->usedby, "BRIDGE_N") == 0 ){
+			
+			chan->common.usedby = BRIDGE_NODE;
+		
+			printk(KERN_INFO "%s: Running in WANPIPE (BRIDGE_NODE) mode.\n", 
+					card->devname);
+		}
+
+		if (!err){
+			/* Dynamic interface configuration option.
+			 * On disconnect, if the options is selected,
+			 * the interface will be brought down */
+			if (conf->if_down == WANOPT_YES){ 
+				set_bit(DYN_OPT_ON,&chan->interface_down);
+				printk(KERN_INFO 
+				    "%s: Dynamic interface configuration enabled.\n",
+					card->devname);
+			}
+		}
+
+        } else if(strcmp(conf->usedby, "API") == 0){
+
+                chan->common.usedby = API;
+                printk(KERN_INFO "%s: Running in API mode.\n",
+			wandev->name);
+        }
+
+	if (err) {
+		
+		kfree(chan);
+		return err;
+	}
+
+	/* place cir,be,bc and other channel specific information into the
+	 * chan structure 
+         */
+	if (conf->cir) {
+
+		chan->cir = max_t(unsigned int, 1,
+				min_t(unsigned int, conf->cir, 512));
+		chan->cir_status = CIR_ENABLED; 
+
+		
+		/* If CIR is enabled, force BC to equal CIR
+                 * this solves number of potential problems if CIR is 
+                 * set and BC is not 
+		 */
+		chan->bc = chan->cir;
+
+		if (conf->be){
+			chan->be = max_t(unsigned int,
+				       0, min_t(unsigned int, conf->be, 511));
+		}else{	
+			conf->be = 0;
+		}
+
+		printk (KERN_INFO "%s: CIR enabled for DLCI %i \n",
+				wandev->name,chan->dlci);
+		printk (KERN_INFO "%s:     CIR = %i ; BC = %i ; BE = %i\n",
+				wandev->name,chan->cir,chan->bc,chan->be);
+
+
+	}else{
+		chan->cir_status = CIR_DISABLED;
+		printk (KERN_INFO "%s: CIR disabled for DLCI %i\n",
+				wandev->name,chan->dlci);
+	}
+
+	chan->mc = conf->mc;
+
+	if (conf->inarp == WANOPT_YES){
+		printk(KERN_INFO "%s: Inverse ARP Support Enabled\n",card->devname);
+		chan->inarp = conf->inarp ? INARP_REQUEST : INARP_NONE;
+		chan->inarp_interval = conf->inarp_interval ? conf->inarp_interval : 10;
+	}else{
+		printk(KERN_INFO "%s: Inverse ARP Support Disabled\n",card->devname);
+		chan->inarp = INARP_NONE;
+		chan->inarp_interval = 10;
+	}
+
+
+	chan->dlci_configured = DLCI_NOT_CONFIGURED;	
+
+
+	/*FIXME: IPX disabled in this WANPIPE version */
+	if (conf->enable_IPX == WANOPT_YES){
+		printk(KERN_INFO "%s: ERROR - This version of WANPIPE doesn't support IPX\n",
+				card->devname);
+		kfree(chan);
+		return -EINVAL;
+	}else{
+		chan->enable_IPX = WANOPT_NO;
+	}	
+
+	if (conf->network_number){
+		chan->network_number = conf->network_number;
+	}else{
+		chan->network_number = 0xDEADBEEF;
+	}
+
+	chan->route_flag = NO_ROUTE;
+	
+	init_chan_statistics(chan);
+
+	chan->transmit_length = 0;
+
+	/* prepare network device data space for registration */
+	strcpy(dev->name,chan->name);
+	
+	dev->init = &if_init;
+	dev->priv = chan;
+
+	/* Initialize FR Polling Task Queue
+         * We need a poll routine for each network
+         * interface. 
+         */
+	INIT_WORK(&chan->fr_poll_work, (void *)fr_poll, dev);
+
+	init_timer(&chan->fr_arp_timer);
+	chan->fr_arp_timer.data=(unsigned long)dev;
+	chan->fr_arp_timer.function = fr_arp;
+
+	wandev->new_if_cnt++;
+
+	/* Tells us that if this interface is a
+         * gateway or not */
+	if ((chan->gateway = conf->gateway) == WANOPT_YES){
+		printk(KERN_INFO "%s: Interface %s is set as a gateway.\n",
+			card->devname,dev->name);
+	}
+
+	/* M. Grant Patch Apr 28 2000 
+         * Disallow duplicate dlci configurations. */
+	if (card->u.f.dlci_to_dev_map[chan->dlci] != NULL) {
+		kfree(chan);
+		return -EBUSY;
+	}
+
+	/* Configure this dlci at a later date, when
+         * the interface comes up. i.e. when if_open() 
+         * executes */
+	set_bit(0,&chan->config_dlci);
+	
+	printk(KERN_INFO "\n");
+
+	return 0;
+}
+
+/*============================================================================
+ * Delete logical channel.
+ */
+static int del_if(struct wan_device* wandev, struct net_device* dev)
+{
+	fr_channel_t* chan = dev->priv;
+	unsigned long smp_flags=0;
+
+	/* This interface is dead, make sure the 
+	 * ARP timer is stopped */
+	del_timer(&chan->fr_arp_timer);
+	
+	/* If we are a NODE, we must unconfigure this DLCI
+	 * Trigger an unconfigure command that will
+	 * be executed in timer interrupt. We must wait
+	 * for the command to complete. */
+	trigger_unconfig_fr(dev);
+
+	lock_adapter_irq(&wandev->lock, &smp_flags);
+	wandev->new_if_cnt--;
+	unlock_adapter_irq(&wandev->lock, &smp_flags);
+
+	return 0;
+}
+
+
+/*=====================================================================
+ * disable_comm
+ *
+ * Description:
+ *	Disable communications.
+ * 	This code runs in shutdown (sdlamain.c)
+ *      under critical flag. Therefore it is not
+ *      necessary to set a critical flag here 
+ *
+ * Usage:
+ * 	Commnunications are disabled only on a card
+ *      shutdown.
+ */
+
+static void disable_comm (sdla_t *card)
+{
+	printk(KERN_INFO "%s: Disabling Communications!\n",
+			card->devname);
+	fr_comm_disable(card);
+}
+
+/****** WANPIPE-specific entry points ***************************************/
+
+/*============================================================================
+ * Execute adapter interface command.
+ */
+static int wpf_exec (struct sdla* card, void* u_cmd, void* u_data)
+{
+	fr_mbox_t* mbox = card->mbox;
+	int retry = MAX_CMD_RETRY;
+	int err, len;
+	fr_cmd_t cmd;
+
+	if(copy_from_user((void*)&cmd, u_cmd, sizeof(cmd)))
+		return -EFAULT;
+	
+	/* execute command */
+	do
+	{
+		memcpy(&mbox->cmd, &cmd, sizeof(cmd));
+		
+		if (cmd.length){
+			if( copy_from_user((void*)&mbox->data, u_data, cmd.length))
+				return -EFAULT;
+		}
+		
+		if (sdla_exec(mbox))
+			err = mbox->cmd.result;
+
+		else return -EIO;
+	
+	} while (err && retry-- && fr_event(card, err, mbox));
+
+	/* return result */
+	if (copy_to_user(u_cmd, (void*)&mbox->cmd, sizeof(fr_cmd_t)))
+		return -EFAULT;
+
+	len = mbox->cmd.length;
+
+	if (len && u_data && !copy_to_user(u_data, (void*)&mbox->data, len))
+		return -EFAULT;
+	return 0;
+}
+
+/****** Network Device Interface ********************************************/
+
+/*============================================================================
+ * Initialize Linux network interface.
+ *
+ * This routine is called only once for each interface, during Linux network
+ * interface registration.  Returning anything but zero will fail interface
+ * registration.
+ */
+static int if_init(struct net_device* dev)
+{
+	fr_channel_t* chan = dev->priv;
+	sdla_t* card = chan->card;
+	struct wan_device* wandev = &card->wandev;
+
+	/* Initialize device driver entry points */
+	dev->open		= &if_open;
+	dev->stop		= &if_close;
+	dev->hard_header	= NULL;
+	dev->rebuild_header	= &if_rebuild_hdr;
+	dev->hard_start_xmit	= &if_send;
+	dev->get_stats		= &if_stats;
+	dev->tx_timeout		= &if_tx_timeout;
+	dev->watchdog_timeo	= TX_TIMEOUT;
+	
+	if (chan->common.usedby == WANPIPE || chan->common.usedby == API){
+
+		/* Initialize media-specific parameters */
+		if (chan->true_if_encoding){
+			dev->type 		= ARPHRD_DLCI;  /* This breaks tcpdump */
+		}else{
+			dev->type		= ARPHRD_PPP; 	/* ARP h/w type */
+		}
+		
+		dev->flags		|= IFF_POINTOPOINT;
+		dev->flags		|= IFF_NOARP;
+
+		/* Enable Multicast addressing */
+		if (chan->mc == WANOPT_YES){
+			dev->flags 	|= IFF_MULTICAST;
+		}
+
+		dev->mtu		= wandev->mtu - FR_HEADER_LEN;
+		/* For an API, the maximum number of bytes that the stack will pass
+		   to the driver is (dev->mtu + dev->hard_header_len). So, adjust the
+		   mtu so that a frame of maximum size can be transmitted by the API. 
+		*/
+		if(chan->common.usedby == API) {
+			dev->mtu += (sizeof(api_tx_hdr_t) - FR_HEADER_LEN);
+		}
+		
+		dev->hard_header_len	= FR_HEADER_LEN;/* media header length */
+		dev->addr_len		= 2; 		/* hardware address length */
+		*(unsigned short*)dev->dev_addr = htons(chan->dlci);
+
+		/* Set transmit buffer queue length */
+        	dev->tx_queue_len = 100;
+
+	}else{
+
+		/* Setup the interface for Bridging */
+		int hw_addr=0;
+		ether_setup(dev);
+		
+		/* Use a random number to generate the MAC address */
+		memcpy(dev->dev_addr, "\xFE\xFC\x00\x00\x00\x00", 6);
+		get_random_bytes(&hw_addr, sizeof(hw_addr));
+		*(int *)(dev->dev_addr + 2) += hw_addr;
+	}
+		
+	/* Initialize hardware parameters (just for reference) */
+	dev->irq	= wandev->irq;
+	dev->dma	= wandev->dma;
+	dev->base_addr	= wandev->ioport;
+	dev->mem_start	= wandev->maddr;
+	dev->mem_end	= wandev->maddr + wandev->msize - 1;
+	SET_MODULE_OWNER(dev);
+
+	return 0;
+}
+
+/*============================================================================
+ * Open network interface.
+ * o if this is the first open, then enable communications and interrupts.
+ * o prevent module from unloading by incrementing use count
+ *
+ * Return 0 if O.k. or errno.
+ */
+static int if_open(struct net_device* dev)
+{
+	fr_channel_t* chan = dev->priv;
+	sdla_t* card = chan->card;
+	int err = 0;
+	struct timeval tv;
+
+	if (netif_running(dev))
+		return -EBUSY;
+	
+	/* Initialize the task queue */
+	chan->tq_working=0;
+
+	INIT_WORK(&chan->common.wanpipe_work, (void *)fr_bh, dev);
+
+	/* Allocate and initialize BH circular buffer */
+	chan->bh_head = kmalloc((sizeof(bh_data_t)*MAX_BH_BUFF),GFP_ATOMIC);
+	memset(chan->bh_head,0,(sizeof(bh_data_t)*MAX_BH_BUFF));
+	atomic_set(&chan->bh_buff_used, 0);
+
+	netif_start_queue(dev);
+
+	wanpipe_open(card);
+	do_gettimeofday( &tv );
+	chan->router_start_time = tv.tv_sec;
+	
+	if (test_bit(0,&chan->config_dlci)){
+		trigger_config_fr (card);
+	}else if (chan->inarp == INARP_REQUEST){
+		trigger_fr_arp(dev);
+	}
+	
+	return err;
+}
+
+/*============================================================================
+ * Close network interface.
+ * o if this is the last open, then disable communications and interrupts.
+ * o reset flags.
+ */
+static int if_close(struct net_device* dev)
+{
+	fr_channel_t* chan = dev->priv;
+	sdla_t* card = chan->card;
+
+	if (chan->inarp == INARP_CONFIGURED) {
+		chan->inarp = INARP_REQUEST;
+	}
+
+	netif_stop_queue(dev);
+	wanpipe_close(card);
+
+	return 0;
+}
+
+/*============================================================================
+ * Re-build media header.
+ *
+ * Return:	1	physical address resolved.
+ *		0	physical address not resolved
+ */
+static int if_rebuild_hdr (struct sk_buff* skb)
+{
+	struct net_device *dev = skb->dev;
+	fr_channel_t* chan = dev->priv;
+	sdla_t* card = chan->card;
+
+	printk(KERN_INFO "%s: rebuild_header() called for interface %s!\n",
+		card->devname, dev->name);
+	return 1;
+}
+
+/*============================================================================
+ * Handle transmit timeout event from netif watchdog
+ */
+static void if_tx_timeout(struct net_device *dev)
+{
+    	fr_channel_t* chan = dev->priv;
+	sdla_t *card = chan->card;
+
+	/* If our device stays busy for at least 5 seconds then we will
+	 * kick start the device by making dev->tbusy = 0.  We expect
+	 * that our device never stays busy more than 5 seconds. So this                 
+	 * is only used as a last resort.
+	 */
+
+	chan->drvstats_if_send.if_send_tbusy++;
+	++chan->ifstats.collisions;
+
+	printk (KERN_INFO "%s: Transmit timed out on %s\n", 
+			card->devname, dev->name);
+	chan->drvstats_if_send.if_send_tbusy_timeout++;
+	netif_wake_queue (dev);
+
+}
+
+
+/*============================================================================
+ * Send a packet on a network interface.
+ * o set tbusy flag (marks start of the transmission) to block a timer-based
+ *   transmit from overlapping.
+ * o set critical flag when accessing board.
+ * o check link state. If link is not up, then drop the packet.
+ * o check channel status. If it's down then initiate a call.
+ * o pass a packet to corresponding WAN device.
+ * o free socket buffer
+ *
+ * Return:	0	complete (socket buffer must be freed)
+ *		non-0	packet may be re-transmitted (tbusy must be set)
+ *
+ * Notes:
+ * 1. This routine is called either by the protocol stack or by the "net
+ *    bottom half" (with interrupts enabled).
+ * 
+ * 2. Using netif_start_queue() and netif_stop_queue()
+ *    will inhibit further transmit requests from the protocol stack 
+ *    and can be used for flow control with protocol layer.
+ */
+static int if_send(struct sk_buff* skb, struct net_device* dev)
+{
+    	fr_channel_t* chan = dev->priv;
+    	sdla_t* card = chan->card;
+        int err;
+    	unsigned char *sendpacket;
+    	fr508_flags_t* adptr_flags = card->flags;
+	int udp_type;
+	long delay_tx_queued = 0;
+	unsigned long smp_flags=0;
+	unsigned char attr = 0;
+
+	chan->drvstats_if_send.if_send_entry++;
+
+	netif_stop_queue(dev);
+	
+        if (skb == NULL) {             
+		/* if we get here, some higher layer thinks we've missed an
+		 * tx-done interrupt.
+		 */
+		printk(KERN_INFO "%s: interface %s got kicked!\n", 
+			card->devname, dev->name);
+		chan->drvstats_if_send.if_send_skb_null ++;
+
+		netif_wake_queue(dev);
+		return 0;
+	}
+
+	/* If a peripheral task is running just drop packets */
+	if (test_bit(PERI_CRIT, &card->wandev.critical)){
+		
+		printk(KERN_INFO "%s: Critical in if_send(): Peripheral running!\n",
+				card->devname);
+		
+		dev_kfree_skb_any(skb);
+		netif_start_queue(dev);
+		return 0;
+	}
+
+	/* We must set the 'tbusy' flag if we already have a packet queued for
+	   transmission in the transmit interrupt handler. However, we must
+	   ensure that the transmit interrupt does not reset the 'tbusy' flag
+	   just before we set it, as this will result in a "transmit timeout".
+	*/
+	set_bit(SEND_TXIRQ_CRIT, (void*)&card->wandev.critical);
+        if(chan->transmit_length) {
+		netif_stop_queue(dev);
+		chan->tick_counter = jiffies;
+ 		clear_bit(SEND_TXIRQ_CRIT, (void*)&card->wandev.critical);
+		return 1;
+	}
+       	clear_bit(SEND_TXIRQ_CRIT, (void*)&card->wandev.critical);
+ 
+	/* Move the if_header() code to here. By inserting frame
+	 * relay header in if_header() we would break the
+	 * tcpdump and other packet sniffers */
+	chan->fr_header_len = setup_fr_header(&skb,dev,chan->common.usedby);
+	if (chan->fr_header_len < 0 ){
+		++chan->ifstats.tx_dropped;
+		++card->wandev.stats.tx_dropped;
+		
+		dev_kfree_skb_any(skb);
+		netif_start_queue(dev);	
+		return 0;
+	}
+
+	sendpacket = skb->data;
+
+	udp_type = udp_pkt_type(skb, card);
+
+        if(udp_type != UDP_INVALID_TYPE) {
+		if(store_udp_mgmt_pkt(udp_type, UDP_PKT_FRM_STACK, card, skb,
+                        chan->dlci)) {
+                        adptr_flags->imask |= FR_INTR_TIMER;
+                        if (udp_type == UDP_FPIPE_TYPE){
+                                chan->drvstats_if_send.
+					if_send_PIPE_request ++;
+			}
+                }
+		netif_start_queue(dev);
+		return 0;
+	}
+
+	//FIXME: can we do better than sendpacket[2]?
+  	if ((chan->common.usedby == WANPIPE) && (sendpacket[2] == 0x45)) {
+		
+               	/* check to see if the source IP address is a broadcast or */
+                /* multicast IP address */
+                if(chk_bcast_mcast_addr(card, dev, skb)){
+            		++chan->ifstats.tx_dropped;
+			++card->wandev.stats.tx_dropped;
+                	dev_kfree_skb_any(skb);
+			netif_start_queue(dev);
+			return 0;
+		}
+	}
+
+	
+	/* Lock the S514/S508 card: SMP Supported */
+    	s508_s514_lock(card,&smp_flags);
+
+	if (test_and_set_bit(SEND_CRIT, (void*)&card->wandev.critical)) {
+		
+		chan->drvstats_if_send.if_send_critical_non_ISR ++;
+		chan->ifstats.tx_dropped ++;
+		printk(KERN_INFO "%s Critical in IF_SEND: if_send() already running!\n", 
+				card->devname);
+		goto if_send_start_and_exit;
+	}
+	
+	/* API packet check: minimum packet size must be greater than 
+	 * 16 byte API header */
+	if((chan->common.usedby == API) && (skb->len <= sizeof(api_tx_hdr_t))) {
+		++chan->ifstats.tx_dropped;
+		++card->wandev.stats.tx_dropped;
+	    
+		
+		goto if_send_start_and_exit;
+
+ 	}else{
+		/* During API transmission, get rid of the API header */
+		if (chan->common.usedby == API) {
+			api_tx_hdr_t* api_tx_hdr;
+			api_tx_hdr = (api_tx_hdr_t*)&skb->data[0x00];
+			attr = api_tx_hdr->attr;
+			skb_pull(skb,sizeof(api_tx_hdr_t));
+		}
+	}
+
+	if (card->wandev.state != WAN_CONNECTED) {
+		chan->drvstats_if_send.if_send_wan_disconnected ++;
+		++chan->ifstats.tx_dropped;
+        	++card->wandev.stats.tx_dropped;
+	
+	} else if (chan->common.state != WAN_CONNECTED) {
+		chan->drvstats_if_send.if_send_dlci_disconnected ++;
+
+		/* Update the DLCI state in timer interrupt */
+		card->u.f.timer_int_enabled |= TMR_INT_ENABLED_UPDATE_STATE;	
+		adptr_flags->imask |= FR_INTR_TIMER;
+
+        	++chan->ifstats.tx_dropped;
+        	++card->wandev.stats.tx_dropped;
+		
+	} else if (!is_tx_ready(card, chan)) {
+		/* No tx buffers available, store for delayed transmit */
+		if (!setup_for_delayed_transmit(dev, skb)){
+			set_bit(1,&delay_tx_queued);
+		}
+		chan->drvstats_if_send.if_send_no_bfrs++;
+		
+	} else if (!skb->protocol) {
+		/* No protocols drop packet */
+		chan->drvstats_if_send.if_send_protocol_error ++;
+		++card->wandev.stats.tx_errors;
+	
+	} else if (test_bit(ARP_CRIT,&card->wandev.critical)){
+		/* We are trying to send an ARP Packet, block IP data until
+		 * ARP is sent */
+		++chan->ifstats.tx_dropped;
+        	++card->wandev.stats.tx_dropped;
+		
+	} else {
+		//FIXME: IPX is not implemented in this version of Frame Relay ?
+		if((chan->common.usedby == WANPIPE) &&
+		 	sendpacket[1] == 0x00 &&
+		    	sendpacket[2] == 0x80 &&
+		    	sendpacket[6] == 0x81 &&
+		    	sendpacket[7] == 0x37) {
+			
+			if( chan->enable_IPX ) {
+				switch_net_numbers(sendpacket, 
+						chan->network_number, 0);
+			} else {
+				//FIXME: Take this out when IPX is fixed 
+				printk(KERN_INFO 
+				"%s: WARNING: Unsupported IPX data in send, packet dropped\n",
+					card->devname);
+			}
+			
+		}else{
+        		err = fr_send_data_header(card, chan->dlci, attr, skb->len, skb->data, chan->fr_header_len);
+			if (err) {
+				switch(err) {
+				case FRRES_CIR_OVERFLOW:
+				case FRRES_BUFFER_OVERFLOW:
+                			if (!setup_for_delayed_transmit(dev, skb)){
+						set_bit(1,&delay_tx_queued);
+					}
+           				chan->drvstats_if_send.
+						if_send_adptr_bfrs_full ++;
+					break;
+					
+				case FRRES_TOO_LONG:
+					if (net_ratelimit()){
+						printk(KERN_INFO 
+						"%s: Error: Frame too long, transmission failed %i\n",
+						 card->devname, (unsigned int)skb->len);
+					}
+					/* Drop down to default */
+				default:
+					chan->drvstats_if_send.
+						if_send_dlci_disconnected ++;
+        				++chan->ifstats.tx_dropped;
+        				++card->wandev.stats.tx_dropped;
+					break;
+				}
+			} else {
+				chan->drvstats_if_send.
+					if_send_bfr_passed_to_adptr++;
+				++chan->ifstats.tx_packets;
+				++card->wandev.stats.tx_packets;
+				
+                                chan->ifstats.tx_bytes += skb->len;
+                                card->wandev.stats.tx_bytes += skb->len;
+				dev->trans_start = jiffies;
+			}
+		}
+	}
+
+if_send_start_and_exit:
+
+	netif_start_queue(dev);
+	
+	/* If we queued the packet for transmission, we must not
+	 * deallocate it. The packet is unlinked from the IP stack
+	 * not copied. Therefore, we must keep the original packet */
+	if (!test_bit(1,&delay_tx_queued)) {
+                dev_kfree_skb_any(skb);
+	}else{
+		adptr_flags->imask |= FR_INTR_TXRDY;
+		card->u.f.tx_interrupts_pending ++;
+	}
+
+        clear_bit(SEND_CRIT, (void*)&card->wandev.critical);
+
+	s508_s514_unlock(card,&smp_flags);
+
+	return 0;
+}
+
+
+
+/*============================================================================
+ * Setup so that a frame can be transmitted on the occurrence of a transmit
+ * interrupt.
+ */
+static int setup_for_delayed_transmit(struct net_device* dev,
+				      struct sk_buff *skb)
+{
+        fr_channel_t* chan = dev->priv;
+        sdla_t* card = chan->card;
+        fr_dlci_interface_t* dlci_interface;
+	int len = skb->len;
+
+	/* Check that the dlci is properly configured,
+         * before using tx interrupt */
+	if (!chan->dlci_int_interface){
+		if (net_ratelimit()){ 
+			printk(KERN_INFO 
+				"%s: ERROR on DLCI %i: Not configured properly !\n",
+					card->devname, chan->dlci);
+			printk(KERN_INFO "%s: Please contact Sangoma Technologies\n",
+					card->devname);
+		}
+		return 1;
+	}
+		
+	dlci_interface = chan->dlci_int_interface;
+
+        if(chan->transmit_length) {
+                printk(KERN_INFO "%s: Big mess in setup_for_del...\n",
+				card->devname);
+                return 1;
+        }
+
+	if(len > FR_MAX_NO_DATA_BYTES_IN_FRAME) {
+		//FIXME: increment some statistic */
+		return 1;
+	}
+
+	skb_unlink(skb);
+	
+        chan->transmit_length = len;
+	chan->delay_skb = skb;
+        
+        dlci_interface->gen_interrupt |= FR_INTR_TXRDY;
+        dlci_interface->packet_length = len;
+
+	/* Turn on TX interrupt at the end of if_send */
+	return 0;
+}
+
+
+/*============================================================================
+ * Check to see if the packet to be transmitted contains a broadcast or
+ * multicast source IP address.
+ * Return 0 if not broadcast/multicast address, otherwise return 1.
+ */
+
+static int chk_bcast_mcast_addr(sdla_t *card, struct net_device* dev,
+                                struct sk_buff *skb)
+{
+        u32 src_ip_addr;
+        u32 broadcast_ip_addr = 0;
+        struct in_device *in_dev;
+        fr_channel_t* chan = dev->priv;
+ 
+        /* read the IP source address from the outgoing packet */
+        src_ip_addr = *(u32 *)(skb->data + 14);
+
+        /* read the IP broadcast address for the device */
+        in_dev = dev->ip_ptr;
+        if(in_dev != NULL) {
+                struct in_ifaddr *ifa= in_dev->ifa_list;
+                if(ifa != NULL)
+                        broadcast_ip_addr = ifa->ifa_broadcast;
+                else
+                        return 0;
+        }
+
+        /* check if the IP Source Address is a Broadcast address */
+        if((dev->flags & IFF_BROADCAST) && (src_ip_addr == broadcast_ip_addr)) {
+                printk(KERN_INFO
+                        "%s: Broadcast Source Address silently discarded\n",
+                        card->devname);
+                return 1;
+        }
+
+        /* check if the IP Source Address is a Multicast address */
+        if((chan->mc == WANOPT_NO) && (ntohl(src_ip_addr) >= 0xE0000001) &&
+                (ntohl(src_ip_addr) <= 0xFFFFFFFE)) {
+                printk(KERN_INFO
+                        "%s: Multicast Source Address silently discarded\n",
+                        card->devname);
+                return 1;
+        }
+
+        return 0;
+}
+
+/*============================================================================
+ * Reply to UDP Management system.
+ * Return nothing.
+ */
+static int reply_udp( unsigned char *data, unsigned int mbox_len ) 
+{
+	unsigned short len, udp_length, temp, ip_length;
+	unsigned long ip_temp;
+	int even_bound = 0;
+
+  
+	fr_udp_pkt_t *fr_udp_pkt = (fr_udp_pkt_t *)data; 
+
+	/* Set length of packet */
+	len = //sizeof(fr_encap_hdr_t)+
+	      sizeof(ip_pkt_t)+ 
+	      sizeof(udp_pkt_t)+
+	      sizeof(wp_mgmt_t)+
+	      sizeof(cblock_t)+
+	      mbox_len;
+ 
+
+	/* fill in UDP reply */
+	fr_udp_pkt->wp_mgmt.request_reply = UDPMGMT_REPLY;
+  
+	/* fill in UDP length */
+	udp_length = sizeof(udp_pkt_t)+ 
+		     sizeof(wp_mgmt_t)+
+		     sizeof(cblock_t)+
+		     mbox_len; 
+
+
+	/* put it on an even boundary */
+	if ( udp_length & 0x0001 ) {
+		udp_length += 1;
+		len += 1;
+		even_bound = 1;
+	}
+
+	temp = (udp_length<<8)|(udp_length>>8);
+	fr_udp_pkt->udp_pkt.udp_length = temp;
+	 
+	/* swap UDP ports */
+	temp = fr_udp_pkt->udp_pkt.udp_src_port;
+	fr_udp_pkt->udp_pkt.udp_src_port = 
+			fr_udp_pkt->udp_pkt.udp_dst_port; 
+	fr_udp_pkt->udp_pkt.udp_dst_port = temp;
+
+
+
+	/* add UDP pseudo header */
+	temp = 0x1100;
+	*((unsigned short *)
+		(fr_udp_pkt->data+mbox_len+even_bound)) = temp;	
+	temp = (udp_length<<8)|(udp_length>>8);
+	*((unsigned short *)
+		(fr_udp_pkt->data+mbox_len+even_bound+2)) = temp;
+		 
+	/* calculate UDP checksum */
+	fr_udp_pkt->udp_pkt.udp_checksum = 0;
+
+	fr_udp_pkt->udp_pkt.udp_checksum = 
+		calc_checksum(&data[UDP_OFFSET/*+sizeof(fr_encap_hdr_t)*/],
+			      udp_length+UDP_OFFSET);
+
+	/* fill in IP length */
+	ip_length = udp_length + sizeof(ip_pkt_t);
+	temp = (ip_length<<8)|(ip_length>>8);
+	fr_udp_pkt->ip_pkt.total_length = temp;
+  
+	/* swap IP addresses */
+	ip_temp = fr_udp_pkt->ip_pkt.ip_src_address;
+	fr_udp_pkt->ip_pkt.ip_src_address = 
+				fr_udp_pkt->ip_pkt.ip_dst_address;
+	fr_udp_pkt->ip_pkt.ip_dst_address = ip_temp;
+
+		 
+	/* fill in IP checksum */
+	fr_udp_pkt->ip_pkt.hdr_checksum = 0;
+	fr_udp_pkt->ip_pkt.hdr_checksum = 
+		calc_checksum(&data[/*sizeof(fr_encap_hdr_t)*/0],
+		      	      sizeof(ip_pkt_t));
+
+	return len;
+} /* reply_udp */
+
+unsigned short calc_checksum (char *data, int len)
+{
+	unsigned short temp; 
+	unsigned long sum=0;
+	int i;
+
+	for( i = 0; i <len; i+=2 ) {
+		memcpy(&temp,&data[i],2);
+		sum += (unsigned long)temp;
+	}
+
+	while (sum >> 16 ) {
+		sum = (sum & 0xffffUL) + (sum >> 16);
+	}
+
+	temp = (unsigned short)sum;
+	temp = ~temp;
+
+	if( temp == 0 ) 
+		temp = 0xffff;
+
+	return temp;	
+}
+
+/*
+   If incoming is 0 (outgoing)- if the net numbers is ours make it 0
+   if incoming is 1 - if the net number is 0 make it ours 
+
+*/
+static void switch_net_numbers(unsigned char *sendpacket, unsigned long network_number, unsigned char incoming)
+{
+	unsigned long pnetwork_number;
+
+	pnetwork_number = (unsigned long)((sendpacket[14] << 24) + 
+			  (sendpacket[15] << 16) + (sendpacket[16] << 8) + 
+			  sendpacket[17]);
+
+	if (!incoming) {
+		/* If the destination network number is ours, make it 0 */
+		if( pnetwork_number == network_number) {
+			sendpacket[14] = sendpacket[15] = sendpacket[16] = 
+					 sendpacket[17] = 0x00;
+		}
+	} else {
+		/* If the incoming network is 0, make it ours */
+		if( pnetwork_number == 0) {
+			sendpacket[14] = (unsigned char)(network_number >> 24);
+			sendpacket[15] = (unsigned char)((network_number & 
+					 0x00FF0000) >> 16);
+			sendpacket[16] = (unsigned char)((network_number & 
+					 0x0000FF00) >> 8);
+			sendpacket[17] = (unsigned char)(network_number & 
+					 0x000000FF);
+		}
+	}
+
+
+	pnetwork_number = (unsigned long)((sendpacket[26] << 24) + 
+			  (sendpacket[27] << 16) + (sendpacket[28] << 8) + 
+			  sendpacket[29]);
+
+	if( !incoming ) {
+		/* If the source network is ours, make it 0 */
+		if( pnetwork_number == network_number) {
+			sendpacket[26] = sendpacket[27] = sendpacket[28] = 
+					 sendpacket[29] = 0x00;
+		}
+	} else {
+		/* If the source network is 0, make it ours */
+		if( pnetwork_number == 0 ) {
+			sendpacket[26] = (unsigned char)(network_number >> 24);
+			sendpacket[27] = (unsigned char)((network_number & 
+					 0x00FF0000) >> 16);
+			sendpacket[28] = (unsigned char)((network_number & 
+					 0x0000FF00) >> 8);
+			sendpacket[29] = (unsigned char)(network_number & 
+					 0x000000FF);
+		}
+	}
+} /* switch_net_numbers */
+
+/*============================================================================
+ * Get ethernet-style interface statistics.
+ * Return a pointer to struct enet_statistics.
+ */
+static struct net_device_stats *if_stats(struct net_device *dev)
+{
+	fr_channel_t* chan = dev->priv;
+	
+	if(chan == NULL)
+		return NULL;
+
+	return &chan->ifstats;
+}
+
+/****** Interrupt Handlers **************************************************/
+
+/*============================================================================
+ * fr_isr:	S508 frame relay interrupt service routine.
+ *
+ * Description:
+ *	Frame relay main interrupt service route. This
+ *      function check the interrupt type and takes
+ *      the appropriate action.
+ */
+static void fr_isr (sdla_t* card)
+{
+	fr508_flags_t* flags = card->flags;
+	char *ptr = &flags->iflag;
+	int i,err;
+	fr_mbox_t* mbox = card->mbox;
+
+	/* This flag prevents nesting of interrupts.  See sdla_isr() routine
+         * in sdlamain.c.  */
+	card->in_isr = 1;
+	
+	++card->statistics.isr_entry;
+
+
+	/* All peripheral (configuraiton, re-configuration) events
+	 * take presidence over the ISR.  Thus, retrigger */
+	if (test_bit(PERI_CRIT, (void*)&card->wandev.critical)) {
+		++card->statistics.isr_already_critical;
+		goto fr_isr_exit;
+	}
+	
+        if(card->hw.type != SDLA_S514) {
+		if (test_bit(SEND_CRIT, (void*)&card->wandev.critical)) {
+                        printk(KERN_INFO "%s: Critical while in ISR: If Send Running!\n",
+                                card->devname);
+			++card->statistics.isr_already_critical;
+			goto fr_isr_exit;
+		}
+	}
+
+	switch (flags->iflag) {
+
+                case FR_INTR_RXRDY:  /* receive interrupt */
+	    		++card->statistics.isr_rx;
+          		rx_intr(card);
+            		break;
+
+
+                case FR_INTR_TXRDY:  /* transmit interrupt */
+	    		++ card->statistics.isr_tx; 
+			tx_intr(card); 
+            		break;
+
+                case FR_INTR_READY:  	
+	    		Intr_test_counter++;
+			++card->statistics.isr_intr_test;
+	    		break;	
+
+                case FR_INTR_DLC: /* Event interrupt occurred */
+			mbox->cmd.command = FR_READ_STATUS;
+			mbox->cmd.length = 0;
+			err = sdla_exec(mbox) ? mbox->cmd.result : CMD_TIMEOUT;
+			if (err)
+				fr_event(card, err, mbox);
+			break;
+
+                case FR_INTR_TIMER:  /* Timer interrupt */
+			timer_intr(card);
+			break;
+	
+		default:
+	    		++card->statistics.isr_spurious;
+            		spur_intr(card);
+	    		printk(KERN_INFO "%s: Interrupt Type 0x%02X!\n", 
+				card->devname, flags->iflag);
+	    
+			printk(KERN_INFO "%s: ID Bytes = ",card->devname);
+ 	    		for(i = 0; i < 8; i ++)
+				printk(KERN_INFO "0x%02X ", *(ptr + 0x28 + i));
+	   	 	printk(KERN_INFO "\n");	
+            
+			break;
+    	}
+
+fr_isr_exit:
+	
+	card->in_isr = 0;
+	flags->iflag = 0;
+	return;
+}
+
+
+
+/*===========================================================
+ * rx_intr	Receive interrupt handler.
+ *
+ * Description
+ * 	Upon receiveing an interrupt: 
+ *	1. Check that the firmware is in sync with 
+ *     	   the driver. 
+ *      2. Find an appropriate network interface
+ *         based on the received dlci number.
+ *	3. Check that the netowrk interface exists
+ *         and that it's setup properly.
+ *	4. Copy the data into an skb buffer.
+ *	5. Check the packet type and take
+ *         appropriate acton: UPD, API, ARP or Data.
+ */
+
+static void rx_intr (sdla_t* card)
+{
+	fr_rx_buf_ctl_t* frbuf = card->rxmb;
+	fr508_flags_t* flags = card->flags;
+	fr_channel_t* chan;
+	char *ptr = &flags->iflag;
+	struct sk_buff* skb;
+	struct net_device* dev;
+	void* buf;
+	unsigned dlci, len, offs, len_incl_hdr;
+	int i, udp_type;	
+
+
+	/* Check that firmware buffers are in sync */
+	if (frbuf->flag != 0x01) {
+
+		printk(KERN_INFO 
+			"%s: corrupted Rx buffer @ 0x%X, flag = 0x%02X!\n", 
+			card->devname, (unsigned)frbuf, frbuf->flag);
+      
+		printk(KERN_INFO "%s: ID Bytes = ",card->devname);
+ 		for(i = 0; i < 8; i ++)
+			printk(KERN_INFO "0x%02X ", *(ptr + 0x28 + i));
+		printk(KERN_INFO "\n");
+	
+		++card->statistics.rx_intr_corrupt_rx_bfr;
+
+		/* Bug Fix: Mar 6 2000
+                 * If we get a corrupted mailbox, it means that driver 
+                 * is out of sync with the firmware. There is no recovery.
+                 * If we don't turn off all interrupts for this card
+                 * the machine will crash. 
+                 */
+		printk(KERN_INFO "%s: Critical router failure ...!!!\n", card->devname);
+		printk(KERN_INFO "Please contact Sangoma Technologies !\n");
+		fr_set_intr_mode(card, 0, 0, 0);	
+		return;
+	}
+
+	len  = frbuf->length;
+	dlci = frbuf->dlci;
+	offs = frbuf->offset;
+
+	/* Find the network interface for this packet */
+	dev = find_channel(card, dlci);
+   
+
+	/* Check that the network interface is active and
+         * properly setup */
+	if (dev == NULL) {
+   		if( net_ratelimit()) { 
+			printk(KERN_INFO "%s: received data on unconfigured DLCI %d!\n",
+                                                card->devname, dlci);
+		}
+		++card->statistics.rx_intr_on_orphaned_DLCI; 
+		++card->wandev.stats.rx_dropped;
+		goto rx_done;
+	}
+
+	if ((chan = dev->priv) == NULL){
+		if( net_ratelimit()) { 
+			printk(KERN_INFO "%s: received data on unconfigured DLCI %d!\n",
+                                                card->devname, dlci);
+		}
+		++card->statistics.rx_intr_on_orphaned_DLCI; 
+		++card->wandev.stats.rx_dropped;
+		goto rx_done;
+	}
+
+	skb = dev_alloc_skb(len); 
+
+	if (!netif_running(dev) || (skb == NULL)){
+
+		++chan->ifstats.rx_dropped;
+	
+		if(skb == NULL) {
+			if (net_ratelimit()) { 
+				printk(KERN_INFO 
+					"%s: no socket buffers available!\n", 
+						card->devname);
+			}
+			chan->drvstats_rx_intr.rx_intr_no_socket ++;
+		} 
+
+		if (!netif_running(dev)){
+			chan->drvstats_rx_intr.
+				rx_intr_dev_not_started ++;
+			if (skb){
+				dev_kfree_skb_any(skb);
+			}
+		}
+		goto rx_done;
+	}
+
+	/* Copy data from the board into the socket buffer */
+	if ((offs + len) > card->u.f.rx_top + 1) {
+		unsigned tmp = card->u.f.rx_top - offs + 1;
+
+		buf = skb_put(skb, tmp);
+		sdla_peek(&card->hw, offs, buf, tmp);
+		offs = card->u.f.rx_base;
+		len -= tmp;
+	}
+
+	buf = skb_put(skb, len);
+	sdla_peek(&card->hw, offs, buf, len);
+
+
+	/* We got the packet from the bard. 
+         * Check the packet type and take appropriate action */
+
+	udp_type = udp_pkt_type( skb, card );
+
+	if(udp_type != UDP_INVALID_TYPE) {
+
+		/* UDP Debug packet received, store the
+		 * packet and handle it in timer interrupt */
+
+		skb_pull(skb, 1); 
+		if (wanrouter_type_trans(skb, dev)){ 
+			if(store_udp_mgmt_pkt(udp_type,UDP_PKT_FRM_NETWORK,card,skb,dlci)){
+
+				flags->imask |= FR_INTR_TIMER;
+
+				if (udp_type == UDP_FPIPE_TYPE){
+					++chan->drvstats_rx_intr.rx_intr_PIPE_request;
+				}
+			}
+		}
+
+	}else if (chan->common.usedby == API) {
+
+		/* We are in API mode. 
+                 * Add an API header to the RAW packet
+                 * and queue it into a circular buffer.
+                 * Then kick the fr_bh() bottom half handler */
+
+		api_rx_hdr_t* api_rx_hdr;
+		chan->drvstats_rx_intr.rx_intr_bfr_passed_to_stack ++;
+		chan->ifstats.rx_packets ++;
+		card->wandev.stats.rx_packets ++;
+
+		chan->ifstats.rx_bytes += skb->len;
+		card->wandev.stats.rx_bytes += skb->len;
+
+		skb_push(skb, sizeof(api_rx_hdr_t));
+		api_rx_hdr = (api_rx_hdr_t*)&skb->data[0x00];
+		api_rx_hdr->attr = frbuf->attr;
+		api_rx_hdr->time_stamp = frbuf->tmstamp;
+
+		skb->protocol = htons(ETH_P_IP);
+		skb->mac.raw  = skb->data;
+		skb->dev      = dev;
+		skb->pkt_type = WAN_PACKET_DATA;
+
+		bh_enqueue(dev, skb);
+
+		trigger_fr_bh(chan);
+
+	}else if (handle_IPXWAN(skb->data,chan->name,chan->enable_IPX, chan->network_number)){
+
+		//FIXME: Frame Relay IPX is not supported, Yet !
+		//if (chan->enable_IPX) {
+		//	fr_send(card, dlci, 0, skb->len,skb->data);
+		//}
+		dev_kfree_skb_any(skb);
+
+	} else if (is_arp(skb->data)) {
+
+		/* ARP support enabled Mar 16 2000 
+		 * Process incoming ARP reply/request, setup
+		 * dynamic routes. */ 
+
+		if (process_ARP((arphdr_1490_t *)skb->data, card, dev)) {
+			if (net_ratelimit()){  
+				printk (KERN_INFO 
+				   "%s: Error processing ARP Packet.\n", 
+					card->devname);
+			}
+		}
+		dev_kfree_skb_any(skb);
+
+	} else if (skb->data[0] != 0x03) {
+
+		if (net_ratelimit()) { 
+			printk(KERN_INFO "%s: Non IETF packet discarded.\n", 
+				card->devname);
+		}
+		dev_kfree_skb_any(skb);
+
+	} else {
+
+		len_incl_hdr = skb->len;
+		/* Decapsulate packet and pass it up the
+		   protocol stack */
+		skb->dev = dev;
+		
+		if (chan->common.usedby == BRIDGE || chan->common.usedby == BRIDGE_NODE){
+		
+			/* Make sure it's an Ethernet frame, otherwise drop it */
+			if (!memcmp(skb->data, "\x03\x00\x80\x00\x80\xC2\x00\x07", 8)) {
+				skb_pull(skb, 8);
+				skb->protocol=eth_type_trans(skb,dev);
+			}else{
+				++chan->drvstats_rx_intr.rx_intr_bfr_not_passed_to_stack;
+				++chan->ifstats.rx_errors;
+				++card->wandev.stats.rx_errors;
+				goto rx_done;
+			}
+		}else{
+		
+			/* remove hardware header */
+			buf = skb_pull(skb, 1); 
+			
+			if (!wanrouter_type_trans(skb, dev)) {
+				
+				/* can't decapsulate packet */
+				dev_kfree_skb_any(skb);
+
+				++chan->drvstats_rx_intr.rx_intr_bfr_not_passed_to_stack;
+				++chan->ifstats.rx_errors;
+				++card->wandev.stats.rx_errors;
+				goto rx_done;	
+			}
+			skb->mac.raw = skb->data;
+		} 
+		
+
+		/* Send a packet up the IP stack */
+		skb->dev->last_rx = jiffies;
+		netif_rx(skb);
+		++chan->drvstats_rx_intr.rx_intr_bfr_passed_to_stack;
+		++chan->ifstats.rx_packets;
+		++card->wandev.stats.rx_packets;
+
+		chan->ifstats.rx_bytes += len_incl_hdr;
+		card->wandev.stats.rx_bytes += len_incl_hdr;
+	}
+
+rx_done:
+
+       	/* Release buffer element and calculate a pointer to the next one */ 
+       	frbuf->flag = 0;
+	card->rxmb = ++frbuf;
+	if ((void*)frbuf > card->u.f.rxmb_last)
+		card->rxmb = card->u.f.rxmb_base;
+
+}
+
+/*==================================================================
+ * tx_intr:	Transmit interrupt handler.
+ *
+ * Rationale:
+ *      If the board is busy transmitting, if_send() will
+ *      buffers a single packet and turn on
+ *      the tx interrupt. Tx interrupt will be called
+ *      by the board, once the firmware can send more
+ *      data. Thus, no polling is required.	 
+ *
+ * Description:
+ *	Tx interrupt is called for each 
+ *      configured dlci channel. Thus: 
+ * 	1. Obtain the netowrk interface based on the
+ *         dlci number.
+ *      2. Check that network interface is up and
+ *         properly setup.
+ * 	3. Check for a buffered packet.
+ *      4. Transmit the packet.
+ *	5. If we are in WANPIPE mode, mark the 
+ *         NET_BH handler. 
+ *      6. If we are in API mode, kick
+ *         the AF_WANPIPE socket for more data. 
+ *	   
+ */
+static void tx_intr(sdla_t *card)
+{
+        fr508_flags_t* flags = card->flags;
+        fr_tx_buf_ctl_t* bctl;
+        struct net_device* dev;
+        fr_channel_t* chan;
+
+        if(card->hw.type == SDLA_S514){
+                bctl = (void*)(flags->tse_offs + card->hw.dpmbase);
+        }else{
+                bctl = (void*)(flags->tse_offs - FR_MB_VECTOR +
+                        card->hw.dpmbase);
+	}
+
+        /* Find the structure and make it unbusy */
+        dev = find_channel(card, flags->dlci);
+	if (dev == NULL){
+		printk(KERN_INFO "NO DEV IN TX Interrupt\n");	
+		goto end_of_tx_intr;
+	}
+
+        if ((chan = dev->priv) == NULL){
+		printk(KERN_INFO "NO CHAN IN TX Interrupt\n");	
+		goto end_of_tx_intr;
+	}
+
+        if(!chan->transmit_length || !chan->delay_skb) {
+                printk(KERN_INFO "%s: tx int error - transmit length zero\n",
+				card->wandev.name);
+                goto end_of_tx_intr;
+        }
+
+	/* If the 'if_send()' procedure is currently checking the 'tbusy'
+	   status, then we cannot transmit. Instead, we configure the microcode
+	   so as to re-issue this transmit interrupt at a later stage. 
+	*/
+	if (test_bit(SEND_TXIRQ_CRIT, (void*)&card->wandev.critical)) {
+
+		fr_dlci_interface_t* dlci_interface = chan->dlci_int_interface;
+		bctl->flag = 0xA0;
+		dlci_interface->gen_interrupt |= FR_INTR_TXRDY;
+		return;
+
+ 	}else{
+        	bctl->dlci = flags->dlci;
+	        bctl->length = chan->transmit_length+chan->fr_header_len;
+        	sdla_poke(&card->hw, 
+		          fr_send_hdr(card,bctl->dlci,bctl->offset), 
+			  chan->delay_skb->data,
+ 	              	  chan->delay_skb->len);
+	        bctl->flag = 0xC0;
+
+		++chan->ifstats.tx_packets;
+		++card->wandev.stats.tx_packets;
+		chan->ifstats.tx_bytes += chan->transmit_length;
+		card->wandev.stats.tx_bytes += chan->transmit_length;
+
+		/* We must free an sk buffer, which we used
+		 * for delayed transmission; Otherwise, the sock
+		 * will run out of memory */
+                dev_kfree_skb_any(chan->delay_skb);
+
+		chan->delay_skb = NULL;				
+        	chan->transmit_length = 0;
+
+		dev->trans_start = jiffies;
+
+		if (netif_queue_stopped(dev)){
+			/* If using API, than wakeup socket BH handler */
+			if (chan->common.usedby == API){
+				netif_start_queue(dev);
+				wakeup_sk_bh(dev);
+			}else{
+				netif_wake_queue(dev);
+			}
+		}
+	}
+
+end_of_tx_intr:
+
+ 	/* if any other interfaces have transmit interrupts pending, 
+	 * do not disable the global transmit interrupt */
+	if(!(-- card->u.f.tx_interrupts_pending))
+       	        flags->imask &= ~FR_INTR_TXRDY;
+
+
+}
+
+
+/*============================================================================
+ * timer_intr:	Timer interrupt handler.
+ *
+ * Rationale:
+ *	All commans must be executed within the timer
+ *      interrupt since no two commands should execute
+ *      at the same time.
+ *
+ * Description:
+ *	The timer interrupt is used to:
+ *    	1. Processing udp calls from 'fpipemon'.
+ *    	2. Processing update calls from /proc file system
+ *   	3. Reading board-level statistics for 
+ *         updating the proc file system.
+ *    	4. Sending inverse ARP request packets.
+ *	5. Configure a dlci/channel.
+ *	6. Unconfigure a dlci/channel. (Node only)
+ */
+
+static void timer_intr(sdla_t *card)
+{
+	fr508_flags_t* flags = card->flags;
+
+	/* UDP Debuging: fpipemon call */
+        if (card->u.f.timer_int_enabled & TMR_INT_ENABLED_UDP) {
+		if(card->u.f.udp_type == UDP_FPIPE_TYPE) {
+                    	if(process_udp_mgmt_pkt(card)) {
+		                card->u.f.timer_int_enabled &=
+					~TMR_INT_ENABLED_UDP;
+			}
+		}
+        }
+
+	/* /proc update call : triggered from update() */
+	if (card->u.f.timer_int_enabled & TMR_INT_ENABLED_UPDATE) {
+		fr_get_err_stats(card);
+		fr_get_stats(card);
+		card->u.f.update_comms_stats = 0;
+		card->u.f.timer_int_enabled &= ~TMR_INT_ENABLED_UPDATE;
+	}
+
+	/* Update the channel state call.  This is call is
+         * triggered by if_send() function */
+	if (card->u.f.timer_int_enabled & TMR_INT_ENABLED_UPDATE_STATE){
+		struct net_device *dev;
+		if (card->wandev.state == WAN_CONNECTED){
+			for (dev = card->wandev.dev; dev;
+			     dev = *((struct net_device **)dev->priv)){
+				fr_channel_t *chan = dev->priv;	
+				if (chan->common.state != WAN_CONNECTED){
+					update_chan_state(dev);
+				}
+			}
+		}
+		card->u.f.timer_int_enabled &= ~TMR_INT_ENABLED_UPDATE_STATE;
+	}
+
+	/* configure a dlci/channel */
+	if (card->u.f.timer_int_enabled & TMR_INT_ENABLED_CONFIG){
+		config_fr(card);
+		card->u.f.timer_int_enabled &= ~TMR_INT_ENABLED_CONFIG;
+	}
+
+	/* unconfigure a dlci/channel */
+	if (card->u.f.timer_int_enabled & TMR_INT_ENABLED_UNCONFIG){
+		unconfig_fr(card);
+		card->u.f.timer_int_enabled &= ~TMR_INT_ENABLED_UNCONFIG;
+	}
+
+	
+	/* Transmit ARP packets */
+	if (card->u.f.timer_int_enabled & TMR_INT_ENABLED_ARP){
+		int i=0;
+		struct net_device *dev;
+
+		if (card->u.f.arp_dev == NULL)
+			card->u.f.arp_dev = card->wandev.dev;
+
+		dev = card->u.f.arp_dev;
+
+		for (;;){ 
+
+			fr_channel_t *chan = dev->priv;
+
+			/* If the interface is brought down cancel sending In-ARPs */
+			if (!(dev->flags&IFF_UP)){
+				clear_bit(0,&chan->inarp_ready);	
+			}
+
+			if (test_bit(0,&chan->inarp_ready)){
+
+				if (check_tx_status(card,dev)){
+					set_bit(ARP_CRIT,&card->wandev.critical);
+					break;
+				}
+
+				if (!send_inarp_request(card,dev)){
+					trigger_fr_arp(dev);
+					chan->inarp_tick = jiffies;
+				}
+
+				clear_bit(0,&chan->inarp_ready);
+				dev = move_dev_to_next(card,dev);
+				break;
+			}
+			dev = move_dev_to_next(card,dev);
+
+			if (++i == card->wandev.new_if_cnt){
+				card->u.f.timer_int_enabled &= ~TMR_INT_ENABLED_ARP;
+				break;
+			}
+		}
+		card->u.f.arp_dev = dev;
+	}
+
+        if(!card->u.f.timer_int_enabled)
+                flags->imask &= ~FR_INTR_TIMER;
+}
+
+
+/*============================================================================
+ * spur_intr:	Spurious interrupt handler.
+ * 
+ * Description:
+ *  	We don't know this interrupt.
+ *      Print a warning.
+ */
+
+static void spur_intr (sdla_t* card)
+{
+	if (net_ratelimit()){ 
+		printk(KERN_INFO "%s: spurious interrupt!\n", card->devname);
+	}
+}
+
+
+//FIXME: Fix the IPX in next version
+/*===========================================================================
+ *  Return 0 for non-IPXWAN packet
+ *         1 for IPXWAN packet or IPX is not enabled!
+ *  FIXME: Use a IPX structure here not offsets
+ */
+static int handle_IPXWAN(unsigned char *sendpacket, 
+			 char *devname, unsigned char enable_IPX, 
+			 unsigned long network_number)
+{
+	int i;
+
+	if( sendpacket[1] == 0x00 && sendpacket[2] == 0x80 &&
+	    sendpacket[6] == 0x81 && sendpacket[7] == 0x37) { 
+
+		/* It's an IPX packet */
+		if (!enable_IPX){
+			/* Return 1 so we don't pass it up the stack. */
+			//FIXME: Take this out when IPX is fixed
+			if (net_ratelimit()){ 
+				printk (KERN_INFO 
+				"%s: WARNING: Unsupported IPX packet received and dropped\n",
+					devname);
+			}
+			return 1;
+		}
+	} else {
+		/* It's not IPX so return and pass it up the stack. */
+		return 0;
+	}
+
+	if( sendpacket[24] == 0x90 && sendpacket[25] == 0x04){
+		/* It's IPXWAN */
+
+		if( sendpacket[10] == 0x02 && sendpacket[42] == 0x00){
+
+			/* It's a timer request packet */
+			printk(KERN_INFO "%s: Received IPXWAN Timer Request packet\n",
+					devname);
+
+			/* Go through the routing options and answer no to every
+			 * option except Unnumbered RIP/SAP
+			 */
+			for(i = 49; sendpacket[i] == 0x00; i += 5){
+				/* 0x02 is the option for Unnumbered RIP/SAP */
+				if( sendpacket[i + 4] != 0x02){
+					sendpacket[i + 1] = 0;
+				}
+			}
+
+			/* Skip over the extended Node ID option */
+			if( sendpacket[i] == 0x04 ){
+				i += 8;
+			}
+
+			/* We also want to turn off all header compression opt.
+			 */
+			for(; sendpacket[i] == 0x80 ;){
+				sendpacket[i + 1] = 0;
+				i += (sendpacket[i + 2] << 8) + (sendpacket[i + 3]) + 4;
+			}
+
+			/* Set the packet type to timer response */
+			sendpacket[42] = 0x01;
+
+			printk(KERN_INFO "%s: Sending IPXWAN Timer Response\n",
+					devname);
+
+		} else if( sendpacket[42] == 0x02 ){
+
+			/* This is an information request packet */
+			printk(KERN_INFO 
+				"%s: Received IPXWAN Information Request packet\n",
+						devname);
+
+			/* Set the packet type to information response */
+			sendpacket[42] = 0x03;
+
+			/* Set the router name */
+			sendpacket[59] = 'F';
+			sendpacket[60] = 'P';
+			sendpacket[61] = 'I';
+			sendpacket[62] = 'P';
+			sendpacket[63] = 'E';
+			sendpacket[64] = '-';
+			sendpacket[65] = CVHexToAscii(network_number >> 28);
+			sendpacket[66] = CVHexToAscii((network_number & 0x0F000000)>> 24);
+			sendpacket[67] = CVHexToAscii((network_number & 0x00F00000)>> 20);
+			sendpacket[68] = CVHexToAscii((network_number & 0x000F0000)>> 16);
+			sendpacket[69] = CVHexToAscii((network_number & 0x0000F000)>> 12);
+			sendpacket[70] = CVHexToAscii((network_number & 0x00000F00)>> 8);
+			sendpacket[71] = CVHexToAscii((network_number & 0x000000F0)>> 4);
+			sendpacket[72] = CVHexToAscii(network_number & 0x0000000F);
+			for(i = 73; i < 107; i+= 1)
+			{
+				sendpacket[i] = 0;
+			}
+
+			printk(KERN_INFO "%s: Sending IPXWAN Information Response packet\n",
+					devname);
+		} else {
+
+			printk(KERN_INFO "%s: Unknown IPXWAN packet!\n",devname);
+			return 0;
+		}
+
+		/* Set the WNodeID to our network address */
+		sendpacket[43] = (unsigned char)(network_number >> 24);
+		sendpacket[44] = (unsigned char)((network_number & 0x00FF0000) >> 16);
+		sendpacket[45] = (unsigned char)((network_number & 0x0000FF00) >> 8);
+		sendpacket[46] = (unsigned char)(network_number & 0x000000FF);
+
+		return 1;
+	}
+
+	/* If we get here, it's an IPX-data packet so it'll get passed up the 
+	 * stack.
+	 * switch the network numbers 
+	 */
+	switch_net_numbers(sendpacket, network_number ,1);
+	return 0;
+}
+/*============================================================================
+ * process_route
+ * 
+ * Rationale:
+ *	If the interface goes down, or we receive an ARP request,
+ *      we have to change the network interface ip addresses.
+ * 	This cannot be done within the interrupt.
+ *
+ * Description:
+ *
+ * 	This routine is called as a polling routine to dynamically 
+ *	add/delete routes negotiated by inverse ARP.  It is in this 
+ *    	"task" because we don't want routes to be added while in 
+ *      interrupt context.
+ *
+ * Usage:
+ *	This function is called by fr_poll() polling funtion.
+ */
+
+static void process_route(struct net_device *dev)
+{
+	fr_channel_t *chan = dev->priv;
+	sdla_t *card = chan->card;
+
+	struct ifreq if_info;
+	struct sockaddr_in *if_data;
+	mm_segment_t fs = get_fs();
+	u32 ip_tmp;
+	int err;
+
+
+	switch(chan->route_flag){
+
+	case ADD_ROUTE:
+				
+		/* Set remote addresses */
+		memset(&if_info, 0, sizeof(if_info));
+		strcpy(if_info.ifr_name, dev->name);
+
+		set_fs(get_ds());     /* get user space block */ 
+		
+		if_data = (struct sockaddr_in *)&if_info.ifr_dstaddr;
+		if_data->sin_addr.s_addr = chan->ip_remote;
+		if_data->sin_family = AF_INET;
+		err = devinet_ioctl( SIOCSIFDSTADDR, &if_info );
+
+		set_fs(fs);           /* restore old block */
+
+		if (err) {
+			printk(KERN_INFO 
+				"%s: Route Add failed.  Error: %d\n", 
+					card->devname,err);
+			printk(KERN_INFO "%s: Address: %u.%u.%u.%u\n",
+				chan->name, NIPQUAD(chan->ip_remote));
+
+		}else {
+			printk(KERN_INFO "%s: Route Added Successfully: %u.%u.%u.%u\n",
+				card->devname,NIPQUAD(chan->ip_remote));
+			chan->route_flag = ROUTE_ADDED;
+		}
+		break;
+
+	case REMOVE_ROUTE:
+
+		/* Set remote addresses */
+		memset(&if_info, 0, sizeof(if_info));
+		strcpy(if_info.ifr_name, dev->name);
+
+		ip_tmp = get_ip_address(dev,WAN_POINTOPOINT_IP);	
+
+		set_fs(get_ds());     /* get user space block */ 
+		
+		if_data = (struct sockaddr_in *)&if_info.ifr_dstaddr;
+		if_data->sin_addr.s_addr = 0;
+		if_data->sin_family = AF_INET;
+		err = devinet_ioctl( SIOCSIFDSTADDR, &if_info );
+
+		set_fs(fs);    
+		
+		if (err) {
+			printk(KERN_INFO 
+				"%s: Deleting of route failed.  Error: %d\n", 
+					card->devname,err);
+			printk(KERN_INFO "%s: Address: %u.%u.%u.%u\n",
+				dev->name,NIPQUAD(chan->ip_remote) );
+
+		} else {
+			printk(KERN_INFO "%s: Route Removed Sucessfuly: %u.%u.%u.%u\n", 
+				card->devname,NIPQUAD(ip_tmp));
+			chan->route_flag = NO_ROUTE;
+		}
+		break;
+
+	} /* Case Statement */
+
+}
+
+
+
+/****** Frame Relay Firmware-Specific Functions *****************************/
+
+/*============================================================================
+ * Read firmware code version.
+ * o fill string str with firmware version info. 
+ */
+static int fr_read_version (sdla_t* card, char* str)
+{
+	fr_mbox_t* mbox = card->mbox;
+	int retry = MAX_CMD_RETRY;
+	int err;
+
+	do
+	{
+		mbox->cmd.command = FR_READ_CODE_VERSION;
+		mbox->cmd.length = 0;
+		err = sdla_exec(mbox) ? mbox->cmd.result : CMD_TIMEOUT;
+	} while (err && retry-- && fr_event(card, err, mbox));
+	
+	if (!err && str) {
+		int len = mbox->cmd.length;
+		memcpy(str, mbox->data, len);
+	        str[len] = '\0';
+	}
+	return err;
+}
+
+/*============================================================================
+ * Set global configuration.
+ */
+static int fr_configure (sdla_t* card, fr_conf_t *conf)
+{
+	fr_mbox_t* mbox = card->mbox;
+	int retry = MAX_CMD_RETRY;
+	int dlci_num = card->u.f.dlci_num;
+	int err, i;
+
+	do
+	{
+		memcpy(mbox->data, conf, sizeof(fr_conf_t));
+
+		if (dlci_num) for (i = 0; i < dlci_num; ++i)
+			((fr_conf_t*)mbox->data)->dlci[i] = 
+					card->u.f.node_dlci[i]; 
+		
+		mbox->cmd.command = FR_SET_CONFIG;
+		mbox->cmd.length =
+			sizeof(fr_conf_t) + dlci_num * sizeof(short);
+
+		err = sdla_exec(mbox) ? mbox->cmd.result : CMD_TIMEOUT;
+	
+	} while (err && retry-- && fr_event(card, err, mbox));
+
+	/*NC Oct 12 2000 */
+	if (err != CMD_OK){
+		printk(KERN_ERR "%s: Frame Relay Configuration Failed: rc=0x%x\n",
+				card->devname,err);
+	}
+	
+	return err;
+}
+
+/*============================================================================
+ * Set DLCI configuration.
+ */
+static int fr_dlci_configure (sdla_t* card, fr_dlc_conf_t *conf, unsigned dlci)
+{
+	fr_mbox_t* mbox = card->mbox;
+	int retry = MAX_CMD_RETRY;
+	int err;
+
+	do
+	{
+		memcpy(mbox->data, conf, sizeof(fr_dlc_conf_t));
+		mbox->cmd.dlci = (unsigned short) dlci; 
+		mbox->cmd.command = FR_SET_CONFIG;
+		mbox->cmd.length = sizeof(fr_dlc_conf_t);
+		err = sdla_exec(mbox) ? mbox->cmd.result : CMD_TIMEOUT;
+	} while (err && retry--);
+	
+	return err;
+}
+/*============================================================================
+ * Set interrupt mode.
+ */
+static int fr_set_intr_mode (sdla_t* card, unsigned mode, unsigned mtu,
+	unsigned short timeout)
+{
+	fr_mbox_t* mbox = card->mbox;
+	fr508_intr_ctl_t* ictl = (void*)mbox->data;
+	int retry = MAX_CMD_RETRY;
+	int err;
+
+	do
+	{
+		memset(ictl, 0, sizeof(fr508_intr_ctl_t));
+		ictl->mode   = mode;
+		ictl->tx_len = mtu;
+		ictl->irq    = card->hw.irq;
+
+		/* indicate timeout on timer */
+		if (mode & 0x20) ictl->timeout = timeout; 
+
+		mbox->cmd.length = sizeof(fr508_intr_ctl_t);
+		mbox->cmd.command = FR_SET_INTR_MODE;
+		err = sdla_exec(mbox) ? mbox->cmd.result : CMD_TIMEOUT;
+
+	} while (err && retry-- && fr_event(card, err, mbox));
+	
+	return err;
+}
+
+/*============================================================================
+ * Enable communications.
+ */
+static int fr_comm_enable (sdla_t* card)
+{
+	fr_mbox_t* mbox = card->mbox;
+	int retry = MAX_CMD_RETRY;
+	int err;
+
+	do
+	{
+		mbox->cmd.command = FR_COMM_ENABLE;
+		mbox->cmd.length = 0;
+		err = sdla_exec(mbox) ? mbox->cmd.result : CMD_TIMEOUT;
+	} while (err && retry-- && fr_event(card, err, mbox));
+	
+	return err;
+}
+
+/*============================================================================
+ * fr_comm_disable 
+ *
+ * Warning: This functin is called by the shutdown() procedure. It is void
+ *          since dev->priv are has already been deallocated and no
+ *          error checking is possible using fr_event() function.
+ */
+static void fr_comm_disable (sdla_t* card)
+{
+	fr_mbox_t* mbox = card->mbox;
+	int retry = MAX_CMD_RETRY;
+	int err;
+
+	do {
+	mbox->cmd.command = FR_SET_MODEM_STATUS;
+	mbox->cmd.length = 1;
+	mbox->data[0] = 0;
+	err = sdla_exec(mbox) ? mbox->cmd.result : CMD_TIMEOUT;
+	} while (err && retry--);
+	
+	retry = MAX_CMD_RETRY;
+	
+	do
+	{
+		mbox->cmd.command = FR_COMM_DISABLE;
+		mbox->cmd.length = 0;
+		err = sdla_exec(mbox) ? mbox->cmd.result : CMD_TIMEOUT;
+	} while (err && retry--);
+
+	return;
+}
+
+
+
+/*============================================================================
+ * Get communications error statistics. 
+ */
+static int fr_get_err_stats (sdla_t* card)
+{
+	fr_mbox_t* mbox = card->mbox;
+	int retry = MAX_CMD_RETRY;
+	int err;
+
+
+	do
+	{
+		mbox->cmd.command = FR_READ_ERROR_STATS;
+		mbox->cmd.length = 0;
+		mbox->cmd.dlci = 0;
+		err = sdla_exec(mbox) ? mbox->cmd.result : CMD_TIMEOUT;
+	} while (err && retry-- && fr_event(card, err, mbox));
+
+	if (!err) {
+		fr_comm_stat_t* stats = (void*)mbox->data;
+		card->wandev.stats.rx_over_errors    = stats->rx_overruns;
+		card->wandev.stats.rx_crc_errors     = stats->rx_bad_crc;
+		card->wandev.stats.rx_missed_errors  = stats->rx_aborts;
+		card->wandev.stats.rx_length_errors  = stats->rx_too_long;
+		card->wandev.stats.tx_aborted_errors = stats->tx_aborts;
+	
+	}
+
+	return err;
+}
+
+/*============================================================================
+ * Get statistics. 
+ */
+static int fr_get_stats (sdla_t* card)
+{
+	fr_mbox_t* mbox = card->mbox;
+	int retry = MAX_CMD_RETRY;
+	int err;
+
+
+	do
+	{
+		mbox->cmd.command = FR_READ_STATISTICS;
+		mbox->cmd.length = 0;
+		mbox->cmd.dlci = 0;
+		err = sdla_exec(mbox) ? mbox->cmd.result : CMD_TIMEOUT;
+	} while (err && retry-- && fr_event(card, err, mbox));
+
+	if (!err) {
+		fr_link_stat_t* stats = (void*)mbox->data;
+		card->wandev.stats.rx_frame_errors = stats->rx_bad_format;
+		card->wandev.stats.rx_dropped =
+			stats->rx_dropped + stats->rx_dropped2;
+	}
+
+	return err;
+}
+
+/*============================================================================
+ * Add DLCI(s) (Access Node only!).
+ * This routine will perform the ADD_DLCIs command for the specified DLCI.
+ */
+static int fr_add_dlci (sdla_t* card, int dlci)
+{
+	fr_mbox_t* mbox = card->mbox;
+	int retry = MAX_CMD_RETRY;
+	int err;
+
+	do
+	{
+		unsigned short* dlci_list = (void*)mbox->data;
+
+		mbox->cmd.length  = sizeof(short);
+		dlci_list[0] = dlci;
+		mbox->cmd.command = FR_ADD_DLCI;
+		err = sdla_exec(mbox) ? mbox->cmd.result : CMD_TIMEOUT;
+
+	} while (err && retry-- && fr_event(card, err, mbox));
+	
+	return err;
+}
+
+/*============================================================================
+ * Activate DLCI(s) (Access Node only!). 
+ * This routine will perform the ACTIVATE_DLCIs command with a DLCI number. 
+ */
+static int fr_activate_dlci (sdla_t* card, int dlci)
+{
+	fr_mbox_t* mbox = card->mbox;
+	int retry = MAX_CMD_RETRY;
+	int err;
+
+	do
+	{
+		unsigned short* dlci_list = (void*)mbox->data;
+
+		mbox->cmd.length  = sizeof(short);
+		dlci_list[0] = dlci;
+		mbox->cmd.command = FR_ACTIVATE_DLCI;
+		err = sdla_exec(mbox) ? mbox->cmd.result : CMD_TIMEOUT;
+
+	} while (err && retry-- && fr_event(card, err, mbox));
+	
+	return err;
+}
+
+/*============================================================================
+ * Delete DLCI(s) (Access Node only!). 
+ * This routine will perform the DELETE_DLCIs command with a DLCI number. 
+ */
+static int fr_delete_dlci (sdla_t* card, int dlci)
+{
+	fr_mbox_t* mbox = card->mbox;
+	int retry = MAX_CMD_RETRY;
+	int err;
+
+	do
+	{
+		unsigned short* dlci_list = (void*)mbox->data;
+
+		mbox->cmd.length  = sizeof(short);
+		dlci_list[0] = dlci;
+		mbox->cmd.command = FR_DELETE_DLCI;
+		err = sdla_exec(mbox) ? mbox->cmd.result : CMD_TIMEOUT;
+
+	} while (err && retry-- && fr_event(card, err, mbox));
+	
+	return err;
+}
+
+
+
+/*============================================================================
+ * Issue in-channel signalling frame. 
+ */
+static int fr_issue_isf (sdla_t* card, int isf)
+{
+	fr_mbox_t* mbox = card->mbox;
+	int retry = MAX_CMD_RETRY;
+	int err;
+
+	do
+	{
+		mbox->data[0] = isf;
+		mbox->cmd.length  = 1;
+		mbox->cmd.command = FR_ISSUE_IS_FRAME;
+		err = sdla_exec(mbox) ? mbox->cmd.result : CMD_TIMEOUT;
+	} while (err && retry-- && fr_event(card, err, mbox));
+	
+	return err;
+}
+
+
+static unsigned int fr_send_hdr (sdla_t*card, int dlci, unsigned int offset)
+{
+	struct net_device *dev = find_channel(card,dlci);	
+	fr_channel_t *chan;
+
+	if (!dev || !(chan=dev->priv))
+		return offset;
+	
+	if (chan->fr_header_len){
+		sdla_poke(&card->hw, offset, chan->fr_header, chan->fr_header_len);
+	}
+	
+	return offset+chan->fr_header_len;
+}
+
+/*============================================================================
+ * Send a frame on a selected DLCI.  
+ */
+static int fr_send_data_header (sdla_t* card, int dlci, unsigned char attr, int len,
+	void *buf, unsigned char hdr_len)
+{
+	fr_mbox_t* mbox = card->mbox + 0x800;
+	int retry = MAX_CMD_RETRY;
+	int err;
+
+	do
+	{
+		mbox->cmd.dlci    = dlci;
+		mbox->cmd.attr    = attr;
+		mbox->cmd.length  = len+hdr_len;
+		mbox->cmd.command = FR_WRITE;
+		err = sdla_exec(mbox) ? mbox->cmd.result : CMD_TIMEOUT;
+	} while (err && retry-- && fr_event(card, err, mbox));
+
+	if (!err) {
+		fr_tx_buf_ctl_t* frbuf;
+ 
+               	if(card->hw.type == SDLA_S514)
+			frbuf = (void*)(*(unsigned long*)mbox->data +
+                        	card->hw.dpmbase);
+		else
+			frbuf = (void*)(*(unsigned long*)mbox->data -
+                        	FR_MB_VECTOR + card->hw.dpmbase);
+
+		sdla_poke(&card->hw, fr_send_hdr(card,dlci,frbuf->offset), buf, len);
+		frbuf->flag = 0x01;
+	}
+
+	return err;
+}
+
+static int fr_send (sdla_t* card, int dlci, unsigned char attr, int len,
+	void *buf)
+{
+	fr_mbox_t* mbox = card->mbox + 0x800;
+	int retry = MAX_CMD_RETRY;
+	int err;
+
+	do
+	{
+		mbox->cmd.dlci    = dlci;
+		mbox->cmd.attr    = attr;
+		mbox->cmd.length  = len;
+		mbox->cmd.command = FR_WRITE;
+		err = sdla_exec(mbox) ? mbox->cmd.result : CMD_TIMEOUT;
+	} while (err && retry-- && fr_event(card, err, mbox));
+
+	if (!err) {
+		fr_tx_buf_ctl_t* frbuf;
+ 
+               	if(card->hw.type == SDLA_S514)
+			frbuf = (void*)(*(unsigned long*)mbox->data +
+                        	card->hw.dpmbase);
+		else
+			frbuf = (void*)(*(unsigned long*)mbox->data -
+                        	FR_MB_VECTOR + card->hw.dpmbase);
+
+		sdla_poke(&card->hw, frbuf->offset, buf, len);
+		frbuf->flag = 0x01;
+	}
+
+	return err;
+}
+
+
+/****** Firmware Asynchronous Event Handlers ********************************/
+
+/*============================================================================
+ * Main asyncronous event/error handler.
+ *	This routine is called whenever firmware command returns non-zero
+ *	return code.
+ *
+ * Return zero if previous command has to be cancelled.
+ */
+static int fr_event (sdla_t *card, int event, fr_mbox_t* mbox)
+{
+	fr508_flags_t* flags = card->flags;
+	char *ptr = &flags->iflag;
+	int i;
+
+	switch (event) {
+
+		case FRRES_MODEM_FAILURE:
+			return fr_modem_failure(card, mbox);
+
+		case FRRES_CHANNEL_DOWN: {
+			struct net_device *dev;
+
+			/* Remove all routes from associated DLCI's */
+			for (dev = card->wandev.dev; dev;
+			     dev = *((struct net_device **)dev->priv)) {
+				fr_channel_t *chan = dev->priv;
+				if (chan->route_flag == ROUTE_ADDED) {
+					chan->route_flag = REMOVE_ROUTE;
+				}
+
+				if (chan->inarp == INARP_CONFIGURED) {
+					chan->inarp = INARP_REQUEST;
+				}
+
+				/* If the link becomes disconnected then,
+                                 * all channels will be disconnected
+                                 * as well.
+                                 */
+				set_chan_state(dev,WAN_DISCONNECTED);
+			}
+				
+			wanpipe_set_state(card, WAN_DISCONNECTED);
+			return 1;
+			}
+
+		case FRRES_CHANNEL_UP: {
+			struct net_device *dev;
+
+			/* FIXME: Only startup devices that are on the list */
+			
+			for (dev = card->wandev.dev; dev;
+			     dev = *((struct net_device **)dev->priv)) {
+				
+				set_chan_state(dev,WAN_CONNECTED);
+			}
+
+			wanpipe_set_state(card, WAN_CONNECTED);
+			return 1;
+			}
+
+		case FRRES_DLCI_CHANGE:
+			return fr_dlci_change(card, mbox);
+
+		case FRRES_DLCI_MISMATCH:
+			printk(KERN_INFO "%s: DLCI list mismatch!\n", 
+				card->devname);
+			return 1;
+
+		case CMD_TIMEOUT:
+			printk(KERN_ERR "%s: command 0x%02X timed out!\n",
+				card->devname, mbox->cmd.command);
+			printk(KERN_INFO "%s: ID Bytes = ",card->devname);
+ 	    		for(i = 0; i < 8; i ++)
+				printk(KERN_INFO "0x%02X ", *(ptr + 0x18 + i));
+	   	 	printk(KERN_INFO "\n");	
+            
+			break;
+
+		case FRRES_DLCI_INACTIVE:
+			break;
+ 
+		case FRRES_CIR_OVERFLOW:
+			break;
+			
+		case FRRES_BUFFER_OVERFLOW:
+			break; 
+			
+		default:
+			printk(KERN_INFO "%s: command 0x%02X returned 0x%02X!\n"
+				, card->devname, mbox->cmd.command, event);
+	}
+
+	return 0;
+}
+
+/*============================================================================
+ * Handle modem error.
+ *
+ * Return zero if previous command has to be cancelled.
+ */
+static int fr_modem_failure (sdla_t *card, fr_mbox_t* mbox)
+{
+	printk(KERN_INFO "%s: physical link down! (modem error 0x%02X)\n",
+		card->devname, mbox->data[0]);
+
+	switch (mbox->cmd.command){
+		case FR_WRITE:
+	
+		case FR_READ:
+			return 0;
+	}
+	
+	return 1;
+}
+
+/*============================================================================
+ * Handle DLCI status change.
+ *
+ * Return zero if previous command has to be cancelled.
+ */
+static int fr_dlci_change (sdla_t *card, fr_mbox_t* mbox)
+{
+	dlci_status_t* status = (void*)mbox->data;
+	int cnt = mbox->cmd.length / sizeof(dlci_status_t);
+	fr_channel_t *chan;
+	struct net_device* dev2;
+	
+
+	for (; cnt; --cnt, ++status) {
+
+		unsigned short dlci= status->dlci;
+		struct net_device* dev = find_channel(card, dlci);
+		
+		if (dev == NULL){
+			printk(KERN_INFO 
+				"%s: CPE contains unconfigured DLCI= %d\n", 
+				card->devname, dlci); 	
+
+                      printk(KERN_INFO
+                                "%s: unconfigured DLCI %d reported by network\n"
+                                , card->devname, dlci);
+ 
+		}else{
+			if (status->state == FR_LINK_INOPER) {
+				printk(KERN_INFO
+					"%s: DLCI %u is inactive!\n",
+					card->devname, dlci);
+
+				if (dev && netif_running(dev))
+					set_chan_state(dev, WAN_DISCONNECTED);
+			}
+	
+			if (status->state & FR_DLCI_DELETED) {
+
+				printk(KERN_INFO
+					"%s: DLCI %u has been deleted!\n",
+					card->devname, dlci);
+
+				if (dev && netif_running(dev)){
+
+					fr_channel_t *chan = dev->priv;
+
+					if (chan->route_flag == ROUTE_ADDED) {
+						chan->route_flag = REMOVE_ROUTE;
+						/* The state change will trigger
+                                                 * the fr polling routine */
+					}
+
+					if (chan->inarp == INARP_CONFIGURED) {
+						chan->inarp = INARP_REQUEST;
+					}
+
+					set_chan_state(dev, WAN_DISCONNECTED);
+				}
+
+			} else if (status->state & FR_DLCI_ACTIVE) {
+
+				chan = dev->priv;
+			
+				/* This flag is used for configuring specific 
+				   DLCI(s) when they become active.
+			 	*/ 
+				chan->dlci_configured = DLCI_CONFIG_PENDING;
+	
+				set_chan_state(dev, WAN_CONNECTED);
+		
+			}
+		}
+	}
+	
+	for (dev2 = card->wandev.dev; dev2;
+	     dev2 = *((struct net_device **)dev2->priv)){
+		
+		chan = dev2->priv;
+	
+		if (chan->dlci_configured == DLCI_CONFIG_PENDING) {
+			if (fr_init_dlci(card, chan)){
+				return 1;
+			}
+		}
+
+	}
+	return 1;
+}
+
+
+static int fr_init_dlci (sdla_t *card, fr_channel_t *chan)
+{
+	fr_dlc_conf_t cfg;
+	
+	memset(&cfg, 0, sizeof(cfg));
+
+	if ( chan->cir_status == CIR_DISABLED) {
+
+		cfg.cir_fwd = cfg.cir_bwd  = 16;
+		cfg.bc_fwd = cfg.bc_bwd = 16;
+		cfg.conf_flags = 0x0001;	
+
+	}else if (chan->cir_status == CIR_ENABLED) {
+	
+		cfg.cir_fwd = cfg.cir_bwd = chan->cir;
+		cfg.bc_fwd  = cfg.bc_bwd  = chan->bc;
+		cfg.be_fwd  = cfg.be_bwd  = chan->be;
+		cfg.conf_flags = 0x0000;
+	}
+	
+	if (fr_dlci_configure( card, &cfg , chan->dlci)){
+		printk(KERN_INFO 
+			"%s: DLCI Configure failed for %d\n",
+				card->devname, chan->dlci);
+		return 1;	
+	}
+	
+	chan->dlci_configured = DLCI_CONFIGURED;
+
+	/* Read the interface byte mapping into the channel 
+	 * structure.
+	 */
+	read_DLCI_IB_mapping( card, chan );
+
+	return 0;
+}
+/******* Miscellaneous ******************************************************/
+
+/*============================================================================
+ * Update channel state. 
+ */
+static int update_chan_state(struct net_device* dev)
+{
+	fr_channel_t* chan = dev->priv;
+	sdla_t* card = chan->card;
+	fr_mbox_t* mbox = card->mbox;
+	int retry = MAX_CMD_RETRY;
+	int err;
+
+	do
+	{
+		mbox->cmd.command = FR_LIST_ACTIVE_DLCI;
+		mbox->cmd.length = 0;
+		err = sdla_exec(mbox) ? mbox->cmd.result : CMD_TIMEOUT;
+	} while (err && retry-- && fr_event(card, err, mbox));
+
+	if (!err) {
+		
+		unsigned short* list = (void*)mbox->data;
+		int cnt = mbox->cmd.length / sizeof(short);
+		
+		err=1;
+		
+		for (; cnt; --cnt, ++list) {
+
+			if (*list == chan->dlci) {
+ 				set_chan_state(dev, WAN_CONNECTED);
+
+
+				/* May 23 2000. NC
+				 * When a dlci is added or restarted,
+                                 * the dlci_int_interface pointer must
+				 * be reinitialized.  */
+				if (!chan->dlci_int_interface){
+					err=fr_init_dlci (card,chan);
+				}
+				break;
+			}
+		}
+	}
+
+	return err;
+}
+
+/*============================================================================
+ * Set channel state.
+ */
+static void set_chan_state(struct net_device* dev, int state)
+{
+	fr_channel_t* chan = dev->priv;
+	sdla_t* card = chan->card;
+
+	if (chan->common.state != state) {
+
+		switch (state) {
+
+			case WAN_CONNECTED:
+				printk(KERN_INFO
+					"%s: Interface %s: DLCI %d connected\n",
+					card->devname, dev->name, chan->dlci);
+
+				/* If the interface was previoulsy down,
+                                 * bring it up, since the channel is active */
+
+				trigger_fr_poll (dev);
+				trigger_fr_arp  (dev);
+				break;
+
+			case WAN_CONNECTING:
+				printk(KERN_INFO 
+				      "%s: Interface %s: DLCI %d connecting\n",
+					card->devname, dev->name, chan->dlci);
+				break;
+
+			case WAN_DISCONNECTED:
+				printk (KERN_INFO 
+				    "%s: Interface %s: DLCI %d disconnected!\n",
+					card->devname, dev->name, chan->dlci);
+			
+				/* If the interface is up, bring it down,
+                                 * since the channel is now disconnected */
+				trigger_fr_poll (dev);
+				break;
+		}
+
+		chan->common.state = state;
+	}
+
+	chan->state_tick = jiffies;
+}
+
+/*============================================================================
+ * Find network device by its channel number.
+ *
+ * We need this critical flag because we change
+ * the dlci_to_dev_map outside the interrupt.
+ *
+ * NOTE: del_if() functions updates this array, it uses
+ *       the spin locks to avoid corruption.
+ */
+static struct net_device* find_channel(sdla_t* card, unsigned dlci)
+{
+	if(dlci > HIGHEST_VALID_DLCI)
+		return NULL;
+
+	return(card->u.f.dlci_to_dev_map[dlci]);
+}
+
+/*============================================================================
+ * Check to see if a frame can be sent. If no transmit buffers available,
+ * enable transmit interrupts.
+ *
+ * Return:	1 - Tx buffer(s) available
+ *		0 - no buffers available
+ */
+static int is_tx_ready (sdla_t* card, fr_channel_t* chan)
+{
+	unsigned char sb;
+
+        if(card->hw.type == SDLA_S514)
+		return 1;
+
+	sb = inb(card->hw.port);
+	if (sb & 0x02) 
+		return 1;
+
+	return 0;
+}
+
+/*============================================================================
+ * Convert decimal string to unsigned integer.
+ * If len != 0 then only 'len' characters of the string are converted.
+ */
+static unsigned int dec_to_uint (unsigned char* str, int len)
+{
+	unsigned val;
+
+	if (!len) 
+		len = strlen(str);
+
+	for (val = 0; len && is_digit(*str); ++str, --len)
+		val = (val * 10) + (*str - (unsigned)'0');
+
+	return val;
+}
+
+
+
+/*=============================================================================
+ * Store a UDP management packet for later processing.
+ */
+
+static int store_udp_mgmt_pkt(int udp_type, char udp_pkt_src, sdla_t* card,
+                                struct sk_buff *skb, int dlci)
+{
+        int udp_pkt_stored = 0;
+	
+	struct net_device *dev = find_channel(card, dlci);
+	fr_channel_t *chan;
+	
+	if (!dev || !(chan=dev->priv))
+		return 1;
+	
+        if(!card->u.f.udp_pkt_lgth && (skb->len <= MAX_LGTH_UDP_MGNT_PKT)){
+                card->u.f.udp_pkt_lgth = skb->len + chan->fr_header_len;
+                card->u.f.udp_type = udp_type;
+                card->u.f.udp_pkt_src = udp_pkt_src;
+                card->u.f.udp_dlci = dlci;
+                memcpy(card->u.f.udp_pkt_data, skb->data, skb->len);
+                card->u.f.timer_int_enabled |= TMR_INT_ENABLED_UDP;
+                udp_pkt_stored = 1;
+
+        }else{
+                printk(KERN_INFO "ERROR: UDP packet not stored for DLCI %d\n", 
+							dlci);
+	}
+
+        if(udp_pkt_src == UDP_PKT_FRM_STACK){
+                dev_kfree_skb_any(skb);
+	}else{
+                dev_kfree_skb_any(skb);
+	}
+		
+        return(udp_pkt_stored);
+}
+
+
+/*==============================================================================
+ * Process UDP call of type FPIPE8ND
+ */
+static int process_udp_mgmt_pkt(sdla_t* card)
+{
+
+	int c_retry = MAX_CMD_RETRY;
+	unsigned char *buf;
+	unsigned char frames;
+	unsigned int len;
+	unsigned short buffer_length;
+	struct sk_buff *new_skb;
+	fr_mbox_t* mbox = card->mbox;
+	int err;
+	struct timeval tv;
+	int udp_mgmt_req_valid = 1;
+        struct net_device* dev;
+        fr_channel_t* chan;
+        fr_udp_pkt_t *fr_udp_pkt;
+	unsigned short num_trc_els;
+	fr_trc_el_t* ptr_trc_el;
+	fr_trc_el_t trc_el;
+	fpipemon_trc_t* fpipemon_trc;
+
+	char udp_pkt_src = card->u.f.udp_pkt_src; 
+	int dlci = card->u.f.udp_dlci;
+
+	/* Find network interface for this packet */
+	dev = find_channel(card, dlci);
+	if (!dev){
+		card->u.f.udp_pkt_lgth = 0;
+		return 1;
+	}
+        if ((chan = dev->priv) == NULL){
+		card->u.f.udp_pkt_lgth = 0;
+		return 1;
+	}
+
+	/* If the UDP packet is from the network, we are going to have to 
+	   transmit a response. Before doing so, we must check to see that
+	   we are not currently transmitting a frame (in 'if_send()') and
+	   that we are not already in a 'delayed transmit' state.
+	*/
+	if(udp_pkt_src == UDP_PKT_FRM_NETWORK) {
+		if (check_tx_status(card,dev)){
+			card->u.f.udp_pkt_lgth = 0;
+			return 1;
+		}
+        }
+
+        fr_udp_pkt = (fr_udp_pkt_t *)card->u.f.udp_pkt_data;
+
+	if(udp_pkt_src == UDP_PKT_FRM_NETWORK) {
+	
+		switch(fr_udp_pkt->cblock.command) {
+
+			case FR_READ_MODEM_STATUS:
+			case FR_READ_STATUS:
+			case FPIPE_ROUTER_UP_TIME:
+			case FR_READ_ERROR_STATS:
+			case FPIPE_DRIVER_STAT_GEN:
+			case FR_READ_STATISTICS:
+			case FR_READ_ADD_DLC_STATS:
+			case FR_READ_CONFIG:
+			case FR_READ_CODE_VERSION:
+				udp_mgmt_req_valid = 1;
+				break;
+			default:
+				udp_mgmt_req_valid = 0;
+				break;
+		}
+	}
+
+	if(!udp_mgmt_req_valid) {
+		/* set length to 0 */
+		fr_udp_pkt->cblock.length = 0;
+		/* set return code */
+		fr_udp_pkt->cblock.result = 0xCD; 
+		
+		chan->drvstats_gen.UDP_PIPE_mgmt_direction_err ++;
+
+		if (net_ratelimit()){	
+			printk(KERN_INFO 
+			"%s: Warning, Illegal UDP command attempted from network: %x\n",
+			card->devname,fr_udp_pkt->cblock.command);
+		}
+		
+	} else {   
+           
+		switch(fr_udp_pkt->cblock.command) {
+
+		case FPIPE_ENABLE_TRACING:
+			if(!card->TracingEnabled) {
+				do {
+                       			mbox->cmd.command = FR_SET_TRACE_CONFIG;
+                       			mbox->cmd.length = 1;
+                     			mbox->cmd.dlci = 0x00;
+                   			mbox->data[0] = fr_udp_pkt->data[0] | 
+						RESET_TRC;
+                    			err = sdla_exec(mbox) ? 
+					     		mbox->cmd.result : CMD_TIMEOUT;
+                       		} while (err && c_retry-- && fr_event(card, err,
+					 mbox));
+
+                        	if(err) {
+					card->TracingEnabled = 0;
+					/* set the return code */
+					fr_udp_pkt->cblock.result =
+  						mbox->cmd.result;
+					mbox->cmd.length = 0;
+					break;
+				}
+
+				sdla_peek(&card->hw, NO_TRC_ELEMENTS_OFF,
+						&num_trc_els, 2);
+				sdla_peek(&card->hw, BASE_TRC_ELEMENTS_OFF,
+						&card->u.f.trc_el_base, 4);
+				card->u.f.curr_trc_el = card->u.f.trc_el_base;
+             			card->u.f.trc_el_last = card->u.f.curr_trc_el +
+							((num_trc_els - 1) * 
+							sizeof(fr_trc_el_t));
+   
+				/* Calculate the maximum trace data area in */
+				/* the UDP packet */
+				card->u.f.trc_bfr_space=(MAX_LGTH_UDP_MGNT_PKT -
+					//sizeof(fr_encap_hdr_t) -
+					sizeof(ip_pkt_t) -
+					sizeof(udp_pkt_t) -
+					sizeof(wp_mgmt_t) -
+					sizeof(cblock_t));
+
+				/* set return code */
+				fr_udp_pkt->cblock.result = 0;
+			
+			} else {
+                        	/* set return code to line trace already 
+				   enabled */
+				fr_udp_pkt->cblock.result = 1;
+                    	}
+
+			mbox->cmd.length = 0;
+			card->TracingEnabled = 1;
+			break;
+
+
+                case FPIPE_DISABLE_TRACING:
+			if(card->TracingEnabled) {
+			
+				do {
+					mbox->cmd.command = FR_SET_TRACE_CONFIG;
+					mbox->cmd.length = 1;
+					mbox->cmd.dlci = 0x00;
+					mbox->data[0] = ~ACTIVATE_TRC;
+					err = sdla_exec(mbox) ? 
+							mbox->cmd.result : CMD_TIMEOUT;
+				} while (err && c_retry-- && fr_event(card, err, mbox));
+                    	}
+
+                    	/* set return code */
+			fr_udp_pkt->cblock.result = 0;
+			mbox->cmd.length = 0;
+			card->TracingEnabled = 0;
+			break;
+
+                case FPIPE_GET_TRACE_INFO:
+
+		        /* Line trace cannot be performed on the 502 */
+                        if(!card->TracingEnabled) {
+                                /* set return code */
+                                fr_udp_pkt->cblock.result = 1;
+                                mbox->cmd.length = 0;
+                                break;
+                        }
+
+			ptr_trc_el = (void *)card->u.f.curr_trc_el;
+
+                        buffer_length = 0;
+			fr_udp_pkt->data[0x00] = 0x00;
+
+                        for(frames = 0; frames < MAX_FRMS_TRACED; frames ++) {
+
+                                sdla_peek(&card->hw, (unsigned long)ptr_trc_el,
+					  (void *)&trc_el.flag,
+					  sizeof(fr_trc_el_t));
+                                if(trc_el.flag == 0x00) {
+                                        break;
+				}
+                                if((card->u.f.trc_bfr_space - buffer_length)
+                                        < sizeof(fpipemon_trc_hdr_t)) { 
+                                        fr_udp_pkt->data[0x00] |= MORE_TRC_DATA;
+                                        break;
+                                }
+
+				fpipemon_trc = 
+					(fpipemon_trc_t *)&fr_udp_pkt->data[buffer_length]; 
+				fpipemon_trc->fpipemon_trc_hdr.status =
+					trc_el.attr;
+                            	fpipemon_trc->fpipemon_trc_hdr.tmstamp =
+					trc_el.tmstamp;
+                            	fpipemon_trc->fpipemon_trc_hdr.length = 
+					trc_el.length;
+
+                                if(!trc_el.offset || !trc_el.length) {
+
+                                     	fpipemon_trc->fpipemon_trc_hdr.data_passed = 0x00;
+
+ 				}else if((trc_el.length + sizeof(fpipemon_trc_hdr_t) + 1) >
+					(card->u.f.trc_bfr_space - buffer_length)){
+
+                                        fpipemon_trc->fpipemon_trc_hdr.data_passed = 0x00;
+                                    	fr_udp_pkt->data[0x00] |= MORE_TRC_DATA;
+ 
+                                }else {
+                                        fpipemon_trc->fpipemon_trc_hdr.data_passed = 0x01;
+                                        sdla_peek(&card->hw, trc_el.offset,
+                           			  fpipemon_trc->data,
+						  trc_el.length);
+				}			
+
+                                trc_el.flag = 0x00;
+                                sdla_poke(&card->hw, (unsigned long)ptr_trc_el,
+					  &trc_el.flag, 1);
+                               
+				ptr_trc_el ++;
+				if((void *)ptr_trc_el > card->u.f.trc_el_last)
+					ptr_trc_el = (void*)card->u.f.trc_el_base;
+
+				buffer_length += sizeof(fpipemon_trc_hdr_t);
+                               	if(fpipemon_trc->fpipemon_trc_hdr.data_passed) {
+                               		buffer_length += trc_el.length;
+                               	}
+
+				if(fr_udp_pkt->data[0x00] & MORE_TRC_DATA) {
+					break;
+				}
+                        }
+                      
+			if(frames == MAX_FRMS_TRACED) {
+                        	fr_udp_pkt->data[0x00] |= MORE_TRC_DATA;
+			}
+             
+			card->u.f.curr_trc_el = (void *)ptr_trc_el;
+
+                        /* set the total number of frames passed */
+			fr_udp_pkt->data[0x00] |=
+				((frames << 1) & (MAX_FRMS_TRACED << 1));
+
+                        /* set the data length and return code */
+			fr_udp_pkt->cblock.length = mbox->cmd.length = buffer_length;
+                        fr_udp_pkt->cblock.result = 0;
+                        break;
+
+                case FPIPE_FT1_READ_STATUS:
+			sdla_peek(&card->hw, 0xF020,
+				&fr_udp_pkt->data[0x00] , 2);
+			fr_udp_pkt->cblock.length = mbox->cmd.length = 2;
+			fr_udp_pkt->cblock.result = 0;
+			break;
+
+		case FPIPE_FLUSH_DRIVER_STATS:
+			init_chan_statistics(chan);
+			init_global_statistics(card);
+			mbox->cmd.length = 0;
+			break;
+		
+		case FPIPE_ROUTER_UP_TIME:
+			do_gettimeofday(&tv);
+			chan->router_up_time = tv.tv_sec - 
+						chan->router_start_time;
+    	                *(unsigned long *)&fr_udp_pkt->data =
+    				chan->router_up_time;	
+			mbox->cmd.length = fr_udp_pkt->cblock.length = 4;
+			fr_udp_pkt->cblock.result = 0;
+			break;
+
+		case FPIPE_DRIVER_STAT_IFSEND:
+			memcpy(fr_udp_pkt->data,
+				&chan->drvstats_if_send.if_send_entry,
+				sizeof(if_send_stat_t));
+			mbox->cmd.length = fr_udp_pkt->cblock.length =sizeof(if_send_stat_t);	
+			fr_udp_pkt->cblock.result = 0;
+			break;
+	
+		case FPIPE_DRIVER_STAT_INTR:
+
+			memcpy(fr_udp_pkt->data,
+                                &card->statistics.isr_entry,
+                                sizeof(global_stats_t));
+
+                        memcpy(&fr_udp_pkt->data[sizeof(global_stats_t)],
+                                &chan->drvstats_rx_intr.rx_intr_no_socket,
+                                sizeof(rx_intr_stat_t));
+
+			mbox->cmd.length = fr_udp_pkt->cblock.length = 
+					sizeof(global_stats_t) +
+					sizeof(rx_intr_stat_t);
+			fr_udp_pkt->cblock.result = 0;
+			break;
+
+		case FPIPE_DRIVER_STAT_GEN:
+                        memcpy(fr_udp_pkt->data,
+                                &chan->drvstats_gen.UDP_PIPE_mgmt_kmalloc_err,
+                                sizeof(pipe_mgmt_stat_t));
+
+                        memcpy(&fr_udp_pkt->data[sizeof(pipe_mgmt_stat_t)],
+                               &card->statistics, sizeof(global_stats_t));
+
+                        mbox->cmd.length = fr_udp_pkt->cblock.length = sizeof(global_stats_t)+
+                                                     sizeof(rx_intr_stat_t);
+			fr_udp_pkt->cblock.result = 0;
+                        break;
+
+
+		case FR_FT1_STATUS_CTRL:
+			if(fr_udp_pkt->data[0] == 1) {
+				if(rCount++ != 0 ){
+					fr_udp_pkt->cblock.result = 0;
+					mbox->cmd.length = 1;
+					break;
+				} 
+			}
+           
+			/* Disable FT1 MONITOR STATUS */
+                        if(fr_udp_pkt->data[0] == 0) {
+				if( --rCount != 0) {
+                                        fr_udp_pkt->cblock.result = 0;
+					mbox->cmd.length = 1;
+					break;
+				} 
+			}  
+			goto udp_mgmt_dflt;
+
+			
+		default:
+udp_mgmt_dflt:
+ 			do {
+				memcpy(&mbox->cmd,
+					&fr_udp_pkt->cblock.command,
+					sizeof(fr_cmd_t));
+				if(mbox->cmd.length) {
+					memcpy(&mbox->data,
+						(char *)fr_udp_pkt->data,
+						mbox->cmd.length);
+				}
+ 				
+				err = sdla_exec(mbox) ? mbox->cmd.result : 
+					CMD_TIMEOUT;
+			} while (err && c_retry-- && fr_event(card, err, mbox));
+
+			if(!err)
+				chan->drvstats_gen.
+					UDP_PIPE_mgmt_adptr_cmnd_OK ++;
+			else
+                                chan->drvstats_gen.
+					UDP_PIPE_mgmt_adptr_cmnd_timeout ++;
+
+       	                /* copy the result back to our buffer */
+			memcpy(&fr_udp_pkt->cblock.command,
+				&mbox->cmd, sizeof(fr_cmd_t));
+
+                       	if(mbox->cmd.length) {
+                               	memcpy(&fr_udp_pkt->data,
+					&mbox->data, mbox->cmd.length);
+			}
+		} 
+        }
+   
+        /* Fill UDP TTL */
+        fr_udp_pkt->ip_pkt.ttl = card->wandev.ttl;
+        len = reply_udp(card->u.f.udp_pkt_data, mbox->cmd.length);
+
+        if(udp_pkt_src == UDP_PKT_FRM_NETWORK) {
+
+		chan->fr_header_len=2;
+		chan->fr_header[0]=Q922_UI;
+		chan->fr_header[1]=NLPID_IP;
+			
+		err = fr_send_data_header(card, dlci, 0, len, 
+			card->u.f.udp_pkt_data,chan->fr_header_len);
+		if (err){ 
+			chan->drvstats_gen.UDP_PIPE_mgmt_adptr_send_passed ++;
+		}else{
+			chan->drvstats_gen.UDP_PIPE_mgmt_adptr_send_failed ++;
+		}
+		
+	} else {
+		/* Allocate socket buffer */
+		if((new_skb = dev_alloc_skb(len)) != NULL) {
+
+			/* copy data into new_skb */
+			buf = skb_put(new_skb, len);
+			memcpy(buf, card->u.f.udp_pkt_data, len);
+        
+			chan->drvstats_gen.
+				UDP_PIPE_mgmt_passed_to_stack ++;
+			new_skb->dev = dev;
+			new_skb->protocol = htons(ETH_P_IP);
+			new_skb->mac.raw = new_skb->data;
+			netif_rx(new_skb);
+            	
+		} else {
+			chan->drvstats_gen.UDP_PIPE_mgmt_no_socket ++;
+			printk(KERN_INFO 
+			"%s: UDP mgmt cmnd, no socket buffers available!\n", 
+			card->devname);
+            	}
+        }
+
+	card->u.f.udp_pkt_lgth = 0;
+
+	return 1;
+}
+
+/*==============================================================================
+ * Send Inverse ARP Request
+ */
+
+int send_inarp_request(sdla_t *card, struct net_device *dev)
+{
+	int err=0;
+
+	arphdr_1490_t *ArpPacket;
+	arphdr_fr_t *arphdr;
+	fr_channel_t *chan = dev->priv;
+	struct in_device *in_dev;
+
+	in_dev = dev->ip_ptr;
+
+	if(in_dev != NULL ) {	
+
+		ArpPacket = kmalloc(sizeof(arphdr_1490_t) + sizeof(arphdr_fr_t), GFP_ATOMIC);
+		/* SNAP Header indicating ARP */
+		ArpPacket->control	= 0x03;
+		ArpPacket->pad		= 0x00;
+		ArpPacket->NLPID	= 0x80;
+		ArpPacket->OUI[0]	= 0;
+		ArpPacket->OUI[1]	= 0;
+		ArpPacket->OUI[2]	= 0;
+		ArpPacket->PID		= 0x0608;
+
+		arphdr = (arphdr_fr_t *)(ArpPacket + 1); // Go to ARP Packet
+
+		/* InARP request */		
+		arphdr->ar_hrd = 0x0F00;	/* Frame Relay HW type */
+		arphdr->ar_pro = 0x0008;	/* IP Protocol	       */
+		arphdr->ar_hln = 2;		/* HW addr length      */
+		arphdr->ar_pln = 4;		/* IP addr length      */
+		arphdr->ar_op = htons(0x08);	/* InARP Request       */
+		arphdr->ar_sha = 0; 		/* src HW DLCI - Doesn't matter */
+		if(in_dev->ifa_list != NULL)
+			arphdr->ar_sip = in_dev->ifa_list->ifa_local;  /* Local Address       */else
+			arphdr->ar_sip = 0;
+		arphdr->ar_tha = 0; 		/* dst HW DLCI - Doesn't matter */
+		arphdr->ar_tip = 0;		/* Remote Address -- what we want */
+
+		err = fr_send(card, chan->dlci, 0, sizeof(arphdr_1490_t) + sizeof(arphdr_fr_t),
+		   			(void *)ArpPacket);
+
+		if (!err){
+			printk(KERN_INFO "\n%s: Sending InARP request on DLCI %d.\n", 
+				card->devname, chan->dlci);
+			clear_bit(ARP_CRIT,&card->wandev.critical);
+		}
+
+		kfree(ArpPacket);
+	}else{
+		printk(KERN_INFO "%s: INARP ERROR: %s doesn't have a local IP address!\n",
+				card->devname,dev->name);
+		return 1;
+	}
+
+	return 0;
+}
+	
+
+/*==============================================================================
+ * Check packet for ARP Type
+ */
+
+int is_arp(void *buf)
+{
+	arphdr_1490_t *arphdr = (arphdr_1490_t *)buf;
+	
+	if (arphdr->pad   == 0x00  &&
+	    arphdr->NLPID == 0x80  &&
+	    arphdr->PID   == 0x0608) 
+		return 1;
+	else return 0;
+}
+
+/*==============================================================================
+ * Process ARP Packet Type
+ */
+
+int process_ARP(arphdr_1490_t *ArpPacket, sdla_t *card, struct net_device* dev)
+{
+
+
+	arphdr_fr_t *arphdr = (arphdr_fr_t *)(ArpPacket + 1); /* Skip header */
+	fr_rx_buf_ctl_t* frbuf = card->rxmb;
+	struct in_device *in_dev;
+	fr_channel_t *chan = dev->priv;		
+	
+	/* Before we transmit ARP packet, we must check 
+	 * to see that we are not currently transmitting a 
+	 * frame (in 'if_send()') and that we are not 
+	 * already in a 'delayed transmit' state. */
+	if (check_tx_status(card,dev)){
+		if (net_ratelimit()){ 	
+			printk(KERN_INFO "%s: Disabling comminication to process ARP\n",
+					card->devname);
+		}
+		set_bit(ARP_CRIT,&card->wandev.critical);
+		return 0;
+	}
+
+	in_dev = dev->ip_ptr;
+
+	/* Check that IP addresses exist for our network address */
+	if (in_dev == NULL || in_dev->ifa_list == NULL) 
+		return -1;
+
+	switch (ntohs(arphdr->ar_op)) {
+
+	case 0x08:  // Inverse ARP request  -- Send Reply, add route.
+			
+		/* Check for valid Address */
+		printk(KERN_INFO "%s: Recvd PtP addr -InArp Req: %u.%u.%u.%u\n", 
+			card->devname, NIPQUAD(arphdr->ar_sip));
+
+
+		/* Check that the network address is the same as ours, only
+                 * if the netowrk mask is not 255.255.255.255. Otherwise
+                 * this check would not make sense */
+
+		if (in_dev->ifa_list->ifa_mask != 0xFFFFFFFF && 
+		    (in_dev->ifa_list->ifa_mask & arphdr->ar_sip) != 
+		    (in_dev->ifa_list->ifa_mask & in_dev->ifa_list->ifa_local)){
+			printk(KERN_INFO 
+				"%s: Invalid PtP address. %u.%u.%u.%u  InARP ignored.\n", 
+					card->devname,NIPQUAD(arphdr->ar_sip));
+
+			printk(KERN_INFO "%s: mask %u.%u.%u.%u\n", 
+				card->devname, NIPQUAD(in_dev->ifa_list->ifa_mask));
+				printk(KERN_INFO "%s: local %u.%u.%u.%u\n", 
+				card->devname,NIPQUAD(in_dev->ifa_list->ifa_local));
+			return -1;
+		}
+
+		if (in_dev->ifa_list->ifa_local == arphdr->ar_sip){
+			printk(KERN_INFO 
+				"%s: Local addr = PtP addr.  InARP ignored.\n", 
+					card->devname);
+			return -1;
+		}
+	
+		arphdr->ar_op = htons(0x09);	/* InARP Reply */
+
+		/* Set addresses */
+		arphdr->ar_tip = arphdr->ar_sip;
+		arphdr->ar_sip = in_dev->ifa_list->ifa_local;
+
+		chan->ip_local = in_dev->ifa_list->ifa_local;
+		chan->ip_remote = arphdr->ar_sip;
+
+		fr_send(card, frbuf->dlci, 0, frbuf->length, (void *)ArpPacket);
+
+		if (test_bit(ARP_CRIT,&card->wandev.critical)){
+			if (net_ratelimit()){ 	
+				printk(KERN_INFO "%s: ARP Processed Enabling Communication!\n",
+					card->devname);
+			}
+		}
+		clear_bit(ARP_CRIT,&card->wandev.critical);
+		
+		chan->ip_local = in_dev->ifa_list->ifa_local;
+		chan->ip_remote = arphdr->ar_sip;
+
+		/* Add Route Flag */
+		/* The route will be added in the polling routine so
+		   that it is not interrupt context. */
+
+		chan->route_flag = ADD_ROUTE;
+		trigger_fr_poll (dev);
+
+		break;
+
+	case 0x09:  // Inverse ARP reply
+
+		/* Check for valid Address */
+		printk(KERN_INFO "%s: Recvd PtP addr %u.%u.%u.%u -InArp Reply\n", 
+				card->devname, NIPQUAD(arphdr->ar_sip));
+
+
+		/* Compare network addresses, only if network mask
+                 * is not 255.255.255.255  It would not make sense
+                 * to perform this test if the mask was all 1's */
+
+		if (in_dev->ifa_list->ifa_mask != 0xffffffff &&
+		    (in_dev->ifa_list->ifa_mask & arphdr->ar_sip) != 
+			(in_dev->ifa_list->ifa_mask & in_dev->ifa_list->ifa_local)) {
+
+			printk(KERN_INFO "%s: Invalid PtP address.  InARP ignored.\n", 
+					card->devname);
+			return -1;
+		}
+
+		/* Make sure that the received IP address is not
+                 * the same as our own local address */
+		if (in_dev->ifa_list->ifa_local == arphdr->ar_sip) {
+			printk(KERN_INFO "%s: Local addr = PtP addr.  InARP ignored.\n", 
+				card->devname);
+			return -1;
+		}			
+
+		chan->ip_local  = in_dev->ifa_list->ifa_local;
+		chan->ip_remote = arphdr->ar_sip;
+
+		/* Add Route Flag */
+		/* The route will be added in the polling routine so
+		   that it is not interrupt context. */
+
+		chan->route_flag = ADD_ROUTE;
+		chan->inarp = INARP_CONFIGURED;
+		trigger_fr_poll(dev);
+		
+		break;
+	default:
+		break; // ARP's and RARP's -- Shouldn't happen.
+	}
+
+	return 0;	
+}
+
+
+/*============================================================
+ * trigger_fr_arp
+ *
+ * Description:
+ * 	Add an fr_arp() task into a arp
+ *      timer handler for a specific dlci/interface.  
+ *      This will kick the fr_arp() routine 
+ *      within the specified time interval. 
+ *
+ * Usage:
+ * 	This timer is used to send ARP requests at
+ *      certain time intervals. 
+ * 	Called by an interrupt to request an action
+ *      at a later date.
+ */	
+
+static void trigger_fr_arp(struct net_device *dev)
+{
+	fr_channel_t* chan = dev->priv;
+
+	mod_timer(&chan->fr_arp_timer, jiffies + chan->inarp_interval * HZ);
+	return;
+}
+
+
+
+/*==============================================================================
+ * ARP Request Action
+ *
+ *	This funciton is called by timer interrupt to send an arp request
+ *      to the remote end.
+ */
+
+static void fr_arp (unsigned long data)
+{
+	struct net_device *dev = (struct net_device *)data;
+	fr_channel_t *chan = dev->priv;
+	volatile sdla_t *card = chan->card;
+	fr508_flags_t* flags = card->flags;
+
+	/* Send ARP packets for all devs' until
+         * ARP state changes to CONFIGURED */
+
+	if (chan->inarp == INARP_REQUEST &&
+	    chan->common.state == WAN_CONNECTED && 
+	    card->wandev.state == WAN_CONNECTED){
+		set_bit(0,&chan->inarp_ready);
+		card->u.f.timer_int_enabled |= TMR_INT_ENABLED_ARP;
+		flags->imask |= FR_INTR_TIMER;	
+	}
+ 
+	return;
+}
+	
+
+/*==============================================================================
+ * Perform the Interrupt Test by running the READ_CODE_VERSION command MAX_INTR_
+ * TEST_COUNTER times.
+ */
+static int intr_test( sdla_t* card )
+{
+	fr_mbox_t* mb = card->mbox;
+	int err,i;
+
+        err = fr_set_intr_mode(card, FR_INTR_READY, card->wandev.mtu, 0 );
+	
+	if (err == CMD_OK) {
+
+		for ( i = 0; i < MAX_INTR_TEST_COUNTER; i++ ) {
+ 			/* Run command READ_CODE_VERSION */
+			mb->cmd.length  = 0;
+			mb->cmd.command = FR_READ_CODE_VERSION;
+			err = sdla_exec(mb) ? mb->cmd.result : CMD_TIMEOUT;
+			if (err != CMD_OK) 
+				fr_event(card, err, mb);
+		}
+	
+	} else {
+		return err;	
+	}
+
+	err = fr_set_intr_mode( card, 0, card->wandev.mtu, 0 );
+
+	if( err != CMD_OK ) 
+		return err;
+
+	return 0;
+}
+
+/*==============================================================================
+ * Determine what type of UDP call it is. FPIPE8ND ?
+ */
+static int udp_pkt_type( struct sk_buff *skb, sdla_t* card )
+{
+	fr_udp_pkt_t *fr_udp_pkt = (fr_udp_pkt_t *)skb->data;
+
+	/* Quick HACK */
+	
+	
+        if((fr_udp_pkt->ip_pkt.protocol == UDPMGMT_UDP_PROTOCOL) &&
+		(fr_udp_pkt->ip_pkt.ver_inet_hdr_length == 0x45) &&
+		(fr_udp_pkt->udp_pkt.udp_dst_port == 
+		ntohs(card->wandev.udp_port)) &&
+		(fr_udp_pkt->wp_mgmt.request_reply == 
+		UDPMGMT_REQUEST)) {
+                        if(!strncmp(fr_udp_pkt->wp_mgmt.signature,
+                                UDPMGMT_FPIPE_SIGNATURE, 8)){
+                                return UDP_FPIPE_TYPE;
+			}
+	}
+        return UDP_INVALID_TYPE;
+}
+
+
+/*==============================================================================
+ * Initializes the Statistics values in the fr_channel structure.
+ */
+void init_chan_statistics( fr_channel_t* chan)
+{
+        memset(&chan->drvstats_if_send.if_send_entry, 0,
+		sizeof(if_send_stat_t));
+        memset(&chan->drvstats_rx_intr.rx_intr_no_socket, 0,
+                sizeof(rx_intr_stat_t));
+        memset(&chan->drvstats_gen.UDP_PIPE_mgmt_kmalloc_err, 0,
+                sizeof(pipe_mgmt_stat_t));
+}
+	
+/*==============================================================================
+ * Initializes the Statistics values in the Sdla_t structure.
+ */
+void init_global_statistics( sdla_t* card )
+{
+	/* Intialize global statistics for a card */
+        memset(&card->statistics.isr_entry, 0, sizeof(global_stats_t));
+}
+
+static void read_DLCI_IB_mapping( sdla_t* card, fr_channel_t* chan )
+{
+	fr_mbox_t* mbox = card->mbox;
+	int retry = MAX_CMD_RETRY;	
+	dlci_IB_mapping_t* result; 
+	int err, counter, found;	
+
+	do {
+		mbox->cmd.command = FR_READ_DLCI_IB_MAPPING;
+		mbox->cmd.length = 0;	
+		err = sdla_exec(mbox) ? mbox->cmd.result : CMD_TIMEOUT;
+	} while (err && retry-- && fr_event(card, err, mbox));
+
+	if( mbox->cmd.result != 0){
+		printk(KERN_INFO "%s: Read DLCI IB Mapping failed\n", 
+			chan->name);
+	}
+
+	counter = mbox->cmd.length / sizeof(dlci_IB_mapping_t);
+	result = (void *)mbox->data;
+	
+	found = 0;
+	for (; counter; --counter, ++result) {
+		if ( result->dlci == chan->dlci ) {
+			chan->IB_addr = result->addr_value;
+			if(card->hw.type == SDLA_S514){
+	             		chan->dlci_int_interface =
+					(void*)(card->hw.dpmbase +
+					chan->IB_addr);
+       			}else{ 
+				chan->dlci_int_interface = 
+					(void*)(card->hw.dpmbase + 
+					(chan->IB_addr & 0x00001FFF));
+
+			}
+			found = 1;
+			break;	
+		} 
+	}
+	if (!found)
+		printk( KERN_INFO "%s: DLCI %d not found by IB MAPPING cmd\n", 
+		card->devname, chan->dlci);
+}
+
+
+
+void s508_s514_lock(sdla_t *card, unsigned long *smp_flags)
+{
+	if (card->hw.type != SDLA_S514){
+
+		spin_lock_irqsave(&card->wandev.lock, *smp_flags);
+	}else{
+		spin_lock(&card->u.f.if_send_lock);
+	}
+	return;
+}
+
+
+void s508_s514_unlock(sdla_t *card, unsigned long *smp_flags)
+{
+	if (card->hw.type != SDLA_S514){
+
+		spin_unlock_irqrestore (&card->wandev.lock, *smp_flags);
+	}else{
+		spin_unlock(&card->u.f.if_send_lock);
+	}
+	return;
+}
+
+
+
+/*----------------------------------------------------------------------
+                  RECEIVE INTERRUPT: BOTTOM HALF HANDLERS 
+ ----------------------------------------------------------------------*/
+
+
+/*========================================================
+ * bh_enqueue
+ *
+ * Description:
+ *	Insert a received packet into a circular
+ *      rx queue.  This packet will be picked up 
+ *      by fr_bh() and sent up the stack to the
+ *      user.
+ *       	
+ * Usage: 
+ *	This function is called by rx interrupt,
+ *      in API mode.
+ *
+ */
+
+static int bh_enqueue(struct net_device *dev, struct sk_buff *skb)
+{
+	/* Check for full */
+	fr_channel_t* chan = dev->priv;
+	sdla_t *card = chan->card;
+
+
+	if (atomic_read(&chan->bh_buff_used) == MAX_BH_BUFF){
+		++card->wandev.stats.rx_dropped;
+		dev_kfree_skb_any(skb);
+		return 1; 
+	}
+
+	((bh_data_t *)&chan->bh_head[chan->bh_write])->skb = skb;
+
+	if (chan->bh_write == (MAX_BH_BUFF-1)){
+		chan->bh_write=0;
+	}else{
+		++chan->bh_write;
+	}
+
+	atomic_inc(&chan->bh_buff_used);
+
+	return 0;
+}
+
+
+/*========================================================
+ * trigger_fr_bh
+ *
+ * Description:
+ * 	Kick the fr_bh() handler
+ *
+ * Usage:
+ *	rx interrupt calls this function during
+ *      the API mode. 
+ */
+
+static void trigger_fr_bh (fr_channel_t *chan)
+{
+	if (!test_and_set_bit(0,&chan->tq_working)){
+		wanpipe_queue_work(&chan->common.wanpipe_work);
+	}
+}
+
+
+/*========================================================
+ * fr_bh
+ *
+ * Description:
+ *	Frame relay receive BH handler. 
+ *	Dequeue data from the BH circular 
+ *	buffer and pass it up the API sock.
+ *       	
+ * Rationale: 
+ *	This fuction is used to offload the 
+ *	rx_interrupt during API operation mode.  
+ *	The fr_bh() function executes for each 
+ *	dlci/interface.  
+ * 
+ *      Once receive interrupt copies data from the
+ *      card into an skb buffer, the skb buffer
+ *  	is appended to a circular BH buffer.
+ *  	Then the interrupt kicks fr_bh() to finish the
+ *      job at a later time (not within the interrupt).
+ *       
+ * Usage:
+ * 	Interrupts use this to defer a task to 
+ *      a polling routine.
+ *
+ */	
+
+static void fr_bh(struct net_device * dev)
+{
+	fr_channel_t* chan = dev->priv;
+	sdla_t *card = chan->card;
+	struct sk_buff *skb;
+
+	if (atomic_read(&chan->bh_buff_used) == 0){
+		clear_bit(0, &chan->tq_working);
+		return;
+	}
+
+	while (atomic_read(&chan->bh_buff_used)){
+
+		if (chan->common.sk == NULL || chan->common.func == NULL){
+			clear_bit(0, &chan->tq_working);
+			return;
+		}
+
+		skb  = ((bh_data_t *)&chan->bh_head[chan->bh_read])->skb;
+
+		if (skb != NULL){
+
+			if (chan->common.sk == NULL || chan->common.func == NULL){
+				++card->wandev.stats.rx_dropped;
+				++chan->ifstats.rx_dropped;
+				dev_kfree_skb_any(skb);
+				fr_bh_cleanup(dev);
+				continue;
+			}
+
+			if (chan->common.func(skb,dev,chan->common.sk) != 0){
+				/* Sock full cannot send, queue us for
+                                 * another try */
+				atomic_set(&chan->common.receive_block,1);
+				return;
+			}else{
+				fr_bh_cleanup(dev);
+			}
+		}else{
+			fr_bh_cleanup(dev);
+		}
+	}	
+	clear_bit(0, &chan->tq_working);
+
+	return;
+}
+
+static int fr_bh_cleanup(struct net_device *dev)
+{
+	fr_channel_t* chan = dev->priv;
+
+	((bh_data_t *)&chan->bh_head[chan->bh_read])->skb = NULL;
+
+	if (chan->bh_read == (MAX_BH_BUFF-1)){
+		chan->bh_read=0;
+	}else{
+		++chan->bh_read;	
+	}
+
+	atomic_dec(&chan->bh_buff_used);
+	return 0;
+}
+
+
+/*----------------------------------------------------------------------
+               POLL BH HANDLERS AND KICK ROUTINES 
+ ----------------------------------------------------------------------*/
+
+/*============================================================
+ * trigger_fr_poll
+ *
+ * Description:
+ * 	Add a fr_poll() task into a tq_scheduler bh handler
+ *      for a specific dlci/interface.  This will kick
+ *      the fr_poll() routine at a later time. 
+ *
+ * Usage:
+ * 	Interrupts use this to defer a taks to 
+ *      a polling routine.
+ *
+ */	
+static void trigger_fr_poll(struct net_device *dev)
+{
+	fr_channel_t* chan = dev->priv;
+	schedule_work(&chan->fr_poll_work);
+	return;
+}
+
+
+/*============================================================
+ * fr_poll
+ *	
+ * Rationale:
+ * 	We cannot manipulate the routing tables, or
+ *      ip addresses withing the interrupt. Therefore
+ *      we must perform such actons outside an interrupt 
+ *      at a later time. 
+ *
+ * Description:	
+ *	Frame relay polling routine, responsible for 
+ *     	shutting down interfaces upon disconnect
+ *     	and adding/removing routes. 
+ *      
+ * Usage:        
+ * 	This function is executed for each frame relay
+ * 	dlci/interface through a tq_schedule bottom half.
+ *      
+ *      trigger_fr_poll() function is used to kick
+ *      the fr_poll routine.  
+ */
+
+static void fr_poll(struct net_device *dev)
+{
+
+	fr_channel_t* chan;
+	sdla_t *card;
+	u8 check_gateway=0;
+
+	if (!dev || (chan = dev->priv) == NULL)
+		return;
+
+	card = chan->card;
+	
+	/* (Re)Configuraiton is in progress, stop what you are 
+	 * doing and get out */
+	if (test_bit(PERI_CRIT,&card->wandev.critical)){
+		return;
+	}
+
+	switch (chan->common.state){
+
+	case WAN_DISCONNECTED:
+
+		if (test_bit(DYN_OPT_ON,&chan->interface_down) &&
+		    !test_bit(DEV_DOWN, &chan->interface_down) &&
+		    dev->flags&IFF_UP){
+
+			printk(KERN_INFO "%s: Interface %s is Down.\n", 
+				card->devname,dev->name);
+			change_dev_flags(dev,dev->flags&~IFF_UP);
+			set_bit(DEV_DOWN, &chan->interface_down);
+			chan->route_flag = NO_ROUTE;
+			
+		}else{
+			if (chan->inarp != INARP_NONE)
+				process_route(dev);	
+		}
+		break;
+
+	case WAN_CONNECTED:
+
+		if (test_bit(DYN_OPT_ON,&chan->interface_down) &&
+		    test_bit(DEV_DOWN, &chan->interface_down) &&
+		    !(dev->flags&IFF_UP)){
+
+			printk(KERN_INFO "%s: Interface %s is Up.\n", 
+					card->devname,dev->name);
+
+			change_dev_flags(dev,dev->flags|IFF_UP);
+			clear_bit(DEV_DOWN, &chan->interface_down);
+			check_gateway=1;
+		}
+
+		if (chan->inarp != INARP_NONE){
+			process_route(dev);
+			check_gateway=1;
+		}
+
+		if (chan->gateway && check_gateway)
+			add_gateway(card,dev);
+
+		break;
+
+	}
+
+	return;	
+}
+
+/*==============================================================
+ * check_tx_status
+ *
+ * Rationale:
+ *	We cannot transmit from an interrupt while
+ *      the if_send is transmitting data.  Therefore,
+ *      we must check whether the tx buffers are
+ *      begin used, before we transmit from an
+ *      interrupt.	
+ * 
+ * Description:	
+ *	Checks whether it's safe to use the transmit 
+ *      buffers. 
+ *
+ * Usage:
+ * 	ARP and UDP handling routines use this function
+ *      because, they need to transmit data during
+ *      an interrupt.
+ */
+
+static int check_tx_status(sdla_t *card, struct net_device *dev)
+{
+
+	if (card->hw.type == SDLA_S514){
+		if (test_bit(SEND_CRIT, (void*)&card->wandev.critical) ||
+			test_bit(SEND_TXIRQ_CRIT, (void*)&card->wandev.critical)) {
+			return 1;
+		}
+	}
+
+	if (netif_queue_stopped(dev) || (card->u.f.tx_interrupts_pending))
+     		return 1; 
+
+	return 0;
+}
+
+/*===============================================================
+ * move_dev_to_next
+ *  
+ * Description:
+ *	Move the dev pointer to the next location in the
+ *      link list.  Check if we are at the end of the 
+ *      list, if so start from the begining.
+ *
+ * Usage:
+ * 	Timer interrupt uses this function to efficiently
+ *      step through the devices that need to send ARP data.
+ *
+ */
+
+struct net_device *move_dev_to_next(sdla_t *card, struct net_device *dev)
+{
+	if (card->wandev.new_if_cnt != 1){
+		if (!*((struct net_device **)dev->priv))
+			return card->wandev.dev;
+		else
+			return *((struct net_device **)dev->priv);
+	}
+	return dev;
+}
+
+/*==============================================================
+ * trigger_config_fr
+ *
+ * Rationale:
+ *	All commands must be performed inside of a  
+ *      interrupt.   
+ *
+ * Description:
+ *	Kick the config_fr() routine throught the
+ *      timer interrupt.
+ */
+
+
+static void trigger_config_fr (sdla_t *card)
+{
+	fr508_flags_t* flags = card->flags;
+
+	card->u.f.timer_int_enabled |= TMR_INT_ENABLED_CONFIG;
+	flags->imask |= FR_INTR_TIMER;
+}
+
+
+/*==============================================================
+ * config_fr
+ *
+ * Rationale:
+ * 	All commands must be performed inside of a  
+ *      interrupt.  
+ &
+ * Description:	
+ * 	Configure a DLCI. This function is executed
+ *      by a timer_interrupt.  The if_open() function
+ *      triggers it.
+ *
+ * Usage:
+ *	new_if() collects all data necessary to
+ *      configure the DLCI. It sets the chan->dlci_ready 
+ *      bit.  When the if_open() function is executed
+ *      it checks this bit, and if its set it triggers
+ *      the timer interrupt to execute the config_fr()
+ *      function.
+ */
+
+static void config_fr (sdla_t *card)
+{
+	struct net_device *dev;
+	fr_channel_t *chan;
+
+	for (dev = card->wandev.dev; dev;
+	     dev = *((struct net_device **)dev->priv)) {
+	
+		if ((chan=dev->priv) == NULL)
+			continue;
+		
+		if (!test_bit(0,&chan->config_dlci))
+			continue;
+
+		clear_bit(0,&chan->config_dlci);
+
+		/* If signalling is set to NO, then setup 
+        	 * DLCI addresses right away.  Don't have to wait for
+		 * link to connect. 
+		 */
+		if (card->wandev.signalling == WANOPT_NO){
+			printk(KERN_INFO "%s: Signalling set to NO: Mapping DLCI's\n",
+					card->wandev.name);
+			if (fr_init_dlci(card,chan)){
+				printk(KERN_INFO "%s: ERROR: Failed to configure DLCI %i !\n",
+					card->devname, chan->dlci);
+				return;
+			}
+		}
+
+		if (card->wandev.station == WANOPT_CPE) {
+	
+			update_chan_state(dev);	
+			
+			/* CPE: issue full status enquiry */
+			fr_issue_isf(card, FR_ISF_FSE);
+
+		} else {	
+			/* FR switch: activate DLCI(s) */
+	
+			/* For Switch emulation we have to ADD and ACTIVATE
+			 * the DLCI(s) that were configured with the SET_DLCI_
+			 * CONFIGURATION command. Add and Activate will fail if
+			 * DLCI specified is not included in the list.
+			 *
+			 * Also If_open is called once for each interface. But
+			 * it does not get in here for all the interface. So
+		 	 * we have to pass the entire list of DLCI(s) to add 
+			 * activate routines.  
+			 */ 
+			
+			if (!check_dlci_config (card, chan)){
+				fr_add_dlci(card, chan->dlci);
+				fr_activate_dlci(card, chan->dlci);
+			}
+		}
+
+		card->u.f.dlci_to_dev_map[chan->dlci] = dev;
+	}
+	return;
+}
+
+
+/*==============================================================
+ * config_fr
+ *
+ * Rationale:
+ *	All commands must be executed during an interrupt.
+ * 
+ * Description:	
+ *	Trigger uncofig_fr() function through 
+ *      the timer interrupt.
+ *
+ */
+
+static void trigger_unconfig_fr(struct net_device *dev)
+{
+	fr_channel_t *chan = dev->priv;
+	volatile sdla_t *card = chan->card;
+	u32 timeout;
+	fr508_flags_t* flags = card->flags;
+	int reset_critical=0;
+	
+	if (test_bit(PERI_CRIT,(void*)&card->wandev.critical)){
+		clear_bit(PERI_CRIT,(void*)&card->wandev.critical);
+		reset_critical=1;
+	}
+		
+	/* run unconfig_dlci() function 
+         * throught the timer interrupt */
+	set_bit(0,(void*)&chan->unconfig_dlci);
+	card->u.f.timer_int_enabled |= TMR_INT_ENABLED_UNCONFIG;
+	flags->imask |= FR_INTR_TIMER;
+
+	/* Wait for the command to complete */
+	timeout = jiffies;
+     	for(;;) {
+
+		if(!(card->u.f.timer_int_enabled & TMR_INT_ENABLED_UNCONFIG))
+			break;
+
+             	if ((jiffies - timeout) > (1 * HZ)){
+    			card->u.f.timer_int_enabled &= ~TMR_INT_ENABLED_UNCONFIG;
+			printk(KERN_INFO "%s: Failed to delete DLCI %i\n",
+				card->devname,chan->dlci);
+ 			break;
+		}
+	}
+
+	if (reset_critical){
+		set_bit(PERI_CRIT,(void*)&card->wandev.critical);
+	}
+}
+
+/*==============================================================
+ * unconfig_fr
+ *
+ * Rationale:
+ *	All commands must be executed during an interrupt.
+ * 
+ * Description:	
+ *	Remove the dlci from firmware.
+ *	This funciton is used in NODE shutdown.
+ */
+
+static void unconfig_fr (sdla_t *card)
+{
+	struct net_device *dev;
+	fr_channel_t *chan;
+
+	for (dev = card->wandev.dev; dev;
+	     dev = *((struct net_device **)dev->priv)){
+	
+		if ((chan=dev->priv) == NULL)
+			continue;
+		
+		if (!test_bit(0,&chan->unconfig_dlci))
+			continue;
+
+		clear_bit(0,&chan->unconfig_dlci);
+
+		if (card->wandev.station == WANOPT_NODE){
+			printk(KERN_INFO "%s: Unconfiguring DLCI %i\n",
+					card->devname,chan->dlci);
+			fr_delete_dlci(card,chan->dlci);
+		}
+		card->u.f.dlci_to_dev_map[chan->dlci] = NULL;
+	}
+}
+
+static int setup_fr_header(struct sk_buff **skb_orig, struct net_device* dev,
+			   char op_mode)
+{
+	struct sk_buff *skb = *skb_orig;
+	fr_channel_t *chan=dev->priv;
+
+	if (op_mode == WANPIPE){
+
+		chan->fr_header[0]=Q922_UI;
+		
+		switch (htons(skb->protocol)){
+			
+		case ETH_P_IP:
+			chan->fr_header[1]=NLPID_IP;
+			break;
+		default:
+			return -EINVAL;
+		}
+			
+		return 2;
+	}
+
+	/* If we are in bridging mode, we must apply
+	 * an Ethernet header */
+	if (op_mode == BRIDGE || op_mode == BRIDGE_NODE){
+
+
+		/* Encapsulate the packet as a bridged Ethernet frame. */
+#ifdef DEBUG
+		printk(KERN_INFO "%s: encapsulating skb for frame relay\n", 
+			dev->name);
+#endif
+		
+		chan->fr_header[0] = 0x03;
+		chan->fr_header[1] = 0x00;
+		chan->fr_header[2] = 0x80;
+		chan->fr_header[3] = 0x00;
+		chan->fr_header[4] = 0x80;
+		chan->fr_header[5] = 0xC2;
+		chan->fr_header[6] = 0x00;
+		chan->fr_header[7] = 0x07;
+
+		/* Yuck. */
+		skb->protocol = ETH_P_802_3;
+		return 8;
+
+	}
+		
+	return 0;
+}
+
+
+static int check_dlci_config (sdla_t *card, fr_channel_t *chan)
+{
+	fr_mbox_t* mbox = card->mbox;
+	int err=0;
+	fr_conf_t *conf=NULL;
+	unsigned short dlci_num = chan->dlci;
+	int dlci_offset=0;
+	struct net_device *dev = NULL;
+	
+	mbox->cmd.command = FR_READ_CONFIG;
+	mbox->cmd.length = 0;
+	mbox->cmd.dlci = dlci_num; 	
+
+	err = sdla_exec(mbox) ? mbox->cmd.result : CMD_TIMEOUT;
+	
+	if (err == CMD_OK){
+		return 0;
+	}
+
+	for (dev = card->wandev.dev; dev;
+	     dev=*((struct net_device **)dev->priv))
+		set_chan_state(dev,WAN_DISCONNECTED);
+	
+	printk(KERN_INFO "DLCI %i Not configured, configuring\n",dlci_num);
+	
+	mbox->cmd.command = FR_COMM_DISABLE;
+	mbox->cmd.length = 0;
+	err = sdla_exec(mbox) ? mbox->cmd.result : CMD_TIMEOUT;
+	if (err != CMD_OK){
+		fr_event(card, err, mbox);
+		return 2;
+	}
+
+	printk(KERN_INFO "Disabled Communications \n");
+	
+	mbox->cmd.command = FR_READ_CONFIG;
+	mbox->cmd.length = 0;
+	mbox->cmd.dlci = 0; 	
+
+	err = sdla_exec(mbox) ? mbox->cmd.result : CMD_TIMEOUT;
+	
+	if (err != CMD_OK){
+		fr_event(card, err, mbox);
+		return 2;
+	}
+	
+	conf = (fr_conf_t *)mbox->data;
+
+	dlci_offset=0;
+	for (dev = card->wandev.dev; dev;
+	     dev = *((struct net_device **)dev->priv)) {
+		fr_channel_t *chan_tmp = dev->priv;
+		conf->dlci[dlci_offset] = chan_tmp->dlci;		
+		dlci_offset++;
+	}
+	
+	printk(KERN_INFO "Got Fr configuration Buffer Length is %x Dlci %i Dlci Off %i\n",
+		mbox->cmd.length,
+		mbox->cmd.length > 0x20 ? conf->dlci[0] : -1, 
+		dlci_offset );
+	
+	mbox->cmd.length = 0x20 + dlci_offset*2;
+
+	mbox->cmd.command = FR_SET_CONFIG;
+	mbox->cmd.dlci = 0; 
+
+	err = sdla_exec(mbox) ? mbox->cmd.result : CMD_TIMEOUT;
+
+	if (err != CMD_OK){
+		fr_event(card, err, mbox);
+		return 2;
+	}
+
+	initialize_rx_tx_buffers (card);
+
+	
+	printk(KERN_INFO "Configuraiton Succeded for new DLCI %i\n",dlci_num);
+
+	if (fr_comm_enable (card)){
+		return 2;
+	}
+
+	printk(KERN_INFO "Enabling Communications \n");
+
+	for (dev = card->wandev.dev; dev;
+	     dev = *((struct net_device **)dev->priv)) {
+		fr_channel_t *chan_tmp = dev->priv;
+		fr_init_dlci(card,chan_tmp);
+		fr_add_dlci(card, chan_tmp->dlci);
+		fr_activate_dlci(card, chan_tmp->dlci);
+	}
+
+	printk(KERN_INFO "END OF CONFIGURAITON %i\n",dlci_num);
+	
+	return 1;
+}
+
+static void initialize_rx_tx_buffers (sdla_t *card)
+{
+	fr_buf_info_t* buf_info;
+	
+	if (card->hw.type == SDLA_S514) {
+	
+                buf_info = (void*)(card->hw.dpmbase + FR_MB_VECTOR +
+			FR508_RXBC_OFFS);
+
+                card->rxmb = (void*)(buf_info->rse_next + card->hw.dpmbase);
+
+                card->u.f.rxmb_base =
+                        (void*)(buf_info->rse_base + card->hw.dpmbase); 
+
+                card->u.f.rxmb_last =
+                        (void*)(buf_info->rse_base +
+                        (buf_info->rse_num - 1) * sizeof(fr_rx_buf_ctl_t) +
+                        card->hw.dpmbase);
+	}else{	
+		buf_info = (void*)(card->hw.dpmbase + FR508_RXBC_OFFS);
+
+		card->rxmb = (void*)(buf_info->rse_next -
+			FR_MB_VECTOR + card->hw.dpmbase);
+		
+		card->u.f.rxmb_base =
+			(void*)(buf_info->rse_base -
+			FR_MB_VECTOR + card->hw.dpmbase);
+		
+		card->u.f.rxmb_last =
+			(void*)(buf_info->rse_base +
+			(buf_info->rse_num - 1) * sizeof(fr_rx_buf_ctl_t) -
+			FR_MB_VECTOR + card->hw.dpmbase);
+	}
+
+	card->u.f.rx_base = buf_info->buf_base;
+	card->u.f.rx_top  = buf_info->buf_top;
+
+	card->u.f.tx_interrupts_pending = 0;
+
+	return;
+}
+
+	
+
+MODULE_LICENSE("GPL");
+
+/****** End *****************************************************************/
diff --git a/drivers/net/wan/sdla_ft1.c b/drivers/net/wan/sdla_ft1.c
new file mode 100644
index 0000000..5e31248
--- /dev/null
+++ b/drivers/net/wan/sdla_ft1.c
@@ -0,0 +1,344 @@
+/*****************************************************************************
+* sdla_chdlc.c	WANPIPE(tm) Multiprotocol WAN Link Driver. Cisco HDLC module.
+*
+* Authors: 	Nenad Corbic <ncorbic@sangoma.com>
+*		Gideon Hack  
+*
+* Copyright:	(c) 1995-1999 Sangoma Technologies Inc.
+*
+*		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.
+* ============================================================================
+* Sep 30, 1999  Nenad Corbic    Fixed dynamic IP and route setup.
+* Sep 23, 1999  Nenad Corbic    Added SMP support, fixed tracing 
+* Sep 13, 1999  Nenad Corbic	Split up Port 0 and 1 into separate devices.
+* Jun 02, 1999  Gideon Hack     Added support for the S514 adapter.
+* Oct 30, 1998	Jaspreet Singh	Added Support for CHDLC API (HDLC STREAMING).
+* Oct 28, 1998	Jaspreet Singh	Added Support for Dual Port CHDLC.
+* Aug 07, 1998	David Fong	Initial version.
+*****************************************************************************/
+
+#include <linux/module.h>
+#include <linux/kernel.h>	/* printk(), and other useful stuff */
+#include <linux/stddef.h>	/* offsetof(), etc. */
+#include <linux/errno.h>	/* return codes */
+#include <linux/string.h>	/* inline memset(), etc. */
+#include <linux/slab.h>		/* kmalloc(), kfree() */
+#include <linux/wanrouter.h>	/* WAN router definitions */
+#include <linux/wanpipe.h>	/* WANPIPE common user API definitions */
+#include <linux/if_arp.h>	/* ARPHRD_* defines */
+
+#include <linux/inetdevice.h>
+#include <asm/uaccess.h>
+
+#include <linux/in.h>		/* sockaddr_in */
+#include <linux/inet.h>	
+#include <linux/if.h>
+#include <asm/byteorder.h>	/* htons(), etc. */
+#include <linux/sdlapci.h>
+#include <asm/io.h>
+
+#include <linux/sdla_chdlc.h>		/* CHDLC firmware API definitions */
+
+/****** Defines & Macros ****************************************************/
+
+/* reasons for enabling the timer interrupt on the adapter */
+#define TMR_INT_ENABLED_UDP   	0x0001
+#define TMR_INT_ENABLED_UPDATE	0x0002
+ 
+#define	CHDLC_DFLT_DATA_LEN	1500		/* default MTU */
+#define CHDLC_HDR_LEN		1
+
+#define IFF_POINTTOPOINT 0x10
+
+#define WANPIPE 0x00
+#define API	0x01
+#define CHDLC_API 0x01
+
+#define PORT(x)   (x == 0 ? "PRIMARY" : "SECONDARY" )
+
+ 
+/******Data Structures*****************************************************/
+
+/* This structure is placed in the private data area of the device structure.
+ * The card structure used to occupy the private area but now the following 
+ * structure will incorporate the card structure along with CHDLC specific data
+ */
+
+typedef struct chdlc_private_area
+{
+	struct net_device *slave;
+	sdla_t		*card;
+	int 		TracingEnabled;		/* For enabling Tracing */
+	unsigned long 	curr_trace_addr;	/* Used for Tracing */
+	unsigned long 	start_trace_addr;
+	unsigned long 	end_trace_addr;
+	unsigned long 	base_addr_trace_buffer;
+	unsigned long 	end_addr_trace_buffer;
+	unsigned short 	number_trace_elements;
+	unsigned  	available_buffer_space;
+	unsigned long 	router_start_time;
+	unsigned char 	route_status;
+	unsigned char 	route_removed;
+	unsigned long 	tick_counter;		/* For 5s timeout counter */
+	unsigned long 	router_up_time;
+        u32             IP_address;		/* IP addressing */
+        u32             IP_netmask;
+	unsigned char  mc;			/* Mulitcast support on/off */
+	unsigned short udp_pkt_lgth;		/* udp packet processing */
+	char udp_pkt_src;
+	char udp_pkt_data[MAX_LGTH_UDP_MGNT_PKT];
+	unsigned short timer_int_enabled;
+	char update_comms_stats;		/* updating comms stats */
+	//FIXME: add driver stats as per frame relay!
+
+} chdlc_private_area_t;
+
+/* Route Status options */
+#define NO_ROUTE	0x00
+#define ADD_ROUTE	0x01
+#define ROUTE_ADDED	0x02
+#define REMOVE_ROUTE	0x03
+
+
+/****** Function Prototypes *************************************************/
+/* WAN link driver entry points. These are called by the WAN router module. */
+static int wpft1_exec (struct sdla *card, void *u_cmd, void *u_data);
+static int chdlc_read_version (sdla_t* card, char* str);
+static int chdlc_error (sdla_t *card, int err, CHDLC_MAILBOX_STRUCT *mb);
+
+/****** Public Functions ****************************************************/
+
+/*============================================================================
+ * Cisco HDLC protocol initialization routine.
+ *
+ * This routine is called by the main WANPIPE module during setup.  At this
+ * point adapter is completely initialized and firmware is running.
+ *  o read firmware version (to make sure it's alive)
+ *  o configure adapter
+ *  o initialize protocol-specific fields of the adapter data space.
+ *
+ * Return:	0	o.k.
+ *		< 0	failure.
+ */
+int wpft1_init (sdla_t* card, wandev_conf_t* conf)
+{
+	unsigned char port_num;
+	int err;
+
+	union
+		{
+		char str[80];
+		} u;
+	volatile CHDLC_MAILBOX_STRUCT* mb;
+	CHDLC_MAILBOX_STRUCT* mb1;
+	unsigned long timeout;
+
+	/* Verify configuration ID */
+	if (conf->config_id != WANCONFIG_CHDLC) {
+		printk(KERN_INFO "%s: invalid configuration ID %u!\n",
+				  card->devname, conf->config_id);
+		return -EINVAL;
+	}
+
+	/* Use primary port */
+	card->u.c.comm_port = 0;
+	
+
+	/* Initialize protocol-specific fields */
+	if(card->hw.type != SDLA_S514){
+		card->mbox  = (void *) card->hw.dpmbase;
+	}else{ 
+		card->mbox = (void *) card->hw.dpmbase + PRI_BASE_ADDR_MB_STRUCT;
+	}
+
+	mb = mb1 = card->mbox;
+
+	if (!card->configured){
+
+		/* The board will place an 'I' in the return code to indicate that it is
+	   	ready to accept commands.  We expect this to be completed in less
+           	than 1 second. */
+
+		timeout = jiffies;
+		while (mb->return_code != 'I')	/* Wait 1s for board to initialize */
+			if ((jiffies - timeout) > 1*HZ) break;
+
+		if (mb->return_code != 'I') {
+			printk(KERN_INFO
+				"%s: Initialization not completed by adapter\n",
+				card->devname);
+			printk(KERN_INFO "Please contact Sangoma representative.\n");
+			return -EIO;
+		}
+	}
+
+	/* Read firmware version.  Note that when adapter initializes, it
+	 * clears the mailbox, so it may appear that the first command was
+	 * executed successfully when in fact it was merely erased. To work
+	 * around this, we execute the first command twice.
+	 */
+
+	if (chdlc_read_version(card, u.str))
+		return -EIO;
+
+	printk(KERN_INFO "%s: Running FT1 Configuration firmware v%s\n",
+		card->devname, u.str); 
+
+	card->isr			= NULL;
+	card->poll			= NULL;
+	card->exec			= &wpft1_exec;
+	card->wandev.update		= NULL;
+ 	card->wandev.new_if		= NULL;
+	card->wandev.del_if		= NULL;
+	card->wandev.state		= WAN_DUALPORT;
+	card->wandev.udp_port   	= conf->udp_port;
+
+	card->wandev.new_if_cnt = 0;
+
+	/* This is for the ports link state */
+	card->u.c.state = WAN_DISCONNECTED;
+	
+	/* reset the number of times the 'update()' proc has been called */
+	card->u.c.update_call_count = 0;
+	
+	card->wandev.ttl = 0x7F;
+	card->wandev.interface = 0; 
+
+	card->wandev.clocking = 0;
+
+	port_num = card->u.c.comm_port;
+
+	/* Setup Port Bps */
+
+       	card->wandev.bps = 0;
+
+	card->wandev.mtu = MIN_LGTH_CHDLC_DATA_CFG;
+
+	/* Set up the interrupt status area */
+	/* Read the CHDLC Configuration and obtain: 
+	 *	Ptr to shared memory infor struct
+         * Use this pointer to calculate the value of card->u.c.flags !
+ 	 */
+	mb1->buffer_length = 0;
+	mb1->command = READ_CHDLC_CONFIGURATION;
+	err = sdla_exec(mb1) ? mb1->return_code : CMD_TIMEOUT;
+	if(err != COMMAND_OK) {
+		chdlc_error(card, err, mb1);
+		return -EIO;
+	}
+
+	if(card->hw.type == SDLA_S514){
+               	card->u.c.flags = (void *)(card->hw.dpmbase +
+               		(((CHDLC_CONFIGURATION_STRUCT *)mb1->data)->
+			ptr_shared_mem_info_struct));
+        }else{
+                card->u.c.flags = (void *)(card->hw.dpmbase +
+                        (((CHDLC_CONFIGURATION_STRUCT *)mb1->data)->
+			ptr_shared_mem_info_struct % SDLA_WINDOWSIZE));
+	}
+
+	card->wandev.state = WAN_FT1_READY;
+	printk(KERN_INFO "%s: FT1 Config Ready !\n",card->devname);
+
+	return 0;
+}
+
+static int wpft1_exec(sdla_t *card, void *u_cmd, void *u_data)
+{
+	CHDLC_MAILBOX_STRUCT* mbox = card->mbox;
+	int len;
+
+	if (copy_from_user((void*)&mbox->command, u_cmd, sizeof(ft1_exec_cmd_t))){
+		return -EFAULT;
+	}
+
+	len = mbox->buffer_length;
+
+	if (len) {
+		if( copy_from_user((void*)&mbox->data, u_data, len)){
+			return -EFAULT;
+		}
+	}
+
+	/* execute command */
+	if (!sdla_exec(mbox)){
+		return -EIO;
+	}
+
+	/* return result */
+	if( copy_to_user(u_cmd, (void*)&mbox->command, sizeof(ft1_exec_cmd_t))){
+		return -EFAULT;
+	}
+
+	len = mbox->buffer_length;
+
+	if (len && u_data && copy_to_user(u_data, (void*)&mbox->data, len)){
+		return -EFAULT;
+	}
+
+	return 0;
+
+}
+
+/*============================================================================
+ * Read firmware code version.
+ *	Put code version as ASCII string in str. 
+ */
+static int chdlc_read_version (sdla_t* card, char* str)
+{
+	CHDLC_MAILBOX_STRUCT* mb = card->mbox;
+	int len;
+	char err;
+	mb->buffer_length = 0;
+	mb->command = READ_CHDLC_CODE_VERSION;
+	err = sdla_exec(mb) ? mb->return_code : CMD_TIMEOUT;
+
+	if(err != COMMAND_OK) {
+		chdlc_error(card,err,mb);
+	}
+	else if (str) {  /* is not null */
+		len = mb->buffer_length;
+		memcpy(str, mb->data, len);
+		str[len] = '\0';
+	}
+	return (err);
+}
+
+/*============================================================================
+ * Firmware error handler.
+ *	This routine is called whenever firmware command returns non-zero
+ *	return code.
+ *
+ * Return zero if previous command has to be cancelled.
+ */
+static int chdlc_error (sdla_t *card, int err, CHDLC_MAILBOX_STRUCT *mb)
+{
+	unsigned cmd = mb->command;
+
+	switch (err) {
+
+	case CMD_TIMEOUT:
+		printk(KERN_ERR "%s: command 0x%02X timed out!\n",
+			card->devname, cmd);
+		break;
+
+	case S514_BOTH_PORTS_SAME_CLK_MODE:
+		if(cmd == SET_CHDLC_CONFIGURATION) {
+			printk(KERN_INFO
+			 "%s: Configure both ports for the same clock source\n",
+				card->devname);
+			break;
+		}
+
+	default:
+		printk(KERN_INFO "%s: command 0x%02X returned 0x%02X!\n",
+			card->devname, cmd, err);
+	}
+
+	return 0;
+}
+
+MODULE_LICENSE("GPL");
diff --git a/drivers/net/wan/sdla_ppp.c b/drivers/net/wan/sdla_ppp.c
new file mode 100644
index 0000000..1761cb6
--- /dev/null
+++ b/drivers/net/wan/sdla_ppp.c
@@ -0,0 +1,3429 @@
+/*****************************************************************************
+* sdla_ppp.c	WANPIPE(tm) Multiprotocol WAN Link Driver. PPP module.
+*
+* Author: 	Nenad Corbic <ncorbic@sangoma.com>
+*
+* Copyright:	(c) 1995-2001 Sangoma Technologies Inc.
+*
+*		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.
+* ============================================================================
+* Feb 28, 2001  Nenad Corbic	o Updated if_tx_timeout() routine for 
+* 				  2.4.X kernels.
+* Nov 29, 2000  Nenad Corbic	o Added the 2.4.x kernel support:
+* 				  get_ip_address() function has moved
+* 				  into the ppp_poll() routine. It cannot
+* 				  be called from an interrupt.
+* Nov 07, 2000  Nenad Corbic	o Added security features for UDP debugging:
+*                                 Deny all and specify allowed requests.
+* May 02, 2000  Nenad Corbic	o Added the dynamic interface shutdown
+*                                 option. When the link goes down, the
+*                                 network interface IFF_UP flag is reset.
+* Mar 06, 2000  Nenad Corbic	o Bug Fix: corrupted mbox recovery.
+* Feb 25, 2000  Nenad Corbic    o Fixed the FT1 UDP debugger problem.
+* Feb 09, 2000  Nenad Coribc    o Shutdown bug fix. update() was called
+*                                 with NULL dev pointer: no check.
+* Jan 24, 2000  Nenad Corbic    o Disabled use of CMD complete inter.
+* Dev 15, 1999  Nenad Corbic    o Fixed up header files for 2.0.X kernels
+* Oct 25, 1999  Nenad Corbic    o Support for 2.0.X kernels
+*                                 Moved dynamic route processing into 
+*                                 a polling routine.
+* Oct 07, 1999  Nenad Corbic    o Support for S514 PCI card.  
+*               Gideon Hack     o UPD and Updates executed using timer interrupt
+* Sep 10, 1999  Nenad Corbic    o Fixed up the /proc statistics
+* Jul 20, 1999  Nenad Corbic    o Remove the polling routines and use 
+*                                 interrupts instead.
+* Sep 17, 1998	Jaspreet Singh	o Updates for 2.2.X Kernels.
+* Aug 13, 1998	Jaspreet Singh	o Improved Line Tracing.
+* Jun 22, 1998	David Fong	o Added remote IP address assignment
+* Mar 15, 1998	Alan Cox	o 2.1.8x basic port.
+* Apr 16, 1998	Jaspreet Singh	o using htons() for the IPX protocol.
+* Dec 09, 1997	Jaspreet Singh	o Added PAP and CHAP.
+*				o Implemented new routines like 
+*				  ppp_set_inbnd_auth(), ppp_set_outbnd_auth(),
+*				  tokenize() and strstrip().
+* Nov 27, 1997	Jaspreet Singh	o Added protection against enabling of irqs 
+*				  while they have been disabled.
+* Nov 24, 1997  Jaspreet Singh  o Fixed another RACE condition caused by
+*                                 disabling and enabling of irqs.
+*                               o Added new counters for stats on disable/enable
+*                                 IRQs.
+* Nov 10, 1997	Jaspreet Singh	o Initialized 'skb->mac.raw' to 'skb->data'
+*				  before every netif_rx().
+*				o Free up the device structure in del_if().
+* Nov 07, 1997	Jaspreet Singh	o Changed the delay to zero for Line tracing
+*				  command.
+* Oct 20, 1997 	Jaspreet Singh	o Added hooks in for Router UP time.
+* Oct 16, 1997	Jaspreet Singh  o The critical flag is used to maintain flow
+*				  control by avoiding RACE conditions.  The 
+*				  cli() and restore_flags() are taken out.
+*				  A new structure, "ppp_private_area", is added 
+*				  to provide Driver Statistics.   
+* Jul 21, 1997 	Jaspreet Singh	o Protected calls to sdla_peek() by adding 
+*				  save_flags(), cli() and restore_flags().
+* Jul 07, 1997	Jaspreet Singh  o Added configurable TTL for UDP packets
+*				o Added ability to discard mulitcast and
+*				  broacast source addressed packets.
+* Jun 27, 1997 	Jaspreet Singh	o Added FT1 monitor capabilities
+*				  New case (0x25) statement in if_send routine.
+*				  Added a global variable rCount to keep track
+*				  of FT1 status enabled on the board.
+* May 22, 1997	Jaspreet Singh	o Added change in the PPP_SET_CONFIG command for
+*				508 card to reflect changes in the new 
+*				ppp508.sfm for supporting:continous transmission
+*				of Configure-Request packets without receiving a
+*				reply 				
+*				OR-ed 0x300 to conf_flags 
+*			        o Changed connect_tmout from 900 to 0
+* May 21, 1997	Jaspreet Singh  o Fixed UDP Management for multiple boards
+* Apr 25, 1997  Farhan Thawar    o added UDP Management stuff
+* Mar 11, 1997  Farhan Thawar   Version 3.1.1
+*                                o fixed (+1) bug in rx_intr()
+*                                o changed if_send() to return 0 if
+*                                  wandev.critical() is true
+*                                o free socket buffer in if_send() if
+*                                  returning 0 
+* Jan 15, 1997	Gene Kozin	Version 3.1.0
+*				 o implemented exec() entry point
+* Jan 06, 1997	Gene Kozin	Initial version.
+*****************************************************************************/
+
+#include <linux/module.h>
+#include <linux/kernel.h>	/* printk(), and other useful stuff */
+#include <linux/stddef.h>	/* offsetof(), etc. */
+#include <linux/errno.h>	/* return codes */
+#include <linux/string.h>	/* inline memset(), etc. */
+#include <linux/slab.h>	/* kmalloc(), kfree() */
+#include <linux/wanrouter.h>	/* WAN router definitions */
+#include <linux/wanpipe.h>	/* WANPIPE common user API definitions */
+#include <linux/if_arp.h>	/* ARPHRD_* defines */
+#include <asm/byteorder.h>	/* htons(), etc. */
+#include <linux/in.h>		/* sockaddr_in */
+
+
+#include <asm/uaccess.h>
+#include <linux/inetdevice.h>
+#include <linux/netdevice.h>
+
+#include <linux/if.h>
+#include <linux/sdla_ppp.h>		/* PPP firmware API definitions */
+#include <linux/sdlasfm.h>		/* S514 Type Definition */
+/****** Defines & Macros ****************************************************/
+
+#define	PPP_DFLT_MTU	1500		/* default MTU */
+#define	PPP_MAX_MTU	4000		/* maximum MTU */
+#define PPP_HDR_LEN	1
+
+#define MAX_IP_ERRORS 100 
+
+#define	CONNECT_TIMEOUT	(90*HZ)		/* link connection timeout */
+#define	HOLD_DOWN_TIME	(5*HZ)		/* link hold down time : Changed from 30 to 5 */
+
+/* For handle_IPXWAN() */
+#define CVHexToAscii(b) (((unsigned char)(b) > (unsigned char)9) ? ((unsigned char)'A' + ((unsigned char)(b) - (unsigned char)10)) : ((unsigned char)'0' + (unsigned char)(b)))
+
+/* Macro for enabling/disabling debugging comments */
+//#define NEX_DEBUG
+#ifdef NEX_DEBUG
+#define NEX_PRINTK(format, a...) printk(format, ## a)
+#else
+#define NEX_PRINTK(format, a...)
+#endif /* NEX_DEBUG */ 
+
+#define DCD(a)   ( a & 0x08 ? "HIGH" : "LOW" )
+#define CTS(a)   ( a & 0x20 ? "HIGH" : "LOW" )
+#define LCP(a)   ( a == 0x09 ? "OPEN" : "CLOSED" )
+#define IP(a)    ( a == 0x09 ? "ENABLED" : "DISABLED" )
+
+#define TMR_INT_ENABLED_UPDATE  	0x01
+#define TMR_INT_ENABLED_PPP_EVENT	0x02
+#define TMR_INT_ENABLED_UDP		0x04
+#define TMR_INT_ENABLED_CONFIG		0x20
+
+/* Set Configuraton Command Definitions */
+#define PERCENT_TX_BUFF			60
+#define TIME_BETWEEN_CONF_REQ  		30
+#define TIME_BETWEEN_PAP_CHAP_REQ	30
+#define WAIT_PAP_CHAP_WITHOUT_REPLY     300
+#define WAIT_AFTER_DCD_CTS_LOW          5
+#define TIME_DCD_CTS_LOW_AFTER_LNK_DOWN 10
+#define WAIT_DCD_HIGH_AFTER_ENABLE_COMM 900
+#define MAX_CONF_REQ_WITHOUT_REPLY      10
+#define MAX_TERM_REQ_WITHOUT_REPLY      2
+#define NUM_CONF_NAK_WITHOUT_REPLY      5
+#define NUM_AUTH_REQ_WITHOUT_REPLY      10
+
+#define END_OFFSET 0x1F0
+
+
+/******Data Structures*****************************************************/
+
+/* This structure is placed in the private data area of the device structure.
+ * The card structure used to occupy the private area but now the following 
+ * structure will incorporate the card structure along with PPP specific data
+ */
+  
+typedef struct ppp_private_area
+{
+	struct net_device *slave;
+	sdla_t* card;	
+	unsigned long router_start_time;	/*router start time in sec */
+	unsigned long tick_counter;		/*used for 5 second counter*/
+	unsigned mc;				/*multicast support on or off*/
+	unsigned char enable_IPX;
+	unsigned long network_number;
+	unsigned char pap;
+	unsigned char chap;
+	unsigned char sysname[31];		/* system name for in-bnd auth*/
+	unsigned char userid[511];		/* list of user ids */
+	unsigned char passwd[511];		/* list of passwords */
+	unsigned protocol;			/* SKB Protocol */
+	u32 ip_local;				/* Local IP Address */
+	u32 ip_remote;				/* remote IP Address */
+
+	u32 ip_local_tmp;
+	u32 ip_remote_tmp;
+	
+	unsigned char timer_int_enabled;	/* Who enabled the timer inter*/
+	unsigned char update_comms_stats;	/* Used by update function */
+	unsigned long curr_trace_addr;		/* Trace information */
+	unsigned long start_trace_addr;
+	unsigned long end_trace_addr;
+
+	unsigned char interface_down;		/* Brind down interface when channel 
+                                                   goes down */
+	unsigned long config_wait_timeout;	/* After if_open() if in dynamic if mode,
+						   wait a few seconds before configuring */
+	
+	unsigned short udp_pkt_lgth;
+	char  udp_pkt_src;
+      	char  udp_pkt_data[MAX_LGTH_UDP_MGNT_PKT];
+
+	/* PPP specific statistics */
+
+	if_send_stat_t if_send_stat;
+	rx_intr_stat_t rx_intr_stat;
+	pipe_mgmt_stat_t pipe_mgmt_stat;
+
+	unsigned long router_up_time; 
+
+	/* Polling work queue entry. Each interface
+         * has its own work queue entry, which is used
+         * to defer events from the interrupt */
+	struct work_struct poll_work;
+	struct timer_list poll_delay_timer;
+
+	u8 gateway;
+	u8 config_ppp;
+	u8 ip_error;
+	
+}ppp_private_area_t;
+
+/* variable for keeping track of enabling/disabling FT1 monitor status */
+static int rCount = 0;
+
+extern void disable_irq(unsigned int);
+extern void enable_irq(unsigned int);
+
+/****** Function Prototypes *************************************************/
+
+/* WAN link driver entry points. These are called by the WAN router module. */
+static int update(struct wan_device *wandev);
+static int new_if(struct wan_device *wandev, struct net_device *dev,
+		  wanif_conf_t *conf);
+static int del_if(struct wan_device *wandev, struct net_device *dev);
+
+/* WANPIPE-specific entry points */
+static int wpp_exec (struct sdla *card, void *u_cmd, void *u_data);
+
+/* Network device interface */
+static int if_init(struct net_device *dev);
+static int if_open(struct net_device *dev);
+static int if_close(struct net_device *dev);
+static int if_header(struct sk_buff *skb, struct net_device *dev,
+		     unsigned short type, 
+		     void *daddr, void *saddr, unsigned len);
+
+static void if_tx_timeout(struct net_device *dev);
+
+static int if_rebuild_hdr(struct sk_buff *skb);
+static struct net_device_stats *if_stats(struct net_device *dev);
+static int if_send(struct sk_buff *skb, struct net_device *dev);
+
+
+/* PPP firmware interface functions */
+static int ppp_read_version(sdla_t *card, char *str);
+static int ppp_set_outbnd_auth(sdla_t *card, ppp_private_area_t *ppp_priv_area);
+static int ppp_set_inbnd_auth(sdla_t *card, ppp_private_area_t *ppp_priv_area);
+static int ppp_configure(sdla_t *card, void *data);
+static int ppp_set_intr_mode(sdla_t *card, unsigned char mode);
+static int ppp_comm_enable(sdla_t *card);
+static int ppp_comm_disable(sdla_t *card);
+static int ppp_comm_disable_shutdown(sdla_t *card);
+static int ppp_get_err_stats(sdla_t *card);
+static int ppp_send(sdla_t *card, void *data, unsigned len, unsigned proto);
+static int ppp_error(sdla_t *card, int err, ppp_mbox_t *mb);
+
+static void wpp_isr(sdla_t *card);
+static void rx_intr(sdla_t *card);
+static void event_intr(sdla_t *card);
+static void timer_intr(sdla_t *card);
+
+/* Background polling routines */
+static void process_route(sdla_t *card);
+static void retrigger_comm(sdla_t *card);
+
+/* Miscellaneous functions */
+static int read_info( sdla_t *card );
+static int read_connection_info (sdla_t *card);
+static void remove_route( sdla_t *card );
+static int config508(struct net_device *dev, sdla_t *card);
+static void show_disc_cause(sdla_t * card, unsigned cause);
+static int reply_udp( unsigned char *data, unsigned int mbox_len );
+static void process_udp_mgmt_pkt(sdla_t *card, struct net_device *dev, 
+				ppp_private_area_t *ppp_priv_area);
+static void init_ppp_tx_rx_buff( sdla_t *card );
+static int intr_test( sdla_t *card );
+static int udp_pkt_type( struct sk_buff *skb , sdla_t *card);
+static void init_ppp_priv_struct( ppp_private_area_t *ppp_priv_area);
+static void init_global_statistics( sdla_t *card );
+static int tokenize(char *str, char **tokens);
+static char* strstrip(char *str, char *s);
+static int chk_bcast_mcast_addr(sdla_t* card, struct net_device* dev,
+				struct sk_buff *skb);
+
+static int config_ppp (sdla_t *);
+static void ppp_poll(struct net_device *dev);
+static void trigger_ppp_poll(struct net_device *dev);
+static void ppp_poll_delay (unsigned long dev_ptr);
+
+
+static int Read_connection_info;
+static int Intr_test_counter;
+static unsigned short available_buffer_space;
+
+
+/* IPX functions */
+static void switch_net_numbers(unsigned char *sendpacket, unsigned long network_number, 
+			       unsigned char incoming);
+static int handle_IPXWAN(unsigned char *sendpacket, char *devname, unsigned char enable_PX, 
+			 unsigned long network_number, unsigned short proto);
+
+/* Lock Functions */
+static void s508_lock (sdla_t *card, unsigned long *smp_flags);
+static void s508_unlock (sdla_t *card, unsigned long *smp_flags);
+
+static int store_udp_mgmt_pkt(char udp_pkt_src, sdla_t* card,
+                                struct sk_buff *skb, struct net_device* dev,
+                                ppp_private_area_t* ppp_priv_area );
+static unsigned short calc_checksum (char *data, int len);
+static void disable_comm (sdla_t *card);
+static int detect_and_fix_tx_bug (sdla_t *card);
+
+/****** Public Functions ****************************************************/
+
+/*============================================================================
+ * PPP protocol initialization routine.
+ *
+ * This routine is called by the main WANPIPE module during setup.  At this
+ * point adapter is completely initialized and firmware is running.
+ *  o read firmware version (to make sure it's alive)
+ *  o configure adapter
+ *  o initialize protocol-specific fields of the adapter data space.
+ *
+ * Return:	0	o.k.
+ *		< 0	failure.
+ */
+int wpp_init(sdla_t *card, wandev_conf_t *conf)
+{
+	ppp_flags_t *flags;
+	union
+	{
+		char str[80];
+	} u;
+
+	/* Verify configuration ID */
+	if (conf->config_id != WANCONFIG_PPP) {
+		
+		printk(KERN_INFO "%s: invalid configuration ID %u!\n",
+			card->devname, conf->config_id);
+		return -EINVAL;
+
+	}
+
+	/* Initialize miscellaneous pointers to structures on the adapter */
+	switch (card->hw.type) {
+
+		case SDLA_S508:
+			card->mbox =(void*)(card->hw.dpmbase + PPP508_MB_OFFS);
+			card->flags=(void*)(card->hw.dpmbase + PPP508_FLG_OFFS);
+			break;
+		
+		case SDLA_S514:
+			card->mbox =(void*)(card->hw.dpmbase + PPP514_MB_OFFS);
+			card->flags=(void*)(card->hw.dpmbase + PPP514_FLG_OFFS);
+			break;
+
+		default:
+			return -EINVAL;
+
+	}
+	flags = card->flags;
+
+	/* Read firmware version.  Note that when adapter initializes, it
+	 * clears the mailbox, so it may appear that the first command was
+	 * executed successfully when in fact it was merely erased. To work
+	 * around this, we execute the first command twice.
+	 */
+	if (ppp_read_version(card, NULL) || ppp_read_version(card, u.str))
+		return -EIO;
+	
+	printk(KERN_INFO "%s: running PPP firmware v%s\n",card->devname, u.str); 
+	/* Adjust configuration and set defaults */
+	card->wandev.mtu = (conf->mtu) ?
+		min_t(unsigned int, conf->mtu, PPP_MAX_MTU) : PPP_DFLT_MTU;
+
+	card->wandev.bps	= conf->bps;
+	card->wandev.interface	= conf->interface;
+	card->wandev.clocking	= conf->clocking;
+	card->wandev.station	= conf->station;
+	card->isr		= &wpp_isr;
+	card->poll		= NULL; 
+	card->exec		= &wpp_exec;
+	card->wandev.update	= &update;
+	card->wandev.new_if	= &new_if;
+	card->wandev.del_if	= &del_if;
+        card->wandev.udp_port   = conf->udp_port;
+	card->wandev.ttl	= conf->ttl;
+	card->wandev.state      = WAN_DISCONNECTED;
+	card->disable_comm	= &disable_comm;
+	card->irq_dis_if_send_count = 0;
+        card->irq_dis_poll_count = 0;
+	card->u.p.authenticator = conf->u.ppp.authenticator;
+	card->u.p.ip_mode 	= conf->u.ppp.ip_mode ?
+				 conf->u.ppp.ip_mode : WANOPT_PPP_STATIC;
+        card->TracingEnabled    = 0;
+	Read_connection_info    = 1;
+
+	/* initialize global statistics */
+	init_global_statistics( card );
+
+
+
+	if (!card->configured){
+		int err;
+
+		Intr_test_counter = 0;
+		err = intr_test(card);
+
+		if(err || (Intr_test_counter < MAX_INTR_TEST_COUNTER)) {
+			printk("%s: Interrupt Test Failed, Counter: %i\n", 
+				card->devname, Intr_test_counter);
+			printk( "%s: Please choose another interrupt\n",card->devname);
+			return -EIO;
+		}
+		
+		printk(KERN_INFO "%s: Interrupt Test Passed, Counter: %i\n", 
+			card->devname, Intr_test_counter);
+		card->configured = 1;
+	}
+
+	ppp_set_intr_mode(card, PPP_INTR_TIMER); 
+
+	/* Turn off the transmit and timer interrupt */
+	flags->imask &= ~PPP_INTR_TIMER;
+
+	printk(KERN_INFO "\n");
+
+	return 0;
+}
+
+/******* WAN Device Driver Entry Points *************************************/
+
+/*============================================================================
+ * Update device status & statistics.
+ */
+static int update(struct wan_device *wandev)
+{
+	sdla_t* card = wandev->private;
+ 	struct net_device* dev;
+        volatile ppp_private_area_t *ppp_priv_area;
+	ppp_flags_t *flags = card->flags;
+	unsigned long timeout;
+
+	/* sanity checks */
+	if ((wandev == NULL) || (wandev->private == NULL))
+		return -EFAULT;
+	
+	if (wandev->state == WAN_UNCONFIGURED)
+		return -ENODEV;
+	
+	/* Shutdown bug fix. This function can be
+         * called with NULL dev pointer during
+         * shutdown 
+	 */
+	if ((dev=card->wandev.dev) == NULL){
+		return -ENODEV;
+	}
+
+	if ((ppp_priv_area=dev->priv) == NULL){
+		return -ENODEV;
+	}
+	
+	ppp_priv_area->update_comms_stats = 2;
+	ppp_priv_area->timer_int_enabled |= TMR_INT_ENABLED_UPDATE;
+	flags->imask |= PPP_INTR_TIMER;	
+	
+	/* wait a maximum of 1 second for the statistics to be updated */ 
+        timeout = jiffies;
+        for(;;) {
+		if(ppp_priv_area->update_comms_stats == 0){
+			break;
+		}
+                if ((jiffies - timeout) > (1 * HZ)){
+    			ppp_priv_area->update_comms_stats = 0;
+ 			ppp_priv_area->timer_int_enabled &=
+				~TMR_INT_ENABLED_UPDATE; 
+ 			return -EAGAIN;
+		}
+        }
+
+	return 0;
+}
+
+/*============================================================================
+ * Create new logical channel.
+ * This routine is called by the router when ROUTER_IFNEW IOCTL is being
+ * handled.
+ * o parse media- and hardware-specific configuration
+ * o make sure that a new channel can be created
+ * o allocate resources, if necessary
+ * o prepare network device structure for registaration.
+ *
+ * Return:	0	o.k.
+ *		< 0	failure (channel will not be created)
+ */
+static int new_if(struct wan_device *wandev, struct net_device *dev,
+		  wanif_conf_t *conf)
+{
+	sdla_t *card = wandev->private;
+	ppp_private_area_t *ppp_priv_area;
+
+	if (wandev->ndev)
+		return -EEXIST;
+	
+
+	printk(KERN_INFO "%s: Configuring Interface: %s\n",
+			card->devname, conf->name);
+
+	if ((conf->name[0] == '\0') || (strlen(conf->name) > WAN_IFNAME_SZ)) {
+
+		printk(KERN_INFO "%s: Invalid interface name!\n",
+			card->devname);
+		return -EINVAL;
+
+	}
+
+	/* allocate and initialize private data */
+	ppp_priv_area = kmalloc(sizeof(ppp_private_area_t), GFP_KERNEL);
+	
+	if( ppp_priv_area == NULL )
+		return	-ENOMEM;
+	
+	memset(ppp_priv_area, 0, sizeof(ppp_private_area_t));
+	
+	ppp_priv_area->card = card; 
+	
+	/* initialize data */
+	strcpy(card->u.p.if_name, conf->name);
+
+	/* initialize data in ppp_private_area structure */
+	
+	init_ppp_priv_struct( ppp_priv_area );
+
+	ppp_priv_area->mc = conf->mc;
+	ppp_priv_area->pap = conf->pap;
+	ppp_priv_area->chap = conf->chap;
+
+	/* Option to bring down the interface when 
+         * the link goes down */
+	if (conf->if_down){
+		set_bit(DYN_OPT_ON,&ppp_priv_area->interface_down);
+		printk("%s: Dynamic interface configuration enabled\n",
+			card->devname);
+	} 
+
+	/* If no user ids are specified */
+	if(!strlen(conf->userid) && (ppp_priv_area->pap||ppp_priv_area->chap)){
+		kfree(ppp_priv_area);
+		return -EINVAL;
+	}
+
+	/* If no passwords are specified */
+	if(!strlen(conf->passwd) && (ppp_priv_area->pap||ppp_priv_area->chap)){
+		kfree(ppp_priv_area);
+		return -EINVAL;
+	}
+
+	if(strlen(conf->sysname) > 31){
+		kfree(ppp_priv_area);
+		return -EINVAL;
+	}
+
+	/* If no system name is specified */
+	if(!strlen(conf->sysname) && (card->u.p.authenticator)){
+		kfree(ppp_priv_area);
+		return -EINVAL;
+	}
+
+	/* copy the data into the ppp private structure */
+	memcpy(ppp_priv_area->userid, conf->userid, strlen(conf->userid));
+	memcpy(ppp_priv_area->passwd, conf->passwd, strlen(conf->passwd));
+	memcpy(ppp_priv_area->sysname, conf->sysname, strlen(conf->sysname));
+
+	
+	ppp_priv_area->enable_IPX = conf->enable_IPX;
+	if (conf->network_number){
+		ppp_priv_area->network_number = conf->network_number;
+	}else{
+		ppp_priv_area->network_number = 0xDEADBEEF;
+	}
+
+	/* Tells us that if this interface is a
+         * gateway or not */
+	if ((ppp_priv_area->gateway = conf->gateway) == WANOPT_YES){
+		printk(KERN_INFO "%s: Interface %s is set as a gateway.\n",
+			card->devname,card->u.p.if_name);
+	}
+
+	/* prepare network device data space for registration */
+ 	strcpy(dev->name,card->u.p.if_name);
+	
+	dev->init = &if_init;
+	dev->priv = ppp_priv_area;
+	dev->mtu = min_t(unsigned int, dev->mtu, card->wandev.mtu);
+
+	/* Initialize the polling work routine */
+	INIT_WORK(&ppp_priv_area->poll_work, (void*)(void*)ppp_poll, dev);
+
+	/* Initialize the polling delay timer */
+	init_timer(&ppp_priv_area->poll_delay_timer);
+	ppp_priv_area->poll_delay_timer.data = (unsigned long)dev;
+	ppp_priv_area->poll_delay_timer.function = ppp_poll_delay;
+	
+	
+	/* Since we start with dummy IP addresses we can say
+	 * that route exists */
+	printk(KERN_INFO "\n");
+
+	return 0;
+}
+
+/*============================================================================
+ * Delete logical channel.
+ */
+static int del_if(struct wan_device *wandev, struct net_device *dev)
+{
+	return 0;
+}
+
+static void disable_comm (sdla_t *card)
+{
+	ppp_comm_disable_shutdown(card);
+	return;
+}
+
+/****** WANPIPE-specific entry points ***************************************/
+
+/*============================================================================
+ * Execute adapter interface command.
+ */
+
+//FIXME: Why do we need this ????
+static int wpp_exec(struct sdla *card, void *u_cmd, void *u_data)
+{
+	ppp_mbox_t *mbox = card->mbox;
+	int len;
+
+	if (copy_from_user((void*)&mbox->cmd, u_cmd, sizeof(ppp_cmd_t)))
+		return -EFAULT;
+
+	len = mbox->cmd.length;
+
+	if (len) {
+
+		if( copy_from_user((void*)&mbox->data, u_data, len))
+			return -EFAULT;
+
+	}
+
+	/* execute command */
+	if (!sdla_exec(mbox))
+		return -EIO;
+
+	/* return result */
+	if( copy_to_user(u_cmd, (void*)&mbox->cmd, sizeof(ppp_cmd_t)))
+		return -EFAULT;
+	len = mbox->cmd.length;
+
+	if (len && u_data && copy_to_user(u_data, (void*)&mbox->data, len))
+		return -EFAULT;
+
+	return 0;
+}
+
+/****** Network Device Interface ********************************************/
+
+/*============================================================================
+ * Initialize Linux network interface.
+ *
+ * This routine is called only once for each interface, during Linux network
+ * interface registration.  Returning anything but zero will fail interface
+ * registration.
+ */
+static int if_init(struct net_device *dev)
+{
+	ppp_private_area_t *ppp_priv_area = dev->priv;
+	sdla_t *card = ppp_priv_area->card;
+	struct wan_device *wandev = &card->wandev;
+
+	/* Initialize device driver entry points */
+	dev->open		= &if_open;
+	dev->stop		= &if_close;
+	dev->hard_header	= &if_header;
+	dev->rebuild_header	= &if_rebuild_hdr;
+	dev->hard_start_xmit	= &if_send;
+	dev->get_stats		= &if_stats;
+	dev->tx_timeout		= &if_tx_timeout;
+	dev->watchdog_timeo	= TX_TIMEOUT;
+
+	/* Initialize media-specific parameters */
+	dev->type		= ARPHRD_PPP;	/* ARP h/w type */
+	dev->flags		|= IFF_POINTOPOINT;
+	dev->flags		|= IFF_NOARP;
+
+	/* Enable Mulitcasting if specified by user*/
+	if (ppp_priv_area->mc == WANOPT_YES){
+		dev->flags	|= IFF_MULTICAST;
+	}
+
+	dev->mtu		= wandev->mtu;
+	dev->hard_header_len	= PPP_HDR_LEN;	/* media header length */
+
+	/* Initialize hardware parameters (just for reference) */
+	dev->irq		= wandev->irq;
+	dev->dma		= wandev->dma;
+	dev->base_addr		= wandev->ioport;
+	dev->mem_start		= wandev->maddr;
+	dev->mem_end		= wandev->maddr + wandev->msize - 1;
+
+        /* Set transmit buffer queue length */
+        dev->tx_queue_len = 100;
+	SET_MODULE_OWNER(dev);
+   
+	return 0;
+}
+
+/*============================================================================
+ * Open network interface.
+ * o enable communications and interrupts.
+ * o prevent module from unloading by incrementing use count
+ *
+ * Return 0 if O.k. or errno.
+ */
+static int if_open(struct net_device *dev)
+{
+	ppp_private_area_t *ppp_priv_area = dev->priv;
+	sdla_t *card = ppp_priv_area->card;
+	struct timeval tv;
+	//unsigned long smp_flags;
+
+	if (netif_running(dev))
+		return -EBUSY;
+
+	wanpipe_open(card);
+
+	netif_start_queue(dev);
+	
+	do_gettimeofday( &tv );
+	ppp_priv_area->router_start_time = tv.tv_sec;
+
+	/* We cannot configure the card here because we don't
+	 * have access to the interface IP addresses.
+         * Once the interface initilization is complete, we will be
+         * able to access the IP addresses.  Therefore,
+         * configure the ppp link in the poll routine */
+	set_bit(0,&ppp_priv_area->config_ppp);
+	ppp_priv_area->config_wait_timeout=jiffies;
+
+	/* Start the PPP configuration after 1sec delay.
+	 * This will give the interface initilization time
+	 * to finish its configuration */
+	mod_timer(&ppp_priv_area->poll_delay_timer, jiffies + HZ);
+	return 0;
+}
+
+/*============================================================================
+ * Close network interface.
+ * o if this is the last open, then disable communications and interrupts.
+ * o reset flags.
+ */
+static int if_close(struct net_device *dev)
+{
+	ppp_private_area_t *ppp_priv_area = dev->priv;
+	sdla_t *card = ppp_priv_area->card;
+
+	netif_stop_queue(dev);
+	wanpipe_close(card);
+
+	del_timer (&ppp_priv_area->poll_delay_timer);
+	return 0;
+}
+
+/*============================================================================
+ * Build media header.
+ *
+ * The trick here is to put packet type (Ethertype) into 'protocol' field of
+ * the socket buffer, so that we don't forget it.  If packet type is not
+ * supported, set skb->protocol to 0 and discard packet later.
+ *
+ * Return:	media header length.
+ */
+static int if_header(struct sk_buff *skb, struct net_device *dev,
+	unsigned short type, void *daddr, void *saddr, unsigned len)
+{
+	switch (type)
+	{
+		case ETH_P_IP:
+		case ETH_P_IPX:
+			skb->protocol = htons(type);
+			break;
+
+		default:
+			skb->protocol = 0;
+	}
+
+	return PPP_HDR_LEN;
+}
+
+/*============================================================================
+ * Re-build media header.
+ *
+ * Return:	1	physical address resolved.
+ *		0	physical address not resolved
+ */
+static int if_rebuild_hdr (struct sk_buff *skb)
+{
+	struct net_device *dev = skb->dev;
+	ppp_private_area_t *ppp_priv_area = dev->priv;
+	sdla_t *card = ppp_priv_area->card;
+
+	printk(KERN_INFO "%s: rebuild_header() called for interface %s!\n",
+		card->devname, dev->name);
+	return 1;
+}
+
+/*============================================================================
+ * Handle transmit timeout event from netif watchdog
+ */
+static void if_tx_timeout(struct net_device *dev)
+{
+    	ppp_private_area_t* chan = dev->priv;
+	sdla_t *card = chan->card;
+	
+	/* If our device stays busy for at least 5 seconds then we will
+	 * kick start the device by making dev->tbusy = 0.  We expect
+	 * that our device never stays busy more than 5 seconds. So this                 
+	 * is only used as a last resort.
+	 */
+
+	++ chan->if_send_stat.if_send_tbusy;
+	++card->wandev.stats.collisions;
+
+	printk (KERN_INFO "%s: Transmit timed out on %s\n", card->devname,dev->name);
+	++chan->if_send_stat.if_send_tbusy_timeout;
+	netif_wake_queue (dev);
+}
+
+
+
+/*============================================================================
+ * Send a packet on a network interface.
+ * o set tbusy flag (marks start of the transmission) to block a timer-based
+ *   transmit from overlapping.
+ * o check link state. If link is not up, then drop the packet.
+ * o execute adapter send command.
+ * o free socket buffer
+ *
+ * Return:	0	complete (socket buffer must be freed)
+ *		non-0	packet may be re-transmitted (tbusy must be set)
+ *
+ * Notes:
+ * 1. This routine is called either by the protocol stack or by the "net
+ *    bottom half" (with interrupts enabled).
+ * 2. Setting tbusy flag will inhibit further transmit requests from the
+ *    protocol stack and can be used for flow control with protocol layer.
+ */
+static int if_send (struct sk_buff *skb, struct net_device *dev)
+{
+	ppp_private_area_t *ppp_priv_area = dev->priv;
+	sdla_t *card = ppp_priv_area->card;
+	unsigned char *sendpacket;
+	unsigned long smp_flags;
+	ppp_flags_t *flags = card->flags;
+	int udp_type;
+	int err=0;
+	
+	++ppp_priv_area->if_send_stat.if_send_entry;
+
+	netif_stop_queue(dev);
+	
+	if (skb == NULL) {
+
+		/* If we get here, some higher layer thinks we've missed an
+		 * tx-done interrupt.
+		 */
+		printk(KERN_INFO "%s: interface %s got kicked!\n",
+			card->devname, dev->name);
+		
+		++ppp_priv_area->if_send_stat.if_send_skb_null;
+	
+		netif_wake_queue(dev);
+		return 0;
+	}
+
+	sendpacket = skb->data;
+
+	udp_type = udp_pkt_type( skb, card );
+
+
+	if (udp_type == UDP_PTPIPE_TYPE){
+		if(store_udp_mgmt_pkt(UDP_PKT_FRM_STACK, card, skb, dev,
+                	              ppp_priv_area)){
+	               	flags->imask |= PPP_INTR_TIMER;
+		}
+		++ppp_priv_area->if_send_stat.if_send_PIPE_request;
+		netif_start_queue(dev);
+		return 0;
+	}
+
+	/* Check for broadcast and multicast addresses 
+	 * If found, drop (deallocate) a packet and return.
+	 */
+	if(chk_bcast_mcast_addr(card, dev, skb)){
+		++card->wandev.stats.tx_dropped;
+		dev_kfree_skb_any(skb);
+		netif_start_queue(dev);
+		return 0;
+	}
+
+
+ 	if(card->hw.type != SDLA_S514){
+		s508_lock(card,&smp_flags);
+	}
+
+    	if (test_and_set_bit(SEND_CRIT, (void*)&card->wandev.critical)) {
+
+		printk(KERN_INFO "%s: Critical in if_send: %lx\n",
+				card->wandev.name,card->wandev.critical);
+		
+		++card->wandev.stats.tx_dropped;
+		++ppp_priv_area->if_send_stat.if_send_critical_non_ISR;
+		netif_start_queue(dev);
+		goto if_send_exit_crit;
+	}
+
+	if (card->wandev.state != WAN_CONNECTED) {
+
+		++ppp_priv_area->if_send_stat.if_send_wan_disconnected;
+        	++card->wandev.stats.tx_dropped;
+		netif_start_queue(dev);
+		
+     	} else if (!skb->protocol) {
+		++ppp_priv_area->if_send_stat.if_send_protocol_error;
+        	++card->wandev.stats.tx_errors;
+		netif_start_queue(dev);
+		
+	} else {
+
+		/*If it's IPX change the network numbers to 0 if they're ours.*/
+		if( skb->protocol == htons(ETH_P_IPX) ) {
+			if(ppp_priv_area->enable_IPX) {
+				switch_net_numbers( skb->data, 
+					ppp_priv_area->network_number, 0);
+			} else {
+				++card->wandev.stats.tx_dropped;
+				netif_start_queue(dev);
+				goto if_send_exit_crit;
+			}
+		}
+
+		if (ppp_send(card, skb->data, skb->len, skb->protocol)) {
+			netif_stop_queue(dev);
+			++ppp_priv_area->if_send_stat.if_send_adptr_bfrs_full;
+			++ppp_priv_area->if_send_stat.if_send_tx_int_enabled;
+		} else {
+			++ppp_priv_area->if_send_stat.if_send_bfr_passed_to_adptr;
+			++card->wandev.stats.tx_packets;
+			card->wandev.stats.tx_bytes += skb->len;
+			netif_start_queue(dev);
+			dev->trans_start = jiffies;
+		}
+    	}
+	
+if_send_exit_crit:
+	
+	if (!(err=netif_queue_stopped(dev))){
+      		dev_kfree_skb_any(skb);
+	}else{
+		ppp_priv_area->tick_counter = jiffies;
+		flags->imask |= PPP_INTR_TXRDY;	/* unmask Tx interrupts */
+	}
+	
+	clear_bit(SEND_CRIT,&card->wandev.critical);
+	if(card->hw.type != SDLA_S514){	
+		s508_unlock(card,&smp_flags);
+	}
+
+	return err;
+}
+
+
+/*=============================================================================
+ * Store a UDP management packet for later processing.
+ */
+
+static int store_udp_mgmt_pkt(char udp_pkt_src, sdla_t* card,
+                                struct sk_buff *skb, struct net_device* dev,
+                                ppp_private_area_t* ppp_priv_area )
+{
+	int udp_pkt_stored = 0;
+
+	if(!ppp_priv_area->udp_pkt_lgth && (skb->len<=MAX_LGTH_UDP_MGNT_PKT)){
+        	ppp_priv_area->udp_pkt_lgth = skb->len;
+		ppp_priv_area->udp_pkt_src = udp_pkt_src;
+       		memcpy(ppp_priv_area->udp_pkt_data, skb->data, skb->len);
+		ppp_priv_area->timer_int_enabled |= TMR_INT_ENABLED_UDP;
+		ppp_priv_area->protocol = skb->protocol;
+		udp_pkt_stored = 1;
+	}else{
+		if (skb->len > MAX_LGTH_UDP_MGNT_PKT){
+			printk(KERN_INFO "%s: PIPEMON UDP request too long : %i\n",
+				card->devname, skb->len);
+		}else{
+			printk(KERN_INFO "%s: PIPEMON UPD request already pending\n",
+				card->devname);
+		}
+		ppp_priv_area->udp_pkt_lgth = 0;
+	}
+
+	if(udp_pkt_src == UDP_PKT_FRM_STACK){
+		dev_kfree_skb_any(skb);
+	}else{
+                dev_kfree_skb_any(skb);
+	}
+
+	return(udp_pkt_stored);
+}
+
+
+
+/*============================================================================
+ * Reply to UDP Management system.
+ * Return length of reply.
+ */
+static int reply_udp( unsigned char *data, unsigned int mbox_len ) 
+{
+	unsigned short len, udp_length, temp, ip_length;
+	unsigned long ip_temp;
+	int even_bound = 0;
+	ppp_udp_pkt_t *p_udp_pkt = (ppp_udp_pkt_t *)data;
+ 
+	/* Set length of packet */
+	len = sizeof(ip_pkt_t)+ 
+	      sizeof(udp_pkt_t)+
+	      sizeof(wp_mgmt_t)+
+	      sizeof(cblock_t)+
+	      mbox_len;
+
+	/* fill in UDP reply */
+  	p_udp_pkt->wp_mgmt.request_reply = UDPMGMT_REPLY; 
+
+	/* fill in UDP length */
+	udp_length = sizeof(udp_pkt_t)+ 
+		     sizeof(wp_mgmt_t)+
+		     sizeof(cblock_t)+
+		     mbox_len; 
+  
+ 
+	/* put it on an even boundary */
+	if ( udp_length & 0x0001 ) {
+		udp_length += 1;
+		len += 1;
+		even_bound=1;
+	} 
+	
+	temp = (udp_length<<8)|(udp_length>>8);
+	p_udp_pkt->udp_pkt.udp_length = temp;		
+
+ 
+	/* swap UDP ports */
+	temp = p_udp_pkt->udp_pkt.udp_src_port;
+	p_udp_pkt->udp_pkt.udp_src_port = 
+			p_udp_pkt->udp_pkt.udp_dst_port; 
+	p_udp_pkt->udp_pkt.udp_dst_port = temp;
+
+
+	/* add UDP pseudo header */
+	temp = 0x1100;
+	*((unsigned short *)(p_udp_pkt->data+mbox_len+even_bound)) = temp;
+	temp = (udp_length<<8)|(udp_length>>8);
+	*((unsigned short *)(p_udp_pkt->data+mbox_len+even_bound+2)) = temp;
+ 
+	/* calculate UDP checksum */
+	p_udp_pkt->udp_pkt.udp_checksum = 0;
+	p_udp_pkt->udp_pkt.udp_checksum = 
+		calc_checksum(&data[UDP_OFFSET],udp_length+UDP_OFFSET);
+
+	/* fill in IP length */
+	ip_length = udp_length + sizeof(ip_pkt_t);
+	temp = (ip_length<<8)|(ip_length>>8);
+  	p_udp_pkt->ip_pkt.total_length = temp;
+ 
+	/* swap IP addresses */
+	ip_temp = p_udp_pkt->ip_pkt.ip_src_address;
+	p_udp_pkt->ip_pkt.ip_src_address = p_udp_pkt->ip_pkt.ip_dst_address;
+	p_udp_pkt->ip_pkt.ip_dst_address = ip_temp;
+
+	/* fill in IP checksum */
+	p_udp_pkt->ip_pkt.hdr_checksum = 0;
+	p_udp_pkt->ip_pkt.hdr_checksum = calc_checksum(data,sizeof(ip_pkt_t));
+
+	return len;
+
+} /* reply_udp */
+
+unsigned short calc_checksum (char *data, int len)
+{
+	unsigned short temp; 
+	unsigned long sum=0;
+	int i;
+
+	for( i = 0; i <len; i+=2 ) {
+		memcpy(&temp,&data[i],2);
+		sum += (unsigned long)temp;
+	}
+
+	while (sum >> 16 ) {
+		sum = (sum & 0xffffUL) + (sum >> 16);
+	}
+
+	temp = (unsigned short)sum;
+	temp = ~temp;
+
+	if( temp == 0 ) 
+		temp = 0xffff;
+
+	return temp;	
+}
+
+/*
+   If incoming is 0 (outgoing)- if the net numbers is ours make it 0
+   if incoming is 1 - if the net number is 0 make it ours 
+
+*/
+static void switch_net_numbers(unsigned char *sendpacket, unsigned long network_number, unsigned char incoming)
+{
+	unsigned long pnetwork_number;
+
+	pnetwork_number = (unsigned long)((sendpacket[6] << 24) + 
+			  (sendpacket[7] << 16) + (sendpacket[8] << 8) + 
+			  sendpacket[9]);
+
+	if (!incoming) {
+		//If the destination network number is ours, make it 0
+		if( pnetwork_number == network_number) {
+			sendpacket[6] = sendpacket[7] = sendpacket[8] = 
+					 sendpacket[9] = 0x00;
+		}
+	} else {
+		//If the incoming network is 0, make it ours
+		if( pnetwork_number == 0) {
+			sendpacket[6] = (unsigned char)(network_number >> 24);
+			sendpacket[7] = (unsigned char)((network_number & 
+					 0x00FF0000) >> 16);
+			sendpacket[8] = (unsigned char)((network_number & 
+					 0x0000FF00) >> 8);
+			sendpacket[9] = (unsigned char)(network_number & 
+					 0x000000FF);
+		}
+	}
+
+
+	pnetwork_number = (unsigned long)((sendpacket[18] << 24) + 
+			  (sendpacket[19] << 16) + (sendpacket[20] << 8) + 
+			  sendpacket[21]);
+
+	if( !incoming ) {
+		//If the source network is ours, make it 0
+		if( pnetwork_number == network_number) {
+			sendpacket[18] = sendpacket[19] = sendpacket[20] = 
+					 sendpacket[21] = 0x00;
+		}
+	} else {
+		//If the source network is 0, make it ours
+		if( pnetwork_number == 0 ) {
+			sendpacket[18] = (unsigned char)(network_number >> 24);
+			sendpacket[19] = (unsigned char)((network_number & 
+					 0x00FF0000) >> 16);
+			sendpacket[20] = (unsigned char)((network_number & 
+					 0x0000FF00) >> 8);
+			sendpacket[21] = (unsigned char)(network_number & 
+					 0x000000FF);
+		}
+	}
+} /* switch_net_numbers */
+
+/*============================================================================
+ * Get ethernet-style interface statistics.
+ * Return a pointer to struct net_device_stats.
+ */
+static struct net_device_stats *if_stats(struct net_device *dev)
+{
+
+	ppp_private_area_t *ppp_priv_area = dev->priv;
+	sdla_t* card;
+	
+	if( ppp_priv_area == NULL )
+		return NULL;
+
+	card = ppp_priv_area->card;
+	return &card->wandev.stats;
+}
+
+/****** PPP Firmware Interface Functions ************************************/
+
+/*============================================================================
+ * Read firmware code version.
+ *	Put code version as ASCII string in str. 
+ */
+static int ppp_read_version(sdla_t *card, char *str)
+{
+	ppp_mbox_t *mb = card->mbox;
+	int err;
+
+	memset(&mb->cmd, 0, sizeof(ppp_cmd_t));
+	mb->cmd.command = PPP_READ_CODE_VERSION;
+	err = sdla_exec(mb) ? mb->cmd.result : CMD_TIMEOUT;
+
+	if (err != CMD_OK)
+ 
+		ppp_error(card, err, mb);
+
+	else if (str) {
+
+		int len = mb->cmd.length;
+
+		memcpy(str, mb->data, len);
+		str[len] = '\0';
+
+	}
+
+	return err;
+}
+/*===========================================================================
+ * Set Out-Bound Authentication.
+*/
+static int ppp_set_outbnd_auth (sdla_t *card, ppp_private_area_t *ppp_priv_area)
+{
+	ppp_mbox_t *mb = card->mbox;
+	int err;
+
+	memset(&mb->cmd, 0, sizeof(ppp_cmd_t));
+	memset(&mb->data, 0, (strlen(ppp_priv_area->userid) + 
+					strlen(ppp_priv_area->passwd) + 2 ) );
+	memcpy(mb->data, ppp_priv_area->userid, strlen(ppp_priv_area->userid));
+	memcpy((mb->data + strlen(ppp_priv_area->userid) + 1), 
+		ppp_priv_area->passwd, strlen(ppp_priv_area->passwd));	
+	
+	mb->cmd.length  = strlen(ppp_priv_area->userid) + 
+					strlen(ppp_priv_area->passwd) + 2 ;
+	
+	mb->cmd.command = PPP_SET_OUTBOUND_AUTH;
+
+	err = sdla_exec(mb) ? mb->cmd.result : CMD_TIMEOUT;
+
+	if (err != CMD_OK)
+		ppp_error(card, err, mb);
+
+	return err;
+}
+
+/*===========================================================================
+ * Set In-Bound Authentication.
+*/
+static int ppp_set_inbnd_auth (sdla_t *card, ppp_private_area_t *ppp_priv_area)
+{
+	ppp_mbox_t *mb = card->mbox;
+	int err, i;
+	char* user_tokens[32];
+	char* pass_tokens[32];
+	int userids, passwds;
+	int add_ptr;
+
+	memset(&mb->cmd, 0, sizeof(ppp_cmd_t));
+	memset(&mb->data, 0, 1008);
+	memcpy(mb->data, ppp_priv_area->sysname, 
+						strlen(ppp_priv_area->sysname));
+	
+	/* Parse the userid string and the password string and build a string
+	   to copy it to the data area of the command structure.   The string
+	   will look like "SYS_NAME<NULL>USER1<NULL>PASS1<NULL>USER2<NULL>PASS2
+	   ....<NULL> " 
+	 */
+	userids = tokenize( ppp_priv_area->userid, user_tokens);
+	passwds = tokenize( ppp_priv_area->passwd, pass_tokens);
+	
+	if (userids != passwds){
+		printk(KERN_INFO "%s: Number of passwords does not equal the number of user ids\n", card->devname);
+		return 1;	
+	}
+
+	add_ptr = strlen(ppp_priv_area->sysname) + 1;
+	for (i=0; i<userids; i++){
+		memcpy((mb->data + add_ptr), user_tokens[i], 
+							strlen(user_tokens[i]));
+		memcpy((mb->data + add_ptr + strlen(user_tokens[i]) + 1), 
+					pass_tokens[i], strlen(pass_tokens[i]));
+		add_ptr = add_ptr + strlen(user_tokens[i]) + 1 + 
+						strlen(pass_tokens[i]) + 1;
+	}
+
+	mb->cmd.length  = add_ptr + 1;
+	mb->cmd.command = PPP_SET_INBOUND_AUTH;
+
+	err = sdla_exec(mb) ? mb->cmd.result : CMD_TIMEOUT;
+
+	if (err != CMD_OK)
+		ppp_error(card, err, mb);
+
+	return err;
+}
+
+
+/*============================================================================
+ * Tokenize string.
+ *      Parse a string of the following syntax:
+ *              <arg1>,<arg2>,...
+ *      and fill array of tokens with pointers to string elements.
+ *
+ */
+static int tokenize (char *str, char **tokens)
+{
+        int cnt = 0;
+
+        tokens[0] = strsep(&str, "/");
+        while (tokens[cnt] && (cnt < 32 - 1))
+        {
+                tokens[cnt] = strstrip(tokens[cnt], " \t");
+                tokens[++cnt] = strsep(&str, "/");
+        }
+	return cnt;
+}
+
+/*============================================================================
+ * Strip leading and trailing spaces off the string str.
+ */
+static char* strstrip (char *str, char* s)
+{
+        char *eos = str + strlen(str);          /* -> end of string */
+
+        while (*str && strchr(s, *str))
+                ++str                           /* strip leading spaces */
+        ;
+        while ((eos > str) && strchr(s, *(eos - 1)))
+                --eos                           /* strip trailing spaces */
+        ;
+        *eos = '\0';
+        return str;
+}
+/*============================================================================
+ * Configure PPP firmware.
+ */
+static int ppp_configure(sdla_t *card, void *data)
+{
+	ppp_mbox_t *mb = card->mbox;
+	int data_len = sizeof(ppp508_conf_t); 
+	int err;
+
+	memset(&mb->cmd, 0, sizeof(ppp_cmd_t));
+	memcpy(mb->data, data, data_len);
+	mb->cmd.length  = data_len;
+	mb->cmd.command = PPP_SET_CONFIG;
+	err = sdla_exec(mb) ? mb->cmd.result : CMD_TIMEOUT;
+
+	if (err != CMD_OK) 
+		ppp_error(card, err, mb);
+	
+	return err;
+}
+
+/*============================================================================
+ * Set interrupt mode.
+ */
+static int ppp_set_intr_mode(sdla_t *card, unsigned char mode)
+{
+	ppp_mbox_t *mb = card->mbox;
+        ppp_intr_info_t *ppp_intr_data = (ppp_intr_info_t *) &mb->data[0];
+	int err;
+
+	memset(&mb->cmd, 0, sizeof(ppp_cmd_t));
+	ppp_intr_data->i_enable = mode;
+
+	ppp_intr_data->irq = card->hw.irq;
+	mb->cmd.length = 2;
+
+       /* If timer has been enabled, set the timer delay to 1sec */
+       if (mode & 0x80){
+       		ppp_intr_data->timer_len = 250; //5;//100; //250;
+                mb->cmd.length = 4;
+        }
+	
+	mb->cmd.command = PPP_SET_INTR_FLAGS;
+	err = sdla_exec(mb) ? mb->cmd.result : CMD_TIMEOUT;
+	
+	if (err != CMD_OK) 
+		ppp_error(card, err, mb);
+ 		
+
+	return err;
+}
+
+/*============================================================================
+ * Enable communications.
+ */
+static int ppp_comm_enable(sdla_t *card)
+{
+	ppp_mbox_t *mb = card->mbox;
+	int err;
+
+	memset(&mb->cmd, 0, sizeof(ppp_cmd_t));
+	mb->cmd.command = PPP_COMM_ENABLE;
+	err = sdla_exec(mb) ? mb->cmd.result : CMD_TIMEOUT;
+	
+	if (err != CMD_OK) 
+		ppp_error(card, err, mb);
+	else	
+		card->u.p.comm_enabled = 1;	
+
+	return err;
+}
+
+/*============================================================================
+ * Disable communications.
+ */
+static int ppp_comm_disable(sdla_t *card)
+{
+	ppp_mbox_t *mb = card->mbox;
+	int err;
+
+	memset(&mb->cmd, 0, sizeof(ppp_cmd_t));
+	mb->cmd.command = PPP_COMM_DISABLE;
+	err = sdla_exec(mb) ? mb->cmd.result : CMD_TIMEOUT;
+	if (err != CMD_OK) 
+		ppp_error(card, err, mb);
+	else
+		card->u.p.comm_enabled = 0;
+
+	return err;
+}
+
+static int ppp_comm_disable_shutdown(sdla_t *card)
+{
+	ppp_mbox_t *mb = card->mbox;
+	ppp_intr_info_t *ppp_intr_data;
+	int err;
+
+	if (!mb){
+		return 1;
+	}
+	
+	ppp_intr_data = (ppp_intr_info_t *) &mb->data[0];
+	
+	/* Disable all interrupts */
+	memset(&mb->cmd, 0, sizeof(ppp_cmd_t));
+	ppp_intr_data->i_enable = 0;
+
+	ppp_intr_data->irq = card->hw.irq;
+	mb->cmd.length = 2;
+
+	mb->cmd.command = PPP_SET_INTR_FLAGS;
+	err = sdla_exec(mb) ? mb->cmd.result : CMD_TIMEOUT;
+
+	/* Disable communicatinons */
+	memset(&mb->cmd, 0, sizeof(ppp_cmd_t));
+	mb->cmd.command = PPP_COMM_DISABLE;
+	err = sdla_exec(mb) ? mb->cmd.result : CMD_TIMEOUT;
+
+	card->u.p.comm_enabled = 0;
+
+	return 0;
+}
+
+
+
+/*============================================================================
+ * Get communications error statistics.
+ */
+static int ppp_get_err_stats(sdla_t *card)
+{
+	ppp_mbox_t *mb = card->mbox;
+	int err;
+
+	memset(&mb->cmd, 0, sizeof(ppp_cmd_t));
+	mb->cmd.command = PPP_READ_ERROR_STATS;
+	err = sdla_exec(mb) ? mb->cmd.result : CMD_TIMEOUT;
+	
+	if (err == CMD_OK) {
+		
+		ppp_err_stats_t* stats = (void*)mb->data;
+		card->wandev.stats.rx_over_errors    = stats->rx_overrun;
+		card->wandev.stats.rx_crc_errors     = stats->rx_bad_crc;
+		card->wandev.stats.rx_missed_errors  = stats->rx_abort;
+		card->wandev.stats.rx_length_errors  = stats->rx_lost;
+		card->wandev.stats.tx_aborted_errors = stats->tx_abort;
+	
+	} else 
+		ppp_error(card, err, mb);
+	
+	return err;
+}
+
+/*============================================================================
+ * Send packet.
+ *	Return:	0 - o.k.
+ *		1 - no transmit buffers available
+ */
+static int ppp_send (sdla_t *card, void *data, unsigned len, unsigned proto)
+{
+	ppp_buf_ctl_t *txbuf = card->u.p.txbuf;
+
+	if (txbuf->flag)
+                return 1;
+	
+	sdla_poke(&card->hw, txbuf->buf.ptr, data, len);
+
+	txbuf->length = len;		/* frame length */
+	
+	if (proto == htons(ETH_P_IPX))
+		txbuf->proto = 0x01;	/* protocol ID */
+	else
+		txbuf->proto = 0x00;	/* protocol ID */
+	
+	txbuf->flag = 1;		/* start transmission */
+
+	/* Update transmit buffer control fields */
+	card->u.p.txbuf = ++txbuf;
+
+	if ((void*)txbuf > card->u.p.txbuf_last)
+		card->u.p.txbuf = card->u.p.txbuf_base;
+
+	return 0;
+}
+
+/****** Firmware Error Handler **********************************************/
+
+/*============================================================================
+ * Firmware error handler.
+ *	This routine is called whenever firmware command returns non-zero
+ *	return code.
+ *
+ * Return zero if previous command has to be cancelled.
+ */
+static int ppp_error(sdla_t *card, int err, ppp_mbox_t *mb)
+{
+	unsigned cmd = mb->cmd.command;
+
+	switch (err) {
+
+		case CMD_TIMEOUT:
+			printk(KERN_ERR "%s: command 0x%02X timed out!\n",
+				card->devname, cmd);
+			break;
+
+		default:
+			printk(KERN_INFO "%s: command 0x%02X returned 0x%02X!\n"
+				, card->devname, cmd, err);
+	}
+
+	return 0;
+}
+
+/****** Interrupt Handlers **************************************************/
+
+/*============================================================================
+ * PPP interrupt service routine.
+ */
+static void wpp_isr (sdla_t *card)
+{
+	ppp_flags_t *flags = card->flags;
+	char *ptr = &flags->iflag;
+	struct net_device *dev = card->wandev.dev;
+	int i;
+
+	card->in_isr = 1;
+	++card->statistics.isr_entry;
+
+	if (!dev && flags->iflag != PPP_INTR_CMD){
+		card->in_isr = 0;
+		flags->iflag = 0;
+		return;
+	}
+	
+	if (test_bit(PERI_CRIT, (void*)&card->wandev.critical)) {
+		card->in_isr = 0;
+		flags->iflag = 0;
+		return;
+	}
+	
+	
+	if(card->hw.type != SDLA_S514){
+		if (test_bit(SEND_CRIT, (void*)&card->wandev.critical)) {
+			++card->statistics.isr_already_critical;
+			printk (KERN_INFO "%s: Critical while in ISR!\n",
+					card->devname);
+			card->in_isr = 0;
+			flags->iflag = 0;
+			return;
+		}
+	}
+
+	switch (flags->iflag) {
+
+		case PPP_INTR_RXRDY:	/* receive interrupt  0x01  (bit 0)*/
+			++card->statistics.isr_rx;
+			rx_intr(card);
+			break;
+
+		case PPP_INTR_TXRDY:	/* transmit interrupt  0x02 (bit 1)*/
+			++card->statistics.isr_tx;
+			flags->imask &= ~PPP_INTR_TXRDY;
+			netif_wake_queue(dev);
+			break;
+
+		case PPP_INTR_CMD:      /* interface command completed */
+			++Intr_test_counter;
+			++card->statistics.isr_intr_test;
+			break;
+
+		case PPP_INTR_MODEM:    /* modem status change (DCD, CTS) 0x04 (bit 2)*/
+		case PPP_INTR_DISC:  	/* Data link disconnected 0x10  (bit 4)*/	
+		case PPP_INTR_OPEN:   	/* Data link open 0x20  (bit 5)*/
+		case PPP_INTR_DROP_DTR:	/* DTR drop timeout expired  0x40 bit 6 */
+			event_intr(card);
+			break;
+	
+		case PPP_INTR_TIMER:
+			timer_intr(card);
+			break;	 
+
+		default:	/* unexpected interrupt */
+			++card->statistics.isr_spurious;
+			printk(KERN_INFO "%s: spurious interrupt 0x%02X!\n", 
+				card->devname, flags->iflag);
+			printk(KERN_INFO "%s: ID Bytes = ",card->devname);
+	 		for(i = 0; i < 8; i ++)
+				printk(KERN_INFO "0x%02X ", *(ptr + 0x28 + i));
+			printk(KERN_INFO "\n");	
+	}
+	
+	card->in_isr = 0;
+	flags->iflag = 0;
+	return;
+}
+
+/*============================================================================
+ * Receive interrupt handler.
+ */
+static void rx_intr(sdla_t *card)
+{
+	ppp_buf_ctl_t *rxbuf = card->rxmb;
+	struct net_device *dev = card->wandev.dev;
+	ppp_private_area_t *ppp_priv_area;
+	struct sk_buff *skb;
+	unsigned len;
+	void *buf;
+	int i;
+        ppp_flags_t *flags = card->flags;
+        char *ptr = &flags->iflag;
+	int udp_type;
+	
+
+	if (rxbuf->flag != 0x01) {
+
+		printk(KERN_INFO 
+			"%s: corrupted Rx buffer @ 0x%X, flag = 0x%02X!\n", 
+			card->devname, (unsigned)rxbuf, rxbuf->flag);
+	
+		printk(KERN_INFO "%s: ID Bytes = ",card->devname);
+	 	
+		for(i = 0; i < 8; i ++)
+			printk(KERN_INFO "0x%02X ", *(ptr + 0x28 + i));
+		printk(KERN_INFO "\n");	
+		
+		++card->statistics.rx_intr_corrupt_rx_bfr;
+
+
+		/* Bug Fix: Mar 6 2000
+                 * If we get a corrupted mailbox, it means that driver 
+                 * is out of sync with the firmware. There is no recovery.
+                 * If we don't turn off all interrupts for this card
+                 * the machine will crash. 
+                 */
+		printk(KERN_INFO "%s: Critical router failure ...!!!\n", card->devname);
+		printk(KERN_INFO "Please contact Sangoma Technologies !\n");
+		ppp_set_intr_mode(card,0);
+		return;
+	}
+      
+	if (dev && netif_running(dev) && dev->priv){
+	
+		len  = rxbuf->length;
+		ppp_priv_area = dev->priv;
+
+		/* Allocate socket buffer */
+		skb = dev_alloc_skb(len);
+
+		if (skb != NULL) {
+		
+			/* Copy data to the socket buffer */
+			unsigned addr = rxbuf->buf.ptr;
+
+			if ((addr + len) > card->u.p.rx_top + 1) {
+			
+				unsigned tmp = card->u.p.rx_top - addr + 1;
+				buf = skb_put(skb, tmp);
+				sdla_peek(&card->hw, addr, buf, tmp);
+				addr = card->u.p.rx_base;
+				len -= tmp;
+			}
+			buf = skb_put(skb, len);
+			sdla_peek(&card->hw, addr, buf, len);
+
+			/* Decapsulate packet */
+        		switch (rxbuf->proto) {
+	
+				case 0x00:
+					skb->protocol = htons(ETH_P_IP);
+					break;
+
+				case 0x01:
+					skb->protocol = htons(ETH_P_IPX);
+					break;
+			}
+
+			udp_type = udp_pkt_type( skb, card );
+
+			if (udp_type == UDP_PTPIPE_TYPE){
+
+				/* Handle a UDP Request in Timer Interrupt */
+				if(store_udp_mgmt_pkt(UDP_PKT_FRM_NETWORK, card, skb, dev,
+                	              			ppp_priv_area)){
+	               			flags->imask |= PPP_INTR_TIMER;
+				}
+				++ppp_priv_area->rx_intr_stat.rx_intr_PIPE_request;
+
+
+			} else if (handle_IPXWAN(skb->data,card->devname, 
+						 ppp_priv_area->enable_IPX, 
+						 ppp_priv_area->network_number, 
+						 skb->protocol)) {
+			
+				/* Handle an IPXWAN packet */
+				if( ppp_priv_area->enable_IPX) {
+					
+					/* Make sure we are not already sending */
+					if (!test_bit(SEND_CRIT, &card->wandev.critical)){
+					 	ppp_send(card, skb->data, skb->len, htons(ETH_P_IPX));
+					}
+					dev_kfree_skb_any(skb);
+
+				} else {
+					++card->wandev.stats.rx_dropped;
+				}
+			} else {
+				/* Pass data up the protocol stack */
+	    			skb->dev = dev;
+				skb->mac.raw  = skb->data;
+
+			    	++card->wandev.stats.rx_packets;
+				card->wandev.stats.rx_bytes += skb->len;
+		    		++ppp_priv_area->rx_intr_stat.rx_intr_bfr_passed_to_stack;	
+				netif_rx(skb);
+				dev->last_rx = jiffies;
+			}
+
+		} else {
+	
+			if (net_ratelimit()){
+				printk(KERN_INFO "%s: no socket buffers available!\n",
+					card->devname);
+			}
+			++card->wandev.stats.rx_dropped;
+			++ppp_priv_area->rx_intr_stat.rx_intr_no_socket;
+		}
+
+	} else {
+		++card->statistics.rx_intr_dev_not_started;
+	}
+
+	/* Release buffer element and calculate a pointer to the next one */
+	rxbuf->flag = 0x00;
+	card->rxmb = ++rxbuf;
+	if ((void*)rxbuf > card->u.p.rxbuf_last)
+		card->rxmb = card->u.p.rxbuf_base;
+}
+
+
+void event_intr (sdla_t *card)
+{
+
+ 	struct net_device* dev = card->wandev.dev;
+        ppp_private_area_t* ppp_priv_area = dev->priv;
+	volatile ppp_flags_t *flags = card->flags;
+
+	switch (flags->iflag){
+
+		case PPP_INTR_MODEM:    /* modem status change (DCD, CTS) 0x04  (bit 2)*/
+
+			if (net_ratelimit()){
+				printk (KERN_INFO "%s: Modem status: DCD=%s CTS=%s\n",
+					card->devname, DCD(flags->mstatus), CTS(flags->mstatus));
+			}
+			break;
+
+		case PPP_INTR_DISC:  	/* Data link disconnected 0x10  (bit 4)*/	
+
+			NEX_PRINTK (KERN_INFO "Data link disconnected intr Cause %X\n",
+					       flags->disc_cause);
+
+			if (flags->disc_cause &
+				(PPP_LOCAL_TERMINATION | PPP_DCD_CTS_DROP |
+				PPP_REMOTE_TERMINATION)) {
+
+				if (card->u.p.ip_mode == WANOPT_PPP_PEER) { 
+					set_bit(0,&Read_connection_info);
+				}
+				wanpipe_set_state(card, WAN_DISCONNECTED);
+
+				show_disc_cause(card, flags->disc_cause);
+				ppp_priv_area->timer_int_enabled |= TMR_INT_ENABLED_PPP_EVENT;
+				flags->imask |= PPP_INTR_TIMER;
+				trigger_ppp_poll(dev);
+			}
+			break;
+
+		case PPP_INTR_OPEN:   	/* Data link open 0x20  (bit 5)*/
+
+			NEX_PRINTK (KERN_INFO "%s: PPP Link Open, LCP=%s IP=%s\n",
+					card->devname,LCP(flags->lcp_state),
+					IP(flags->ip_state));
+
+			if (flags->lcp_state == 0x09 && 
+                           (flags->ip_state == 0x09 || flags->ipx_state == 0x09)){
+
+                                /* Initialize the polling timer and set the state
+                                 * to WAN_CONNNECTED */
+
+
+				/* BUG FIX: When the protocol restarts, during heavy 
+                                 * traffic, board tx buffers and driver tx buffers
+                                 * can go out of sync.  This checks the condition
+                                 * and if the tx buffers are out of sync, the 
+                                 * protocols are restarted. 
+                                 * I don't know why the board tx buffer is out
+                                 * of sync. It could be that a packets is tx
+                                 * while the link is down, but that is not 
+                                 * possible. The other possiblility is that the
+                                 * firmware doesn't reinitialize properly.
+                                 * FIXME: A better fix should be found.
+                                 */ 
+				if (detect_and_fix_tx_bug(card)){
+
+					ppp_comm_disable(card);
+
+					wanpipe_set_state(card, WAN_DISCONNECTED);
+
+					ppp_priv_area->timer_int_enabled |= 
+						TMR_INT_ENABLED_PPP_EVENT;
+					flags->imask |= PPP_INTR_TIMER;
+					break;	
+				}
+
+				card->state_tick = jiffies;
+				wanpipe_set_state(card, WAN_CONNECTED);
+
+				NEX_PRINTK(KERN_INFO "CON: L Tx: %lx  B Tx: %lx || L Rx %lx B Rx %lx\n",
+					(unsigned long)card->u.p.txbuf, *card->u.p.txbuf_next,
+					(unsigned long)card->rxmb, *card->u.p.rxbuf_next);
+
+				/* Tell timer interrupt that PPP event occurred */
+				ppp_priv_area->timer_int_enabled |= TMR_INT_ENABLED_PPP_EVENT;
+				flags->imask |= PPP_INTR_TIMER;
+
+				/* If we are in PEER mode, we must first obtain the
+				 * IP information and then go into the poll routine */
+				if (card->u.p.ip_mode != WANOPT_PPP_PEER){	
+					trigger_ppp_poll(dev);
+				}
+			}
+                   	break;
+
+		case PPP_INTR_DROP_DTR:		/* DTR drop timeout expired  0x40 bit 6 */
+
+			NEX_PRINTK(KERN_INFO "DTR Drop Timeout Interrrupt \n"); 
+
+			if (card->u.p.ip_mode == WANOPT_PPP_PEER) { 
+				set_bit(0,&Read_connection_info);
+			}
+		
+			wanpipe_set_state(card, WAN_DISCONNECTED);
+
+			show_disc_cause(card, flags->disc_cause);
+			ppp_priv_area->timer_int_enabled |= TMR_INT_ENABLED_PPP_EVENT;
+			flags->imask |= PPP_INTR_TIMER;
+			trigger_ppp_poll(dev);
+			break;
+		
+		default:
+			printk(KERN_INFO "%s: Error, Invalid PPP Event\n",card->devname);
+	}
+}
+
+
+
+/* TIMER INTERRUPT */
+
+void timer_intr (sdla_t *card)
+{
+
+        struct net_device* dev = card->wandev.dev;
+        ppp_private_area_t* ppp_priv_area = dev->priv;
+	ppp_flags_t *flags = card->flags;
+
+
+	if (ppp_priv_area->timer_int_enabled & TMR_INT_ENABLED_CONFIG){
+		if (!config_ppp(card)){
+			ppp_priv_area->timer_int_enabled &= 
+					~TMR_INT_ENABLED_CONFIG;	
+		}
+	}
+
+	/* Update statistics */
+	if (ppp_priv_area->timer_int_enabled & TMR_INT_ENABLED_UPDATE){
+		ppp_get_err_stats(card);
+                if(!(--ppp_priv_area->update_comms_stats)){
+			ppp_priv_area->timer_int_enabled &= 
+				~TMR_INT_ENABLED_UPDATE;
+		}
+	}
+
+	/* PPIPEMON UDP request */
+
+	if (ppp_priv_area->timer_int_enabled & TMR_INT_ENABLED_UDP){
+		process_udp_mgmt_pkt(card,dev, ppp_priv_area);
+		ppp_priv_area->timer_int_enabled &= ~TMR_INT_ENABLED_UDP;
+	}
+
+	/* PPP Event */
+	if (ppp_priv_area->timer_int_enabled & TMR_INT_ENABLED_PPP_EVENT){
+
+		if (card->wandev.state == WAN_DISCONNECTED){
+			retrigger_comm(card);
+		}
+
+		/* If the state is CONNECTING, it means that communicatins were
+	 	 * enabled. When the remote side enables its comminication we
+	 	 * should get an interrupt PPP_INTR_OPEN, thus turn off polling 
+		 */
+
+		else if (card->wandev.state == WAN_CONNECTING){
+			/* Turn off the timer interrupt */
+			ppp_priv_area->timer_int_enabled &= ~TMR_INT_ENABLED_PPP_EVENT;
+		}
+
+		/* If state is connected and we are in PEER mode 
+	 	 * poll for an IP address which will be provided by remote end.
+	 	 */
+		else if ((card->wandev.state == WAN_CONNECTED && 
+		  	  card->u.p.ip_mode == WANOPT_PPP_PEER) && 
+		  	  test_bit(0,&Read_connection_info)){
+
+			card->state_tick = jiffies;
+			if (read_connection_info (card)){
+				printk(KERN_INFO "%s: Failed to read PEER IP Addresses\n",
+					card->devname);
+			}else{
+				clear_bit(0,&Read_connection_info);
+				set_bit(1,&Read_connection_info);
+				trigger_ppp_poll(dev);
+			}
+		}else{
+			//FIXME Put the comment back int
+			ppp_priv_area->timer_int_enabled &= ~TMR_INT_ENABLED_PPP_EVENT;
+		}
+
+	}/* End of PPP_EVENT */
+
+
+	/* Only disable the timer interrupt if there are no udp, statistic */
+	/* updates or events pending */
+        if(!ppp_priv_area->timer_int_enabled) {
+                flags->imask &= ~PPP_INTR_TIMER;
+        }
+}
+
+
+static int handle_IPXWAN(unsigned char *sendpacket, char *devname, unsigned char enable_IPX, unsigned long network_number, unsigned short proto)
+{
+	int i;
+
+	if( proto == htons(ETH_P_IPX) ) {
+		//It's an IPX packet
+		if(!enable_IPX) {
+			//Return 1 so we don't pass it up the stack.
+			return 1;
+		}
+	} else {
+		//It's not IPX so pass it up the stack.
+		return 0;
+	}
+
+	if( sendpacket[16] == 0x90 &&
+	    sendpacket[17] == 0x04)
+	{
+		//It's IPXWAN
+
+		if( sendpacket[2] == 0x02 &&
+		    sendpacket[34] == 0x00)
+		{
+			//It's a timer request packet
+			printk(KERN_INFO "%s: Received IPXWAN Timer Request packet\n",devname);
+
+			//Go through the routing options and answer no to every
+			//option except Unnumbered RIP/SAP
+			for(i = 41; sendpacket[i] == 0x00; i += 5)
+			{
+				//0x02 is the option for Unnumbered RIP/SAP
+				if( sendpacket[i + 4] != 0x02)
+				{
+					sendpacket[i + 1] = 0;
+				}
+			}
+
+			//Skip over the extended Node ID option
+			if( sendpacket[i] == 0x04 )
+			{
+				i += 8;
+			}
+
+			//We also want to turn off all header compression opt.
+			for(; sendpacket[i] == 0x80 ;)
+			{
+				sendpacket[i + 1] = 0;
+				i += (sendpacket[i + 2] << 8) + (sendpacket[i + 3]) + 4;
+			}
+
+			//Set the packet type to timer response
+			sendpacket[34] = 0x01;
+
+			printk(KERN_INFO "%s: Sending IPXWAN Timer Response\n",devname);
+		}
+		else if( sendpacket[34] == 0x02 )
+		{
+			//This is an information request packet
+			printk(KERN_INFO "%s: Received IPXWAN Information Request packet\n",devname);
+
+			//Set the packet type to information response
+			sendpacket[34] = 0x03;
+
+			//Set the router name
+			sendpacket[51] = 'P';
+			sendpacket[52] = 'T';
+			sendpacket[53] = 'P';
+			sendpacket[54] = 'I';
+			sendpacket[55] = 'P';
+			sendpacket[56] = 'E';
+			sendpacket[57] = '-';
+			sendpacket[58] = CVHexToAscii(network_number >> 28);
+			sendpacket[59] = CVHexToAscii((network_number & 0x0F000000)>> 24);
+			sendpacket[60] = CVHexToAscii((network_number & 0x00F00000)>> 20);
+			sendpacket[61] = CVHexToAscii((network_number & 0x000F0000)>> 16);
+			sendpacket[62] = CVHexToAscii((network_number & 0x0000F000)>> 12);
+			sendpacket[63] = CVHexToAscii((network_number & 0x00000F00)>> 8);
+			sendpacket[64] = CVHexToAscii((network_number & 0x000000F0)>> 4);
+			sendpacket[65] = CVHexToAscii(network_number & 0x0000000F);
+			for(i = 66; i < 99; i+= 1)
+			{
+				sendpacket[i] = 0;
+			}
+
+			printk(KERN_INFO "%s: Sending IPXWAN Information Response packet\n",devname);
+		}
+		else
+		{
+			printk(KERN_INFO "%s: Unknown IPXWAN packet!\n",devname);
+			return 0;
+		}
+
+		//Set the WNodeID to our network address
+		sendpacket[35] = (unsigned char)(network_number >> 24);
+		sendpacket[36] = (unsigned char)((network_number & 0x00FF0000) >> 16);
+		sendpacket[37] = (unsigned char)((network_number & 0x0000FF00) >> 8);
+		sendpacket[38] = (unsigned char)(network_number & 0x000000FF);
+
+		return 1;
+	} else {
+		//If we get here it's an IPX-data packet, so it'll get passed up the stack.
+
+		//switch the network numbers
+		switch_net_numbers(sendpacket, network_number, 1);	
+		return 0;
+	}
+}
+
+/****** Background Polling Routines  ****************************************/
+
+/* All polling functions are invoked by the TIMER interrupt in the wpp_isr 
+ * routine.  
+ */
+
+/*============================================================================
+ * Monitor active link phase.
+ */
+static void process_route (sdla_t *card)
+{
+	ppp_flags_t *flags = card->flags;
+	struct net_device *dev = card->wandev.dev;
+	ppp_private_area_t *ppp_priv_area = dev->priv;
+	
+	if ((card->u.p.ip_mode == WANOPT_PPP_PEER) &&
+	    (flags->ip_state == 0x09)){ 
+
+		/* We get ip_local from the firmware in PEER mode.
+	         * Therefore, if ip_local is 0, we failed to obtain
+         	 * the remote IP address. */
+		if (ppp_priv_area->ip_local == 0) 
+			return;
+		
+		printk(KERN_INFO "%s: IPCP State Opened.\n", card->devname);
+		if (read_info( card )) {
+   			printk(KERN_INFO 
+				"%s: An error occurred in IP assignment.\n", 
+				card->devname);
+		} else {
+			struct in_device *in_dev = dev->ip_ptr;
+			if (in_dev != NULL ) {
+				struct in_ifaddr *ifa = in_dev->ifa_list;
+
+				printk(KERN_INFO "%s: Assigned Lcl. Addr: %u.%u.%u.%u\n", 
+					card->devname, NIPQUAD(ifa->ifa_local));
+				printk(KERN_INFO "%s: Assigned Rmt. Addr: %u.%u.%u.%u\n", 
+						card->devname, NIPQUAD(ifa->ifa_address));
+			}else{
+				printk(KERN_INFO 
+				"%s: Error: Failed to add a route for PPP interface %s\n",
+					card->devname,dev->name);	
+			}
+		}
+	}
+}
+
+/*============================================================================
+ * Monitor physical link disconnected phase.
+ *  o if interface is up and the hold-down timeout has expired, then retry
+ *    connection.
+ */
+static void retrigger_comm(sdla_t *card)
+{
+	struct net_device *dev = card->wandev.dev;
+
+	if (dev && ((jiffies - card->state_tick) > HOLD_DOWN_TIME)) {
+
+		wanpipe_set_state(card, WAN_CONNECTING);
+
+		if(ppp_comm_enable(card) == CMD_OK){
+			init_ppp_tx_rx_buff( card );
+		}	         
+	}
+}
+
+/****** Miscellaneous Functions *********************************************/
+
+/*============================================================================
+ * Configure S508 adapter.
+ */
+static int config508(struct net_device *dev, sdla_t *card)
+{
+	ppp508_conf_t cfg;
+	struct in_device *in_dev = dev->ip_ptr;
+	ppp_private_area_t *ppp_priv_area = dev->priv;
+
+	/* Prepare PPP configuration structure */
+	memset(&cfg, 0, sizeof(ppp508_conf_t));
+
+	if (card->wandev.clocking)
+		cfg.line_speed = card->wandev.bps;
+
+	if (card->wandev.interface == WANOPT_RS232)
+		cfg.conf_flags |= INTERFACE_LEVEL_RS232;
+
+
+        cfg.conf_flags 	|= DONT_TERMINATE_LNK_MAX_CONFIG; /*send Configure-Request packets forever*/
+	cfg.txbuf_percent	= PERCENT_TX_BUFF;	/* % of Tx bufs */
+	cfg.mtu_local		= card->wandev.mtu;
+	cfg.mtu_remote		= card->wandev.mtu;                  /*    Default   */
+	cfg.restart_tmr		= TIME_BETWEEN_CONF_REQ;  	     /*    30 = 3sec */
+	cfg.auth_rsrt_tmr	= TIME_BETWEEN_PAP_CHAP_REQ;         /*    30 = 3sec */
+	cfg.auth_wait_tmr	= WAIT_PAP_CHAP_WITHOUT_REPLY;       /*   300 = 30s  */
+	cfg.mdm_fail_tmr	= WAIT_AFTER_DCD_CTS_LOW;            /*     5 = 0.5s */
+	cfg.dtr_drop_tmr	= TIME_DCD_CTS_LOW_AFTER_LNK_DOWN;   /*    10 = 1s   */
+	cfg.connect_tmout	= WAIT_DCD_HIGH_AFTER_ENABLE_COMM;   /*   900 = 90s  */
+	cfg.conf_retry		= MAX_CONF_REQ_WITHOUT_REPLY;        /*    10 = 1s   */
+	cfg.term_retry		= MAX_TERM_REQ_WITHOUT_REPLY;	     /*     2 times  */
+	cfg.fail_retry		= NUM_CONF_NAK_WITHOUT_REPLY;        /*     5 times  */
+	cfg.auth_retry		= NUM_AUTH_REQ_WITHOUT_REPLY;        /*     10 times */   
+
+
+	if( !card->u.p.authenticator ) {
+		printk(KERN_INFO "%s: Device is not configured as an authenticator\n", 
+				card->devname);
+		cfg.auth_options = NO_AUTHENTICATION;
+	}else{
+		printk(KERN_INFO "%s: Device is configured as an authenticator\n", 
+				card->devname);
+		cfg.auth_options = INBOUND_AUTH;
+	}
+
+	if( ppp_priv_area->pap == WANOPT_YES){
+		cfg.auth_options |=PAP_AUTH;
+		printk(KERN_INFO "%s: Pap enabled\n", card->devname);
+	}
+	if( ppp_priv_area->chap == WANOPT_YES){
+		cfg.auth_options |= CHAP_AUTH;
+		printk(KERN_INFO "%s: Chap enabled\n", card->devname);
+	}
+
+
+	if (ppp_priv_area->enable_IPX == WANOPT_YES){
+		printk(KERN_INFO "%s: Enabling IPX Protocol\n",card->devname);
+		cfg.ipx_options		= ENABLE_IPX | ROUTING_PROT_DEFAULT;
+	}else{
+		cfg.ipx_options 	= DISABLE_IPX;
+	}
+
+	switch (card->u.p.ip_mode) {
+	
+		case WANOPT_PPP_STATIC:
+
+			printk(KERN_INFO "%s: PPP IP Mode: STATIC\n",card->devname);
+			cfg.ip_options		= L_AND_R_IP_NO_ASSIG | 
+							    ENABLE_IP;
+			cfg.ip_local		= in_dev->ifa_list->ifa_local;
+			cfg.ip_remote		= in_dev->ifa_list->ifa_address;
+			/* Debugging code used to check that IP addresses
+                         * obtained from the kernel are correct */
+
+                        NEX_PRINTK(KERN_INFO "Local %u.%u.%u.%u Remote %u.%u.%u.%u Name %s\n",
+					NIPQUAD(ip_local),NIPQUAD(ip_remote), dev->name);
+			break;
+
+		case WANOPT_PPP_HOST:
+
+			printk(KERN_INFO "%s: PPP IP Mode: HOST\n",card->devname);
+			cfg.ip_options		= L_IP_LOCAL_ASSIG |
+						  R_IP_LOCAL_ASSIG | 
+						  ENABLE_IP;
+			cfg.ip_local		= in_dev->ifa_list->ifa_local;
+			cfg.ip_remote		= in_dev->ifa_list->ifa_address;
+			/* Debugging code used to check that IP addresses
+                         * obtained from the kernel are correct */
+                        NEX_PRINTK (KERN_INFO "Local %u.%u.%u.%u Remote %u.%u.%u.%u Name %s\n",
+					NIPQUAD(ip_local),NIPQUAD(ip_remote), dev->name);
+			
+			break;
+	
+		case WANOPT_PPP_PEER:
+
+			printk(KERN_INFO "%s: PPP IP Mode: PEER\n",card->devname);
+			cfg.ip_options		= L_IP_REMOTE_ASSIG | 
+						  R_IP_REMOTE_ASSIG | 
+							  ENABLE_IP;
+			cfg.ip_local		= 0x00;
+			cfg.ip_remote		= 0x00;
+			break;
+
+		default:
+			printk(KERN_INFO "%s: ERROR: Unsupported PPP Mode Selected\n",
+					card->devname);
+			printk(KERN_INFO "%s:        PPP IP Modes: STATIC, PEER or HOST\n",
+					card->devname);	
+			return 1;
+	}
+
+	return ppp_configure(card, &cfg);
+}
+
+/*============================================================================
+ * Show disconnection cause.
+ */
+static void show_disc_cause(sdla_t *card, unsigned cause)
+{
+	if (cause & 0x0802) 
+
+		printk(KERN_INFO "%s: link terminated by peer\n", 
+			card->devname);
+
+	else if (cause & 0x0004) 
+
+		printk(KERN_INFO "%s: link terminated by user\n", 
+			card->devname);
+
+	else if (cause & 0x0008) 
+
+		printk(KERN_INFO "%s: authentication failed\n", card->devname);
+	
+	else if (cause & 0x0010) 
+
+		printk(KERN_INFO 
+			"%s: authentication protocol negotiation failed\n", 
+			card->devname);
+
+	else if (cause & 0x0020) 
+		
+		printk(KERN_INFO
+		"%s: peer's request for authentication rejected\n",
+		card->devname);
+
+	else if (cause & 0x0040) 
+	
+		printk(KERN_INFO "%s: MRU option rejected by peer\n", 
+		card->devname);
+
+	else if (cause & 0x0080) 
+	
+		printk(KERN_INFO "%s: peer's MRU was too small\n", 
+		card->devname);
+
+	else if (cause & 0x0100) 
+
+		printk(KERN_INFO "%s: failed to negotiate peer's LCP options\n",
+		card->devname);
+
+	else if (cause & 0x0200) 
+		
+		printk(KERN_INFO "%s: failed to negotiate peer's IPCP options\n"
+		, card->devname);
+
+	else if (cause & 0x0400) 
+
+		printk(KERN_INFO 
+			"%s: failed to negotiate peer's IPXCP options\n",
+			card->devname);
+}
+
+/*=============================================================================
+ * Process UDP call of type PTPIPEAB.
+ */
+static void process_udp_mgmt_pkt(sdla_t *card, struct net_device *dev, 
+				 ppp_private_area_t *ppp_priv_area ) 
+{
+	unsigned char buf2[5];
+	unsigned char *buf;
+	unsigned int frames, len;
+	struct sk_buff *new_skb;
+	unsigned short data_length, buffer_length, real_len;
+	unsigned long data_ptr;
+	int udp_mgmt_req_valid = 1;
+	ppp_mbox_t *mbox = card->mbox;
+	struct timeval tv;
+	int err;
+	ppp_udp_pkt_t *ppp_udp_pkt = (ppp_udp_pkt_t*)&ppp_priv_area->udp_pkt_data;
+
+	memcpy(&buf2, &card->wandev.udp_port, 2 );
+
+
+	if(ppp_priv_area->udp_pkt_src == UDP_PKT_FRM_NETWORK) {
+
+		switch(ppp_udp_pkt->cblock.command) {
+
+			case PPIPE_GET_IBA_DATA:
+			case PPP_READ_CONFIG:
+			case PPP_GET_CONNECTION_INFO:
+			case PPIPE_ROUTER_UP_TIME:
+			case PPP_READ_STATISTICS:
+			case PPP_READ_ERROR_STATS:
+			case PPP_READ_PACKET_STATS:
+			case PPP_READ_LCP_STATS:
+			case PPP_READ_IPCP_STATS:
+			case PPP_READ_IPXCP_STATS:
+			case PPP_READ_PAP_STATS:
+			case PPP_READ_CHAP_STATS:
+			case PPP_READ_CODE_VERSION:
+				udp_mgmt_req_valid = 1;
+				break;
+			   
+			default:
+				udp_mgmt_req_valid = 0;
+				break;
+		} 
+	}
+	
+  	if(!udp_mgmt_req_valid) {
+	    
+		/* set length to 0 */
+    		ppp_udp_pkt->cblock.length = 0x00;
+
+    		/* set return code */
+    		ppp_udp_pkt->cblock.result = 0xCD; 
+		++ppp_priv_area->pipe_mgmt_stat.UDP_PIPE_mgmt_direction_err;
+	
+		if (net_ratelimit()){	
+			printk(KERN_INFO 
+			"%s: Warning, Illegal UDP command attempted from network: %x\n",
+			card->devname,ppp_udp_pkt->cblock.command);
+		}
+   	} else {
+		/* Initialize the trace element */
+		trace_element_t trace_element;		    
+
+		switch (ppp_udp_pkt->cblock.command){
+
+		/* PPIPE_ENABLE_TRACING */
+    		case PPIPE_ENABLE_TRACING:
+			if (!card->TracingEnabled) {
+    			
+				/* OPERATE_DATALINE_MONITOR */
+    				mbox->cmd.command = PPP_DATALINE_MONITOR;
+    				mbox->cmd.length = 0x01;
+    				mbox->data[0] = ppp_udp_pkt->data[0];
+	    			err = sdla_exec(mbox) ? 
+					mbox->cmd.result : CMD_TIMEOUT;
+	   
+				if (err != CMD_OK) { 
+	        			
+					ppp_error(card, err, mbox);
+	        			card->TracingEnabled = 0;
+	        		
+					/* set the return code */
+
+		        		ppp_udp_pkt->cblock.result = mbox->cmd.result;
+	        			mbox->cmd.length = 0;
+	        			break;
+	    			} 
+
+				sdla_peek(&card->hw, 0xC000, &buf2, 2);
+		    
+				ppp_priv_area->curr_trace_addr = 0;
+		    		memcpy(&ppp_priv_area->curr_trace_addr, &buf2, 2);
+		    		ppp_priv_area->start_trace_addr = 
+						ppp_priv_area->curr_trace_addr;
+				ppp_priv_area->end_trace_addr = 
+					ppp_priv_area->start_trace_addr + END_OFFSET;
+		    	
+				/* MAX_SEND_BUFFER_SIZE - 28 (IP header) 
+				   - 32 (ppipemon CBLOCK) */
+		    		available_buffer_space = MAX_LGTH_UDP_MGNT_PKT - 
+							 sizeof(ip_pkt_t)-
+							 sizeof(udp_pkt_t)-
+							 sizeof(wp_mgmt_t)-
+							 sizeof(cblock_t);
+	       	  	}
+	       	  	ppp_udp_pkt->cblock.result = 0;
+	       	  	mbox->cmd.length = 0;
+	       	  	card->TracingEnabled = 1;
+	       	  	break;
+	   
+		/* PPIPE_DISABLE_TRACING */
+		case PPIPE_DISABLE_TRACING:
+	      		
+			if(card->TracingEnabled) {
+		   	
+				/* OPERATE_DATALINE_MONITOR */
+		    		mbox->cmd.command = 0x33;
+		    		mbox->cmd.length = 1;
+		    		mbox->data[0] = 0x00;
+		    		err = sdla_exec(mbox) ? 
+					mbox->cmd.result : CMD_TIMEOUT;
+	       	  
+			} 
+		
+			/*set return code*/
+			ppp_udp_pkt->cblock.result = 0;
+			mbox->cmd.length = 0;
+			card->TracingEnabled = 0;
+			break;
+	   
+		/* PPIPE_GET_TRACE_INFO */
+		case PPIPE_GET_TRACE_INFO:
+
+			if(!card->TracingEnabled) {
+				/* set return code */
+	    			ppp_udp_pkt->cblock.result = 1;
+	    			mbox->cmd.length = 0;
+			}		    
+
+			buffer_length = 0;
+			
+			/* frames < 62, where 62 is the number of trace
+			   information elements.  There is in total 496
+			   bytes of space and each trace information
+			   element is 8 bytes. 
+			 */
+			for ( frames=0; frames<62; frames++) {
+	
+				trace_pkt_t *trace_pkt = (trace_pkt_t *)
+					&ppp_udp_pkt->data[buffer_length];
+	
+				/* Read the whole trace packet */
+				sdla_peek(&card->hw, ppp_priv_area->curr_trace_addr, 
+					  &trace_element, sizeof(trace_element_t));
+	
+				/* no data on board so exit */
+				if( trace_element.opp_flag == 0x00 ) 
+					break;
+	      
+				data_ptr = trace_element.trace_data_ptr;
+
+				/* See if there is actual data on the trace buffer */
+				if (data_ptr){
+					data_length = trace_element.trace_length;
+				}else{
+					data_length = 0;
+					ppp_udp_pkt->data[0] |= 0x02;
+				}
+
+				//FIXME: Do we need this check
+				if ((available_buffer_space - buffer_length) 
+				     < (sizeof(trace_element_t)+1)){
+					
+					/*indicate we have more frames 
+					 * on board and exit 
+					 */
+					ppp_udp_pkt->data[0] |= 0x02;
+					break;
+				}
+				
+				trace_pkt->status = trace_element.trace_type;
+				trace_pkt->time_stamp = trace_element.trace_time_stamp;
+				trace_pkt->real_length = trace_element.trace_length;
+
+				real_len = trace_element.trace_length;	
+				
+				if(data_ptr == 0){
+					trace_pkt->data_avail = 0x00;
+				}else{
+					/* we can take it next time */
+					if ((available_buffer_space - buffer_length)<
+						(real_len + sizeof(trace_pkt_t))){
+					
+						ppp_udp_pkt->data[0] |= 0x02;
+						break;
+					} 
+					trace_pkt->data_avail = 0x01;
+				
+					/* get the data */
+					sdla_peek(&card->hw, data_ptr, 
+						  &trace_pkt->data,
+						  real_len);
+				}	
+				/* zero the opp flag to 
+				   show we got the frame */
+				buf2[0] = 0x00;
+				sdla_poke(&card->hw, ppp_priv_area->curr_trace_addr,
+					  &buf2, 1);
+
+				/* now move onto the next 
+				   frame */
+				ppp_priv_area->curr_trace_addr += 8;
+
+				/* check if we passed the last address */
+				if ( ppp_priv_area->curr_trace_addr >= 
+					ppp_priv_area->end_trace_addr){
+
+					ppp_priv_area->curr_trace_addr = 
+						ppp_priv_area->start_trace_addr;
+				}
+ 
+				/* update buffer length and make sure its even */ 
+
+				if ( trace_pkt->data_avail == 0x01 ) {
+					buffer_length += real_len - 1;
+				}
+ 
+				/* for the header */
+				buffer_length += 8;
+
+				if( buffer_length & 0x0001 )
+					buffer_length += 1;
+			}
+
+			/* ok now set the total number of frames passed
+			   in the high 5 bits */
+			ppp_udp_pkt->data[0] |= (frames << 2);
+	 
+			/* set the data length */
+			mbox->cmd.length = buffer_length;
+			ppp_udp_pkt->cblock.length = buffer_length;
+	 
+			/* set return code */
+			ppp_udp_pkt->cblock.result = 0;
+	      	  	break;
+
+   		/* PPIPE_GET_IBA_DATA */
+		case PPIPE_GET_IBA_DATA:
+	        
+			mbox->cmd.length = 0x09;
+		
+			sdla_peek(&card->hw, 0xF003, &ppp_udp_pkt->data, 
+					mbox->cmd.length);
+	        
+			/* set the length of the data */
+			ppp_udp_pkt->cblock.length = 0x09;
+
+			/* set return code */
+			ppp_udp_pkt->cblock.result = 0x00;
+			ppp_udp_pkt->cblock.result = 0;
+			break;
+
+		/* PPIPE_FT1_READ_STATUS */
+		case PPIPE_FT1_READ_STATUS:
+			sdla_peek(&card->hw, 0xF020, &ppp_udp_pkt->data[0], 2);
+			ppp_udp_pkt->cblock.length = mbox->cmd.length = 2;
+			ppp_udp_pkt->cblock.result = 0;
+			break;
+		
+		case PPIPE_FLUSH_DRIVER_STATS:   
+			init_ppp_priv_struct( ppp_priv_area );
+			init_global_statistics( card );
+			mbox->cmd.length = 0;
+			ppp_udp_pkt->cblock.result = 0;
+			break;
+
+		
+		case PPIPE_ROUTER_UP_TIME:
+
+			do_gettimeofday( &tv );
+			ppp_priv_area->router_up_time = tv.tv_sec - 
+					ppp_priv_area->router_start_time;
+			*(unsigned long *)&ppp_udp_pkt->data = ppp_priv_area->router_up_time;
+			mbox->cmd.length = 4;
+			ppp_udp_pkt->cblock.result = 0;
+			break;
+
+				/* PPIPE_DRIVER_STATISTICS */   
+		case PPIPE_DRIVER_STAT_IFSEND:
+			memcpy(&ppp_udp_pkt->data, &ppp_priv_area->if_send_stat, 
+				sizeof(if_send_stat_t));
+
+
+			ppp_udp_pkt->cblock.result = 0;
+			ppp_udp_pkt->cblock.length = sizeof(if_send_stat_t);
+			mbox->cmd.length = sizeof(if_send_stat_t);	
+			break;
+
+		case PPIPE_DRIVER_STAT_INTR:
+			memcpy(&ppp_udp_pkt->data, &card->statistics, 
+				sizeof(global_stats_t));
+
+			memcpy(&ppp_udp_pkt->data+sizeof(global_stats_t),
+				&ppp_priv_area->rx_intr_stat,
+				sizeof(rx_intr_stat_t));
+
+			ppp_udp_pkt->cblock.result = 0;
+			ppp_udp_pkt->cblock.length = sizeof(global_stats_t)+
+						     sizeof(rx_intr_stat_t);
+			mbox->cmd.length = ppp_udp_pkt->cblock.length;
+			break;
+
+		case PPIPE_DRIVER_STAT_GEN:
+			memcpy( &ppp_udp_pkt->data,
+				&ppp_priv_area->pipe_mgmt_stat,
+				sizeof(pipe_mgmt_stat_t));
+
+			memcpy(&ppp_udp_pkt->data+sizeof(pipe_mgmt_stat_t), 
+			       &card->statistics, sizeof(global_stats_t));
+
+			ppp_udp_pkt->cblock.result = 0;
+			ppp_udp_pkt->cblock.length = sizeof(global_stats_t)+
+						     sizeof(rx_intr_stat_t);
+			mbox->cmd.length = ppp_udp_pkt->cblock.length;
+			break;
+
+
+		/* FT1 MONITOR STATUS */
+   		case FT1_MONITOR_STATUS_CTRL:
+	
+			/* Enable FT1 MONITOR STATUS */
+	        	if( ppp_udp_pkt->data[0] == 1) {
+			
+				if( rCount++ != 0 ) {
+		        		ppp_udp_pkt->cblock.result = 0;
+	          			mbox->cmd.length = 1;
+		  			break;
+		    		}	
+	      		}
+
+	      		/* Disable FT1 MONITOR STATUS */
+	      		if( ppp_udp_pkt->data[0] == 0) {
+
+	      	   		if( --rCount != 0) {
+		  			ppp_udp_pkt->cblock.result = 0;
+		  			mbox->cmd.length = 1;
+		  			break;
+	   	    		} 
+	      		} 	
+			goto udp_dflt_cmd;
+			
+		/* WARNING: FIXME: This should be fixed.
+		 * The FT1 Status Ctrl doesn't have a break
+                 * statment.  Thus, no code must be inserted
+                 * HERE: between default and above case statement */
+
+		default:
+udp_dflt_cmd:
+	        
+			/* it's a board command */
+			mbox->cmd.command = ppp_udp_pkt->cblock.command;
+			mbox->cmd.length = ppp_udp_pkt->cblock.length;
+ 
+			if(mbox->cmd.length) {
+				memcpy(&mbox->data,(unsigned char *)ppp_udp_pkt->data,
+				       mbox->cmd.length);
+	      		} 
+	          
+			/* run the command on the board */
+			err = sdla_exec(mbox) ? mbox->cmd.result : CMD_TIMEOUT;
+		
+			if (err != CMD_OK) {
+		
+		    		ppp_error(card, err, mbox);
+		    		++ppp_priv_area->pipe_mgmt_stat.
+					 UDP_PIPE_mgmt_adptr_cmnd_timeout;
+				break;
+			}
+	          
+		  	++ppp_priv_area->pipe_mgmt_stat.UDP_PIPE_mgmt_adptr_cmnd_OK;
+		
+			/* copy the result back to our buffer */
+			memcpy(&ppp_udp_pkt->cblock,mbox, sizeof(cblock_t));
+	          
+			if(mbox->cmd.length) {
+				memcpy(&ppp_udp_pkt->data,&mbox->data,mbox->cmd.length);
+			} 
+
+		} /* end of switch */
+     	} /* end of else */
+
+     	/* Fill UDP TTL */
+     	ppp_udp_pkt->ip_pkt.ttl = card->wandev.ttl; 
+     	len = reply_udp(ppp_priv_area->udp_pkt_data, mbox->cmd.length);
+
+     	if (ppp_priv_area->udp_pkt_src == UDP_PKT_FRM_NETWORK) {
+
+		/* Make sure we are not already sending */
+		if (!test_bit(SEND_CRIT,&card->wandev.critical)){
+			++ppp_priv_area->pipe_mgmt_stat.UDP_PIPE_mgmt_passed_to_adptr;
+			ppp_send(card,ppp_priv_area->udp_pkt_data,len,ppp_priv_area->protocol);
+		}
+
+	} else {	
+	
+		/* Pass it up the stack
+    		   Allocate socket buffer */
+		if ((new_skb = dev_alloc_skb(len)) != NULL) {
+	    	
+			/* copy data into new_skb */
+
+  	    		buf = skb_put(new_skb, len);
+  	    		memcpy(buf,ppp_priv_area->udp_pkt_data, len);
+
+	    		++ppp_priv_area->pipe_mgmt_stat.UDP_PIPE_mgmt_passed_to_stack;
+			
+            		/* Decapsulate packet and pass it up the protocol 
+			   stack */
+	    		new_skb->protocol = htons(ETH_P_IP);
+            		new_skb->dev = dev;
+	    		new_skb->mac.raw  = new_skb->data;
+			netif_rx(new_skb);
+			dev->last_rx = jiffies;
+		
+		} else {
+	    	
+			++ppp_priv_area->pipe_mgmt_stat.UDP_PIPE_mgmt_no_socket;
+			printk(KERN_INFO "no socket buffers available!\n");
+  		}
+    	}	
+
+	ppp_priv_area->udp_pkt_lgth = 0;
+	
+	return; 
+}
+
+/*=============================================================================
+ * Initial the ppp_private_area structure.
+ */
+static void init_ppp_priv_struct( ppp_private_area_t *ppp_priv_area )
+{
+
+	memset(&ppp_priv_area->if_send_stat, 0, sizeof(if_send_stat_t));
+	memset(&ppp_priv_area->rx_intr_stat, 0, sizeof(rx_intr_stat_t));
+	memset(&ppp_priv_area->pipe_mgmt_stat, 0, sizeof(pipe_mgmt_stat_t));	
+}
+
+/*============================================================================
+ * Initialize Global Statistics
+ */
+static void init_global_statistics( sdla_t *card )
+{
+	memset(&card->statistics, 0, sizeof(global_stats_t));
+}
+
+/*============================================================================
+ * Initialize Receive and Transmit Buffers.
+ */
+static void init_ppp_tx_rx_buff( sdla_t *card )
+{
+	ppp508_buf_info_t* info;
+
+	if (card->hw.type == SDLA_S514) {
+		
+		info = (void*)(card->hw.dpmbase + PPP514_BUF_OFFS);
+
+       		card->u.p.txbuf_base = (void*)(card->hw.dpmbase +
+			info->txb_ptr);
+
+                card->u.p.txbuf_last = (ppp_buf_ctl_t*)card->u.p.txbuf_base +
+                        (info->txb_num - 1);
+
+                card->u.p.rxbuf_base = (void*)(card->hw.dpmbase +
+                        info->rxb_ptr);
+
+                card->u.p.rxbuf_last = (ppp_buf_ctl_t*)card->u.p.rxbuf_base +
+                        (info->rxb_num - 1);
+
+	} else {
+		
+		info = (void*)(card->hw.dpmbase + PPP508_BUF_OFFS);
+
+		card->u.p.txbuf_base = (void*)(card->hw.dpmbase +
+			(info->txb_ptr - PPP508_MB_VECT));
+
+		card->u.p.txbuf_last = (ppp_buf_ctl_t*)card->u.p.txbuf_base +
+			(info->txb_num - 1);
+
+		card->u.p.rxbuf_base = (void*)(card->hw.dpmbase +
+			(info->rxb_ptr - PPP508_MB_VECT));
+
+		card->u.p.rxbuf_last = (ppp_buf_ctl_t*)card->u.p.rxbuf_base +
+			(info->rxb_num - 1);
+	}
+
+	card->u.p.txbuf_next = (unsigned long*)&info->txb_nxt; 
+	card->u.p.rxbuf_next = (unsigned long*)&info->rxb1_ptr;
+
+	card->u.p.rx_base = info->rxb_base;
+        card->u.p.rx_top  = info->rxb_end;
+      
+	card->u.p.txbuf = card->u.p.txbuf_base;
+	card->rxmb = card->u.p.rxbuf_base;
+
+}
+
+/*=============================================================================
+ * Read Connection Information (ie for Remote IP address assginment).
+ * Called when ppp interface connected.
+ */
+static int read_info( sdla_t *card )
+{
+	struct net_device *dev = card->wandev.dev;
+	ppp_private_area_t *ppp_priv_area = dev->priv;
+	int err;
+
+	struct ifreq if_info;
+	struct sockaddr_in *if_data1, *if_data2;
+	mm_segment_t fs;
+
+	/* Set Local and remote addresses */
+	memset(&if_info, 0, sizeof(if_info));
+	strcpy(if_info.ifr_name, dev->name);
+
+
+	fs = get_fs();
+	set_fs(get_ds());     /* get user space block */ 
+
+	/* Change the local and remote ip address of the interface.
+	 * This will also add in the destination route.
+	 */	
+	if_data1 = (struct sockaddr_in *)&if_info.ifr_addr;
+	if_data1->sin_addr.s_addr = ppp_priv_area->ip_local;
+	if_data1->sin_family = AF_INET;
+	err = devinet_ioctl( SIOCSIFADDR, &if_info );
+	if_data2 = (struct sockaddr_in *)&if_info.ifr_dstaddr;
+	if_data2->sin_addr.s_addr = ppp_priv_area->ip_remote;
+	if_data2->sin_family = AF_INET;
+	err = devinet_ioctl( SIOCSIFDSTADDR, &if_info );
+
+	set_fs(fs);           /* restore old block */
+	
+	if (err) {
+		printk (KERN_INFO "%s: Adding of route failed: %i\n",
+			card->devname,err);
+		printk (KERN_INFO "%s:	Local : %u.%u.%u.%u\n",
+			card->devname,NIPQUAD(ppp_priv_area->ip_local));
+		printk (KERN_INFO "%s:	Remote: %u.%u.%u.%u\n",
+			card->devname,NIPQUAD(ppp_priv_area->ip_remote));
+	}
+	return err;
+}
+
+/*=============================================================================
+ * Remove Dynamic Route.
+ * Called when ppp interface disconnected.
+ */
+
+static void remove_route( sdla_t *card )
+{
+
+	struct net_device *dev = card->wandev.dev;
+	long ip_addr;
+	int err;
+
+        mm_segment_t fs;
+	struct ifreq if_info;
+	struct sockaddr_in *if_data1;
+        struct in_device *in_dev = dev->ip_ptr;
+        struct in_ifaddr *ifa = in_dev->ifa_list;	
+
+	ip_addr = ifa->ifa_local;
+
+	/* Set Local and remote addresses */
+	memset(&if_info, 0, sizeof(if_info));
+	strcpy(if_info.ifr_name, dev->name);
+
+	fs = get_fs();
+       	set_fs(get_ds());     /* get user space block */ 
+
+	/* Change the local ip address of the interface to 0.
+	 * This will also delete the destination route.
+	 */	
+	if_data1 = (struct sockaddr_in *)&if_info.ifr_addr;
+	if_data1->sin_addr.s_addr = 0;
+	if_data1->sin_family = AF_INET;
+	err = devinet_ioctl( SIOCSIFADDR, &if_info );
+
+        set_fs(fs);           /* restore old block */
+
+	
+	if (err) {
+		printk (KERN_INFO "%s: Deleting dynamic route failed %d!\n",
+			 card->devname, err);
+		return;
+	}else{
+		printk (KERN_INFO "%s: PPP Deleting dynamic route %u.%u.%u.%u successfuly\n",
+			card->devname, NIPQUAD(ip_addr));
+	}
+	return;
+}
+
+/*=============================================================================
+ * Perform the Interrupt Test by running the READ_CODE_VERSION command MAX_INTR
+ * _TEST_COUNTER times.
+ */
+static int intr_test( sdla_t *card )
+{
+	ppp_mbox_t *mb = card->mbox;
+	int err,i;
+
+	err = ppp_set_intr_mode( card, 0x08 );
+	
+	if (err == CMD_OK) { 
+		
+		for (i = 0; i < MAX_INTR_TEST_COUNTER; i ++) {	
+			/* Run command READ_CODE_VERSION */
+			memset(&mb->cmd, 0, sizeof(ppp_cmd_t));
+			mb->cmd.length  = 0;
+			mb->cmd.command = PPP_READ_CODE_VERSION;
+			err = sdla_exec(mb) ? mb->cmd.result : CMD_TIMEOUT;
+			if (err != CMD_OK) 
+				ppp_error(card, err, mb);
+		}
+	}
+	else return err;
+
+	err = ppp_set_intr_mode( card, 0 );
+	if (err != CMD_OK) 
+		return err;
+
+	return 0;
+}
+
+/*==============================================================================
+ * Determine what type of UDP call it is. DRVSTATS or PTPIPEAB ?
+ */
+static int udp_pkt_type( struct sk_buff *skb, sdla_t *card )
+{
+	unsigned char *sendpacket;
+	unsigned char buf2[5]; 
+	ppp_udp_pkt_t *ppp_udp_pkt = (ppp_udp_pkt_t *)skb->data; 
+	
+	sendpacket = skb->data;
+	memcpy(&buf2, &card->wandev.udp_port, 2);
+	
+	if( 	ppp_udp_pkt->ip_pkt.ver_inet_hdr_length  == 0x45 &&        /* IP packet */ 
+		sendpacket[9]  == 0x11 &&        /* UDP packet */
+		sendpacket[22] == buf2[1] &&     /* UDP Port */
+		sendpacket[23] == buf2[0] &&
+		sendpacket[36] == 0x01 ) {
+	
+		if (    sendpacket[28] == 0x50 &&    /* PTPIPEAB: Signature */ 
+			sendpacket[29] == 0x54 &&      
+			sendpacket[30] == 0x50 &&      
+			sendpacket[31] == 0x49 &&      
+			sendpacket[32] == 0x50 &&      
+			sendpacket[33] == 0x45 &&      
+			sendpacket[34] == 0x41 &&      
+			sendpacket[35] == 0x42 ){ 
+
+			return UDP_PTPIPE_TYPE;
+	
+		} else if(sendpacket[28] == 0x44 &&  /* DRVSTATS: Signature */
+			sendpacket[29] == 0x52 &&      
+      			sendpacket[30] == 0x56 &&      
+      			sendpacket[31] == 0x53 &&      
+      			sendpacket[32] == 0x54 &&      
+      			sendpacket[33] == 0x41 &&      
+      			sendpacket[34] == 0x54 &&      
+      			sendpacket[35] == 0x53 ){
+	
+			return UDP_DRVSTATS_TYPE;
+
+		} else
+			return UDP_INVALID_TYPE;
+
+	} else
+		return UDP_INVALID_TYPE;
+
+}
+
+/*============================================================================
+ * Check to see if the packet to be transmitted contains a broadcast or
+ * multicast source IP address.
+ */
+
+static int chk_bcast_mcast_addr(sdla_t *card, struct net_device* dev,
+				struct sk_buff *skb)
+{
+	u32 src_ip_addr;
+        u32 broadcast_ip_addr = 0;
+        struct in_device *in_dev;
+
+        /* read the IP source address from the outgoing packet */
+        src_ip_addr = *(u32 *)(skb->data + 12);
+
+	/* read the IP broadcast address for the device */
+        in_dev = dev->ip_ptr;
+        if(in_dev != NULL) {
+                struct in_ifaddr *ifa= in_dev->ifa_list;
+                if(ifa != NULL)
+                        broadcast_ip_addr = ifa->ifa_broadcast;
+                else
+                        return 0;
+        }
+ 
+        /* check if the IP Source Address is a Broadcast address */
+        if((dev->flags & IFF_BROADCAST) && (src_ip_addr == broadcast_ip_addr)) {
+                printk(KERN_INFO "%s: Broadcast Source Address silently discarded\n",
+				card->devname);
+                return 1;
+        } 
+
+        /* check if the IP Source Address is a Multicast address */
+        if((ntohl(src_ip_addr) >= 0xE0000001) &&
+		(ntohl(src_ip_addr) <= 0xFFFFFFFE)) {
+                printk(KERN_INFO "%s: Multicast Source Address silently discarded\n",
+				card->devname);
+                return 1;
+        }
+
+        return 0;
+}
+
+void s508_lock (sdla_t *card, unsigned long *smp_flags)
+{
+	spin_lock_irqsave(&card->wandev.lock, *smp_flags);
+}
+
+void s508_unlock (sdla_t *card, unsigned long *smp_flags)
+{
+        spin_unlock_irqrestore(&card->wandev.lock, *smp_flags);
+}
+
+static int read_connection_info (sdla_t *card)
+{
+	ppp_mbox_t *mb = card->mbox;
+	struct net_device *dev = card->wandev.dev;
+	ppp_private_area_t *ppp_priv_area = dev->priv;
+	ppp508_connect_info_t *ppp508_connect_info;
+	int err;
+
+	memset(&mb->cmd, 0, sizeof(ppp_cmd_t));
+	mb->cmd.length  = 0;
+	mb->cmd.command = PPP_GET_CONNECTION_INFO;
+	err = sdla_exec(mb) ? mb->cmd.result : CMD_TIMEOUT;
+
+	if (err != CMD_OK) { 
+		ppp_error(card, err, mb);
+		ppp_priv_area->ip_remote = 0;
+		ppp_priv_area->ip_local = 0;
+	}
+	else {
+		ppp508_connect_info = (ppp508_connect_info_t *)mb->data;
+		ppp_priv_area->ip_remote = ppp508_connect_info->ip_remote;
+		ppp_priv_area->ip_local = ppp508_connect_info->ip_local;
+
+		NEX_PRINTK(KERN_INFO "READ CONNECTION GOT IP ADDRESS %x, %x\n",
+				ppp_priv_area->ip_remote,
+				ppp_priv_area->ip_local);
+	}
+
+	return err;
+}
+
+/*===============================================================================
+ * config_ppp
+ *
+ *	Configure the ppp protocol and enable communications.		
+ *
+ *   	The if_open function binds this function to the poll routine.
+ *      Therefore, this function will run every time the ppp interface
+ *      is brought up.  
+ *      
+ *	If the communications are not enabled, proceed to configure
+ *      the card and enable communications.
+ *
+ *      If the communications are enabled, it means that the interface
+ *      was shutdown by ether the user or driver. In this case, we 
+ *      have to check that the IP addresses have not changed.  If
+ *      the IP addresses changed, we have to reconfigure the firmware
+ *      and update the changed IP addresses.  Otherwise, just exit.
+ */
+static int config_ppp (sdla_t *card)
+{
+
+	struct net_device *dev = card->wandev.dev;
+	ppp_flags_t *flags = card->flags;
+	ppp_private_area_t *ppp_priv_area = dev->priv;
+
+	if (card->u.p.comm_enabled){
+
+		if (ppp_priv_area->ip_local_tmp != ppp_priv_area->ip_local ||
+		    ppp_priv_area->ip_remote_tmp != ppp_priv_area->ip_remote){
+			
+			/* The IP addersses have changed, we must
+                         * stop the communications and reconfigure
+                         * the card. Reason: the firmware must know
+                         * the local and remote IP addresses. */
+			disable_comm(card);
+			wanpipe_set_state(card, WAN_DISCONNECTED);
+			printk(KERN_INFO 
+				"%s: IP addresses changed!\n",
+					card->devname);
+			printk(KERN_INFO "%s: Restarting communications ...\n",
+					card->devname);
+		}else{ 
+			/* IP addresses are the same and the link is up, 
+                         * we don't have to do anything here. Therefore, exit */
+			return 0;
+		}
+	}
+
+	/* Record the new IP addreses */
+	ppp_priv_area->ip_local = ppp_priv_area->ip_local_tmp;
+	ppp_priv_area->ip_remote = ppp_priv_area->ip_remote_tmp;
+
+	if (config508(dev, card)){
+		printk(KERN_INFO "%s: Failed to configure PPP device\n",
+			card->devname);
+		return 0;
+	}
+
+	if (ppp_set_intr_mode(card, PPP_INTR_RXRDY|
+			    		PPP_INTR_TXRDY|
+				    	PPP_INTR_MODEM|
+				    	PPP_INTR_DISC |
+				    	PPP_INTR_OPEN |
+				    	PPP_INTR_DROP_DTR |
+					PPP_INTR_TIMER)) {
+
+		printk(KERN_INFO "%s: Failed to configure board interrupts !\n", 
+			card->devname);
+		return 0;
+	}
+
+        /* Turn off the transmit and timer interrupt */
+	flags->imask &= ~(PPP_INTR_TXRDY | PPP_INTR_TIMER) ;
+
+
+	/* If you are not the authenticator and any one of the protocol is 
+	 * enabled then we call the set_out_bound_authentication.
+	 */
+	if ( !card->u.p.authenticator  && (ppp_priv_area->pap || ppp_priv_area->chap)) {
+		if ( ppp_set_outbnd_auth(card, ppp_priv_area) ){
+			printk(KERN_INFO "%s: Outbound authentication failed !\n",
+				card->devname);
+			return 0;
+		}
+	} 
+	
+	/* If you are the authenticator and any one of the protocol is enabled
+	 * then we call the set_in_bound_authentication.
+	 */
+	if (card->u.p.authenticator && (ppp_priv_area->pap || ppp_priv_area->chap)){
+		if (ppp_set_inbnd_auth(card, ppp_priv_area)){
+			printk(KERN_INFO "%s: Inbound authentication failed !\n",
+				card->devname);	
+			return 0;
+		}
+	}
+
+	/* If we fail to enable communications here it's OK,
+	 * since the DTR timer will cause a disconnected, which
+	 * will retrigger communication in timer_intr() */
+	if (ppp_comm_enable(card) == CMD_OK) {
+		wanpipe_set_state(card, WAN_CONNECTING);
+		init_ppp_tx_rx_buff(card);
+	}
+
+	return 0; 
+}
+
+/*============================================================
+ * ppp_poll
+ *	
+ * Rationale:
+ * 	We cannot manipulate the routing tables, or
+ *      ip addresses withing the interrupt. Therefore
+ *      we must perform such actons outside an interrupt 
+ *      at a later time. 
+ *
+ * Description:	
+ *	PPP polling routine, responsible for 
+ *     	shutting down interfaces upon disconnect
+ *     	and adding/removing routes. 
+ *      
+ * Usage:        
+ * 	This function is executed for each ppp  
+ * 	interface through a tq_schedule bottom half.
+ *      
+ *      trigger_ppp_poll() function is used to kick
+ *      the ppp_poll routine.  
+ */
+static void ppp_poll(struct net_device *dev)
+{
+	ppp_private_area_t *ppp_priv_area; 	
+	sdla_t *card;
+	u8 check_gateway=0;
+	ppp_flags_t *flags;
+
+	if (!dev || (ppp_priv_area = dev->priv) == NULL)
+		return;
+
+	card = ppp_priv_area->card;
+	flags = card->flags;
+
+	/* Shutdown is in progress, stop what you are 
+	 * doing and get out */
+	if (test_bit(PERI_CRIT,&card->wandev.critical)){
+		clear_bit(POLL_CRIT,&card->wandev.critical);
+		return;
+	}
+
+	/* if_open() function has triggered the polling routine
+	 * to determine the configured IP addresses.  Once the
+	 * addresses are found, trigger the chdlc configuration */
+	if (test_bit(0,&ppp_priv_area->config_ppp)){
+
+		ppp_priv_area->ip_local_tmp  = get_ip_address(dev,WAN_LOCAL_IP);
+		ppp_priv_area->ip_remote_tmp = get_ip_address(dev,WAN_POINTOPOINT_IP);
+
+		if (ppp_priv_area->ip_local_tmp == ppp_priv_area->ip_remote_tmp && 
+	            card->u.p.ip_mode == WANOPT_PPP_HOST){
+			
+			if (++ppp_priv_area->ip_error > MAX_IP_ERRORS){
+				printk(KERN_INFO "\n%s: --- WARNING ---\n",
+						card->devname);
+				printk(KERN_INFO "%s: The local IP address is the same as the\n",
+						card->devname);
+				printk(KERN_INFO "%s: Point-to-Point IP address.\n",
+						card->devname);
+				printk(KERN_INFO "%s: --- WARNING ---\n\n",
+						card->devname);
+			}else{
+				clear_bit(POLL_CRIT,&card->wandev.critical);
+				ppp_priv_area->poll_delay_timer.expires = jiffies+HZ;
+				add_timer(&ppp_priv_area->poll_delay_timer);
+				return;
+			}
+		}
+
+		ppp_priv_area->timer_int_enabled |= TMR_INT_ENABLED_CONFIG;
+		flags->imask |= PPP_INTR_TIMER;	
+		ppp_priv_area->ip_error=0;	
+		
+		clear_bit(0,&ppp_priv_area->config_ppp);
+		clear_bit(POLL_CRIT,&card->wandev.critical);
+		return;
+	}
+
+	/* Dynamic interface implementation, as well as dynamic
+	 * routing.  */
+	
+	switch (card->wandev.state) {
+	
+	case WAN_DISCONNECTED:
+
+		/* If the dynamic interface configuration is on, and interface 
+		 * is up, then bring down the netowrk interface */
+
+		if (test_bit(DYN_OPT_ON,&ppp_priv_area->interface_down) &&
+		    !test_bit(DEV_DOWN,&ppp_priv_area->interface_down)	&&	
+		    card->wandev.dev->flags & IFF_UP){	
+
+			printk(KERN_INFO "%s: Interface %s down.\n",
+				card->devname,card->wandev.dev->name);
+			change_dev_flags(card->wandev.dev,
+					(card->wandev.dev->flags&~IFF_UP));
+			set_bit(DEV_DOWN,&ppp_priv_area->interface_down);
+		}else{
+			/* We need to check if the local IP address is
+               	   	 * zero. If it is, we shouldn't try to remove it.
+                 	 * For some reason the kernel crashes badly if 
+                 	 * we try to remove the route twice */
+
+			if (card->wandev.dev->flags & IFF_UP && 
+		    	    get_ip_address(card->wandev.dev,WAN_LOCAL_IP) &&
+		    	    card->u.p.ip_mode == WANOPT_PPP_PEER){
+
+				remove_route(card);
+			}
+		}
+		break;
+
+	case WAN_CONNECTED:
+		
+		/* In SMP machine this code can execute before the interface
+		 * comes up.  In this case, we must make sure that we do not
+		 * try to bring up the interface before dev_open() is finished */
+
+
+		/* DEV_DOWN will be set only when we bring down the interface
+		 * for the very first time. This way we know that it was us
+		 * that brought the interface down */
+		
+		if (test_bit(DYN_OPT_ON,&ppp_priv_area->interface_down) &&
+	            test_bit(DEV_DOWN,  &ppp_priv_area->interface_down) &&
+ 		    !(card->wandev.dev->flags & IFF_UP)){
+			
+			printk(KERN_INFO "%s: Interface %s up.\n",
+				card->devname,card->wandev.dev->name);
+			
+			change_dev_flags(card->wandev.dev,(card->wandev.dev->flags|IFF_UP));
+			clear_bit(DEV_DOWN,&ppp_priv_area->interface_down);
+			check_gateway=1;
+		}
+
+		if ((card->u.p.ip_mode == WANOPT_PPP_PEER) && 
+		    test_bit(1,&Read_connection_info)) { 
+			
+			process_route(card);
+			clear_bit(1,&Read_connection_info);
+			check_gateway=1;
+		}
+
+		if (ppp_priv_area->gateway && check_gateway)
+			add_gateway(card,dev);
+
+		break;
+	}
+	clear_bit(POLL_CRIT,&card->wandev.critical);
+	return;
+}
+
+/*============================================================
+ * trigger_ppp_poll
+ *
+ * Description:
+ * 	Add a ppp_poll() task into a tq_scheduler bh handler
+ *      for a specific interface.  This will kick
+ *      the ppp_poll() routine at a later time. 
+ *
+ * Usage:
+ * 	Interrupts use this to defer a taks to 
+ *      a polling routine.
+ *
+ */	
+
+static void trigger_ppp_poll(struct net_device *dev)
+{
+	ppp_private_area_t *ppp_priv_area;
+	if ((ppp_priv_area=dev->priv) != NULL){ 	
+		
+		sdla_t *card = ppp_priv_area->card;
+
+		if (test_bit(PERI_CRIT,&card->wandev.critical)){
+			return;
+		}
+		
+		if (test_and_set_bit(POLL_CRIT,&card->wandev.critical)){
+			return;
+		}
+
+		schedule_work(&ppp_priv_area->poll_work);
+	}
+	return;
+}
+
+static void ppp_poll_delay (unsigned long dev_ptr)
+{
+	struct net_device *dev = (struct net_device *)dev_ptr;
+	trigger_ppp_poll(dev);
+}
+
+/*============================================================
+ * detect_and_fix_tx_bug
+ *
+ * Description:
+ *	On connect, if the board tx buffer ptr is not the same
+ *      as the driver tx buffer ptr, we found a firmware bug.
+ *      Report the bug to the above layer.  To fix the
+ *      error restart communications again.
+ *
+ * Usage:
+ *
+ */	
+
+static int detect_and_fix_tx_bug (sdla_t *card)
+{
+	if (((unsigned long)card->u.p.txbuf_base&0xFFF) != ((*card->u.p.txbuf_next)&0xFFF)){
+		NEX_PRINTK(KERN_INFO "Major Error, Fix the bug\n");
+		return 1;
+	}
+	return 0;
+}
+
+MODULE_LICENSE("GPL");
+
+/****** End *****************************************************************/
diff --git a/drivers/net/wan/sdla_x25.c b/drivers/net/wan/sdla_x25.c
new file mode 100644
index 0000000..3a93d2f
--- /dev/null
+++ b/drivers/net/wan/sdla_x25.c
@@ -0,0 +1,5496 @@
+/*****************************************************************************
+* sdla_x25.c	WANPIPE(tm) Multiprotocol WAN Link Driver.  X.25 module.
+*
+* Author:	Nenad Corbic	<ncorbic@sangoma.com>
+*
+* Copyright:	(c) 1995-2001 Sangoma Technologies Inc.
+*
+*		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.
+* ============================================================================
+* Apr 03, 2001  Nenad Corbic	 o Fixed the rx_skb=NULL bug in x25 in rx_intr().
+* Dec 26, 2000  Nenad Corbic	 o Added a new polling routine, that uses
+*                                  a kernel timer (more efficient).
+* Dec 25, 2000  Nenad Corbic	 o Updated for 2.4.X kernel
+* Jul 26, 2000  Nenad Corbic	 o Increased the local packet buffering
+* 				   for API to 4096+header_size. 
+* Jul 17, 2000  Nenad Corbic	 o Fixed the x25 startup bug. Enable 
+* 				   communications only after all interfaces
+* 				   come up.  HIGH SVC/PVC is used to calculate
+* 				   the number of channels.
+*                                  Enable protocol only after all interfaces
+*                                  are enabled.
+* Jul 10, 2000	Nenad Corbic	 o Fixed the M_BIT bug. 
+* Apr 25, 2000  Nenad Corbic	 o Pass Modem messages to the API.
+*                                  Disable idle timeout in X25 API.
+* Apr 14, 2000  Nenad Corbic	 o Fixed: Large LCN number support.
+*                                  Maximum LCN number is 4095.
+*                                  Maximum number of X25 channels is 255.
+* Apr 06, 2000  Nenad Corbic	 o Added SMP Support.
+* Mar 29, 2000  Nenad Corbic	 o Added support for S514 PCI Card
+* Mar 23, 2000  Nenad Corbic	 o Improved task queue, BH handling.
+* Mar 14, 2000  Nenad Corbic  	 o Updated Protocol Violation handling
+*                                  routines.  Bug Fix.
+* Mar 10, 2000  Nenad Corbic	 o Bug Fix: corrupted mbox recovery.
+* Mar 09, 2000  Nenad Corbic     o Fixed the auto HDLC bug.
+* Mar 08, 2000	Nenad Corbic     o Fixed LAPB HDLC startup problems.
+*                                  Application must bring the link up 
+*                                  before tx/rx, and bring the 
+*                                  link down on close().
+* Mar 06, 2000	Nenad Corbic	 o Added an option for logging call setup 
+*                                  information. 
+* Feb 29, 2000  Nenad Corbic 	 o Added support for LAPB HDLC API
+* Feb 25, 2000  Nenad Corbic     o Fixed the modem failure handling.
+*                                  No Modem OOB message will be passed 
+*                                  to the user.
+* Feb 21, 2000  Nenad Corbic 	 o Added Xpipemon Debug Support
+* Dec 30, 1999 	Nenad Corbic	 o Socket based X25API 
+* Sep 17, 1998	Jaspreet Singh	 o Updates for 2.2.X  kernel
+* Mar 15, 1998	Alan Cox	 o 2.1.x porting
+* Dec 19, 1997	Jaspreet Singh	 o Added multi-channel IPX support
+* Nov 27, 1997	Jaspreet Singh	 o Added protection against enabling of irqs
+*				   when they are disabled.
+* Nov 17, 1997  Farhan Thawar    o Added IPX support
+*				 o Changed if_send() to now buffer packets when
+*				   the board is busy
+*				 o Removed queueing of packets via the polling
+*				   routing
+*				 o Changed if_send() critical flags to properly
+*				   handle race conditions
+* Nov 06, 1997  Farhan Thawar    o Added support for SVC timeouts
+*				 o Changed PVC encapsulation to ETH_P_IP
+* Jul 21, 1997  Jaspreet Singh	 o Fixed freeing up of buffers using kfree()
+*				   when packets are received.
+* Mar 11, 1997  Farhan Thawar   Version 3.1.1
+*                                o added support for V35
+*                                o changed if_send() to return 0 if
+*                                  wandev.critical() is true
+*                                o free socket buffer in if_send() if
+*                                  returning 0
+*                                o added support for single '@' address to
+*                                  accept all incoming calls
+*                                o fixed bug in set_chan_state() to disconnect
+* Jan 15, 1997	Gene Kozin	Version 3.1.0
+*				 o implemented exec() entry point
+* Jan 07, 1997	Gene Kozin	Initial version.
+*****************************************************************************/
+
+/*======================================================
+ * 	Includes 
+ *=====================================================*/
+
+#include <linux/module.h>
+#include <linux/kernel.h>	/* printk(), and other useful stuff */
+#include <linux/stddef.h>	/* offsetof(), etc. */
+#include <linux/errno.h>	/* return codes */
+#include <linux/string.h>	/* inline memset(), etc. */
+#include <linux/ctype.h>
+#include <linux/slab.h>	/* kmalloc(), kfree() */
+#include <linux/wanrouter.h>	/* WAN router definitions */
+#include <linux/wanpipe.h>	/* WANPIPE common user API definitions */
+#include <linux/workqueue.h>
+#include <asm/byteorder.h>	/* htons(), etc. */
+#include <asm/atomic.h>
+#include <linux/delay.h>	/* Experimental delay */
+
+#include <asm/uaccess.h>
+
+#include <linux/if.h>
+#include <linux/if_arp.h>
+#include <linux/sdla_x25.h>	/* X.25 firmware API definitions */
+#include <linux/if_wanpipe_common.h>
+#include <linux/if_wanpipe.h>
+
+
+/*======================================================
+ * 	Defines & Macros 
+ *=====================================================*/
+
+
+#define	CMD_OK		0		/* normal firmware return code */
+#define	CMD_TIMEOUT	0xFF		/* firmware command timed out */
+#define	MAX_CMD_RETRY	10		/* max number of firmware retries */
+
+#define	X25_CHAN_MTU	4096		/* unfragmented logical channel MTU */
+#define	X25_HRDHDR_SZ	7		/* max encapsulation header size */
+#define	X25_CONCT_TMOUT	(90*HZ)		/* link connection timeout */
+#define	X25_RECON_TMOUT	(10*HZ)		/* link connection timeout */
+#define	CONNECT_TIMEOUT	(90*HZ)		/* link connection timeout */
+#define	HOLD_DOWN_TIME	(30*HZ)		/* link hold down time */
+#define MAX_BH_BUFF	10
+#define M_BIT		0x01	
+
+//#define PRINT_DEBUG 1
+#ifdef PRINT_DEBUG
+#define DBG_PRINTK(format, a...) printk(format, ## a)
+#else
+#define DBG_PRINTK(format, a...)
+#endif  
+
+#define TMR_INT_ENABLED_POLL_ACTIVE      0x01
+#define TMR_INT_ENABLED_POLL_CONNECT_ON  0x02
+#define TMR_INT_ENABLED_POLL_CONNECT_OFF 0x04
+#define TMR_INT_ENABLED_POLL_DISCONNECT  0x08
+#define TMR_INT_ENABLED_CMD_EXEC	 0x10
+#define TMR_INT_ENABLED_UPDATE		 0x20
+#define TMR_INT_ENABLED_UDP_PKT		 0x40
+
+#define MAX_X25_ADDR_SIZE	16
+#define MAX_X25_DATA_SIZE 	129
+#define MAX_X25_FACL_SIZE	110
+
+#define TRY_CMD_AGAIN	2
+#define DELAY_RESULT    1
+#define RETURN_RESULT   0
+
+#define DCD(x) (x & 0x03 ? "HIGH" : "LOW")
+#define CTS(x) (x & 0x05 ? "HIGH" : "LOW")
+
+
+/* Driver will not write log messages about 
+ * modem status if defined.*/
+#define MODEM_NOT_LOG 1
+
+/*==================================================== 
+ * 	For IPXWAN 
+ *===================================================*/
+
+#define CVHexToAscii(b) (((unsigned char)(b) > (unsigned char)9) ? ((unsigned char)'A' + ((unsigned char)(b) - (unsigned char)10)) : ((unsigned char)'0' + (unsigned char)(b)))
+
+
+/*====================================================
+ *           MEMORY DEBUGGING FUNCTION
+ *====================================================
+
+#define KMEM_SAFETYZONE 8
+
+static void * dbg_kmalloc(unsigned int size, int prio, int line) {
+	int i = 0;
+	void * v = kmalloc(size+sizeof(unsigned int)+2*KMEM_SAFETYZONE*8,prio);
+	char * c1 = v;	
+	c1 += sizeof(unsigned int);
+	*((unsigned int *)v) = size;
+
+	for (i = 0; i < KMEM_SAFETYZONE; i++) {
+		c1[0] = 'D'; c1[1] = 'E'; c1[2] = 'A'; c1[3] = 'D';
+		c1[4] = 'B'; c1[5] = 'E'; c1[6] = 'E'; c1[7] = 'F';
+		c1 += 8;
+	}
+	c1 += size;
+	for (i = 0; i < KMEM_SAFETYZONE; i++) {
+		c1[0] = 'M'; c1[1] = 'U'; c1[2] = 'N'; c1[3] = 'G';
+		c1[4] = 'W'; c1[5] = 'A'; c1[6] = 'L'; c1[7] = 'L';
+		c1 += 8;
+	}
+	v = ((char *)v) + sizeof(unsigned int) + KMEM_SAFETYZONE*8;
+	printk(KERN_INFO "line %d  kmalloc(%d,%d) = %p\n",line,size,prio,v);
+	return v;
+}
+static void dbg_kfree(void * v, int line) {
+	unsigned int * sp = (unsigned int *)(((char *)v) - (sizeof(unsigned int) + KMEM_SAFETYZONE*8));
+	unsigned int size = *sp;
+	char * c1 = ((char *)v) - KMEM_SAFETYZONE*8;
+	int i = 0;
+	for (i = 0; i < KMEM_SAFETYZONE; i++) {
+		if (   c1[0] != 'D' || c1[1] != 'E' || c1[2] != 'A' || c1[3] != 'D'
+		    || c1[4] != 'B' || c1[5] != 'E' || c1[6] != 'E' || c1[7] != 'F') {
+			printk(KERN_INFO "kmalloced block at %p has been corrupted (underrun)!\n",v);
+			printk(KERN_INFO " %4x: %2x %2x %2x %2x %2x %2x %2x %2x\n", i*8,
+			                c1[0],c1[1],c1[2],c1[3],c1[4],c1[5],c1[6],c1[7] );
+		}
+		c1 += 8;
+	}
+	c1 += size;
+	for (i = 0; i < KMEM_SAFETYZONE; i++) {
+		if (   c1[0] != 'M' || c1[1] != 'U' || c1[2] != 'N' || c1[3] != 'G'
+		    || c1[4] != 'W' || c1[5] != 'A' || c1[6] != 'L' || c1[7] != 'L'
+		   ) {
+			printk(KERN_INFO "kmalloced block at %p has been corrupted (overrun):\n",v);
+			printk(KERN_INFO " %4x: %2x %2x %2x %2x %2x %2x %2x %2x\n", i*8,
+			                c1[0],c1[1],c1[2],c1[3],c1[4],c1[5],c1[6],c1[7] );
+		}
+		c1 += 8;
+	}
+	printk(KERN_INFO "line %d  kfree(%p)\n",line,v);
+	v = ((char *)v) - (sizeof(unsigned int) + KMEM_SAFETYZONE*8);
+	kfree(v);
+}
+
+#define kmalloc(x,y) dbg_kmalloc(x,y,__LINE__)
+#define kfree(x) dbg_kfree(x,__LINE__)
+
+==============================================================*/
+
+
+
+/*===============================================
+ * 	Data Structures 
+ *===============================================*/
+
+
+/*========================================================
+ * Name: 	x25_channel
+ *
+ * Purpose:	To hold private informaton for each  
+ *              logical channel.
+ *		
+ * Rationale:  	Per-channel debugging is possible if each 
+ *              channel has its own private area.
+ *	
+ * Assumptions:
+ *
+ * Description:	This is an extention of the struct net_device
+ *              we create for each network interface to keep 
+ *              the rest of X.25 channel-specific data. 
+ *
+ * Construct:	Typedef
+ */
+typedef struct x25_channel
+{
+	wanpipe_common_t common;	/* common area for x25api and socket */
+	char name[WAN_IFNAME_SZ+1];	/* interface name, ASCIIZ */
+	char addr[WAN_ADDRESS_SZ+1];	/* media address, ASCIIZ */
+	unsigned tx_pkt_size;
+	unsigned short protocol;	/* ethertype, 0 - multiplexed */
+	char drop_sequence;		/* mark sequence for dropping */
+	unsigned long state_tick;	/* time of the last state change */
+	unsigned idle_timeout;		/* sec, before disconnecting */
+	unsigned long i_timeout_sofar;  /* # of sec's we've been idle */
+	unsigned hold_timeout;		/* sec, before re-connecting */
+	unsigned long tick_counter;	/* counter for transmit time out */
+	char devtint;			/* Weather we should dev_tint() */
+	struct sk_buff* rx_skb;		/* receive socket buffer */
+	struct sk_buff* tx_skb;		/* transmit socket buffer */
+
+	bh_data_t *bh_head;	  	  /* Circular buffer for x25api_bh */
+	unsigned long  tq_working;
+	volatile int  bh_write;
+	volatile int  bh_read;
+	atomic_t  bh_buff_used;
+
+	sdla_t* card;			/* -> owner */
+	struct net_device *dev;		/* -> bound devce */
+
+	int ch_idx;
+	unsigned char enable_IPX;
+	unsigned long network_number;
+	struct net_device_stats ifstats;	/* interface statistics */
+	unsigned short transmit_length;
+	unsigned short tx_offset;
+	char transmit_buffer[X25_CHAN_MTU+sizeof(x25api_hdr_t)];
+
+	if_send_stat_t   if_send_stat;
+        rx_intr_stat_t   rx_intr_stat;
+        pipe_mgmt_stat_t pipe_mgmt_stat;    
+
+	unsigned long router_start_time; /* Router start time in seconds */
+	unsigned long router_up_time;
+	
+} x25_channel_t;
+
+/* FIXME Take this out */
+
+#ifdef NEX_OLD_CALL_INFO
+typedef struct x25_call_info
+{
+	char dest[17];			PACKED;/* ASCIIZ destination address */
+	char src[17];			PACKED;/* ASCIIZ source address */
+	char nuser;			PACKED;/* number of user data bytes */
+	unsigned char user[127];	PACKED;/* user data */
+	char nfacil;			PACKED;/* number of facilities */
+	struct
+	{
+		unsigned char code;     PACKED;
+		unsigned char parm;     PACKED;
+	} facil[64];			        /* facilities */
+} x25_call_info_t;
+#else
+typedef struct x25_call_info
+{
+	char dest[MAX_X25_ADDR_SIZE]		PACKED;/* ASCIIZ destination address */
+	char src[MAX_X25_ADDR_SIZE]		PACKED;/* ASCIIZ source address */
+	unsigned char nuser			PACKED;
+	unsigned char user[MAX_X25_DATA_SIZE]	PACKED;/* user data */
+	unsigned char nfacil			PACKED;
+	unsigned char facil[MAX_X25_FACL_SIZE]	PACKED;
+	unsigned short lcn             		PACKED;
+} x25_call_info_t;
+#endif
+
+
+  
+/*===============================================
+ *	Private Function Prototypes
+ *==============================================*/
+
+
+/*================================================= 
+ * WAN link driver entry points. These are 
+ * called by the WAN router module.
+ */
+static int update(struct wan_device* wandev);
+static int new_if(struct wan_device* wandev, struct net_device* dev,
+		  wanif_conf_t* conf);
+static int del_if(struct wan_device* wandev, struct net_device* dev);
+static void disable_comm (sdla_t* card);
+static void disable_comm_shutdown(sdla_t *card);
+
+
+
+/*================================================= 
+ *	WANPIPE-specific entry points 
+ */
+static int wpx_exec (struct sdla* card, void* u_cmd, void* u_data);
+static void x25api_bh(struct net_device *dev);
+static int x25api_bh_cleanup(struct net_device *dev);
+static int bh_enqueue(struct net_device *dev, struct sk_buff *skb);
+
+
+/*=================================================  
+ * 	Network device interface 
+ */
+static int if_init(struct net_device* dev);
+static int if_open(struct net_device* dev);
+static int if_close(struct net_device* dev);
+static int if_header(struct sk_buff* skb, struct net_device* dev,
+	unsigned short type, void* daddr, void* saddr, unsigned len);
+static int if_rebuild_hdr (struct sk_buff* skb);
+static int if_send(struct sk_buff* skb, struct net_device* dev);
+static struct net_device_stats *if_stats(struct net_device* dev);
+
+static void if_tx_timeout(struct net_device *dev);
+
+/*=================================================  
+ * 	Interrupt handlers 
+ */
+static void wpx_isr	(sdla_t *);
+static void rx_intr	(sdla_t *);
+static void tx_intr	(sdla_t *);
+static void status_intr	(sdla_t *);
+static void event_intr	(sdla_t *);
+static void spur_intr	(sdla_t *);
+static void timer_intr  (sdla_t *);
+
+static int tx_intr_send(sdla_t *card, struct net_device *dev);
+static struct net_device *move_dev_to_next(sdla_t *card,
+					   struct net_device *dev);
+
+/*=================================================  
+ *	Background polling routines 
+ */
+static void wpx_poll (sdla_t* card);
+static void poll_disconnected (sdla_t* card);
+static void poll_connecting (sdla_t* card);
+static void poll_active (sdla_t* card);
+static void trigger_x25_poll(sdla_t *card);
+static void x25_timer_routine(unsigned long data);
+
+
+
+/*=================================================  
+ *	X.25 firmware interface functions 
+ */
+static int x25_get_version (sdla_t* card, char* str);
+static int x25_configure (sdla_t* card, TX25Config* conf);
+static int hdlc_configure (sdla_t* card, TX25Config* conf);
+static int set_hdlc_level (sdla_t* card);
+static int x25_get_err_stats (sdla_t* card);
+static int x25_get_stats (sdla_t* card);
+static int x25_set_intr_mode (sdla_t* card, int mode);
+static int x25_close_hdlc (sdla_t* card);
+static int x25_open_hdlc (sdla_t* card);
+static int x25_setup_hdlc (sdla_t* card);
+static int x25_set_dtr (sdla_t* card, int dtr);
+static int x25_get_chan_conf (sdla_t* card, x25_channel_t* chan);
+static int x25_place_call (sdla_t* card, x25_channel_t* chan);
+static int x25_accept_call (sdla_t* card, int lcn, int qdm);
+static int x25_clear_call (sdla_t* card, int lcn, int cause, int diagn);
+static int x25_send (sdla_t* card, int lcn, int qdm, int len, void* buf);
+static int x25_fetch_events (sdla_t* card);
+static int x25_error (sdla_t* card, int err, int cmd, int lcn);
+
+/*=================================================  
+ *	X.25 asynchronous event handlers 
+ */
+static int incoming_call (sdla_t* card, int cmd, int lcn, TX25Mbox* mb);
+static int call_accepted (sdla_t* card, int cmd, int lcn, TX25Mbox* mb);
+static int call_cleared (sdla_t* card, int cmd, int lcn, TX25Mbox* mb);
+static int timeout_event (sdla_t* card, int cmd, int lcn, TX25Mbox* mb);
+static int restart_event (sdla_t* card, int cmd, int lcn, TX25Mbox* mb);
+
+
+/*=================================================  
+ *	Miscellaneous functions 
+ */
+static int connect (sdla_t* card);
+static int disconnect (sdla_t* card);
+static struct net_device* get_dev_by_lcn(struct wan_device* wandev,
+					 unsigned lcn);
+static int chan_connect(struct net_device* dev);
+static int chan_disc(struct net_device* dev);
+static void set_chan_state(struct net_device* dev, int state);
+static int chan_send(struct net_device *dev, void* buff, unsigned data_len,
+		     unsigned char tx_intr);
+static unsigned char bps_to_speed_code (unsigned long bps);
+static unsigned int dec_to_uint (unsigned char* str, int len);
+static unsigned int hex_to_uint (unsigned char*, int);
+static void parse_call_info (unsigned char*, x25_call_info_t*);
+static struct net_device *find_channel(sdla_t *card, unsigned lcn);
+static void bind_lcn_to_dev(sdla_t *card, struct net_device *dev, unsigned lcn);
+static void setup_for_delayed_transmit(struct net_device *dev,
+				       void *buf, unsigned len);
+
+
+/*=================================================  
+ *      X25 API Functions 
+ */
+static int wanpipe_pull_data_in_skb(sdla_t *card, struct net_device *dev,
+				    struct sk_buff **);
+static void timer_intr_exec(sdla_t *, unsigned char);
+static int execute_delayed_cmd(sdla_t *card, struct net_device *dev,
+			       mbox_cmd_t *usr_cmd, char bad_cmd);
+static int api_incoming_call (sdla_t*, TX25Mbox *, int);
+static int alloc_and_init_skb_buf (sdla_t *,struct sk_buff **, int);
+static void send_delayed_cmd_result(sdla_t *card, struct net_device *dev,
+				    TX25Mbox* mbox);
+static int clear_confirm_event (sdla_t *, TX25Mbox*);
+static void send_oob_msg (sdla_t *card, struct net_device *dev, TX25Mbox *mbox);
+static int timer_intr_cmd_exec(sdla_t *card);
+static void api_oob_event (sdla_t *card,TX25Mbox *mbox);
+static int check_bad_command(sdla_t *card, struct net_device *dev);
+static int channel_disconnect(sdla_t* card, struct net_device *dev);
+static void hdlc_link_down (sdla_t*);
+
+/*=================================================
+ *     XPIPEMON Functions
+ */
+static int process_udp_mgmt_pkt(sdla_t *);
+static int udp_pkt_type( struct sk_buff *, sdla_t*);
+static int reply_udp( unsigned char *, unsigned int); 
+static void init_x25_channel_struct( x25_channel_t *);
+static void init_global_statistics( sdla_t *);
+static int store_udp_mgmt_pkt(int udp_type, char udp_pkt_src, sdla_t *card,
+			      struct net_device *dev,
+			      struct sk_buff *skb, int lcn);
+static unsigned short calc_checksum (char *, int);
+
+
+
+/*================================================= 
+ *	IPX functions 
+ */
+static void switch_net_numbers(unsigned char *, unsigned long, unsigned char);
+static int handle_IPXWAN(unsigned char *, char *, unsigned char , 
+			 unsigned long , unsigned short );
+
+extern void disable_irq(unsigned int);
+extern void enable_irq(unsigned int);
+
+static void S508_S514_lock(sdla_t *, unsigned long *);
+static void S508_S514_unlock(sdla_t *, unsigned long *);
+
+
+/*=================================================  
+ * 	Global Variables 
+ *=================================================*/
+
+
+
+/*================================================= 
+ *	Public Functions 
+ *=================================================*/
+
+
+
+
+/*===================================================================
+ * wpx_init:	X.25 Protocol Initialization routine.
+ *
+ * Purpose:	To initialize the protocol/firmware.
+ * 
+ * Rationale:	This function is called by setup() function, in
+ *              sdlamain.c, to dynamically setup the x25 protocol.
+ *		This is the first protocol specific function, which
+ *              executes once on startup.
+ *                
+ * Description:	This procedure initializes the x25 firmware and
+ *    		sets up the mailbox, transmit and receive buffer
+ *              pointers. It also initializes all debugging structures
+ *              and sets up the X25 environment.
+ *
+ *		Sets up hardware options defined by user in [wanpipe#] 
+ *		section of wanpipe#.conf configuration file. 
+ *
+ * 		At this point adapter is completely initialized 
+ *      	and X.25 firmware is running.
+ *  		o read firmware version (to make sure it's alive)
+ *  		o configure adapter
+ *  		o initialize protocol-specific fields of the 
+ *                adapter data space.
+ *
+ * Called by:	setup() function in sdlamain.c
+ *
+ * Assumptions:	None
+ *
+ * Warnings:	None
+ *
+ * Return: 	0	o.k.
+ *	 	< 0	failure.
+ */
+
+int wpx_init (sdla_t* card, wandev_conf_t* conf)
+{
+	union{
+		char str[80];
+		TX25Config cfg;
+	} u;
+
+	/* Verify configuration ID */
+	if (conf->config_id != WANCONFIG_X25){
+		printk(KERN_INFO "%s: invalid configuration ID %u!\n",
+			card->devname, conf->config_id)
+		;
+		return -EINVAL;
+	}
+
+	/* Initialize protocol-specific fields */
+	card->mbox  = (void*)(card->hw.dpmbase + X25_MBOX_OFFS);
+	card->rxmb  = (void*)(card->hw.dpmbase + X25_RXMBOX_OFFS);
+	card->flags = (void*)(card->hw.dpmbase + X25_STATUS_OFFS);
+
+	/* Initialize for S514 Card */
+	if(card->hw.type == SDLA_S514) {
+		card->mbox += X25_MB_VECTOR;
+		card->flags += X25_MB_VECTOR;
+		card->rxmb += X25_MB_VECTOR;
+	}
+
+
+	/* Read firmware version.  Note that when adapter initializes, it
+	 * clears the mailbox, so it may appear that the first command was
+	 * executed successfully when in fact it was merely erased. To work
+	 * around this, we execute the first command twice.
+	 */
+	if (x25_get_version(card, NULL) || x25_get_version(card, u.str))
+		return -EIO;
+
+
+	/* X25 firmware can run ether in X25 or LAPB HDLC mode.
+         * Check the user defined option and configure accordingly */
+	if (conf->u.x25.LAPB_hdlc_only == WANOPT_YES){
+		if (set_hdlc_level(card) != CMD_OK){
+			return -EIO;	
+		}else{
+			printk(KERN_INFO "%s: running LAP_B HDLC firmware v%s\n",
+				card->devname, u.str);
+		}
+		card->u.x.LAPB_hdlc = 1;
+	}else{
+		printk(KERN_INFO "%s: running X.25 firmware v%s\n",
+				card->devname, u.str);
+		card->u.x.LAPB_hdlc = 0;
+	}
+
+	/* Configure adapter. Here we set resonable defaults, then parse
+	 * device configuration structure and set configuration options.
+	 * Most configuration options are verified and corrected (if
+	 * necessary) since we can't rely on the adapter to do so.
+	 */
+	memset(&u.cfg, 0, sizeof(u.cfg));
+	u.cfg.t1		= 3;
+	u.cfg.n2		= 10;
+	u.cfg.autoHdlc		= 1;		/* automatic HDLC connection */
+	u.cfg.hdlcWindow	= 7;
+	u.cfg.pktWindow		= 2;
+	u.cfg.station		= 1;		/* DTE */
+	u.cfg.options		= 0x0090;	/* disable D-bit pragmatics */
+	u.cfg.ccittCompat	= 1988;
+	u.cfg.t10t20		= 30;
+	u.cfg.t11t21		= 30;
+	u.cfg.t12t22		= 30;
+	u.cfg.t13t23		= 30;
+	u.cfg.t16t26		= 30;
+	u.cfg.t28		= 30;
+	u.cfg.r10r20		= 5;
+	u.cfg.r12r22		= 5;
+	u.cfg.r13r23		= 5;
+	u.cfg.responseOpt	= 1;		/* RR's after every packet */
+
+	if (card->u.x.LAPB_hdlc){
+		u.cfg.hdlcMTU = 1027;
+	}
+
+	if (conf->u.x25.x25_conf_opt){
+		u.cfg.options = conf->u.x25.x25_conf_opt;
+	}
+
+	if (conf->clocking != WANOPT_EXTERNAL)
+		u.cfg.baudRate = bps_to_speed_code(conf->bps);
+
+	if (conf->station != WANOPT_DTE){
+		u.cfg.station = 0;		/* DCE mode */
+	}
+
+        if (conf->interface != WANOPT_RS232 ){
+	        u.cfg.hdlcOptions |= 0x80;      /* V35 mode */
+	} 
+
+	/* adjust MTU */
+	if (!conf->mtu || (conf->mtu >= 1024))
+		card->wandev.mtu = 1024;
+	else if (conf->mtu >= 512)
+		card->wandev.mtu = 512;
+	else if (conf->mtu >= 256)
+		card->wandev.mtu = 256;
+	else if (conf->mtu >= 128)
+		card->wandev.mtu = 128;
+	else 
+		card->wandev.mtu = 64;
+
+	u.cfg.defPktSize = u.cfg.pktMTU = card->wandev.mtu;
+
+	if (conf->u.x25.hi_pvc){
+		card->u.x.hi_pvc = min_t(unsigned int, conf->u.x25.hi_pvc, MAX_LCN_NUM);
+		card->u.x.lo_pvc = min_t(unsigned int, conf->u.x25.lo_pvc, card->u.x.hi_pvc);
+	}
+
+	if (conf->u.x25.hi_svc){
+		card->u.x.hi_svc = min_t(unsigned int, conf->u.x25.hi_svc, MAX_LCN_NUM);
+		card->u.x.lo_svc = min_t(unsigned int, conf->u.x25.lo_svc, card->u.x.hi_svc);
+	}
+
+	/* Figure out the total number of channels to configure */
+	card->u.x.num_of_ch = 0;
+	if (card->u.x.hi_svc != 0){
+		card->u.x.num_of_ch = (card->u.x.hi_svc - card->u.x.lo_svc) + 1;
+	}
+	if (card->u.x.hi_pvc != 0){
+		card->u.x.num_of_ch += (card->u.x.hi_pvc - card->u.x.lo_pvc) + 1;
+	}
+
+	if (card->u.x.num_of_ch == 0){
+		printk(KERN_INFO "%s: ERROR, Minimum number of PVC/SVC channels is 1 !\n"
+				 "%s: Please set the Lowest/Highest PVC/SVC values !\n",
+				 card->devname,card->devname);
+		return -ECHRNG;
+	}
+	
+	u.cfg.loPVC = card->u.x.lo_pvc;
+	u.cfg.hiPVC = card->u.x.hi_pvc;
+	u.cfg.loTwoWaySVC = card->u.x.lo_svc;
+	u.cfg.hiTwoWaySVC = card->u.x.hi_svc;
+
+	if (conf->u.x25.hdlc_window)
+		u.cfg.hdlcWindow = min_t(unsigned int, conf->u.x25.hdlc_window, 7);
+	if (conf->u.x25.pkt_window)
+		u.cfg.pktWindow = min_t(unsigned int, conf->u.x25.pkt_window, 7);
+
+	if (conf->u.x25.t1)
+		u.cfg.t1 = min_t(unsigned int, conf->u.x25.t1, 30);
+	if (conf->u.x25.t2)
+		u.cfg.t2 = min_t(unsigned int, conf->u.x25.t2, 29);
+	if (conf->u.x25.t4)
+		u.cfg.t4 = min_t(unsigned int, conf->u.x25.t4, 240);
+	if (conf->u.x25.n2)
+		u.cfg.n2 = min_t(unsigned int, conf->u.x25.n2, 30);
+
+	if (conf->u.x25.t10_t20)
+		u.cfg.t10t20 = min_t(unsigned int, conf->u.x25.t10_t20,255);
+	if (conf->u.x25.t11_t21)
+		u.cfg.t11t21 = min_t(unsigned int, conf->u.x25.t11_t21,255);
+	if (conf->u.x25.t12_t22)
+		u.cfg.t12t22 = min_t(unsigned int, conf->u.x25.t12_t22,255);
+	if (conf->u.x25.t13_t23)	
+		u.cfg.t13t23 = min_t(unsigned int, conf->u.x25.t13_t23,255);
+	if (conf->u.x25.t16_t26)
+		u.cfg.t16t26 = min_t(unsigned int, conf->u.x25.t16_t26, 255);
+	if (conf->u.x25.t28)
+		u.cfg.t28 = min_t(unsigned int, conf->u.x25.t28, 255);
+
+	if (conf->u.x25.r10_r20)
+		u.cfg.r10r20 = min_t(unsigned int, conf->u.x25.r10_r20,250);
+	if (conf->u.x25.r12_r22)
+		u.cfg.r12r22 = min_t(unsigned int, conf->u.x25.r12_r22,250);
+	if (conf->u.x25.r13_r23)
+		u.cfg.r13r23 = min_t(unsigned int, conf->u.x25.r13_r23,250);
+
+
+	if (conf->u.x25.ccitt_compat)
+		u.cfg.ccittCompat = conf->u.x25.ccitt_compat;
+
+	/* initialize adapter */
+	if (card->u.x.LAPB_hdlc){
+		if (hdlc_configure(card, &u.cfg) != CMD_OK)
+			return -EIO;
+	}else{
+		if (x25_configure(card, &u.cfg) != CMD_OK)
+			return -EIO;
+	}
+
+	if ((x25_close_hdlc(card) != CMD_OK) ||		/* close HDLC link */
+	    (x25_set_dtr(card, 0) != CMD_OK))		/* drop DTR */
+		return -EIO;
+
+	/* Initialize protocol-specific fields of adapter data space */
+	card->wandev.bps	= conf->bps;
+	card->wandev.interface	= conf->interface;
+	card->wandev.clocking	= conf->clocking;
+	card->wandev.station	= conf->station;
+	card->isr		= &wpx_isr;
+	card->poll		= NULL; //&wpx_poll;
+	card->disable_comm	= &disable_comm;
+	card->exec		= &wpx_exec;
+	card->wandev.update	= &update;
+	card->wandev.new_if	= &new_if;
+	card->wandev.del_if	= &del_if;
+
+	/* WARNING: This function cannot exit with an error
+	 *          after the change of state */
+	card->wandev.state	= WAN_DISCONNECTED;
+	
+	card->wandev.enable_tx_int = 0;
+	card->irq_dis_if_send_count = 0;
+        card->irq_dis_poll_count = 0;
+	card->u.x.tx_dev = NULL;
+	card->u.x.no_dev = 0;
+
+
+	/* Configure for S514 PCI Card */
+	if (card->hw.type == SDLA_S514) {
+		card->u.x.hdlc_buf_status = 
+			(volatile unsigned char *)
+				(card->hw.dpmbase + X25_MB_VECTOR+ X25_MISC_HDLC_BITS);
+	}else{
+		card->u.x.hdlc_buf_status = 
+			(volatile unsigned char *)(card->hw.dpmbase + X25_MISC_HDLC_BITS); 
+	}
+
+	card->u.x.poll_device=NULL;
+	card->wandev.udp_port = conf->udp_port;
+
+	/* Enable or disable call setup logging */
+	if (conf->u.x25.logging == WANOPT_YES){
+		printk(KERN_INFO "%s: Enabling Call Logging.\n",
+			card->devname);
+		card->u.x.logging = 1;
+	}else{	
+		card->u.x.logging = 0;
+	}
+
+	/* Enable or disable modem status reporting */
+	if (conf->u.x25.oob_on_modem == WANOPT_YES){
+		printk(KERN_INFO "%s: Enabling OOB on Modem change.\n",
+			card->devname);
+		card->u.x.oob_on_modem = 1;
+	}else{
+		card->u.x.oob_on_modem = 0;
+	}
+	
+	init_global_statistics(card);	
+
+	INIT_WORK(&card->u.x.x25_poll_work, (void *)wpx_poll, card);
+
+	init_timer(&card->u.x.x25_timer);
+	card->u.x.x25_timer.data = (unsigned long)card;
+	card->u.x.x25_timer.function = x25_timer_routine;
+	
+	return 0;
+}
+
+/*=========================================================
+ *	WAN Device Driver Entry Points 
+ *========================================================*/
+
+/*============================================================
+ * Name:	update(),  Update device status & statistics.
+ *
+ * Purpose:	To provide debugging and statitical
+ *              information to the /proc file system.
+ *              /proc/net/wanrouter/wanpipe#
+ *              	
+ * Rationale:	The /proc file system is used to collect
+ *              information about the kernel and drivers.
+ *              Using the /proc file system the user
+ *              can see exactly what the sangoma drivers are
+ *              doing. And in what state they are in. 
+ *                
+ * Description: Collect all driver statistical information
+ *              and pass it to the top laywer. 
+ *		
+ *		Since we have to execute a debugging command, 
+ *              to obtain firmware statitics, we trigger a 
+ *              UPDATE function within the timer interrtup.
+ *              We wait until the timer update is complete.
+ *              Once complete return the appropriate return
+ *              code to indicate that the update was successful.
+ *              
+ * Called by:	device_stat() in wanmain.c
+ *
+ * Assumptions:	
+ *
+ * Warnings:	This function will degrade the performance
+ *              of the router, since it uses the mailbox. 
+ *
+ * Return: 	0 	OK
+ * 		<0	Failed (or busy).
+ */
+
+static int update(struct wan_device* wandev)
+{
+	volatile sdla_t* card;
+	TX25Status* status;
+	unsigned long timeout;
+
+	/* sanity checks */
+	if ((wandev == NULL) || (wandev->private == NULL))
+		return -EFAULT;
+
+	if (wandev->state == WAN_UNCONFIGURED)
+		return -ENODEV;
+
+	if (test_bit(SEND_CRIT, (void*)&wandev->critical))
+		return -EAGAIN;
+
+	if (!wandev->dev)
+		return -ENODEV;
+	
+	card = wandev->private;
+	status = card->flags;
+
+	card->u.x.timer_int_enabled |= TMR_INT_ENABLED_UPDATE;
+	status->imask |= INTR_ON_TIMER;
+	timeout = jiffies;	
+
+	for (;;){
+		if (!(card->u.x.timer_int_enabled & TMR_INT_ENABLED_UPDATE)){	
+			break;
+		}
+		if ((jiffies-timeout) > 1*HZ){
+			card->u.x.timer_int_enabled &= ~TMR_INT_ENABLED_UPDATE;
+			return -EAGAIN;
+		}
+	}
+	return 0;
+}
+
+
+/*===================================================================
+ * Name:	new_if
+ *
+ * Purpose:	To allocate and initialize resources for a 
+ *              new logical channel.  
+ * 
+ * Rationale:	A new channel can be added dynamically via
+ *              ioctl call.
+ *                
+ * Description:	Allocate a private channel structure, x25_channel_t.
+ *		Parse the user interface options from wanpipe#.conf 
+ *		configuration file. 
+ *		Bind the private are into the network device private
+ *              area pointer (dev->priv).
+ *		Prepare the network device structure for registration.
+ *
+ * Called by:	ROUTER_IFNEW Ioctl call, from wanrouter_ioctl() 
+ *              (wanmain.c)
+ *
+ * Assumptions: None
+ *
+ * Warnings:	None
+ *
+ * Return: 	0 	Ok
+ *		<0 	Failed (channel will not be created)
+ */
+static int new_if(struct wan_device* wandev, struct net_device* dev,
+		  wanif_conf_t* conf)
+{
+	sdla_t* card = wandev->private;
+	x25_channel_t* chan;
+	int err = 0;
+
+	if ((conf->name[0] == '\0') || (strlen(conf->name) > WAN_IFNAME_SZ)){
+		printk(KERN_INFO "%s: invalid interface name!\n",
+			card->devname);
+		return -EINVAL;
+	}
+
+	if(card->wandev.new_if_cnt++ > 0 && card->u.x.LAPB_hdlc) {
+		printk(KERN_INFO "%s: Error: Running LAPB HDLC Mode !\n",
+						card->devname);
+		printk(KERN_INFO 
+			"%s: Maximum number of network interfaces must be one !\n",
+						card->devname);
+		return -EEXIST;
+	}
+
+	/* allocate and initialize private data */
+	chan = kmalloc(sizeof(x25_channel_t), GFP_ATOMIC);
+	if (chan == NULL){
+		return -ENOMEM;
+	}
+	
+	memset(chan, 0, sizeof(x25_channel_t));
+
+	/* Bug Fix: Seg Err on PVC startup
+	 * It must be here since bind_lcn_to_dev expects 
+	 * it bellow */
+	dev->priv = chan;
+	
+	strcpy(chan->name, conf->name);
+	chan->card = card;
+	chan->dev = dev;
+	chan->common.sk = NULL;
+	chan->common.func = NULL;
+	chan->common.rw_bind = 0;
+	chan->tx_skb = chan->rx_skb = NULL;
+
+	/* verify media address */
+	if (conf->addr[0] == '@'){		/* SVC */
+		chan->common.svc = 1;
+		strncpy(chan->addr, &conf->addr[1], WAN_ADDRESS_SZ);
+
+		/* Set channel timeouts (default if not specified) */
+		chan->idle_timeout = (conf->idle_timeout) ? 
+					conf->idle_timeout : 90;
+		chan->hold_timeout = (conf->hold_timeout) ? 
+					conf->hold_timeout : 10;
+
+	}else if (is_digit(conf->addr[0])){	/* PVC */
+		int lcn = dec_to_uint(conf->addr, 0);
+
+		if ((lcn >= card->u.x.lo_pvc) && (lcn <= card->u.x.hi_pvc)){
+			bind_lcn_to_dev (card, dev, lcn);
+		}else{
+			printk(KERN_ERR
+				"%s: PVC %u is out of range on interface %s!\n",
+				wandev->name, lcn, chan->name);
+			err = -EINVAL;
+		}
+	}else{
+		printk(KERN_ERR
+			"%s: invalid media address on interface %s!\n",
+			wandev->name, chan->name);
+		err = -EINVAL;
+	}
+
+	if(strcmp(conf->usedby, "WANPIPE") == 0){
+                printk(KERN_INFO "%s: Running in WANPIPE mode %s\n",
+			wandev->name, chan->name);
+                chan->common.usedby = WANPIPE;
+		chan->protocol = htons(ETH_P_IP);
+
+        }else if(strcmp(conf->usedby, "API") == 0){
+		chan->common.usedby = API;
+                printk(KERN_INFO "%s: Running in API mode %s\n",
+			wandev->name, chan->name);
+		chan->protocol = htons(X25_PROT);
+	}
+
+
+	if (err){
+		kfree(chan);
+		dev->priv = NULL;
+		return err;
+	}
+	
+	chan->enable_IPX = conf->enable_IPX;
+	
+	if (chan->enable_IPX)
+		chan->protocol = htons(ETH_P_IPX);
+	
+	if (conf->network_number)
+		chan->network_number = conf->network_number;
+	else
+		chan->network_number = 0xDEADBEEF;
+
+	/* prepare network device data space for registration */
+	strcpy(dev->name,chan->name);
+
+	dev->init = &if_init;
+
+	init_x25_channel_struct(chan);
+
+	return 0;
+}
+
+/*===================================================================
+ * Name:	del_if(),  Remove a logical channel.	 
+ *
+ * Purpose:	To dynamically remove a logical channel.
+ * 
+ * Rationale:	Each logical channel should be dynamically
+ *              removable. This functin is called by an 
+ *              IOCTL_IFDEL ioctl call or shutdown(). 
+ *                
+ * Description: Do nothing.
+ *
+ * Called by:	IOCTL_IFDEL : wanrouter_ioctl() from wanmain.c
+ *              shutdown() from sdlamain.c
+ *
+ * Assumptions: 
+ *
+ * Warnings:
+ *
+ * Return: 	0 Ok. Void function.
+ */
+
+//FIXME Del IF Should be taken out now.
+
+static int del_if(struct wan_device* wandev, struct net_device* dev)
+{
+	return 0;
+}
+
+
+/*============================================================
+ * Name:	wpx_exec
+ *
+ * Description:	Execute adapter interface command.
+ * 		This option is currently dissabled.
+ *===========================================================*/
+
+static int wpx_exec (struct sdla* card, void* u_cmd, void* u_data)
+{
+        return 0;
+}
+
+/*============================================================
+ * Name:	disable_comm	
+ *
+ * Description:	Disable communications during shutdown.
+ *              Dont check return code because there is 
+ *              nothing we can do about it.  
+ *
+ * Warning:	Dev and private areas are gone at this point.
+ *===========================================================*/
+
+static void disable_comm(sdla_t* card)
+{
+	disable_comm_shutdown(card);
+	del_timer(&card->u.x.x25_timer);
+	return;
+}
+
+
+/*============================================================
+ *	Network Device Interface 
+ *===========================================================*/
+
+/*===================================================================
+ * Name:	if_init(),   Netowrk Interface Initialization 	 
+ *
+ * Purpose:	To initialize a network interface device structure.
+ * 
+ * Rationale:	During network interface startup, the if_init
+ *              is called by the kernel to initialize the
+ *              netowrk device structure.  Thus a driver
+ *              can customze a network device. 
+ *                
+ * Description:	Initialize the netowrk device call back
+ *              routines.  This is where we tell the kernel
+ *              which function to use when it wants to send
+ *              via our interface. 
+ *		Furthermore, we initialize the device flags, 
+ *              MTU and physical address of the board.
+ *
+ * Called by:	Kernel (/usr/src/linux/net/core/dev.c)
+ * 		(dev->init())
+ *
+ * Assumptions: None
+ *	
+ * Warnings:	None
+ *
+ * Return: 	0 	Ok : Void function.
+ */
+static int if_init(struct net_device* dev)
+{
+	x25_channel_t* chan = dev->priv;
+	sdla_t* card = chan->card;
+	struct wan_device* wandev = &card->wandev;
+
+	/* Initialize device driver entry points */
+	dev->open		= &if_open;
+	dev->stop		= &if_close;
+	dev->hard_header	= &if_header;
+	dev->rebuild_header	= &if_rebuild_hdr;
+	dev->hard_start_xmit	= &if_send;
+	dev->get_stats		= &if_stats;
+	dev->tx_timeout		= &if_tx_timeout;
+	dev->watchdog_timeo	= TX_TIMEOUT;
+
+	/* Initialize media-specific parameters */
+	dev->type		= ARPHRD_PPP;		/* ARP h/w type */
+	dev->flags		|= IFF_POINTOPOINT;
+	dev->flags		|= IFF_NOARP;
+
+	if (chan->common.usedby == API){
+		dev->mtu	= X25_CHAN_MTU+sizeof(x25api_hdr_t);
+	}else{
+		dev->mtu	= card->wandev.mtu; 	
+	}
+	
+	dev->hard_header_len	= X25_HRDHDR_SZ; /* media header length */
+	dev->addr_len		= 2;		/* hardware address length */
+	
+	if (!chan->common.svc){
+		*(unsigned short*)dev->dev_addr = htons(chan->common.lcn);
+	}
+	
+	/* Initialize hardware parameters (just for reference) */
+	dev->irq	= wandev->irq;
+	dev->dma	= wandev->dma;
+	dev->base_addr	= wandev->ioport;
+	dev->mem_start	= (unsigned long)wandev->maddr;
+	dev->mem_end	= wandev->maddr + wandev->msize - 1;
+
+        /* Set transmit buffer queue length */
+        dev->tx_queue_len = 100;
+	SET_MODULE_OWNER(dev);
+
+	/* FIXME Why are we doing this */
+	set_chan_state(dev, WAN_DISCONNECTED);
+	return 0;
+}
+
+
+/*===================================================================
+ * Name:	if_open(),   Open/Bring up the Netowrk Interface 
+ *
+ * Purpose:	To bring up a network interface.
+ * 
+ * Rationale:	
+ *                
+ * Description:	Open network interface.
+ * 		o prevent module from unloading by incrementing use count
+ * 		o if link is disconnected then initiate connection
+ *
+ * Called by:	Kernel (/usr/src/linux/net/core/dev.c)
+ * 		(dev->open())
+ *
+ * Assumptions: None
+ *	
+ * Warnings:	None
+ *
+ * Return: 	0 	Ok
+ * 		<0 	Failure: Interface will not come up.
+ */
+
+static int if_open(struct net_device* dev)
+{
+	x25_channel_t* chan = dev->priv;
+	sdla_t* card = chan->card;
+	struct timeval tv;
+	unsigned long smp_flags;
+	
+	if (netif_running(dev))
+		return -EBUSY;
+
+	chan->tq_working = 0;
+
+	/* Initialize the workqueue */
+	INIT_WORK(&chan->common.wanpipe_work, (void *)x25api_bh, dev);
+
+	/* Allocate and initialize BH circular buffer */
+	/* Add 1 to MAX_BH_BUFF so we don't have test with (MAX_BH_BUFF-1) */
+	chan->bh_head = kmalloc((sizeof(bh_data_t)*(MAX_BH_BUFF+1)),GFP_ATOMIC);
+
+	if (chan->bh_head == NULL){
+		printk(KERN_INFO "%s: ERROR, failed to allocate memory ! BH_BUFFERS !\n",
+				card->devname);
+
+		return -ENOBUFS;
+	}
+	memset(chan->bh_head,0,(sizeof(bh_data_t)*(MAX_BH_BUFF+1)));
+	atomic_set(&chan->bh_buff_used, 0);
+
+	/* Increment the number of interfaces */
+	++card->u.x.no_dev;
+	
+	wanpipe_open(card);
+
+	/* LAPB protocol only uses one interface, thus
+	 * start the protocol after it comes up. */
+	if (card->u.x.LAPB_hdlc){
+		if (card->open_cnt == 1){
+			TX25Status* status = card->flags;
+			S508_S514_lock(card, &smp_flags);
+			x25_set_intr_mode(card, INTR_ON_TIMER); 
+			status->imask &= ~INTR_ON_TIMER;
+			S508_S514_unlock(card, &smp_flags);
+		}
+	}else{
+		/* X25 can have multiple interfaces thus, start the 
+		 * protocol once all interfaces are up */
+
+		//FIXME: There is a bug here. If interface is
+		//brought down and up, it will try to enable comm.
+		if (card->open_cnt == card->u.x.num_of_ch){
+
+			S508_S514_lock(card, &smp_flags);
+			connect(card);
+			S508_S514_unlock(card, &smp_flags);
+
+			mod_timer(&card->u.x.x25_timer, jiffies + HZ);
+		}
+	}
+	/* Device is not up until the we are in connected state */
+	do_gettimeofday( &tv );
+	chan->router_start_time = tv.tv_sec;
+
+	netif_start_queue(dev);
+
+	return 0;
+}
+
+/*===================================================================
+ * Name:	if_close(),   Close/Bring down the Netowrk Interface 
+ *
+ * Purpose:	To bring down a network interface.
+ * 
+ * Rationale:	
+ *                
+ * Description:	Close network interface.
+ * 		o decrement use module use count
+ *
+ * Called by:	Kernel (/usr/src/linux/net/core/dev.c)
+ * 		(dev->close())
+ *		ifconfig <name> down: will trigger the kernel
+ *              which will call this function.
+ *
+ * Assumptions: None
+ *	
+ * Warnings:	None
+ *
+ * Return: 	0 	Ok
+ * 		<0 	Failure: Interface will not exit properly.
+ */
+static int if_close(struct net_device* dev)
+{
+	x25_channel_t* chan = dev->priv;
+	sdla_t* card = chan->card;
+	unsigned long smp_flags;
+	
+	netif_stop_queue(dev);
+
+	if ((chan->common.state == WAN_CONNECTED) || 
+	    (chan->common.state == WAN_CONNECTING)){
+		S508_S514_lock(card, &smp_flags);
+		chan_disc(dev);
+		S508_S514_unlock(card, &smp_flags);
+	}
+
+	wanpipe_close(card);
+
+	S508_S514_lock(card, &smp_flags);
+	if (chan->bh_head){
+		int i;
+		struct sk_buff *skb;
+	
+		for (i=0; i<(MAX_BH_BUFF+1); i++){
+			skb = ((bh_data_t *)&chan->bh_head[i])->skb;
+			if (skb != NULL){
+                		dev_kfree_skb_any(skb);
+			}
+		}
+		kfree(chan->bh_head);
+		chan->bh_head=NULL;
+	}
+	S508_S514_unlock(card, &smp_flags);
+
+	/* If this is the last close, disconnect physical link */
+	if (!card->open_cnt){
+		S508_S514_lock(card, &smp_flags);
+		disconnect(card);
+		x25_set_intr_mode(card, 0);
+		S508_S514_unlock(card, &smp_flags);
+	}
+	
+	/* Decrement the number of interfaces */
+	--card->u.x.no_dev;
+	return 0;
+}
+
+/*======================================================================
+ * 	Build media header.
+ * 	o encapsulate packet according to encapsulation type.
+ *
+ * 	The trick here is to put packet type (Ethertype) into 'protocol' 
+ *      field of the socket buffer, so that we don't forget it.  
+ *      If encapsulation fails, set skb->protocol to 0 and discard 
+ *      packet later.
+ *
+ * 	Return:		media header length.
+ *======================================================================*/
+
+static int if_header(struct sk_buff* skb, struct net_device* dev,
+		     unsigned short type, void* daddr, void* saddr,
+		     unsigned len)
+{
+	x25_channel_t* chan = dev->priv;
+	int hdr_len = dev->hard_header_len;
+	
+	skb->protocol = htons(type);
+	if (!chan->protocol){
+		hdr_len = wanrouter_encapsulate(skb, dev, type);
+		if (hdr_len < 0){
+			hdr_len = 0;
+			skb->protocol = htons(0);
+		}
+	}
+	return hdr_len;
+}
+
+/*===============================================================
+ * 	Re-build media header.
+ *
+ * 	Return:		1	physical address resolved.
+ *			0	physical address not resolved
+ *==============================================================*/
+
+static int if_rebuild_hdr (struct sk_buff* skb)
+{
+	struct net_device *dev = skb->dev; 
+	x25_channel_t* chan = dev->priv;
+	sdla_t* card = chan->card;
+
+	printk(KERN_INFO "%s: rebuild_header() called for interface %s!\n",
+		card->devname, dev->name);
+	return 1;
+}
+
+
+/*============================================================================
+ * Handle transmit timeout event from netif watchdog
+ */
+static void if_tx_timeout(struct net_device *dev)
+{
+    	x25_channel_t* chan = dev->priv;
+	sdla_t *card = chan->card;
+
+	/* If our device stays busy for at least 5 seconds then we will
+	 * kick start the device by making dev->tbusy = 0.  We expect
+	 * that our device never stays busy more than 5 seconds. So this                 
+	 * is only used as a last resort.
+	 */
+
+	++chan->if_send_stat.if_send_tbusy_timeout;
+	printk (KERN_INFO "%s: Transmit timed out on %s\n", 
+			card->devname, dev->name);
+	netif_wake_queue (dev);
+}
+
+
+/*=========================================================================
+ * 	Send a packet on a network interface.
+ * 	o set tbusy flag (marks start of the transmission).
+ * 	o check link state. If link is not up, then drop the packet.
+ * 	o check channel status. If it's down then initiate a call.
+ * 	o pass a packet to corresponding WAN device.
+ * 	o free socket buffer
+ *
+ * 	Return:	0	complete (socket buffer must be freed)
+ *		non-0	packet may be re-transmitted (tbusy must be set)
+ *
+ * 	Notes:
+ * 	1. This routine is called either by the protocol stack or by the "net
+ *    	bottom half" (with interrupts enabled).
+ * 	2. Setting tbusy flag will inhibit further transmit requests from the
+ *    	protocol stack and can be used for flow control with protocol layer.
+ *
+ *========================================================================*/
+
+static int if_send(struct sk_buff* skb, struct net_device* dev)
+{
+	x25_channel_t* chan = dev->priv;
+	sdla_t* card = chan->card;
+	TX25Status* status = card->flags;
+	int udp_type;
+	unsigned long smp_flags=0;
+
+	++chan->if_send_stat.if_send_entry;
+
+	netif_stop_queue(dev);
+
+	/* No need to check frame length, since socket code
+         * will perform the check for us */
+
+	chan->tick_counter = jiffies;
+	
+	/* Critical region starts here */
+	S508_S514_lock(card, &smp_flags);
+	
+	if (test_and_set_bit(SEND_CRIT, (void*)&card->wandev.critical)){
+		printk(KERN_INFO "Hit critical in if_send()! %lx\n",card->wandev.critical);
+		goto if_send_crit_exit;
+	}
+	
+	udp_type = udp_pkt_type(skb, card);
+
+        if(udp_type != UDP_INVALID_TYPE) {
+
+                if(store_udp_mgmt_pkt(udp_type, UDP_PKT_FRM_STACK, card, dev, skb,
+                        chan->common.lcn)) {
+
+                        status->imask |= INTR_ON_TIMER;
+                        if (udp_type == UDP_XPIPE_TYPE){
+                                chan->if_send_stat.if_send_PIPE_request++;
+			}
+               	}
+		netif_start_queue(dev);
+		clear_bit(SEND_CRIT,(void*)&card->wandev.critical);
+		S508_S514_unlock(card, &smp_flags);
+		return 0;
+	}
+
+	if (chan->transmit_length){
+		//FIXME: This check doesn't make sense any more
+		if (chan->common.state != WAN_CONNECTED){
+			chan->transmit_length=0;
+			atomic_set(&chan->common.driver_busy,0);
+		}else{
+			netif_stop_queue(dev);
+			++card->u.x.tx_interrupts_pending;
+		        status->imask |= INTR_ON_TX_FRAME;
+			clear_bit(SEND_CRIT,(void*)&card->wandev.critical);
+			S508_S514_unlock(card, &smp_flags);
+			return 1;
+		}
+	}
+
+	if (card->wandev.state != WAN_CONNECTED){
+		++chan->ifstats.tx_dropped;
+		++card->wandev.stats.tx_dropped;
+		++chan->if_send_stat.if_send_wan_disconnected;	
+		
+	}else if ( chan->protocol && (chan->protocol != skb->protocol)){
+		printk(KERN_INFO
+			"%s: unsupported Ethertype 0x%04X on interface %s!\n",
+			chan->name, htons(skb->protocol), dev->name);
+		
+		printk(KERN_INFO "PROTO %Xn", htons(chan->protocol));
+		++chan->ifstats.tx_errors;
+		++chan->ifstats.tx_dropped;
+		++card->wandev.stats.tx_dropped;
+		++chan->if_send_stat.if_send_protocol_error;
+		
+	}else switch (chan->common.state){
+
+		case WAN_DISCONNECTED:
+			/* Try to establish connection. If succeded, then start
+			 * transmission, else drop a packet.
+			 */
+			if (chan->common.usedby == API){
+				++chan->ifstats.tx_dropped;
+				++card->wandev.stats.tx_dropped;
+				break;
+			}else{
+				if (chan_connect(dev) != 0){
+					++chan->ifstats.tx_dropped;
+					++card->wandev.stats.tx_dropped;
+					break;
+				}
+			}
+			/* fall through */
+
+		case WAN_CONNECTED:
+			if( skb->protocol == htons(ETH_P_IPX)) {
+				if(chan->enable_IPX) {
+					switch_net_numbers( skb->data, 
+						chan->network_number, 0);
+				} else {
+					++card->wandev.stats.tx_dropped;
+					++chan->ifstats.tx_dropped;
+					++chan->if_send_stat.if_send_protocol_error;
+					goto if_send_crit_exit;
+				}
+			}
+			/* We never drop here, if cannot send than, copy
+	                 * a packet into a transmit buffer 
+                         */
+			chan_send(dev, skb->data, skb->len, 0);
+			break;
+
+		default:
+			++chan->ifstats.tx_dropped;	
+			++card->wandev.stats.tx_dropped;
+			break;
+	}
+
+
+if_send_crit_exit:
+	
+       	dev_kfree_skb_any(skb);
+
+	netif_start_queue(dev);
+	clear_bit(SEND_CRIT,(void*)&card->wandev.critical);
+	S508_S514_unlock(card, &smp_flags);
+	return 0;
+}
+
+/*============================================================================
+ * Setup so that a frame can be transmitted on the occurrence of a transmit
+ * interrupt.
+ *===========================================================================*/
+
+static void setup_for_delayed_transmit(struct net_device* dev, void* buf,
+				       unsigned len)
+{
+        x25_channel_t* chan = dev->priv;
+        sdla_t* card = chan->card;
+	TX25Status* status = card->flags;
+
+	++chan->if_send_stat.if_send_adptr_bfrs_full;
+
+        if(chan->transmit_length) {
+                printk(KERN_INFO "%s: Error, transmit length set in delayed transmit!\n",
+				card->devname);
+                return;
+        }
+
+	if (chan->common.usedby == API){
+		if (len > X25_CHAN_MTU+sizeof(x25api_hdr_t)) {
+			++chan->ifstats.tx_dropped;	
+			++card->wandev.stats.tx_dropped;
+			printk(KERN_INFO "%s: Length is too big for delayed transmit\n",
+				card->devname);
+			return;
+		}
+	}else{
+		if (len > X25_MAX_DATA) {
+			++chan->ifstats.tx_dropped;	
+			++card->wandev.stats.tx_dropped;
+			printk(KERN_INFO "%s: Length is too big for delayed transmit\n",
+				card->devname);
+			return;
+		}
+	}
+
+        chan->transmit_length = len;
+	atomic_set(&chan->common.driver_busy,1);
+        memcpy(chan->transmit_buffer, buf, len);
+
+	++chan->if_send_stat.if_send_tx_int_enabled;
+
+	/* Enable Transmit Interrupt */
+	++card->u.x.tx_interrupts_pending;
+        status->imask |= INTR_ON_TX_FRAME;
+}
+
+
+/*===============================================================
+ * net_device_stats
+ *
+ * 	Get ethernet-style interface statistics.
+ * 	Return a pointer to struct enet_statistics.
+ *
+ *==============================================================*/
+static struct net_device_stats *if_stats(struct net_device* dev)
+{
+	x25_channel_t *chan = dev->priv;
+
+	if(chan == NULL)
+		return NULL;
+
+	return &chan->ifstats;
+}
+
+
+/*
+ *	Interrupt Handlers 
+ */
+
+/*
+ * X.25 Interrupt Service Routine.
+ */
+
+static void wpx_isr (sdla_t* card)
+{
+	TX25Status* status = card->flags;
+
+	card->in_isr = 1;
+	++card->statistics.isr_entry;
+
+	if (test_bit(PERI_CRIT,(void*)&card->wandev.critical)){
+		card->in_isr=0;
+		status->iflags = 0;
+		return;
+	}
+	
+	if (test_bit(SEND_CRIT, (void*)&card->wandev.critical)){
+
+ 		printk(KERN_INFO "%s: wpx_isr: wandev.critical set to 0x%02lx, int type = 0x%02x\n", 
+			card->devname, card->wandev.critical, status->iflags);
+		card->in_isr = 0;
+		status->iflags = 0;
+		return;
+	}
+
+	/* For all interrupts set the critical flag to CRITICAL_RX_INTR.
+         * If the if_send routine is called with this flag set it will set
+         * the enable transmit flag to 1. (for a delayed interrupt)
+         */
+	switch (status->iflags){
+
+		case RX_INTR_PENDING:		/* receive interrupt */
+			rx_intr(card);
+			break;
+
+		case TX_INTR_PENDING:		/* transmit interrupt */
+			tx_intr(card);
+			break;
+
+		case MODEM_INTR_PENDING:	/* modem status interrupt */
+			status_intr(card);
+			break;
+
+		case X25_ASY_TRANS_INTR_PENDING:	/* network event interrupt */
+			event_intr(card);
+			break;
+
+		case TIMER_INTR_PENDING:
+			timer_intr(card);
+			break;
+
+		default:		/* unwanted interrupt */
+			spur_intr(card);
+	}
+
+	card->in_isr = 0;
+	status->iflags = 0;	/* clear interrupt condition */
+}
+
+/*
+ * 	Receive interrupt handler.
+ * 	This routine handles fragmented IP packets using M-bit according to the
+ * 	RFC1356.
+ * 	o map ligical channel number to network interface.
+ * 	o allocate socket buffer or append received packet to the existing one.
+ * 	o if M-bit is reset (i.e. it's the last packet in a sequence) then 
+ *   	decapsulate packet and pass socket buffer to the protocol stack.
+ *
+ * 	Notes:
+ * 	1. When allocating a socket buffer, if M-bit is set then more data is
+ *    	coming and we have to allocate buffer for the maximum IP packet size
+ *    	expected on this channel.
+ * 	2. If something goes wrong and X.25 packet has to be dropped (e.g. no
+ *    	socket buffers available) the whole packet sequence must be discarded.
+ */
+
+static void rx_intr (sdla_t* card)
+{
+	TX25Mbox* rxmb = card->rxmb;
+	unsigned lcn = rxmb->cmd.lcn;
+	struct net_device* dev = find_channel(card,lcn);
+	x25_channel_t* chan;
+	struct sk_buff* skb=NULL;
+
+	if (dev == NULL){
+		/* Invalid channel, discard packet */
+		printk(KERN_INFO "%s: receiving on orphaned LCN %d!\n",
+			card->devname, lcn);
+		return;
+	}
+
+	chan = dev->priv;
+	chan->i_timeout_sofar = jiffies;
+
+
+	/* Copy the data from the board, into an
+         * skb buffer 
+	 */
+	if (wanpipe_pull_data_in_skb(card,dev,&skb)){
+		++chan->ifstats.rx_dropped;
+		++card->wandev.stats.rx_dropped;
+		++chan->rx_intr_stat.rx_intr_no_socket;
+		++chan->rx_intr_stat.rx_intr_bfr_not_passed_to_stack;
+		return;
+	}
+
+	dev->last_rx = jiffies;		/* timestamp */
+
+
+	/* ------------ API ----------------*/
+
+	if (chan->common.usedby == API){
+
+		if (bh_enqueue(dev, skb)){
+			++chan->ifstats.rx_dropped;
+			++card->wandev.stats.rx_dropped;
+			++chan->rx_intr_stat.rx_intr_bfr_not_passed_to_stack;
+			dev_kfree_skb_any(skb);
+			return;
+		}		
+
+		++chan->ifstats.rx_packets;
+		chan->ifstats.rx_bytes += skb->len;
+		
+
+		chan->rx_skb = NULL;
+		if (!test_and_set_bit(0, &chan->tq_working)){
+			wanpipe_queue_work(&chan->common.wanpipe_work);
+		}
+		return;
+	}
+
+
+	/* ------------- WANPIPE -------------------*/
+	
+	/* set rx_skb to NULL so we won't access it later when kernel already owns it */
+	chan->rx_skb=NULL;
+	
+	/* Decapsulate packet, if necessary */
+	if (!skb->protocol && !wanrouter_type_trans(skb, dev)){
+		/* can't decapsulate packet */
+                dev_kfree_skb_any(skb);
+		++chan->ifstats.rx_errors;
+		++chan->ifstats.rx_dropped;
+		++card->wandev.stats.rx_dropped;
+		++chan->rx_intr_stat.rx_intr_bfr_not_passed_to_stack;
+
+	}else{
+		if( handle_IPXWAN(skb->data, chan->name, 
+				  chan->enable_IPX, chan->network_number, 
+				  skb->protocol)){
+
+			if( chan->enable_IPX ){
+				if(chan_send(dev, skb->data, skb->len,0)){
+					chan->tx_skb = skb;
+				}else{
+                                        dev_kfree_skb_any(skb);
+					++chan->rx_intr_stat.rx_intr_bfr_not_passed_to_stack;
+				}
+			}else{
+				/* increment IPX packet dropped statistic */
+				++chan->ifstats.rx_dropped;
+				++chan->rx_intr_stat.rx_intr_bfr_not_passed_to_stack;
+			}
+		}else{
+			skb->mac.raw = skb->data;
+			chan->ifstats.rx_bytes += skb->len;
+			++chan->ifstats.rx_packets;
+			++chan->rx_intr_stat.rx_intr_bfr_passed_to_stack;
+			netif_rx(skb);
+		}
+	}
+	
+	return;
+}
+
+
+static int wanpipe_pull_data_in_skb(sdla_t *card, struct net_device *dev,
+				    struct sk_buff **skb)
+{
+	void *bufptr;
+	TX25Mbox* rxmb = card->rxmb;
+	unsigned len = rxmb->cmd.length;	/* packet length */
+	unsigned qdm = rxmb->cmd.qdm;		/* Q,D and M bits */
+	x25_channel_t *chan = dev->priv;
+	struct sk_buff *new_skb = *skb;
+
+	if (chan->common.usedby == WANPIPE){
+		if (chan->drop_sequence){
+			if (!(qdm & 0x01)){ 
+				chan->drop_sequence = 0;
+			}
+			return 1;
+		}
+		new_skb = chan->rx_skb;
+	}else{
+		/* Add on the API header to the received
+                 * data 
+		 */
+		len += sizeof(x25api_hdr_t);
+	}
+
+	if (new_skb == NULL){
+		int bufsize;
+
+		if (chan->common.usedby == WANPIPE){
+			bufsize = (qdm & 0x01) ? dev->mtu : len;
+		}else{
+			bufsize = len;
+		}
+
+		/* Allocate new socket buffer */
+		new_skb = dev_alloc_skb(bufsize + dev->hard_header_len);
+		if (new_skb == NULL){
+			printk(KERN_INFO "%s: no socket buffers available!\n",
+				card->devname);
+			chan->drop_sequence = 1;	/* set flag */
+			++chan->ifstats.rx_dropped;
+			return 1;
+		}
+	}
+
+	if (skb_tailroom(new_skb) < len){
+		/* No room for the packet. Call off the whole thing! */
+                dev_kfree_skb_any(new_skb);
+		if (chan->common.usedby == WANPIPE){
+			chan->rx_skb = NULL;
+			if (qdm & 0x01){ 
+				chan->drop_sequence = 1;
+			}
+		}
+
+		printk(KERN_INFO "%s: unexpectedly long packet sequence "
+			"on interface %s!\n", card->devname, dev->name);
+		++chan->ifstats.rx_length_errors;
+		return 1;
+	}
+
+	bufptr = skb_put(new_skb,len);
+
+
+	if (chan->common.usedby == API){
+		/* Fill in the x25api header 
+		 */
+		x25api_t * api_data = (x25api_t*)bufptr;
+		api_data->hdr.qdm = rxmb->cmd.qdm;
+		api_data->hdr.cause = rxmb->cmd.cause;
+		api_data->hdr.diagn = rxmb->cmd.diagn;
+		api_data->hdr.length = rxmb->cmd.length;
+		memcpy(api_data->data, rxmb->data, rxmb->cmd.length);
+	}else{
+		memcpy(bufptr, rxmb->data, len);
+	}
+
+	new_skb->dev = dev;
+
+	if (chan->common.usedby == API){
+		new_skb->mac.raw = new_skb->data;
+		new_skb->protocol = htons(X25_PROT);
+		new_skb->pkt_type = WAN_PACKET_DATA;
+	}else{
+		new_skb->protocol = chan->protocol;
+		chan->rx_skb = new_skb;
+	}
+
+	/* If qdm bit is set, more data is coming 
+         * thus, exit and wait for more data before
+         * sending the packet up. (Used by router only) 
+	 */
+	if ((qdm & 0x01) && (chan->common.usedby == WANPIPE)) 
+		return 1;	
+
+	*skb = new_skb; 
+
+	return 0;
+}
+
+/*===============================================================
+ * tx_intr
+ *  
+ * 	Transmit interrupt handler.
+ *	For each dev, check that there is something to send.
+ *	If data available, transmit. 	
+ *
+ *===============================================================*/
+
+static void tx_intr (sdla_t* card)
+{
+	struct net_device *dev;
+	TX25Status* status = card->flags;
+	unsigned char more_to_tx=0;
+	x25_channel_t *chan=NULL;
+	int i=0;	
+
+	if (card->u.x.tx_dev == NULL){
+		card->u.x.tx_dev = card->wandev.dev;
+	}
+
+	dev = card->u.x.tx_dev;
+
+	for (;;){
+
+		chan = dev->priv;
+		if (chan->transmit_length){
+			/* Device was set to transmit, check if the TX
+                         * buffers are available 
+			 */		
+			if (chan->common.state != WAN_CONNECTED){
+				chan->transmit_length = 0;
+				atomic_set(&chan->common.driver_busy,0);
+				chan->tx_offset=0;
+				if (netif_queue_stopped(dev)){
+					if (chan->common.usedby == API){
+						netif_start_queue(dev);
+						wakeup_sk_bh(dev);
+					}else{
+						netif_wake_queue(dev);
+					}
+				}
+				dev = move_dev_to_next(card,dev);
+				break;
+			}				
+
+			if ((status->cflags[chan->ch_idx] & 0x40 || card->u.x.LAPB_hdlc) && 
+			     (*card->u.x.hdlc_buf_status & 0x40) ){
+				/* Tx buffer available, we can send */
+				
+				if (tx_intr_send(card, dev)){
+					more_to_tx=1;
+				}
+
+				/* If more than one interface present, move the
+                                 * device pointer to the next interface, so on the 
+                                 * next TX interrupt we will try sending from it. 
+                                 */
+				dev = move_dev_to_next(card,dev);
+				break;
+			}else{
+				/* Tx buffers not available, but device set
+                                 * the TX interrupt.  Set more_to_tx and try  
+                                 * to transmit for other devices.
+				 */
+				more_to_tx=1;
+				dev = move_dev_to_next(card,dev);
+			}
+
+		}else{
+			/* This device was not set to transmit,
+                         * go to next 
+			 */
+			dev = move_dev_to_next(card,dev);
+		}	
+
+		if (++i == card->u.x.no_dev){
+			if (!more_to_tx){
+				DBG_PRINTK(KERN_INFO "%s: Nothing to Send in TX INTR\n",
+					card->devname);
+			}
+			break;
+		}
+
+	} //End of FOR
+
+	card->u.x.tx_dev = dev;
+	
+	if (!more_to_tx){
+		/* if any other interfaces have transmit interrupts pending, */
+		/* do not disable the global transmit interrupt */
+		if (!(--card->u.x.tx_interrupts_pending)){
+			status->imask &= ~INTR_ON_TX_FRAME;
+		}
+	}
+	return;
+}
+
+/*===============================================================
+ * move_dev_to_next
+ *  
+ *
+ *===============================================================*/
+
+
+struct net_device *move_dev_to_next(sdla_t *card, struct net_device *dev)
+{
+	if (card->u.x.no_dev != 1){
+		if (!*((struct net_device **)dev->priv))
+			return card->wandev.dev;
+		else
+			return *((struct net_device **)dev->priv);
+	}
+	return dev;
+}
+
+/*===============================================================
+ *  tx_intr_send
+ *  
+ *
+ *===============================================================*/
+
+static int tx_intr_send(sdla_t *card, struct net_device *dev)
+{
+	x25_channel_t* chan = dev->priv; 
+
+	if (chan_send (dev,chan->transmit_buffer,chan->transmit_length,1)){
+		 
+                /* Packet was split up due to its size, do not disable
+                 * tx_intr 
+                 */
+		return 1;
+	}
+
+	chan->transmit_length=0;
+	atomic_set(&chan->common.driver_busy,0);
+	chan->tx_offset=0;
+
+	/* If we are in API mode, wakeup the 
+         * sock BH handler, not the NET_BH */
+	if (netif_queue_stopped(dev)){
+		if (chan->common.usedby == API){
+			netif_start_queue(dev);
+			wakeup_sk_bh(dev);
+		}else{
+			netif_wake_queue(dev);
+		}
+	}
+	return 0;
+}
+
+
+/*===============================================================
+ * timer_intr
+ *  
+ * 	Timer interrupt handler.
+ *	Check who called the timer interrupt and perform
+ *      action accordingly.
+ *
+ *===============================================================*/
+
+static void timer_intr (sdla_t *card)
+{
+	TX25Status* status = card->flags;
+
+	if (card->u.x.timer_int_enabled & TMR_INT_ENABLED_CMD_EXEC){
+
+		if (timer_intr_cmd_exec(card) == 0){
+			card->u.x.timer_int_enabled &=
+				~TMR_INT_ENABLED_CMD_EXEC;
+		}
+
+	}else  if(card->u.x.timer_int_enabled & TMR_INT_ENABLED_UDP_PKT) {
+
+		if ((*card->u.x.hdlc_buf_status & 0x40) && 
+		    card->u.x.udp_type == UDP_XPIPE_TYPE){
+
+                    	if(process_udp_mgmt_pkt(card)) {
+		                card->u.x.timer_int_enabled &= 
+					~TMR_INT_ENABLED_UDP_PKT;
+			}
+		}
+
+	}else if (card->u.x.timer_int_enabled & TMR_INT_ENABLED_POLL_ACTIVE) {
+
+		struct net_device *dev = card->u.x.poll_device;
+		x25_channel_t *chan = NULL;
+
+		if (!dev){
+			card->u.x.timer_int_enabled &= ~TMR_INT_ENABLED_POLL_ACTIVE;
+			return;
+		}
+		chan = dev->priv;
+
+		printk(KERN_INFO 
+			"%s: Closing down Idle link %s on LCN %d\n",
+					card->devname,chan->name,chan->common.lcn); 
+		chan->i_timeout_sofar = jiffies;
+		chan_disc(dev);	
+         	card->u.x.timer_int_enabled &= ~TMR_INT_ENABLED_POLL_ACTIVE;
+		card->u.x.poll_device=NULL;
+
+	}else if (card->u.x.timer_int_enabled & TMR_INT_ENABLED_POLL_CONNECT_ON) {
+
+		wanpipe_set_state(card, WAN_CONNECTED);
+		if (card->u.x.LAPB_hdlc){
+			struct net_device *dev = card->wandev.dev;
+			set_chan_state(dev,WAN_CONNECTED);
+			send_delayed_cmd_result(card,dev,card->mbox);	
+		}
+
+		/* 0x8F enable all interrupts */
+		x25_set_intr_mode(card, INTR_ON_RX_FRAME|	
+					INTR_ON_TX_FRAME|
+					INTR_ON_MODEM_STATUS_CHANGE|
+					//INTR_ON_COMMAND_COMPLETE|
+					X25_ASY_TRANS_INTR_PENDING |
+					INTR_ON_TIMER |
+					DIRECT_RX_INTR_USAGE
+				); 
+
+		status->imask &= ~INTR_ON_TX_FRAME;	/* mask Tx interrupts */
+		card->u.x.timer_int_enabled &= ~TMR_INT_ENABLED_POLL_CONNECT_ON;
+
+	}else if (card->u.x.timer_int_enabled & TMR_INT_ENABLED_POLL_CONNECT_OFF) {
+
+		//printk(KERN_INFO "Poll connect, Turning OFF\n");
+		disconnect(card);
+		card->u.x.timer_int_enabled &= ~TMR_INT_ENABLED_POLL_CONNECT_OFF;
+
+	}else if (card->u.x.timer_int_enabled & TMR_INT_ENABLED_POLL_DISCONNECT) {
+
+		//printk(KERN_INFO "POll disconnect, trying to connect\n");
+		connect(card);
+		card->u.x.timer_int_enabled &= ~TMR_INT_ENABLED_POLL_DISCONNECT;
+
+	}else if (card->u.x.timer_int_enabled & TMR_INT_ENABLED_UPDATE){
+
+		if (*card->u.x.hdlc_buf_status & 0x40){
+			x25_get_err_stats(card);
+			x25_get_stats(card);
+			card->u.x.timer_int_enabled &= ~TMR_INT_ENABLED_UPDATE;
+		}
+	}
+
+	if(!card->u.x.timer_int_enabled){
+		//printk(KERN_INFO "Turning Timer Off \n");
+                status->imask &= ~INTR_ON_TIMER;	
+	}
+}
+
+/*====================================================================
+ * 	Modem status interrupt handler.
+ *===================================================================*/
+static void status_intr (sdla_t* card)
+{
+
+	/* Added to avoid Modem status message flooding */
+	static TX25ModemStatus last_stat;
+
+	TX25Mbox* mbox = card->mbox;
+	TX25ModemStatus *modem_status;
+	struct net_device *dev;
+	x25_channel_t *chan;
+	int err;
+
+	memset(&mbox->cmd, 0, sizeof(TX25Cmd));
+	mbox->cmd.command = X25_READ_MODEM_STATUS;
+	err = sdla_exec(mbox) ? mbox->cmd.result : CMD_TIMEOUT;
+	if (err){ 
+		x25_error(card, err, X25_READ_MODEM_STATUS, 0);
+	}else{
+	
+		modem_status = (TX25ModemStatus*)mbox->data;	
+	
+           	/* Check if the last status was the same
+           	 * if it was, do NOT print message again */
+	
+		if (last_stat.status != modem_status->status){
+
+	     		printk(KERN_INFO "%s: Modem Status Change: DCD=%s, CTS=%s\n",
+				card->devname,DCD(modem_status->status),CTS(modem_status->status));
+
+			last_stat.status = modem_status->status;
+		
+			if (card->u.x.oob_on_modem){
+
+				mbox->cmd.pktType = mbox->cmd.command;
+				mbox->cmd.result = 0x08;
+
+				/* Send a OOB to all connected sockets */
+				for (dev = card->wandev.dev; dev;
+				     dev = *((struct net_device**)dev->priv)) {
+					chan=dev->priv;
+					if (chan->common.usedby == API){
+						send_oob_msg(card,dev,mbox);				
+					}
+				}
+
+				/* The modem OOB message will probably kill the
+				 * the link. If we don't clear the flag here,
+				 * a deadlock could occur */ 
+				if (atomic_read(&card->u.x.command_busy)){
+					atomic_set(&card->u.x.command_busy,0);
+				}
+			}
+		}
+	}
+
+	memset(&mbox->cmd, 0, sizeof(TX25Cmd));
+	mbox->cmd.command = X25_HDLC_LINK_STATUS;
+	err = sdla_exec(mbox) ? mbox->cmd.result : CMD_TIMEOUT;
+	if (err){ 
+		x25_error(card, err, X25_HDLC_LINK_STATUS, 0);
+	}
+
+}
+
+/*====================================================================
+ * 	Network event interrupt handler.
+ *===================================================================*/
+static void event_intr (sdla_t* card)
+{
+	x25_fetch_events(card);
+}
+
+/*====================================================================
+ * 	Spurious interrupt handler.
+ * 	o print a warning
+ * 	o	 
+ *====================================================================*/
+
+static void spur_intr (sdla_t* card)
+{
+	printk(KERN_INFO "%s: spurious interrupt!\n", card->devname);
+}
+
+
+/*
+ *	Background Polling Routines  
+ */
+
+/*====================================================================
+ * 	Main polling routine.
+ * 	This routine is repeatedly called by the WANPIPE 'thread' to allow for
+ * 	time-dependent housekeeping work.
+ *
+ * 	Notes:
+ * 	1. This routine may be called on interrupt context with all interrupts
+ *    	enabled. Beware!
+ *====================================================================*/
+
+static void wpx_poll (sdla_t *card)
+{
+	if (!card->wandev.dev){
+		goto wpx_poll_exit;
+	}
+
+	if (card->open_cnt != card->u.x.num_of_ch){
+		goto wpx_poll_exit;
+	}
+	
+	if (test_bit(PERI_CRIT,&card->wandev.critical)){
+		goto wpx_poll_exit;
+	}
+
+	if (test_bit(SEND_CRIT,&card->wandev.critical)){
+		goto wpx_poll_exit;
+	}
+
+	switch(card->wandev.state){
+		case WAN_CONNECTED:
+			poll_active(card);
+			break;
+
+		case WAN_CONNECTING:
+			poll_connecting(card);
+			break;
+
+		case WAN_DISCONNECTED:
+			poll_disconnected(card);
+			break;
+	}
+
+wpx_poll_exit:
+	clear_bit(POLL_CRIT,&card->wandev.critical);
+	return;
+}
+
+static void trigger_x25_poll(sdla_t *card)
+{
+	schedule_work(&card->u.x.x25_poll_work);
+}
+
+/*====================================================================
+ * 	Handle physical link establishment phase.
+ * 	o if connection timed out, disconnect the link.
+ *===================================================================*/
+
+static void poll_connecting (sdla_t* card)
+{
+	volatile TX25Status* status = card->flags;
+
+	if (status->gflags & X25_HDLC_ABM){
+
+		timer_intr_exec (card, TMR_INT_ENABLED_POLL_CONNECT_ON);
+
+	}else if ((jiffies - card->state_tick) > CONNECT_TIMEOUT){
+
+		timer_intr_exec (card, TMR_INT_ENABLED_POLL_CONNECT_OFF);
+
+	}
+}
+
+/*====================================================================
+ * 	Handle physical link disconnected phase.
+ * 	o if hold-down timeout has expired and there are open interfaces, 
+ *	connect link.
+ *===================================================================*/
+
+static void poll_disconnected (sdla_t* card)
+{
+	struct net_device *dev; 
+	x25_channel_t *chan;
+	TX25Status* status = card->flags;
+
+	if (!card->u.x.LAPB_hdlc && card->open_cnt && 
+	    ((jiffies - card->state_tick) > HOLD_DOWN_TIME)){
+		timer_intr_exec(card, TMR_INT_ENABLED_POLL_DISCONNECT);
+	}
+
+
+	if ((dev=card->wandev.dev) == NULL)
+		return;
+
+	if ((chan=dev->priv) == NULL)
+		return;
+
+	if (chan->common.usedby == API && 
+	    atomic_read(&chan->common.command) && 
+	    card->u.x.LAPB_hdlc){
+
+		if (!(card->u.x.timer_int_enabled & TMR_INT_ENABLED_CMD_EXEC)) 
+			card->u.x.timer_int_enabled |= TMR_INT_ENABLED_CMD_EXEC;
+
+		if (!(status->imask & INTR_ON_TIMER))
+			status->imask |= INTR_ON_TIMER;
+	}	
+
+}
+
+/*====================================================================
+ * 	Handle active link phase.
+ * 	o fetch X.25 asynchronous events.
+ * 	o kick off transmission on all interfaces.
+ *===================================================================*/
+
+static void poll_active (sdla_t* card)
+{
+	struct net_device* dev;
+	TX25Status* status = card->flags;
+
+	for (dev = card->wandev.dev; dev;
+	     dev = *((struct net_device **)dev->priv)){
+		x25_channel_t* chan = dev->priv;
+
+		/* If SVC has been idle long enough, close virtual circuit */
+		if ( chan->common.svc && 
+		     chan->common.state == WAN_CONNECTED &&
+		     chan->common.usedby == WANPIPE ){
+		
+			if( (jiffies - chan->i_timeout_sofar) / HZ > chan->idle_timeout ){
+				/* Close svc */
+				card->u.x.poll_device=dev;
+				timer_intr_exec	(card, TMR_INT_ENABLED_POLL_ACTIVE);
+			}
+		}
+
+#ifdef PRINT_DEBUG
+		chan->ifstats.tx_compressed = atomic_read(&chan->common.command);
+		chan->ifstats.tx_errors = chan->common.state;
+		chan->ifstats.rx_fifo_errors = atomic_read(&card->u.x.command_busy);
+		++chan->ifstats.tx_bytes;
+
+		chan->ifstats.rx_fifo_errors=atomic_read(&chan->common.disconnect);
+		chan->ifstats.multicast=atomic_read(&chan->bh_buff_used);
+		chan->ifstats.rx_length_errors=*card->u.x.hdlc_buf_status;
+#endif	
+
+		if (chan->common.usedby == API && 
+		    atomic_read(&chan->common.command) && 
+	            !card->u.x.LAPB_hdlc){
+
+			if (!(card->u.x.timer_int_enabled & TMR_INT_ENABLED_CMD_EXEC)) 
+				card->u.x.timer_int_enabled |= TMR_INT_ENABLED_CMD_EXEC;
+
+			if (!(status->imask & INTR_ON_TIMER))
+				status->imask |= INTR_ON_TIMER;
+		}	
+
+		if ((chan->common.usedby == API) && 
+		     atomic_read(&chan->common.disconnect)){
+
+			if (chan->common.state == WAN_DISCONNECTED){
+				atomic_set(&chan->common.disconnect,0);
+				return;
+			}
+
+			atomic_set(&chan->common.command,X25_CLEAR_CALL);
+			if (!(card->u.x.timer_int_enabled & TMR_INT_ENABLED_CMD_EXEC)) 
+				card->u.x.timer_int_enabled |= TMR_INT_ENABLED_CMD_EXEC;
+
+			if (!(status->imask & INTR_ON_TIMER))
+				status->imask |= INTR_ON_TIMER;
+		}
+	}
+}
+
+static void timer_intr_exec(sdla_t *card, unsigned char TYPE)
+{
+	TX25Status* status = card->flags;
+	card->u.x.timer_int_enabled |= TYPE;
+	if (!(status->imask & INTR_ON_TIMER))
+		status->imask |= INTR_ON_TIMER;
+}
+
+
+/*==================================================================== 
+ * SDLA Firmware-Specific Functions 
+ *
+ *  Almost all X.25 commands can unexpetedly fail due to so called 'X.25
+ *  asynchronous events' such as restart, interrupt, incoming call request,
+ *  call clear request, etc.  They can't be ignored and have to be delt with
+ *  immediately.  To tackle with this problem we execute each interface 
+ *  command in a loop until good return code is received or maximum number 
+ *  of retries is reached.  Each interface command returns non-zero return 
+ *  code, an asynchronous event/error handler x25_error() is called.
+ *====================================================================*/
+
+/*====================================================================
+ * 	Read X.25 firmware version.
+ *		Put code version as ASCII string in str. 
+ *===================================================================*/
+
+static int x25_get_version (sdla_t* card, char* str)
+{
+	TX25Mbox* mbox = card->mbox;
+  	int retry = MAX_CMD_RETRY;
+	int err;
+
+	do
+	{
+		memset(&mbox->cmd, 0, sizeof(TX25Cmd));
+		mbox->cmd.command = X25_READ_CODE_VERSION;
+		err = sdla_exec(mbox) ? mbox->cmd.result : CMD_TIMEOUT;
+	} while (err && retry-- &&
+		 x25_error(card, err, X25_READ_CODE_VERSION, 0));
+
+	if (!err && str)
+	{
+		int len = mbox->cmd.length;
+
+		memcpy(str, mbox->data, len);
+		str[len] = '\0';
+	}
+	return err;
+}
+
+/*====================================================================
+ * 	Configure adapter.
+ *===================================================================*/
+
+static int x25_configure (sdla_t* card, TX25Config* conf)
+{
+	TX25Mbox* mbox = card->mbox;
+  	int retry = MAX_CMD_RETRY;
+	int err;
+
+	do{
+		memset(&mbox->cmd, 0, sizeof(TX25Cmd));
+		memcpy(mbox->data, (void*)conf, sizeof(TX25Config));
+		mbox->cmd.length  = sizeof(TX25Config);
+		mbox->cmd.command = X25_SET_CONFIGURATION;
+		err = sdla_exec(mbox) ? mbox->cmd.result : CMD_TIMEOUT;
+	} while (err && retry-- && x25_error(card, err, X25_SET_CONFIGURATION, 0));
+	return err;
+}
+
+/*====================================================================
+ * 	Configure adapter for HDLC only.
+ *===================================================================*/
+
+static int hdlc_configure (sdla_t* card, TX25Config* conf)
+{
+	TX25Mbox* mbox = card->mbox;
+  	int retry = MAX_CMD_RETRY;
+	int err;
+
+	do{
+		memset(&mbox->cmd, 0, sizeof(TX25Cmd));
+		memcpy(mbox->data, (void*)conf, sizeof(TX25Config));
+		mbox->cmd.length  = sizeof(TX25Config);
+		mbox->cmd.command = X25_HDLC_SET_CONFIG;
+		err = sdla_exec(mbox) ? mbox->cmd.result : CMD_TIMEOUT;
+	} while (err && retry-- && x25_error(card, err, X25_SET_CONFIGURATION, 0));
+
+	return err;
+}
+
+static int set_hdlc_level (sdla_t* card)
+{
+
+	TX25Mbox* mbox = card->mbox;
+  	int retry = MAX_CMD_RETRY;
+	int err;
+
+	do{
+		memset(&mbox->cmd, 0, sizeof(TX25Cmd));
+		mbox->cmd.command = SET_PROTOCOL_LEVEL;
+		mbox->cmd.length = 1;
+		mbox->data[0] = HDLC_LEVEL; //| DO_HDLC_LEVEL_ERROR_CHECKING; 	
+		err = sdla_exec(mbox) ? mbox->cmd.result : CMD_TIMEOUT;
+	} while (err && retry-- && x25_error(card, err, SET_PROTOCOL_LEVEL, 0));
+
+	return err;
+}
+
+
+
+/*====================================================================
+ * Get communications error statistics.
+ *====================================================================*/
+
+static int x25_get_err_stats (sdla_t* card)
+{
+	TX25Mbox* mbox = card->mbox;
+  	int retry = MAX_CMD_RETRY;
+	int err;
+
+	do
+	{
+		memset(&mbox->cmd, 0, sizeof(TX25Cmd));
+		mbox->cmd.command = X25_HDLC_READ_COMM_ERR;
+		err = sdla_exec(mbox) ? mbox->cmd.result : CMD_TIMEOUT;
+	} while (err && retry-- && x25_error(card, err, X25_HDLC_READ_COMM_ERR, 0));
+	
+	if (!err)
+	{
+		THdlcCommErr* stats = (void*)mbox->data;
+
+		card->wandev.stats.rx_over_errors    = stats->rxOverrun;
+		card->wandev.stats.rx_crc_errors     = stats->rxBadCrc;
+		card->wandev.stats.rx_missed_errors  = stats->rxAborted;
+		card->wandev.stats.tx_aborted_errors = stats->txAborted;
+	}
+	return err;
+}
+
+/*====================================================================
+ * 	Get protocol statistics.
+ *===================================================================*/
+
+static int x25_get_stats (sdla_t* card)
+{
+	TX25Mbox* mbox = card->mbox;
+  	int retry = MAX_CMD_RETRY;
+	int err;
+
+	do
+	{
+		memset(&mbox->cmd, 0, sizeof(TX25Cmd));
+		mbox->cmd.command = X25_READ_STATISTICS;
+		err = sdla_exec(mbox) ? mbox->cmd.result : CMD_TIMEOUT;
+	} while (err && retry-- && x25_error(card, err, X25_READ_STATISTICS, 0)) ;
+	
+	if (!err)
+	{
+		TX25Stats* stats = (void*)mbox->data;
+
+		card->wandev.stats.rx_packets = stats->rxData;
+		card->wandev.stats.tx_packets = stats->txData;
+	}
+	return err;
+}
+
+/*====================================================================
+ * 	Close HDLC link.
+ *===================================================================*/
+
+static int x25_close_hdlc (sdla_t* card)
+{
+	TX25Mbox* mbox = card->mbox;
+  	int retry = MAX_CMD_RETRY;
+	int err;
+
+	do
+	{
+		memset(&mbox->cmd, 0, sizeof(TX25Cmd));
+		mbox->cmd.command = X25_HDLC_LINK_CLOSE;
+		err = sdla_exec(mbox) ? mbox->cmd.result : CMD_TIMEOUT;
+	} while (err && retry-- && x25_error(card, err, X25_HDLC_LINK_CLOSE, 0));
+	
+	return err;
+}
+
+
+/*====================================================================
+ * 	Open HDLC link.
+ *===================================================================*/
+
+static int x25_open_hdlc (sdla_t* card)
+{
+	TX25Mbox* mbox = card->mbox;
+  	int retry = MAX_CMD_RETRY;
+	int err;
+
+	do
+	{
+		memset(&mbox->cmd, 0, sizeof(TX25Cmd));
+		mbox->cmd.command = X25_HDLC_LINK_OPEN;
+		err = sdla_exec(mbox) ? mbox->cmd.result : CMD_TIMEOUT;
+	} while (err && retry-- && x25_error(card, err, X25_HDLC_LINK_OPEN, 0));
+
+	return err;
+}
+
+/*=====================================================================
+ * Setup HDLC link.
+ *====================================================================*/
+static int x25_setup_hdlc (sdla_t* card)
+{
+	TX25Mbox* mbox = card->mbox;
+  	int retry = MAX_CMD_RETRY;
+	int err;
+
+	do
+	{
+		memset(&mbox->cmd, 0, sizeof(TX25Cmd));
+		mbox->cmd.command = X25_HDLC_LINK_SETUP;
+		err = sdla_exec(mbox) ? mbox->cmd.result : CMD_TIMEOUT;
+	} while (err && retry-- && x25_error(card, err, X25_HDLC_LINK_SETUP, 0));
+	
+	return err;
+}
+
+/*====================================================================
+ * Set (raise/drop) DTR.
+ *===================================================================*/
+
+static int x25_set_dtr (sdla_t* card, int dtr)
+{
+	TX25Mbox* mbox = card->mbox;
+  	int retry = MAX_CMD_RETRY;
+	int err;
+
+	do
+	{
+		memset(&mbox->cmd, 0, sizeof(TX25Cmd));
+		mbox->data[0] = 0;
+		mbox->data[2] = 0;
+		mbox->data[1] = dtr ? 0x02 : 0x01;
+		mbox->cmd.length  = 3;
+		mbox->cmd.command = X25_SET_GLOBAL_VARS;
+		err = sdla_exec(mbox) ? mbox->cmd.result : CMD_TIMEOUT;
+	} while (err && retry-- && x25_error(card, err, X25_SET_GLOBAL_VARS, 0));
+	
+	return err;
+}
+
+/*====================================================================
+ * 	Set interrupt mode.
+ *===================================================================*/
+
+static int x25_set_intr_mode (sdla_t* card, int mode)
+{
+	TX25Mbox* mbox = card->mbox;
+  	int retry = MAX_CMD_RETRY;
+	int err;
+
+	do
+	{
+		memset(&mbox->cmd, 0, sizeof(TX25Cmd));
+		mbox->data[0] = mode;
+		if (card->hw.fwid == SFID_X25_508){
+			mbox->data[1] = card->hw.irq;
+			mbox->data[2] = 2;
+			mbox->cmd.length = 3;
+		}else {
+		 	mbox->cmd.length  = 1;
+		}
+		mbox->cmd.command = X25_SET_INTERRUPT_MODE;
+		err = sdla_exec(mbox) ? mbox->cmd.result : CMD_TIMEOUT;
+	} while (err && retry-- && x25_error(card, err, X25_SET_INTERRUPT_MODE, 0));
+	
+	return err;
+}
+
+/*====================================================================
+ * 	Read X.25 channel configuration.
+ *===================================================================*/
+
+static int x25_get_chan_conf (sdla_t* card, x25_channel_t* chan)
+{
+	TX25Mbox* mbox = card->mbox;
+  	int retry = MAX_CMD_RETRY;
+	int lcn = chan->common.lcn;
+	int err;
+
+	do{
+		memset(&mbox->cmd, 0, sizeof(TX25Cmd));
+		mbox->cmd.lcn     = lcn;
+		mbox->cmd.command = X25_READ_CHANNEL_CONFIG;
+		err = sdla_exec(mbox) ? mbox->cmd.result : CMD_TIMEOUT;
+	} while (err && retry-- && x25_error(card, err, X25_READ_CHANNEL_CONFIG, lcn));
+
+	if (!err)
+	{
+		TX25Status* status = card->flags;
+
+		/* calculate an offset into the array of status bytes */
+		if (card->u.x.hi_svc <= X25_MAX_CHAN){ 
+
+			chan->ch_idx = lcn - 1;
+
+		}else{
+			int offset;
+
+			/* FIX: Apr 14 2000 : Nenad Corbic
+			 * The data field was being compared to 0x1F using
+                         * '&&' instead of '&'. 
+			 * This caused X25API to fail for LCNs greater than 255.
+			 */
+			switch (mbox->data[0] & 0x1F)
+			{
+				case 0x01: 
+					offset = status->pvc_map; break;
+				case 0x03: 
+					offset = status->icc_map; break;
+				case 0x07: 
+					offset = status->twc_map; break;
+				case 0x0B: 
+					offset = status->ogc_map; break;
+				default: 
+					offset = 0;
+			}
+			chan->ch_idx = lcn - 1 - offset;
+		}
+
+		/* get actual transmit packet size on this channel */
+		switch(mbox->data[1] & 0x38)
+		{
+			case 0x00: 
+				chan->tx_pkt_size = 16; 
+				break;
+			case 0x08: 
+				chan->tx_pkt_size = 32; 
+				break;
+			case 0x10: 
+				chan->tx_pkt_size = 64; 
+				break;
+			case 0x18: 
+				chan->tx_pkt_size = 128; 
+				break;
+			case 0x20: 
+				chan->tx_pkt_size = 256; 
+				break;
+			case 0x28: 
+				chan->tx_pkt_size = 512; 
+				break;
+			case 0x30: 
+				chan->tx_pkt_size = 1024; 
+				break;
+		}
+		if (card->u.x.logging)
+			printk(KERN_INFO "%s: X.25 packet size on LCN %d is %d.\n",
+				card->devname, lcn, chan->tx_pkt_size);
+	}
+	return err;
+}
+
+/*====================================================================
+ * 	Place X.25 call.
+ *====================================================================*/
+
+static int x25_place_call (sdla_t* card, x25_channel_t* chan)
+{
+	TX25Mbox* mbox = card->mbox;
+  	int retry = MAX_CMD_RETRY;
+	int err;
+	char str[64];
+
+
+	if (chan->protocol == htons(ETH_P_IP)){
+		sprintf(str, "-d%s -uCC", chan->addr);
+	
+	}else if (chan->protocol == htons(ETH_P_IPX)){
+		sprintf(str, "-d%s -u800000008137", chan->addr);
+	
+	}
+	
+	do
+	{
+		memset(&mbox->cmd, 0, sizeof(TX25Cmd));
+		strcpy(mbox->data, str);
+		mbox->cmd.length  = strlen(str);
+		mbox->cmd.command = X25_PLACE_CALL;
+		err = sdla_exec(mbox) ? mbox->cmd.result : CMD_TIMEOUT;
+	} while (err && retry-- && x25_error(card, err, X25_PLACE_CALL, 0));
+
+	if (!err){
+		bind_lcn_to_dev (card, chan->dev, mbox->cmd.lcn);
+	}
+	return err;
+}
+
+/*====================================================================
+ * 	Accept X.25 call.
+ *====================================================================*/
+
+static int x25_accept_call (sdla_t* card, int lcn, int qdm)
+{
+	TX25Mbox* mbox = card->mbox;
+  	int retry = MAX_CMD_RETRY;
+	int err;
+
+	do
+	{
+		memset(&mbox->cmd, 0, sizeof(TX25Cmd));
+		mbox->cmd.lcn     = lcn;
+		mbox->cmd.qdm     = qdm;
+		mbox->cmd.command = X25_ACCEPT_CALL;
+		err = sdla_exec(mbox) ? mbox->cmd.result : CMD_TIMEOUT;
+	} while (err && retry-- && x25_error(card, err, X25_ACCEPT_CALL, lcn));
+	
+	return err;
+}
+
+/*====================================================================
+ * 	Clear X.25 call.
+ *====================================================================*/
+
+static int x25_clear_call (sdla_t* card, int lcn, int cause, int diagn)
+{
+	TX25Mbox* mbox = card->mbox;
+  	int retry = MAX_CMD_RETRY;
+	int err;
+
+	do
+	{
+		memset(&mbox->cmd, 0, sizeof(TX25Cmd));
+		mbox->cmd.lcn     = lcn;
+		mbox->cmd.cause   = cause;
+		mbox->cmd.diagn   = diagn;
+		mbox->cmd.command = X25_CLEAR_CALL;
+		err = sdla_exec(mbox) ? mbox->cmd.result : CMD_TIMEOUT;
+	} while (err && retry-- && x25_error(card, err, X25_CLEAR_CALL, lcn));
+	
+	return err;
+}
+
+/*====================================================================
+ * 	Send X.25 data packet.
+ *====================================================================*/
+
+static int x25_send (sdla_t* card, int lcn, int qdm, int len, void* buf)
+{
+	TX25Mbox* mbox = card->mbox;
+  	int retry = MAX_CMD_RETRY;
+	int err;
+	unsigned char cmd;
+		
+	if (card->u.x.LAPB_hdlc)
+		cmd = X25_HDLC_WRITE;
+	else
+		cmd = X25_WRITE;
+
+	do
+	{
+		memset(&mbox->cmd, 0, sizeof(TX25Cmd));
+		memcpy(mbox->data, buf, len);
+		mbox->cmd.length  = len;
+		mbox->cmd.lcn     = lcn;
+
+		if (card->u.x.LAPB_hdlc){
+			mbox->cmd.pf = qdm;
+		}else{			
+			mbox->cmd.qdm = qdm;
+		}
+
+		mbox->cmd.command = cmd;
+		err = sdla_exec(mbox) ? mbox->cmd.result : CMD_TIMEOUT;
+	} while (err && retry-- && x25_error(card, err, cmd , lcn));
+
+
+	/* If buffers are busy the return code for LAPB HDLC is
+         * 1. The above functions are looking for return code
+         * of X25RES_NOT_READY if busy. */
+
+	if (card->u.x.LAPB_hdlc && err == 1){
+		err = X25RES_NOT_READY;
+	}
+
+	return err;
+}
+
+/*====================================================================
+ * 	Fetch X.25 asynchronous events.
+ *===================================================================*/
+
+static int x25_fetch_events (sdla_t* card)
+{
+	TX25Status* status = card->flags;
+	TX25Mbox* mbox = card->mbox;
+	int err = 0;
+
+	if (status->gflags & 0x20)
+	{
+		memset(&mbox->cmd, 0, sizeof(TX25Cmd));
+		mbox->cmd.command = X25_IS_DATA_AVAILABLE;
+		err = sdla_exec(mbox) ? mbox->cmd.result : CMD_TIMEOUT;
+ 		if (err) x25_error(card, err, X25_IS_DATA_AVAILABLE, 0);
+	}
+	return err;
+}
+
+/*====================================================================
+ * 	X.25 asynchronous event/error handler.
+ *		This routine is called each time interface command returns 
+ *		non-zero return code to handle X.25 asynchronous events and 
+ *		common errors. Return non-zero to repeat command or zero to 
+ *		cancel it.
+ *
+ * 	Notes:
+ * 	1. This function may be called recursively, as handling some of the
+ *    	asynchronous events (e.g. call request) requires execution of the
+ *    	interface command(s) that, in turn, may also return asynchronous
+ *    	events.  To avoid re-entrancy problems we copy mailbox to dynamically
+ *    	allocated memory before processing events.
+ *====================================================================*/
+
+static int x25_error (sdla_t* card, int err, int cmd, int lcn)
+{
+	int retry = 1;
+	unsigned dlen = ((TX25Mbox*)card->mbox)->cmd.length;
+	TX25Mbox* mb;
+
+	mb = kmalloc(sizeof(TX25Mbox) + dlen, GFP_ATOMIC);
+	if (mb == NULL)
+	{
+		printk(KERN_ERR "%s: x25_error() out of memory!\n",
+			card->devname);
+		return 0;
+	}
+	memcpy(mb, card->mbox, sizeof(TX25Mbox) + dlen);
+	switch (err){
+
+	case X25RES_ASYNC_PACKET:	/* X.25 asynchronous packet was received */
+
+		mb->data[dlen] = '\0';
+
+		switch (mb->cmd.pktType & 0x7F){
+
+		case ASE_CALL_RQST:		/* incoming call */
+			retry = incoming_call(card, cmd, lcn, mb);
+			break;
+
+		case ASE_CALL_ACCEPTED:		/* connected */
+			retry = call_accepted(card, cmd, lcn, mb);
+			break;
+
+		case ASE_CLEAR_RQST:		/* call clear request */
+			retry = call_cleared(card, cmd, lcn, mb);
+			break;
+
+		case ASE_RESET_RQST:		/* reset request */
+			printk(KERN_INFO "%s: X.25 reset request on LCN %d! "
+				"Cause:0x%02X Diagn:0x%02X\n",
+				card->devname, mb->cmd.lcn, mb->cmd.cause,
+				mb->cmd.diagn);
+			api_oob_event (card,mb);
+			break;
+
+		case ASE_RESTART_RQST:		/* restart request */
+			retry = restart_event(card, cmd, lcn, mb);
+			break;
+
+		case ASE_CLEAR_CONFRM:
+			if (clear_confirm_event (card,mb))
+				break;
+
+			/* I use the goto statement here so if 
+	                 * somebody inserts code between the
+        	         * case and default, we will not have
+                	 * ghost problems */
+
+			goto dflt_1;
+
+		default:
+dflt_1:
+			printk(KERN_INFO "%s: X.25 event 0x%02X on LCN %d! "
+				"Cause:0x%02X Diagn:0x%02X\n",
+				card->devname, mb->cmd.pktType,
+				mb->cmd.lcn, mb->cmd.cause, mb->cmd.diagn);
+		}
+		break;
+
+	case X25RES_PROTO_VIOLATION:	/* X.25 protocol violation indication */
+
+		/* Bug Fix: Mar 14 2000
+                 * The Protocol violation error conditions were  
+                 * not handled previously */
+
+		switch (mb->cmd.pktType & 0x7F){
+
+		case PVE_CLEAR_RQST:	/* Clear request */		
+			retry = call_cleared(card, cmd, lcn, mb);
+			break;	
+
+		case PVE_RESET_RQST:	/* Reset request */
+			printk(KERN_INFO "%s: X.25 reset request on LCN %d! "
+				"Cause:0x%02X Diagn:0x%02X\n",
+				card->devname, mb->cmd.lcn, mb->cmd.cause,
+				mb->cmd.diagn);
+			api_oob_event (card,mb);
+			break;
+
+		case PVE_RESTART_RQST:	/* Restart request */
+			retry = restart_event(card, cmd, lcn, mb);
+			break;
+
+		default :
+			printk(KERN_INFO
+				"%s: X.25 protocol violation on LCN %d! "
+				"Packet:0x%02X Cause:0x%02X Diagn:0x%02X\n",
+				card->devname, mb->cmd.lcn,
+				mb->cmd.pktType & 0x7F, mb->cmd.cause, mb->cmd.diagn);
+			api_oob_event(card,mb);
+		}
+		break;
+
+	case 0x42:	/* X.25 timeout */
+		retry = timeout_event(card, cmd, lcn, mb);
+		break;
+
+	case 0x43:	/* X.25 retry limit exceeded */
+		printk(KERN_INFO
+			"%s: exceeded X.25 retry limit on LCN %d! "
+			"Packet:0x%02X Diagn:0x%02X\n", card->devname,
+			mb->cmd.lcn, mb->cmd.pktType, mb->cmd.diagn)
+		;
+		break;
+
+	case 0x08:	/* modem failure */
+#ifndef MODEM_NOT_LOG
+		printk(KERN_INFO "%s: modem failure!\n", card->devname);
+#endif /* MODEM_NOT_LOG */
+		api_oob_event(card,mb);
+		break;
+
+	case 0x09:	/* N2 retry limit */
+		printk(KERN_INFO "%s: exceeded HDLC retry limit!\n",
+			card->devname);
+		api_oob_event(card,mb);
+		break;
+
+	case 0x06:	/* unnumbered frame was received while in ABM */
+		printk(KERN_INFO "%s: received Unnumbered frame 0x%02X!\n",
+			card->devname, mb->data[0]);
+		api_oob_event(card,mb);
+		break;
+
+	case CMD_TIMEOUT:
+		printk(KERN_ERR "%s: command 0x%02X timed out!\n",
+			card->devname, cmd)
+		;
+		retry = 0;	/* abort command */
+		break;
+
+	case X25RES_NOT_READY:
+		retry = 1;
+		break;
+
+	case 0x01:
+		if (card->u.x.LAPB_hdlc)
+			break;
+
+		if (mb->cmd.command == 0x16)
+			break;
+		/* I use the goto statement here so if 
+                 * somebody inserts code between the
+                 * case and default, we will not have
+                 * ghost problems */
+		goto dflt_2;
+
+	default:
+dflt_2:
+		printk(KERN_INFO "%s: command 0x%02X returned 0x%02X! Lcn %i\n",
+			card->devname, cmd, err, mb->cmd.lcn)
+		;
+		retry = 0;	/* abort command */
+	}
+	kfree(mb);
+	return retry;
+}
+
+/*==================================================================== 
+ *	X.25 Asynchronous Event Handlers
+ * 	These functions are called by the x25_error() and should return 0, if
+ * 	the command resulting in the asynchronous event must be aborted.
+ *====================================================================*/
+
+
+
+/*====================================================================
+ *Handle X.25 incoming call request.
+ *	RFC 1356 establishes the following rules:
+ *	1. The first octet in the Call User Data (CUD) field of the call
+ *     	   request packet contains NLPID identifying protocol encapsulation
+ * 	2. Calls MUST NOT be accepted unless router supports requested
+ *   	   protocol encapsulation.
+ *	3. A diagnostic code 249 defined by ISO/IEC 8208 may be used 
+ *	   when clearing a call because protocol encapsulation is not 
+ *	   supported.
+ *	4. If an incoming call is received while a call request is 
+ *	   pending (i.e. call collision has occurred), the incoming call 
+ *	   shall be rejected and call request shall be retried.
+ *====================================================================*/
+
+static int incoming_call (sdla_t* card, int cmd, int lcn, TX25Mbox* mb)
+{
+	struct wan_device* wandev = &card->wandev;
+	int new_lcn = mb->cmd.lcn;
+	struct net_device* dev = get_dev_by_lcn(wandev, new_lcn);
+	x25_channel_t* chan = NULL;
+	int accept = 0;		/* set to '1' if o.k. to accept call */
+	unsigned int user_data;
+	x25_call_info_t* info;
+	
+	/* Make sure there is no call collision */
+	if (dev != NULL)
+	{
+		printk(KERN_INFO
+			"%s: X.25 incoming call collision on LCN %d!\n",
+			card->devname, new_lcn);
+
+		x25_clear_call(card, new_lcn, 0, 0);
+		return 1;
+	}
+
+	/* Make sure D bit is not set in call request */
+//FIXME: THIS IS NOT TURE !!!! TAKE IT OUT
+//	if (mb->cmd.qdm & 0x02)
+//	{
+//		printk(KERN_INFO
+//			"%s: X.25 incoming call on LCN %d with D-bit set!\n",
+//			card->devname, new_lcn);
+//
+//		x25_clear_call(card, new_lcn, 0, 0);
+//		return 1;
+//	}
+
+	/* Parse call request data */
+	info = kmalloc(sizeof(x25_call_info_t), GFP_ATOMIC);
+	if (info == NULL)
+	{
+		printk(KERN_ERR
+			"%s: not enough memory to parse X.25 incoming call "
+			"on LCN %d!\n", card->devname, new_lcn);
+		x25_clear_call(card, new_lcn, 0, 0);
+		return 1;
+	}
+ 
+	parse_call_info(mb->data, info);
+
+	if (card->u.x.logging)
+		printk(KERN_INFO "\n%s: X.25 incoming call on LCN %d!\n",
+			card->devname, new_lcn);
+
+	/* Conver the first two ASCII characters into an
+         * interger. Used to check the incoming protocol 
+         */
+	user_data = hex_to_uint(info->user,2);
+
+	/* Find available channel */
+	for (dev = wandev->dev; dev; dev = *((struct net_device **)dev->priv)) {
+		chan = dev->priv;
+
+		if (chan->common.usedby == API)
+			continue;
+
+		if (!chan->common.svc || (chan->common.state != WAN_DISCONNECTED))
+			continue;
+
+		if (user_data == NLPID_IP && chan->protocol != htons(ETH_P_IP)){
+			printk(KERN_INFO "IP packet but configured for IPX : %x, %x\n",
+				       htons(chan->protocol), info->user[0]);
+			continue;
+		}
+	
+		if (user_data == NLPID_SNAP && chan->protocol != htons(ETH_P_IPX)){
+			printk(KERN_INFO "IPX packet but configured for IP: %x\n",
+				       htons(chan->protocol));
+			continue;
+		}
+		if (strcmp(info->src, chan->addr) == 0)
+			break;
+
+	        /* If just an '@' is specified, accept all incoming calls */
+	        if (strcmp(chan->addr, "") == 0)
+	                break;
+	}
+
+	if (dev == NULL){
+
+		/* If the call is not for any WANPIPE interfaces
+                 * check to see if there is an API listening queue
+                 * waiting for data. If there is send the packet
+                 * up the stack.
+                 */
+		if (card->sk != NULL && card->func != NULL){
+			if (api_incoming_call(card,mb,new_lcn)){
+				x25_clear_call(card, new_lcn, 0, 0);
+			}
+			accept = 0;
+		}else{
+			printk(KERN_INFO "%s: no channels available!\n",
+				card->devname);
+			
+			x25_clear_call(card, new_lcn, 0, 0);
+		}
+
+	}else if (info->nuser == 0){
+
+		printk(KERN_INFO
+			"%s: no user data in incoming call on LCN %d!\n",
+			card->devname, new_lcn)
+		;
+		x25_clear_call(card, new_lcn, 0, 0);
+
+	}else switch (info->user[0]){
+
+		case 0:		/* multiplexed */
+			chan->protocol = htons(0);
+			accept = 1;
+			break;
+
+		case NLPID_IP:	/* IP datagrams */
+			accept = 1;
+			break;
+
+		case NLPID_SNAP: /* IPX datagrams */
+			accept = 1;
+			break;
+
+		default:
+			printk(KERN_INFO
+				"%s: unsupported NLPID 0x%02X in incoming call "
+				"on LCN %d!\n", card->devname, info->user[0], new_lcn);
+			x25_clear_call(card, new_lcn, 0, 249);
+	}
+	
+	if (accept && (x25_accept_call(card, new_lcn, 0) == CMD_OK)){
+
+		bind_lcn_to_dev (card, chan->dev, new_lcn);
+		
+		if (x25_get_chan_conf(card, chan) == CMD_OK)
+			set_chan_state(dev, WAN_CONNECTED);
+		else 
+			x25_clear_call(card, new_lcn, 0, 0);
+	}
+	kfree(info);
+	return 1;
+}
+
+/*====================================================================
+ * 	Handle accepted call.
+ *====================================================================*/
+
+static int call_accepted (sdla_t* card, int cmd, int lcn, TX25Mbox* mb)
+{
+	unsigned new_lcn = mb->cmd.lcn;
+	struct net_device* dev = find_channel(card, new_lcn);
+	x25_channel_t* chan;
+
+	if (dev == NULL){
+		printk(KERN_INFO
+			"%s: clearing orphaned connection on LCN %d!\n",
+			card->devname, new_lcn);
+		x25_clear_call(card, new_lcn, 0, 0);
+		return 1;
+	}
+
+	if (card->u.x.logging)	
+		printk(KERN_INFO "%s: X.25 call accepted on Dev %s and LCN %d!\n",
+			card->devname, dev->name, new_lcn);
+
+	/* Get channel configuration and notify router */
+	chan = dev->priv;
+	if (x25_get_chan_conf(card, chan) != CMD_OK)
+	{
+		x25_clear_call(card, new_lcn, 0, 0);
+		return 1;
+	}
+
+	set_chan_state(dev, WAN_CONNECTED);
+
+	if (chan->common.usedby == API){
+		send_delayed_cmd_result(card,dev,mb);
+		bind_lcn_to_dev (card, dev, new_lcn);
+	}
+
+	return 1;
+}
+
+/*====================================================================
+ * 	Handle cleared call.
+ *====================================================================*/
+
+static int call_cleared (sdla_t* card, int cmd, int lcn, TX25Mbox* mb)
+{
+	unsigned new_lcn = mb->cmd.lcn;
+	struct net_device* dev = find_channel(card, new_lcn);
+	x25_channel_t *chan;
+	unsigned char old_state;
+
+	if (card->u.x.logging){
+		printk(KERN_INFO "%s: X.25 clear request on LCN %d! Cause:0x%02X "
+		"Diagn:0x%02X\n",
+		card->devname, new_lcn, mb->cmd.cause, mb->cmd.diagn);
+	}
+
+	if (dev == NULL){ 
+		printk(KERN_INFO "%s: X.25 clear request : No device for clear\n",
+				card->devname);
+		return 1;
+	}
+
+	chan=dev->priv;
+
+	old_state = chan->common.state;
+
+	set_chan_state(dev, WAN_DISCONNECTED);
+
+	if (chan->common.usedby == API){
+
+		switch (old_state){
+		
+		case WAN_CONNECTING:
+			send_delayed_cmd_result(card,dev,mb);
+			break;
+		case WAN_CONNECTED:
+			send_oob_msg(card,dev,mb);				
+			break;
+		}
+	}
+	
+	return ((cmd == X25_WRITE) && (lcn == new_lcn)) ? 0 : 1;
+}
+
+/*====================================================================
+ * 	Handle X.25 restart event.
+ *====================================================================*/
+
+static int restart_event (sdla_t* card, int cmd, int lcn, TX25Mbox* mb)
+{
+	struct wan_device* wandev = &card->wandev;
+	struct net_device* dev;
+	x25_channel_t *chan;
+	unsigned char old_state;
+
+	printk(KERN_INFO
+		"%s: X.25 restart request! Cause:0x%02X Diagn:0x%02X\n",
+		card->devname, mb->cmd.cause, mb->cmd.diagn);
+
+	/* down all logical channels */
+	for (dev = wandev->dev; dev; dev = *((struct net_device **)dev->priv)) {
+		chan=dev->priv;
+		old_state = chan->common.state;
+
+		set_chan_state(dev, WAN_DISCONNECTED);
+
+		if (chan->common.usedby == API){
+			switch (old_state){
+		
+			case WAN_CONNECTING:
+				send_delayed_cmd_result(card,dev,mb);
+				break;
+			case WAN_CONNECTED:
+				send_oob_msg(card,dev,mb);				
+				break;
+			}
+		}
+	}
+	return (cmd == X25_WRITE) ? 0 : 1;
+}
+
+/*====================================================================
+ * Handle timeout event.
+ *====================================================================*/
+
+static int timeout_event (sdla_t* card, int cmd, int lcn, TX25Mbox* mb)
+{
+	unsigned new_lcn = mb->cmd.lcn;
+
+	if (mb->cmd.pktType == 0x05)	/* call request time out */
+	{
+		struct net_device* dev = find_channel(card,new_lcn);
+
+		printk(KERN_INFO "%s: X.25 call timed timeout on LCN %d!\n",
+			card->devname, new_lcn);
+
+		if (dev){
+			x25_channel_t *chan = dev->priv;
+			set_chan_state(dev, WAN_DISCONNECTED);
+
+			if (chan->common.usedby == API){
+				send_delayed_cmd_result(card,dev,card->mbox);
+			}
+		}
+	}else{ 
+		printk(KERN_INFO "%s: X.25 packet 0x%02X timeout on LCN %d!\n",
+		card->devname, mb->cmd.pktType, new_lcn);
+	}
+	return 1;
+}
+
+/* 
+ *	Miscellaneous 
+ */
+
+/*====================================================================
+ * 	Establish physical connection.
+ * 	o open HDLC and raise DTR
+ *
+ * 	Return:		0	connection established
+ *			1	connection is in progress
+ *			<0	error
+ *===================================================================*/
+
+static int connect (sdla_t* card)
+{
+	TX25Status* status = card->flags;
+
+	if (x25_open_hdlc(card) || x25_setup_hdlc(card))
+		return -EIO;
+
+	wanpipe_set_state(card, WAN_CONNECTING);
+
+	x25_set_intr_mode(card, INTR_ON_TIMER); 
+	status->imask &= ~INTR_ON_TIMER;
+
+	return 1;
+}
+
+/*
+ * 	Tear down physical connection.
+ * 	o close HDLC link
+ * 	o drop DTR
+ *
+ * 	Return:		0
+ *			<0	error
+ */
+
+static int disconnect (sdla_t* card)
+{
+	wanpipe_set_state(card, WAN_DISCONNECTED);
+	x25_set_intr_mode(card, INTR_ON_TIMER);	/* disable all interrupt except timer */
+	x25_close_hdlc(card);			/* close HDLC link */
+	x25_set_dtr(card, 0);			/* drop DTR */
+	return 0;
+}
+
+/*
+ * Find network device by its channel number.
+ */
+
+static struct net_device* get_dev_by_lcn(struct wan_device* wandev,
+					 unsigned lcn)
+{
+	struct net_device* dev;
+
+	for (dev = wandev->dev; dev; dev = *((struct net_device **)dev->priv))
+		if (((x25_channel_t*)dev->priv)->common.lcn == lcn) 
+			break;
+	return dev;
+}
+
+/*
+ * 	Initiate connection on the logical channel.
+ * 	o for PVC we just get channel configuration
+ * 	o for SVCs place an X.25 call
+ *
+ * 	Return:		0	connected
+ *			>0	connection in progress
+ *			<0	failure
+ */
+
+static int chan_connect(struct net_device* dev)
+{
+	x25_channel_t* chan = dev->priv;
+	sdla_t* card = chan->card;
+
+	if (chan->common.svc && chan->common.usedby == WANPIPE){
+		if (!chan->addr[0]){
+			printk(KERN_INFO "%s: No Destination Address\n",
+					card->devname);
+			return -EINVAL;	/* no destination address */
+		}
+		printk(KERN_INFO "%s: placing X.25 call to %s ...\n",
+			card->devname, chan->addr);
+
+		if (x25_place_call(card, chan) != CMD_OK)
+			return -EIO;
+
+		set_chan_state(dev, WAN_CONNECTING);
+		return 1;
+	}else{
+		if (x25_get_chan_conf(card, chan) != CMD_OK)
+			return -EIO;
+
+		set_chan_state(dev, WAN_CONNECTED);
+	}
+	return 0;
+}
+
+/*
+ * 	Disconnect logical channel.
+ * 	o if SVC then clear X.25 call
+ */
+
+static int chan_disc(struct net_device* dev)
+{
+	x25_channel_t* chan = dev->priv;
+
+	if (chan->common.svc){ 
+		x25_clear_call(chan->card, chan->common.lcn, 0, 0);
+
+		/* For API we disconnect on clear
+                 * confirmation. 
+                 */
+		if (chan->common.usedby == API)
+			return 0;
+	}
+
+	set_chan_state(dev, WAN_DISCONNECTED);
+	
+	return 0;
+}
+
+/*
+ * 	Set logical channel state.
+ */
+
+static void set_chan_state(struct net_device* dev, int state)
+{
+	x25_channel_t* chan = dev->priv;
+	sdla_t* card = chan->card;
+	unsigned long flags;
+
+	save_flags(flags);
+	cli();
+	if (chan->common.state != state)
+	{
+		switch (state)
+		{
+			case WAN_CONNECTED:
+				if (card->u.x.logging){
+					printk (KERN_INFO 
+						"%s: interface %s connected, lcn %i !\n", 
+						card->devname, dev->name,chan->common.lcn);
+				}
+				*(unsigned short*)dev->dev_addr = htons(chan->common.lcn);
+				chan->i_timeout_sofar = jiffies;
+
+				/* LAPB is PVC Based */
+				if (card->u.x.LAPB_hdlc)
+					chan->common.svc=0;
+				break;
+
+			case WAN_CONNECTING:
+				if (card->u.x.logging){
+					printk (KERN_INFO 
+						"%s: interface %s connecting, lcn %i ...\n", 
+						card->devname, dev->name, chan->common.lcn);
+				}
+				break;
+
+			case WAN_DISCONNECTED:
+				if (card->u.x.logging){
+					printk (KERN_INFO 
+						"%s: interface %s disconnected, lcn %i !\n", 
+						card->devname, dev->name,chan->common.lcn);
+				}
+				atomic_set(&chan->common.disconnect,0);
+				
+				if (chan->common.svc) {
+					*(unsigned short*)dev->dev_addr = 0;
+					card->u.x.svc_to_dev_map[(chan->common.lcn%X25_MAX_CHAN)]=NULL;
+		                	chan->common.lcn = 0;
+				}
+
+				if (chan->transmit_length){
+					chan->transmit_length=0;
+					atomic_set(&chan->common.driver_busy,0);
+					chan->tx_offset=0;
+					if (netif_queue_stopped(dev)){
+						netif_wake_queue(dev);
+					}
+				}
+				atomic_set(&chan->common.command,0);
+				break;
+
+			case WAN_DISCONNECTING:
+				if (card->u.x.logging){
+					printk (KERN_INFO 
+					"\n%s: interface %s disconnecting, lcn %i ...\n", 
+					card->devname, dev->name,chan->common.lcn);
+				}
+				atomic_set(&chan->common.disconnect,0);
+				break;
+		}
+		chan->common.state = state;
+	}
+	chan->state_tick = jiffies;
+	restore_flags(flags);
+}
+
+/*
+ * 	Send packet on a logical channel.
+ *		When this function is called, tx_skb field of the channel data 
+ *		space points to the transmit socket buffer.  When transmission 
+ *		is complete, release socket buffer and reset 'tbusy' flag.
+ *
+ * 	Return:		0	- transmission complete
+ *			1	- busy
+ *
+ * 	Notes:
+ * 	1. If packet length is greater than MTU for this channel, we'll fragment
+ *    	the packet into 'complete sequence' using M-bit.
+ * 	2. When transmission is complete, an event notification should be issued
+ *    	to the router.
+ */
+
+static int chan_send(struct net_device* dev, void* buff, unsigned data_len,
+		     unsigned char tx_intr)
+{
+	x25_channel_t* chan = dev->priv;
+	sdla_t* card = chan->card;
+	TX25Status* status = card->flags;
+	unsigned len=0, qdm=0, res=0, orig_len = 0;
+	void *data;
+
+	/* Check to see if channel is ready */
+	if ((!(status->cflags[chan->ch_idx] & 0x40) && !card->u.x.LAPB_hdlc)  || 
+             !(*card->u.x.hdlc_buf_status & 0x40)){ 
+            
+		if (!tx_intr){
+			setup_for_delayed_transmit (dev, buff, data_len);
+			return 0;
+		}else{
+			/* By returning 0 to tx_intr the packet will be dropped */
+			++card->wandev.stats.tx_dropped;
+			++chan->ifstats.tx_dropped;
+			printk(KERN_INFO "%s: ERROR, Tx intr could not send, dropping %s:\n", 
+				card->devname,dev->name);
+			++chan->if_send_stat.if_send_bfr_not_passed_to_adptr;
+			return 0;
+		}
+	}
+
+	if (chan->common.usedby == API){
+		/* Remove the API Header */
+		x25api_hdr_t *api_data = (x25api_hdr_t *)buff;
+
+		/* Set the qdm bits from the packet header 
+                 * User has the option to set the qdm bits
+                 */
+		qdm = api_data->qdm;
+
+		orig_len = len = data_len - sizeof(x25api_hdr_t);
+		data = (unsigned char*)buff + sizeof(x25api_hdr_t);
+	}else{
+		data = buff;
+		orig_len = len = data_len;
+	}	
+
+	if (tx_intr){
+		/* We are in tx_intr, minus the tx_offset from 
+                 * the total length. The tx_offset part of the
+		 * data has already been sent. Also, move the 
+		 * data pointer to proper offset location.
+                 */
+		len -= chan->tx_offset;
+		data = (unsigned char*)data + chan->tx_offset;
+	}
+		
+	/* Check if the packet length is greater than MTU
+         * If YES: Cut the len to MTU and set the M bit 
+         */
+	if (len > chan->tx_pkt_size && !card->u.x.LAPB_hdlc){
+		len = chan->tx_pkt_size;
+		qdm |= M_BIT;		
+	} 
+
+
+	/* Pass only first three bits of the qdm byte to the send
+         * routine. In case user sets any other bit which might
+         * cause errors. 
+         */
+
+	switch(x25_send(card, chan->common.lcn, (qdm&0x07), len, data)){
+		case 0x00:	/* success */
+			chan->i_timeout_sofar = jiffies;
+
+			dev->trans_start=jiffies;
+			
+			if ((qdm & M_BIT) && !card->u.x.LAPB_hdlc){
+				if (!tx_intr){
+					/* The M bit was set, which means that part of the
+                                         * packet has been sent. Copy the packet into a buffer
+				         * and set the offset to len, so on next tx_inter 
+					 * the packet will be sent using the below offset.
+					 */
+					chan->tx_offset += len;
+
+					++chan->ifstats.tx_packets;
+					chan->ifstats.tx_bytes += len;
+					
+					if (chan->tx_offset < orig_len){
+						setup_for_delayed_transmit (dev, buff, data_len);
+					}
+					res=0;
+				}else{
+					/* We are already in tx_inter, thus data is already
+                                         * in the buffer. Update the offset and wait for
+                                         * next tx_intr. We add on to the offset, since data can
+                                         * be X number of times larger than max data size.
+					 */
+					++chan->ifstats.tx_packets;
+					chan->ifstats.tx_bytes += len;
+					
+					++chan->if_send_stat.if_send_bfr_passed_to_adptr;
+					chan->tx_offset += len;
+
+					/* The user can set the qdm bit as well.
+                                         * If the entire packet was sent and qdm is still
+                                         * set, than it's the user who has set the M bit. In that,
+                                         * case indicate that the packet was send by returning 
+					 * 0 and wait for a new packet. Otherwise, wait for next
+                                         * tx interrupt to send the rest of the packet */
+
+					if (chan->tx_offset < orig_len){
+						res=1;
+					}else{	
+						res=0;
+					}
+				}
+			}else{
+				++chan->ifstats.tx_packets;
+				chan->ifstats.tx_bytes += len;
+				++chan->if_send_stat.if_send_bfr_passed_to_adptr;
+				res=0;
+			}
+			break;
+
+		case 0x33:	/* Tx busy */
+			if (tx_intr){
+				printk(KERN_INFO "%s: Tx_intr: Big Error dropping packet %s\n",
+						card->devname,dev->name);
+				++chan->ifstats.tx_dropped;
+				++card->wandev.stats.tx_dropped;
+				++chan->if_send_stat.if_send_bfr_not_passed_to_adptr;
+				res=0;
+			}else{
+				DBG_PRINTK(KERN_INFO 
+					"%s: Send: Big Error should have tx: storring %s\n",
+						card->devname,dev->name);
+				setup_for_delayed_transmit (dev, buff, data_len);	
+				res=1;
+			}
+			break;
+
+		default:	/* failure */
+			++chan->ifstats.tx_errors;
+			if (tx_intr){
+				printk(KERN_INFO "%s: Tx_intr: Failure to send, dropping %s\n",
+					card->devname,dev->name);
+				++chan->ifstats.tx_dropped;
+				++card->wandev.stats.tx_dropped;
+				++chan->if_send_stat.if_send_bfr_not_passed_to_adptr;
+				res=0;
+			}else{
+				DBG_PRINTK(KERN_INFO "%s: Send: Failure to send !!!, storing %s\n",
+					card->devname,dev->name);			
+				setup_for_delayed_transmit (dev, buff, data_len);
+				res=1;
+			}
+			break;	
+	}
+	return res;
+}
+
+
+/*
+ * 	Parse X.25 call request data and fill x25_call_info_t structure.
+ */
+
+static void parse_call_info (unsigned char* str, x25_call_info_t* info)
+{
+	memset(info, 0, sizeof(x25_call_info_t));
+	for (; *str; ++str)
+	{
+		int i;
+		unsigned char ch;
+
+		if (*str == '-') switch (str[1]) {
+
+			/* Take minus 2 off the maximum size so that 
+                         * last byte is 0. This way we can use string
+                         * manipulaton functions on call information.
+                         */
+
+			case 'd':	/* destination address */
+				for (i = 0; i < (MAX_X25_ADDR_SIZE-2); ++i){
+					ch = str[2+i];
+					if (isspace(ch)) break;
+					info->dest[i] = ch;
+				}
+				break;
+
+			case 's':	/* source address */
+				for (i = 0; i < (MAX_X25_ADDR_SIZE-2); ++i){
+					ch = str[2+i];
+					if (isspace(ch)) break;
+					info->src[i] = ch;
+				}
+				break;
+
+			case 'u':	/* user data */
+				for (i = 0; i < (MAX_X25_DATA_SIZE-2); ++i){
+					ch = str[2+i];
+					if (isspace(ch)) break;
+					info->user[i] = ch; 
+				}
+				info->nuser = i;
+				break;
+
+			case 'f':	/* facilities */
+				for (i = 0; i < (MAX_X25_FACL_SIZE-2); ++i){
+					ch = str[2+i];
+					if (isspace(ch)) break;
+					info->facil[i] = ch;
+				}
+				info->nfacil = i;
+				break;
+		}
+	}
+}
+
+/*
+ * 	Convert line speed in bps to a number used by S502 code.
+ */
+
+static unsigned char bps_to_speed_code (unsigned long bps)
+{
+	unsigned char	number;
+
+	if (bps <= 1200)        number = 0x01;
+	else if (bps <= 2400)   number = 0x02;
+	else if (bps <= 4800)   number = 0x03;
+	else if (bps <= 9600)   number = 0x04;
+	else if (bps <= 19200)  number = 0x05;
+	else if (bps <= 38400)  number = 0x06;
+	else if (bps <= 45000)  number = 0x07;
+	else if (bps <= 56000)  number = 0x08;
+	else if (bps <= 64000)  number = 0x09;
+	else if (bps <= 74000)  number = 0x0A;
+	else if (bps <= 112000) number = 0x0B;
+	else if (bps <= 128000) number = 0x0C;
+	else number = 0x0D;
+
+	return number;
+}
+
+/*
+ * 	Convert decimal string to unsigned integer.
+ * 	If len != 0 then only 'len' characters of the string are converted.
+ */
+
+static unsigned int dec_to_uint (unsigned char* str, int len)
+{
+	unsigned val;
+
+	if (!len) 
+		len = strlen(str);
+
+	for (val = 0; len && is_digit(*str); ++str, --len)
+		val = (val * 10) + (*str - (unsigned)'0');
+	
+	return val;
+}
+
+/*
+ * 	Convert hex string to unsigned integer.
+ * 	If len != 0 then only 'len' characters of the string are conferted.
+ */
+
+static unsigned int hex_to_uint (unsigned char* str, int len)
+{
+	unsigned val, ch;
+
+	if (!len) 
+		len = strlen(str);
+
+	for (val = 0; len; ++str, --len)
+	{
+		ch = *str;
+		if (is_digit(ch))
+			val = (val << 4) + (ch - (unsigned)'0');
+		else if (is_hex_digit(ch))
+			val = (val << 4) + ((ch & 0xDF) - (unsigned)'A' + 10);
+		else break;
+	}
+	return val;
+}
+
+
+static int handle_IPXWAN(unsigned char *sendpacket, char *devname, unsigned char enable_IPX, unsigned long network_number, unsigned short proto)
+{
+	int i;
+
+	if( proto == ETH_P_IPX) {
+		/* It's an IPX packet */
+		if(!enable_IPX) {
+			/* Return 1 so we don't pass it up the stack. */
+			return 1;
+		}
+	} else {
+		/* It's not IPX so pass it up the stack.*/ 
+		return 0;
+	}
+
+	if( sendpacket[16] == 0x90 &&
+	    sendpacket[17] == 0x04)
+	{
+		/* It's IPXWAN  */
+
+		if( sendpacket[2] == 0x02 &&
+		    sendpacket[34] == 0x00)
+		{
+			/* It's a timer request packet */
+			printk(KERN_INFO "%s: Received IPXWAN Timer Request packet\n",devname);
+
+			/* Go through the routing options and answer no to every
+			 * option except Unnumbered RIP/SAP
+			 */
+			for(i = 41; sendpacket[i] == 0x00; i += 5)
+			{
+				/* 0x02 is the option for Unnumbered RIP/SAP */
+				if( sendpacket[i + 4] != 0x02)
+				{
+					sendpacket[i + 1] = 0;
+				}
+			}
+
+			/* Skip over the extended Node ID option */
+			if( sendpacket[i] == 0x04 )
+			{
+				i += 8;
+			}
+
+			/* We also want to turn off all header compression opt. 			 */ 
+			for(; sendpacket[i] == 0x80 ;)
+			{
+				sendpacket[i + 1] = 0;
+				i += (sendpacket[i + 2] << 8) + (sendpacket[i + 3]) + 4;
+			}
+
+			/* Set the packet type to timer response */
+			sendpacket[34] = 0x01;
+
+			printk(KERN_INFO "%s: Sending IPXWAN Timer Response\n",devname);
+		}
+		else if( sendpacket[34] == 0x02 )
+		{
+			/* This is an information request packet */
+			printk(KERN_INFO "%s: Received IPXWAN Information Request packet\n",devname);
+
+			/* Set the packet type to information response */
+			sendpacket[34] = 0x03;
+
+			/* Set the router name */
+			sendpacket[51] = 'X';
+			sendpacket[52] = 'T';
+			sendpacket[53] = 'P';
+			sendpacket[54] = 'I';
+			sendpacket[55] = 'P';
+			sendpacket[56] = 'E';
+			sendpacket[57] = '-';
+			sendpacket[58] = CVHexToAscii(network_number >> 28);
+			sendpacket[59] = CVHexToAscii((network_number & 0x0F000000)>> 24);
+			sendpacket[60] = CVHexToAscii((network_number & 0x00F00000)>> 20);
+			sendpacket[61] = CVHexToAscii((network_number & 0x000F0000)>> 16);
+			sendpacket[62] = CVHexToAscii((network_number & 0x0000F000)>> 12);
+			sendpacket[63] = CVHexToAscii((network_number & 0x00000F00)>> 8);
+			sendpacket[64] = CVHexToAscii((network_number & 0x000000F0)>> 4);
+			sendpacket[65] = CVHexToAscii(network_number & 0x0000000F);
+			for(i = 66; i < 99; i+= 1)
+			{
+				sendpacket[i] = 0;
+			}
+
+			printk(KERN_INFO "%s: Sending IPXWAN Information Response packet\n",devname);
+		}
+		else
+		{
+			printk(KERN_INFO "%s: Unknown IPXWAN packet!\n",devname);
+			return 0;
+		}
+
+		/* Set the WNodeID to our network address */
+		sendpacket[35] = (unsigned char)(network_number >> 24);
+		sendpacket[36] = (unsigned char)((network_number & 0x00FF0000) >> 16);
+		sendpacket[37] = (unsigned char)((network_number & 0x0000FF00) >> 8);
+		sendpacket[38] = (unsigned char)(network_number & 0x000000FF);
+
+		return 1;
+	} else {
+		/*If we get here it's an IPX-data packet, so it'll get passed up the stack.
+		 */
+		/* switch the network numbers */
+		switch_net_numbers(sendpacket, network_number, 1);	
+		return 0;
+	}
+}
+
+/*
+ *  	If incoming is 0 (outgoing)- if the net numbers is ours make it 0
+ *  	if incoming is 1 - if the net number is 0 make it ours 
+ */
+
+static void switch_net_numbers(unsigned char *sendpacket, unsigned long network_number, unsigned char incoming)
+{
+	unsigned long pnetwork_number;
+
+	pnetwork_number = (unsigned long)((sendpacket[6] << 24) + 
+			  (sendpacket[7] << 16) + (sendpacket[8] << 8) + 
+			  sendpacket[9]);
+	
+
+	if (!incoming) {
+		/*If the destination network number is ours, make it 0 */
+		if( pnetwork_number == network_number) {
+			sendpacket[6] = sendpacket[7] = sendpacket[8] = 
+					 sendpacket[9] = 0x00;
+		}
+	} else {
+		/* If the incoming network is 0, make it ours */
+		if( pnetwork_number == 0) {
+			sendpacket[6] = (unsigned char)(network_number >> 24);
+			sendpacket[7] = (unsigned char)((network_number & 
+					 0x00FF0000) >> 16);
+			sendpacket[8] = (unsigned char)((network_number & 
+					 0x0000FF00) >> 8);
+			sendpacket[9] = (unsigned char)(network_number & 
+					 0x000000FF);
+		}
+	}
+
+
+	pnetwork_number = (unsigned long)((sendpacket[18] << 24) + 
+			  (sendpacket[19] << 16) + (sendpacket[20] << 8) + 
+			  sendpacket[21]);
+	
+	
+	if( !incoming ) {
+		/* If the source network is ours, make it 0 */
+		if( pnetwork_number == network_number) {
+			sendpacket[18] = sendpacket[19] = sendpacket[20] = 
+				 sendpacket[21] = 0x00;
+		}
+	} else {
+		/* If the source network is 0, make it ours */
+		if( pnetwork_number == 0 ) {
+			sendpacket[18] = (unsigned char)(network_number >> 24);
+			sendpacket[19] = (unsigned char)((network_number & 
+					 0x00FF0000) >> 16);
+			sendpacket[20] = (unsigned char)((network_number & 
+					 0x0000FF00) >> 8);
+			sendpacket[21] = (unsigned char)(network_number & 
+					 0x000000FF);
+		}
+	}
+} /* switch_net_numbers */
+
+
+
+
+/********************* X25API SPECIFIC FUNCTIONS ****************/
+
+
+/*===============================================================
+ *  find_channel
+ *
+ *	Manages the lcn to device map. It increases performance
+ *      because it eliminates the need to search through the link  
+ *      list for a device which is bounded to a specific lcn.
+ *
+ *===============================================================*/
+
+
+struct net_device *find_channel(sdla_t *card, unsigned lcn)
+{
+	if (card->u.x.LAPB_hdlc){
+
+		return card->wandev.dev;
+
+	}else{
+		/* We don't know whether the incoming lcn
+                 * is a PVC or an SVC channel. But we do know that
+                 * the lcn cannot be for both the PVC and the SVC
+                 * channel.
+
+		 * If the lcn number is greater or equal to 255, 
+                 * take the modulo 255 of that number. We only have
+                 * 255 locations, thus higher numbers must be mapped
+                 * to a number between 0 and 245. 
+
+		 * We must separate pvc's and svc's since two don't
+                 * have to be contiguous.  Meaning pvc's can start
+                 * from 1 to 10 and svc's can start from 256 to 266.
+                 * But 256%255 is 1, i.e. CONFLICT.
+		 */
+
+
+		/* Highest LCN number must be less or equal to 4096 */
+		if ((lcn <= MAX_LCN_NUM) && (lcn > 0)){
+
+			if (lcn < X25_MAX_CHAN){
+				if (card->u.x.svc_to_dev_map[lcn])
+					return card->u.x.svc_to_dev_map[lcn];
+
+				if (card->u.x.pvc_to_dev_map[lcn])
+					return card->u.x.pvc_to_dev_map[lcn];
+			
+			}else{
+				int new_lcn = lcn%X25_MAX_CHAN;
+				if (card->u.x.svc_to_dev_map[new_lcn])
+					return card->u.x.svc_to_dev_map[new_lcn];
+
+				if (card->u.x.pvc_to_dev_map[new_lcn])
+					return card->u.x.pvc_to_dev_map[new_lcn];
+			}
+		}
+		return NULL;
+	}
+}
+
+void bind_lcn_to_dev(sdla_t *card, struct net_device *dev, unsigned lcn)
+{
+	x25_channel_t *chan = dev->priv;
+
+	/* Modulo the lcn number by X25_MAX_CHAN (255)
+	 * because the lcn number can be greater than 255 
+         *
+	 * We need to split svc and pvc since they don't have
+         * to be contigous. 
+	 */
+
+	if (chan->common.svc){
+		card->u.x.svc_to_dev_map[(lcn % X25_MAX_CHAN)] = dev;
+	}else{
+		card->u.x.pvc_to_dev_map[(lcn % X25_MAX_CHAN)] = dev;
+	}
+	chan->common.lcn = lcn;
+}
+
+
+
+/*===============================================================
+ * x25api_bh 
+ *
+ *
+ *==============================================================*/
+
+static void x25api_bh(struct net_device* dev)
+{
+	x25_channel_t* chan = dev->priv;
+	sdla_t* card = chan->card;
+	struct sk_buff *skb;
+
+	if (atomic_read(&chan->bh_buff_used) == 0){
+		printk(KERN_INFO "%s: BH Buffer Empty in BH\n",
+				card->devname);
+		clear_bit(0, &chan->tq_working);
+		return;
+	}
+
+	while (atomic_read(&chan->bh_buff_used)){
+
+		/* If the sock is in the process of unlinking the
+		 * driver from the socket, we must get out. 
+		 * This never happends but is a sanity check. */
+		if (test_bit(0,&chan->common.common_critical)){
+			clear_bit(0, &chan->tq_working);
+			return;
+		}
+		
+		/* If LAPB HDLC, do not drop packets if socket is
+                 * not connected.  Let the buffer fill up and
+                 * turn off rx interrupt */
+		if (card->u.x.LAPB_hdlc){
+			if (chan->common.sk == NULL || chan->common.func == NULL){
+				clear_bit(0, &chan->tq_working);			
+				return;
+			}
+		}
+
+		skb  = ((bh_data_t *)&chan->bh_head[chan->bh_read])->skb;
+
+		if (skb == NULL){
+			printk(KERN_INFO "%s: BH Skb empty for read %i\n",
+					card->devname,chan->bh_read);
+		}else{
+			
+			if (chan->common.sk == NULL || chan->common.func == NULL){
+				printk(KERN_INFO "%s: BH: Socket disconnected, dropping\n",
+						card->devname);
+				dev_kfree_skb_any(skb);
+				x25api_bh_cleanup(dev);
+				++chan->ifstats.rx_dropped;
+				++chan->rx_intr_stat.rx_intr_bfr_not_passed_to_stack;
+				continue;
+			}
+
+
+			if (chan->common.func(skb,dev,chan->common.sk) != 0){
+				/* Sock full cannot send, queue us for another
+                                 * try 
+				 */
+				printk(KERN_INFO "%s: BH: !!! Packet failed to send !!!!! \n",
+						card->devname);
+				atomic_set(&chan->common.receive_block,1);
+				return;
+			}else{
+				x25api_bh_cleanup(dev);
+				++chan->rx_intr_stat.rx_intr_bfr_passed_to_stack;
+			}
+		}
+	}	
+	clear_bit(0, &chan->tq_working);
+
+	return;
+}
+
+/*===============================================================
+ * x25api_bh_cleanup 
+ *
+ *
+ *==============================================================*/
+
+static int x25api_bh_cleanup(struct net_device *dev)
+{
+	x25_channel_t* chan = dev->priv;
+	sdla_t *card = chan->card;
+	TX25Status* status = card->flags;
+
+
+	((bh_data_t *)&chan->bh_head[chan->bh_read])->skb = NULL;
+
+	if (chan->bh_read == MAX_BH_BUFF){
+		chan->bh_read=0;
+	}else{
+		++chan->bh_read;	
+	}
+
+	/* If the Receive interrupt was off, it means
+         * that we filled up our circular buffer. Check    
+         * that we have space in the buffer. If so 
+         * turn the RX interrupt back on. 
+	 */
+	if (!(status->imask & INTR_ON_RX_FRAME)){
+		if (atomic_read(&chan->bh_buff_used) < (MAX_BH_BUFF+1)){
+			printk(KERN_INFO "%s: BH: Turning on the interrupt\n",
+					card->devname);
+			status->imask |= INTR_ON_RX_FRAME;
+		}
+	}	
+
+	atomic_dec(&chan->bh_buff_used);
+	return 0;
+}
+
+
+/*===============================================================
+ * bh_enqueue 
+ *
+ *
+ *==============================================================*/
+
+static int bh_enqueue(struct net_device *dev, struct sk_buff *skb)
+{
+	x25_channel_t* chan = dev->priv;
+	sdla_t *card = chan->card;
+	TX25Status* status = card->flags;
+
+	if (atomic_read(&chan->bh_buff_used) == (MAX_BH_BUFF+1)){
+		printk(KERN_INFO "%s: Bottom half buffer FULL\n",
+				card->devname);
+		return 1; 
+	}
+
+	((bh_data_t *)&chan->bh_head[chan->bh_write])->skb = skb;
+
+	if (chan->bh_write == MAX_BH_BUFF){
+		chan->bh_write=0;
+	}else{
+		++chan->bh_write;
+	}
+
+	atomic_inc(&chan->bh_buff_used);
+
+	if (atomic_read(&chan->bh_buff_used) == (MAX_BH_BUFF+1)){
+		printk(KERN_INFO "%s: Buffer is now full, Turning off RX Intr\n",
+				card->devname);
+		status->imask &= ~INTR_ON_RX_FRAME;
+	}
+
+	return 0;
+}
+
+
+/*===============================================================
+ * timer_intr_cmd_exec
+ *  
+ *	Called by timer interrupt to execute a command
+ *===============================================================*/
+
+static int timer_intr_cmd_exec (sdla_t* card)
+{
+	struct net_device *dev;
+	unsigned char more_to_exec=0;
+	volatile x25_channel_t *chan=NULL;
+	int i=0,bad_cmd=0,err=0;	
+
+	if (card->u.x.cmd_dev == NULL){
+		card->u.x.cmd_dev = card->wandev.dev;
+	}
+
+	dev = card->u.x.cmd_dev;
+
+	for (;;){
+
+		chan = dev->priv;
+		
+		if (atomic_read(&chan->common.command)){ 
+
+			bad_cmd = check_bad_command(card,dev);
+
+			if ((!chan->common.mbox || atomic_read(&chan->common.disconnect)) && 
+			     !bad_cmd){
+
+				/* Socket has died or exited, We must bring the
+                                 * channel down before anybody else tries to 
+                                 * use it */
+				err = channel_disconnect(card,dev);
+			}else{
+			        err = execute_delayed_cmd(card, dev,
+							 (mbox_cmd_t*)chan->common.mbox,
+							  bad_cmd);
+			}
+
+			switch (err){
+
+			case RETURN_RESULT:
+
+				/* Return the result to the socket without
+                                 * delay. NO_WAIT Command */	
+				atomic_set(&chan->common.command,0);
+				if (atomic_read(&card->u.x.command_busy))
+					atomic_set(&card->u.x.command_busy,0);
+
+				send_delayed_cmd_result(card,dev,card->mbox);
+
+				more_to_exec=0;
+				break;
+			case DELAY_RESULT:
+		
+				/* Wait for the remote to respond, before
+                                 * sending the result up to the socket.
+                                 * WAIT command */
+				if (atomic_read(&card->u.x.command_busy))
+					atomic_set(&card->u.x.command_busy,0);
+				
+				atomic_set(&chan->common.command,0);
+				more_to_exec=0;
+				break;
+			default:
+
+				/* If command could not be executed for
+                                 * some reason (i.e return code 0x33 busy)
+                                 * set the more_to_exec bit which will
+                                 * indicate that this command must be exectued
+                                 * again during next timer interrupt 
+				 */
+				more_to_exec=1;
+				if (atomic_read(&card->u.x.command_busy) == 0)
+					atomic_set(&card->u.x.command_busy,1);
+				break;
+			}
+
+			bad_cmd=0;
+
+			/* If flags is set, there are no hdlc buffers,
+                         * thus, wait for the next pass and try the
+                         * same command again. Otherwise, start searching 
+                         * from next device on the next pass. 
+			 */
+			if (!more_to_exec){
+				dev = move_dev_to_next(card,dev);
+			}
+			break;
+		}else{
+			/* This device has nothing to execute,
+                         * go to next. 
+			 */
+			if (atomic_read(&card->u.x.command_busy))
+					atomic_set(&card->u.x.command_busy,0);
+			dev = move_dev_to_next(card,dev);
+		}	
+
+		if (++i == card->u.x.no_dev){
+			if (!more_to_exec){
+				DBG_PRINTK(KERN_INFO "%s: Nothing to execute in Timer\n",
+					card->devname);
+				if (atomic_read(&card->u.x.command_busy)){
+					atomic_set(&card->u.x.command_busy,0);
+				}
+			}
+			break;
+		}
+
+	} //End of FOR
+
+	card->u.x.cmd_dev = dev;
+	
+	if (more_to_exec){
+		/* If more commands are pending, do not turn off timer 
+                 * interrupt */
+		return 1;
+	}else{
+		/* No more commands, turn off timer interrupt */
+		return 0;
+	}	
+}
+
+/*===============================================================
+ * execute_delayed_cmd 
+ *
+ *	Execute an API command which was passed down from the
+ *      sock.  Sock is very limited in which commands it can
+ *      execute.  Wait and No Wait commands are supported.  
+ *      Place Call, Clear Call and Reset wait commands, where
+ *      Accept Call is a no_wait command.
+ *
+ *===============================================================*/
+
+static int execute_delayed_cmd(sdla_t* card, struct net_device *dev,
+			       mbox_cmd_t *usr_cmd, char bad_cmd)
+{
+	TX25Mbox* mbox = card->mbox;
+	int err;
+	x25_channel_t *chan = dev->priv;
+	int delay=RETURN_RESULT;
+
+	if (!(*card->u.x.hdlc_buf_status & 0x40) && !bad_cmd){
+		return TRY_CMD_AGAIN;
+	}
+
+	/* This way a command is guaranteed to be executed for
+         * a specific lcn, the network interface is bound to. */
+	usr_cmd->cmd.lcn = chan->common.lcn;
+	
+
+	/* If channel is pvc, instead of place call
+         * run x25_channel configuration. If running LAPB HDLC
+         * enable communications. 
+         */
+	if ((!chan->common.svc) && (usr_cmd->cmd.command == X25_PLACE_CALL)){
+
+		if (card->u.x.LAPB_hdlc){
+			DBG_PRINTK(KERN_INFO "LAPB: Connecting\n");
+			connect(card);
+			set_chan_state(dev,WAN_CONNECTING);
+			return DELAY_RESULT;
+		}else{
+			DBG_PRINTK(KERN_INFO "%s: PVC is CONNECTING\n",card->devname);
+			if (x25_get_chan_conf(card, chan) == CMD_OK){
+				set_chan_state(dev, WAN_CONNECTED);
+			}else{ 
+				set_chan_state(dev, WAN_DISCONNECTED);
+			}
+			return RETURN_RESULT;
+		}
+	}
+
+	/* Copy the socket mbox command onto the board */
+
+	memcpy(&mbox->cmd, &usr_cmd->cmd, sizeof(TX25Cmd));
+	if (usr_cmd->cmd.length){
+		memcpy(mbox->data, usr_cmd->data, usr_cmd->cmd.length);
+	}
+
+	/* Check if command is bad. We need to copy the cmd into
+         * the buffer regardless since we return the, mbox to
+         * the user */
+	if (bad_cmd){
+		mbox->cmd.result=0x01;
+		return RETURN_RESULT;
+	}
+
+	err = sdla_exec(mbox) ? mbox->cmd.result : CMD_TIMEOUT;
+
+	if (err != CMD_OK && err != X25RES_NOT_READY)
+		x25_error(card, err, usr_cmd->cmd.command, usr_cmd->cmd.lcn);
+
+	if (mbox->cmd.result == X25RES_NOT_READY){
+		return TRY_CMD_AGAIN;
+	}
+
+	switch (mbox->cmd.command){
+
+	case X25_PLACE_CALL:
+		
+		switch (mbox->cmd.result){
+
+		case CMD_OK:
+
+			/* Check if Place call is a wait command or a 
+               	  	 * no wait command */
+			if (atomic_read(&chan->common.command) & 0x80)
+ 				delay=RETURN_RESULT;
+			else
+				delay=DELAY_RESULT;
+		
+
+			DBG_PRINTK(KERN_INFO "\n%s: PLACE CALL Binding dev %s to lcn %i\n",
+					card->devname,dev->name, mbox->cmd.lcn);
+		
+			bind_lcn_to_dev (card, dev, mbox->cmd.lcn);
+			set_chan_state(dev, WAN_CONNECTING);
+			break;
+
+
+		default:
+			delay=RETURN_RESULT;
+			set_chan_state(dev, WAN_DISCONNECTED);
+			break;
+		}
+		break;
+
+	case X25_ACCEPT_CALL: 
+		
+		switch (mbox->cmd.result){
+
+		case CMD_OK:
+
+			DBG_PRINTK(KERN_INFO "\n%s: ACCEPT Binding dev %s to lcn %i\n",
+				card->devname,dev->name,mbox->cmd.lcn);
+
+			bind_lcn_to_dev (card, dev, mbox->cmd.lcn);
+
+			if (x25_get_chan_conf(card, chan) == CMD_OK){
+
+				set_chan_state(dev, WAN_CONNECTED);
+				delay=RETURN_RESULT;
+
+			}else{ 
+				if (x25_clear_call(card, usr_cmd->cmd.lcn, 0, 0) == CMD_OK){
+					/* if clear is successful, wait for clear confirm 
+					 */ 
+					delay=DELAY_RESULT;
+				}else{
+					/* Do not change the state here. If we fail 
+					 * the accept the return code is send up 
+					 *the stack, which will ether retry
+                               	  	 * or clear the call 
+					 */
+					DBG_PRINTK(KERN_INFO 
+						"%s: ACCEPT: STATE MAY BE CURRUPTED 2 !!!!!\n",
+						card->devname);
+					delay=RETURN_RESULT;
+				}
+			}
+			break;
+
+
+		case X25RES_ASYNC_PACKET:
+			delay=TRY_CMD_AGAIN;
+			break;
+
+		default: 
+			DBG_PRINTK(KERN_INFO "%s: ACCEPT FAILED\n",card->devname);
+			if (x25_clear_call(card, usr_cmd->cmd.lcn, 0, 0) == CMD_OK){
+				delay=DELAY_RESULT;
+			}else{
+				/* Do not change the state here. If we fail the accept. The
+                                 * return code is send up the stack, which will ether retry
+                                 * or clear the call */
+				DBG_PRINTK(KERN_INFO 
+					"%s: ACCEPT: STATE MAY BE CORRUPTED 1 !!!!!\n",
+						card->devname);
+				delay=RETURN_RESULT;
+			}
+		}
+		break;
+
+	case X25_CLEAR_CALL:
+
+		switch (mbox->cmd.result){
+
+		case CMD_OK:
+			DBG_PRINTK(KERN_INFO 
+					"CALL CLEAR OK: Dev %s Mbox Lcn %i  Chan Lcn %i\n",
+					dev->name,mbox->cmd.lcn,chan->common.lcn);
+			set_chan_state(dev, WAN_DISCONNECTING);
+			delay = DELAY_RESULT;
+			break;
+
+		case X25RES_CHANNEL_IN_USE:
+		case X25RES_ASYNC_PACKET:
+			delay = TRY_CMD_AGAIN;
+			break;
+			
+		case X25RES_LINK_NOT_IN_ABM:
+		case X25RES_INVAL_LCN:
+		case X25RES_INVAL_STATE:
+			set_chan_state(dev, WAN_DISCONNECTED);
+			delay = RETURN_RESULT;
+			break;
+		
+		default:
+			/* If command did not execute because of user
+                         * fault, do not change the state. This will
+                         * signal the socket that clear command failed.
+                         * User can retry or close the socket.
+                         * When socket gets killed, it will set the 
+                         * chan->disconnect which will signal
+                         * driver to clear the call */
+			printk(KERN_INFO "%s: Clear Command Failed, Rc %x\n",
+				card->devname,mbox->cmd.command); 
+			delay = RETURN_RESULT;
+		}
+		break;
+	}	
+
+	return delay;
+}
+
+/*===============================================================
+ * api_incoming_call 
+ *
+ *	Pass an incoming call request up the listening
+ *      sock.  If the API sock is not listening reject the
+ *      call.
+ *
+ *===============================================================*/
+
+static int api_incoming_call (sdla_t* card, TX25Mbox *mbox, int lcn)
+{
+	struct sk_buff *skb;
+	int len = sizeof(TX25Cmd)+mbox->cmd.length;
+
+	if (alloc_and_init_skb_buf(card, &skb, len)){
+		printk(KERN_INFO "%s: API incoming call, no memory\n",card->devname);
+		return 1;
+	}
+
+	memcpy(skb_put(skb,len),&mbox->cmd,len);
+
+	skb->mac.raw = skb->data;
+	skb->protocol = htons(X25_PROT);
+	skb->pkt_type = WAN_PACKET_ASYNC;
+
+	if (card->func(skb,card->sk) < 0){
+		printk(KERN_INFO "%s: MAJOR ERROR: Failed to send up place call \n",card->devname);
+                dev_kfree_skb_any(skb);
+		return 1;
+	}
+
+	return 0;
+}
+
+/*===============================================================
+ * send_delayed_cmd_result
+ *
+ *	Wait commands like PLEACE CALL or CLEAR CALL must wait
+ *      until the result arrives. This function passes
+ *      the result to a waiting sock. 
+ *
+ *===============================================================*/
+static void send_delayed_cmd_result(sdla_t *card, struct net_device *dev,
+				    TX25Mbox* mbox)
+{
+	x25_channel_t *chan = dev->priv;
+	mbox_cmd_t *usr_cmd = (mbox_cmd_t *)chan->common.mbox;
+	struct sk_buff *skb;
+	int len=sizeof(unsigned char);
+
+	atomic_set(&chan->common.command,0);
+
+	/* If the sock is in the process of unlinking the
+	 * driver from the socket, we must get out. 
+	 * This never happends but is a sanity check. */
+	if (test_bit(0,&chan->common.common_critical)){
+		return;
+	}
+
+	if (!usr_cmd || !chan->common.sk || !chan->common.func){
+		DBG_PRINTK(KERN_INFO "Delay result: Sock not bounded sk: %u, func: %u, mbox: %u\n",
+			(unsigned int)chan->common.sk,
+			(unsigned int)chan->common.func,
+			(unsigned int)usr_cmd); 
+		return;
+	}
+
+	memcpy(&usr_cmd->cmd, &mbox->cmd, sizeof(TX25Cmd)); 
+	if (mbox->cmd.length > 0){
+		memcpy(usr_cmd->data, mbox->data, mbox->cmd.length);
+	}
+
+	if (alloc_and_init_skb_buf(card,&skb,len)){
+		printk(KERN_INFO "Delay result: No sock buffers\n");
+		return;
+	}
+
+	memcpy(skb_put(skb,len),&mbox->cmd.command,len);
+	
+	skb->mac.raw = skb->data;
+	skb->pkt_type = WAN_PACKET_CMD;
+			
+	chan->common.func(skb,dev,chan->common.sk);
+}
+
+/*===============================================================
+ * clear_confirm_event
+ *
+ * 	Pass the clear confirmation event up the sock. The
+ *      API will disconnect only after the clear confirmation
+ *      has been received. 
+ *
+ *      Depending on the state, clear confirmation could 
+ *      be an OOB event, or a result of an API command.
+ *===============================================================*/
+
+static int clear_confirm_event (sdla_t *card, TX25Mbox* mb)
+{
+	struct net_device *dev;
+	x25_channel_t *chan;
+	unsigned char old_state;	
+
+	dev = find_channel(card,mb->cmd.lcn);
+	if (!dev){
+		DBG_PRINTK(KERN_INFO "%s: *** GOT CLEAR BUT NO DEV %i\n",
+				card->devname,mb->cmd.lcn);
+		return 0;
+	}
+
+	chan=dev->priv;
+	DBG_PRINTK(KERN_INFO "%s: GOT CLEAR CONFIRM %s:  Mbox lcn %i  Chan lcn %i\n",
+			card->devname, dev->name, mb->cmd.lcn, chan->common.lcn);
+
+	/* If not API fall through to default. 
+	 * If API, send the result to a waiting
+         * socket.
+	 */
+	
+	old_state = chan->common.state;
+	set_chan_state(dev, WAN_DISCONNECTED);
+
+	if (chan->common.usedby == API){
+		switch (old_state) {
+
+		case WAN_DISCONNECTING:
+		case WAN_CONNECTING:
+			send_delayed_cmd_result(card,dev,mb);
+			break;
+		case WAN_CONNECTED:
+			send_oob_msg(card,dev,mb);
+			break;
+		}
+		return 1;
+	}
+
+	return 0;
+}
+
+/*===============================================================
+ * send_oob_msg
+ *
+ *    Construct an NEM Message and pass it up the connected
+ *    sock. If the sock is not bounded discard the NEM.
+ *
+ *===============================================================*/
+
+static void send_oob_msg(sdla_t *card, struct net_device *dev, TX25Mbox *mbox)
+{
+	x25_channel_t *chan = dev->priv;
+	mbox_cmd_t *usr_cmd = (mbox_cmd_t *)chan->common.mbox;
+	struct sk_buff *skb;
+	int len=sizeof(x25api_hdr_t)+mbox->cmd.length;
+	x25api_t *api_hdr;
+
+	/* If the sock is in the process of unlinking the
+	 * driver from the socket, we must get out. 
+	 * This never happends but is a sanity check. */
+	if (test_bit(0,&chan->common.common_critical)){
+		return;
+	}
+
+	if (!usr_cmd || !chan->common.sk || !chan->common.func){
+		DBG_PRINTK(KERN_INFO "OOB MSG: Sock not bounded\n"); 
+		return;
+	}
+
+	memcpy(&usr_cmd->cmd, &mbox->cmd, sizeof(TX25Cmd)); 
+	if (mbox->cmd.length > 0){
+		memcpy(usr_cmd->data, mbox->data, mbox->cmd.length);
+	}
+
+	if (alloc_and_init_skb_buf(card,&skb,len)){
+		printk(KERN_INFO "%s: OOB MSG: No sock buffers\n",card->devname);
+		return;
+	}
+
+	api_hdr = (x25api_t*)skb_put(skb,len); 
+	api_hdr->hdr.pktType = mbox->cmd.pktType & 0x7F;
+	api_hdr->hdr.qdm     = mbox->cmd.qdm;
+	api_hdr->hdr.cause   = mbox->cmd.cause;
+	api_hdr->hdr.diagn   = mbox->cmd.diagn;
+	api_hdr->hdr.length  = mbox->cmd.length;
+	api_hdr->hdr.result  = mbox->cmd.result;
+	api_hdr->hdr.lcn     = mbox->cmd.lcn;
+
+	if (mbox->cmd.length > 0){
+		memcpy(api_hdr->data,mbox->data,mbox->cmd.length);
+	}
+	
+	skb->mac.raw = skb->data;
+	skb->pkt_type = WAN_PACKET_ERR;
+			
+	if (chan->common.func(skb,dev,chan->common.sk) < 0){
+		if (bh_enqueue(dev,skb)){
+			printk(KERN_INFO "%s: Dropping OOB MSG\n",card->devname);
+                	dev_kfree_skb_any(skb);
+		}
+	}
+
+	DBG_PRINTK(KERN_INFO "%s: OOB MSG OK, %s, lcn %i\n",
+			card->devname, dev->name, mbox->cmd.lcn);
+}	
+
+/*===============================================================
+ *  alloc_and_init_skb_buf 
+ *
+ *	Allocate and initialize an skb buffer. 
+ *
+ *===============================================================*/
+
+static int alloc_and_init_skb_buf (sdla_t *card, struct sk_buff **skb, int len)
+{
+	struct sk_buff *new_skb = *skb;
+
+	new_skb = dev_alloc_skb(len + X25_HRDHDR_SZ);
+	if (new_skb == NULL){
+		printk(KERN_INFO "%s: no socket buffers available!\n",
+			card->devname);
+		return 1;
+	}
+
+	if (skb_tailroom(new_skb) < len){
+		/* No room for the packet. Call off the whole thing! */
+                dev_kfree_skb_any(new_skb);
+		printk(KERN_INFO "%s: Listen: unexpectedly long packet sequence\n"
+			,card->devname);
+		*skb = NULL;
+		return 1;
+	}
+
+	*skb = new_skb;
+	return 0;
+
+}
+
+/*===============================================================
+ *  api_oob_event 
+ *
+ *	Send an OOB event up to the sock 
+ *
+ *===============================================================*/
+
+static void api_oob_event (sdla_t *card,TX25Mbox *mbox)
+{
+	struct net_device *dev = find_channel(card, mbox->cmd.lcn);
+	x25_channel_t *chan;
+
+	if (!dev)
+		return;
+
+	chan=dev->priv;
+
+	if (chan->common.usedby == API)
+		send_oob_msg(card,dev,mbox);
+	
+}
+
+
+
+
+static int channel_disconnect(sdla_t* card, struct net_device *dev)
+{
+
+	int err;
+	x25_channel_t *chan = dev->priv;
+
+	DBG_PRINTK(KERN_INFO "%s: TIMER: %s, Device down disconnecting\n",
+				card->devname,dev->name);
+
+	if (chan->common.svc){
+		err = x25_clear_call(card,chan->common.lcn,0,0);
+	}else{
+		/* If channel is PVC or LAPB HDLC, there is no call
+                 * to be cleared, thus drop down to the default
+                 * area 
+	         */
+		err = 1;
+	}
+
+	switch (err){
+	
+		case X25RES_CHANNEL_IN_USE:	
+		case X25RES_NOT_READY:
+			err = TRY_CMD_AGAIN;
+			break;
+		case CMD_OK:
+			DBG_PRINTK(KERN_INFO "CALL CLEAR OK: Dev %s Chan Lcn %i\n",
+						dev->name,chan->common.lcn);
+
+			set_chan_state(dev,WAN_DISCONNECTING);
+			atomic_set(&chan->common.command,0);
+			err = DELAY_RESULT;
+			break;
+		default:
+			/* If LAPB HDLC protocol, bring the whole link down
+                         * once the application terminates 
+			 */
+
+			set_chan_state(dev,WAN_DISCONNECTED);
+
+			if (card->u.x.LAPB_hdlc){
+				DBG_PRINTK(KERN_INFO "LAPB: Disconnecting Link\n");
+				hdlc_link_down (card);
+			}
+			atomic_set(&chan->common.command,0);
+			err = RETURN_RESULT;
+			break;
+	}
+
+	return err;
+}
+
+static void hdlc_link_down (sdla_t *card)
+{
+	TX25Mbox* mbox = card->mbox;
+	int retry = 5;
+	int err=0;
+
+	do {
+		memset(mbox,0,sizeof(TX25Mbox));
+		mbox->cmd.command = X25_HDLC_LINK_DISC;
+		mbox->cmd.length = 1;
+		mbox->data[0]=0;
+		err = sdla_exec(mbox) ? mbox->cmd.result : CMD_TIMEOUT;
+
+	} while (err && retry-- && x25_error(card, err, X25_HDLC_LINK_DISC, 0));
+
+	if (err)
+		printk(KERN_INFO "%s: Hdlc Link Down Failed %x\n",card->devname,err);
+
+	disconnect (card);
+	
+}
+
+static int check_bad_command(sdla_t* card, struct net_device *dev)
+{
+	x25_channel_t *chan = dev->priv;
+	int bad_cmd = 0;
+
+	switch (atomic_read(&chan->common.command)&0x7F){
+
+		case X25_PLACE_CALL:
+			if (chan->common.state != WAN_DISCONNECTED)
+				bad_cmd=1;
+			break;
+		case X25_CLEAR_CALL:
+			if (chan->common.state == WAN_DISCONNECTED)
+				bad_cmd=1;
+			break;
+		case X25_ACCEPT_CALL:
+			if (chan->common.state != WAN_CONNECTING)
+				bad_cmd=1;
+			break;
+		case X25_RESET:
+			if (chan->common.state != WAN_CONNECTED)
+				bad_cmd=1;
+			break;
+		default:
+			bad_cmd=1;
+			break;
+	}
+
+	if (bad_cmd){
+		printk(KERN_INFO "%s: Invalid State, BAD Command %x, dev %s, lcn %i, st %i\n", 
+			card->devname,atomic_read(&chan->common.command),dev->name, 
+			chan->common.lcn, chan->common.state);
+	}
+
+	return bad_cmd;
+}
+
+
+
+/*************************** XPIPEMON FUNCTIONS **************************/
+
+/*==============================================================================
+ * Process UDP call of type XPIPE
+ */
+
+static int process_udp_mgmt_pkt(sdla_t *card)
+{
+	int            c_retry = MAX_CMD_RETRY;
+	unsigned int   len;
+	struct sk_buff *new_skb;
+	TX25Mbox       *mbox = card->mbox;
+	int            err;
+	int            udp_mgmt_req_valid = 1;
+	struct net_device *dev;
+        x25_channel_t  *chan;
+	unsigned short lcn;
+	struct timeval tv;
+	
+
+	x25_udp_pkt_t *x25_udp_pkt;
+	x25_udp_pkt = (x25_udp_pkt_t *)card->u.x.udp_pkt_data;
+
+	dev = card->u.x.udp_dev;
+	chan = dev->priv;
+	lcn = chan->common.lcn;
+
+	switch(x25_udp_pkt->cblock.command) {
+            
+		/* XPIPE_ENABLE_TRACE */
+		case XPIPE_ENABLE_TRACING:
+
+		/* XPIPE_GET_TRACE_INFO */
+		case XPIPE_GET_TRACE_INFO:
+ 
+		/* SET FT1 MODE */
+		case XPIPE_SET_FT1_MODE:
+           
+			if(card->u.x.udp_pkt_src == UDP_PKT_FRM_NETWORK) {
+                    		++chan->pipe_mgmt_stat.UDP_PIPE_mgmt_direction_err;
+				udp_mgmt_req_valid = 0;
+				break;
+			}
+
+		/* XPIPE_FT1_READ_STATUS */
+		case XPIPE_FT1_READ_STATUS:
+
+		/* FT1 MONITOR STATUS */
+		case XPIPE_FT1_STATUS_CTRL:
+			if(card->hw.fwid !=  SFID_X25_508) {
+				++chan->pipe_mgmt_stat.UDP_PIPE_mgmt_adptr_type_err;
+				udp_mgmt_req_valid = 0;
+				break;
+			}
+		default:
+			break;
+       	}
+
+	if(!udp_mgmt_req_valid) {
+           	/* set length to 0 */
+		x25_udp_pkt->cblock.length = 0;
+		/* set return code */
+		x25_udp_pkt->cblock.result = (card->hw.fwid != SFID_X25_508) ? 0x1F : 0xCD;
+		
+	} else {   
+        
+		switch (x25_udp_pkt->cblock.command) {
+    
+	
+		case XPIPE_FLUSH_DRIVER_STATS:
+			init_x25_channel_struct(chan);
+			init_global_statistics(card);
+			mbox->cmd.length = 0;
+			break;
+
+
+		case XPIPE_DRIVER_STAT_IFSEND:
+			memcpy(x25_udp_pkt->data, &chan->if_send_stat, sizeof(if_send_stat_t));
+			mbox->cmd.length = sizeof(if_send_stat_t);
+			x25_udp_pkt->cblock.length =  mbox->cmd.length;	
+			break;
+	
+		case XPIPE_DRIVER_STAT_INTR:
+			memcpy(&x25_udp_pkt->data[0], &card->statistics, sizeof(global_stats_t));
+                        memcpy(&x25_udp_pkt->data[sizeof(global_stats_t)],
+                                &chan->rx_intr_stat, sizeof(rx_intr_stat_t));
+			
+			mbox->cmd.length = sizeof(global_stats_t) +
+					sizeof(rx_intr_stat_t);
+			x25_udp_pkt->cblock.length =  mbox->cmd.length;
+			break;
+
+		case XPIPE_DRIVER_STAT_GEN:
+                        memcpy(x25_udp_pkt->data,
+                                &chan->pipe_mgmt_stat.UDP_PIPE_mgmt_kmalloc_err,
+                                sizeof(pipe_mgmt_stat_t));
+
+                        memcpy(&x25_udp_pkt->data[sizeof(pipe_mgmt_stat_t)],
+                               &card->statistics, sizeof(global_stats_t));
+
+                        x25_udp_pkt->cblock.result = 0;
+                        x25_udp_pkt->cblock.length = sizeof(global_stats_t)+
+                                                     sizeof(rx_intr_stat_t);
+                        mbox->cmd.length = x25_udp_pkt->cblock.length;
+                        break;
+
+		case XPIPE_ROUTER_UP_TIME:
+			do_gettimeofday(&tv);
+			chan->router_up_time = tv.tv_sec - chan->router_start_time;
+    	                *(unsigned long *)&x25_udp_pkt->data = chan->router_up_time;	
+			x25_udp_pkt->cblock.length = mbox->cmd.length = 4;
+			x25_udp_pkt->cblock.result = 0;
+			break;
+	
+		default :
+
+			do {
+				memcpy(&mbox->cmd, &x25_udp_pkt->cblock.command, sizeof(TX25Cmd));
+				if(mbox->cmd.length){ 
+					memcpy(&mbox->data, 
+					       (char *)x25_udp_pkt->data, 
+					       mbox->cmd.length);
+				}	
+		
+				err = sdla_exec(mbox) ? mbox->cmd.result : CMD_TIMEOUT;
+			} while (err && c_retry-- && x25_error(card, err, mbox->cmd.command, 0));
+
+
+			if ( err == CMD_OK || 
+			    (err == 1 && 
+			     (mbox->cmd.command == 0x06 || 
+			      mbox->cmd.command == 0x16)  ) ){
+
+				++chan->pipe_mgmt_stat.UDP_PIPE_mgmt_adptr_cmnd_OK;
+			} else {
+				++chan->pipe_mgmt_stat.UDP_PIPE_mgmt_adptr_cmnd_timeout;
+			}
+
+			  /* copy the result back to our buffer */
+			memcpy(&x25_udp_pkt->cblock.command, &mbox->cmd, sizeof(TX25Cmd));
+
+      	         	if(mbox->cmd.length) {
+        	               memcpy(&x25_udp_pkt->data, &mbox->data, mbox->cmd.length);
+			}
+			break;
+
+		} //switch
+
+        }
+    
+        /* Fill UDP TTL */
+
+	x25_udp_pkt->ip_pkt.ttl = card->wandev.ttl;
+        len = reply_udp(card->u.x.udp_pkt_data, mbox->cmd.length);
+
+
+        if(card->u.x.udp_pkt_src == UDP_PKT_FRM_NETWORK) {
+		
+		err = x25_send(card, lcn, 0, len, card->u.x.udp_pkt_data);
+		if (!err) 
+			++chan->pipe_mgmt_stat.UDP_PIPE_mgmt_adptr_send_passed;
+		else
+			++chan->pipe_mgmt_stat.UDP_PIPE_mgmt_adptr_send_failed;
+	
+	} else {
+
+		/* Allocate socket buffer */
+		if((new_skb = dev_alloc_skb(len)) != NULL) {
+			void *buf;
+
+			/* copy data into new_skb */
+			buf = skb_put(new_skb, len);
+			memcpy(buf, card->u.x.udp_pkt_data, len);
+        
+			/* Decapsulate packet and pass it up the protocol 
+			   stack */
+			new_skb->dev = dev;
+	
+			if (chan->common.usedby == API)
+                        	new_skb->protocol = htons(X25_PROT);
+			else 
+				new_skb->protocol = htons(ETH_P_IP);
+	
+                        new_skb->mac.raw = new_skb->data;
+
+			netif_rx(new_skb);
+			++chan->pipe_mgmt_stat.UDP_PIPE_mgmt_passed_to_stack;
+            	
+		} else {
+			++chan->pipe_mgmt_stat.UDP_PIPE_mgmt_no_socket;
+			printk(KERN_INFO 
+			"%s: UDP mgmt cmnd, no socket buffers available!\n", 
+			card->devname);
+            	}
+        }
+
+	card->u.x.udp_pkt_lgth = 0;
+
+	return 1;
+}
+
+
+/*==============================================================================
+ * Determine what type of UDP call it is. DRVSTATS or XPIPE8ND ?
+ */
+static int udp_pkt_type( struct sk_buff *skb, sdla_t* card )
+{
+	x25_udp_pkt_t *x25_udp_pkt = (x25_udp_pkt_t *)skb->data;
+
+        if((x25_udp_pkt->ip_pkt.protocol == UDPMGMT_UDP_PROTOCOL) &&
+		(x25_udp_pkt->ip_pkt.ver_inet_hdr_length == 0x45) &&
+		(x25_udp_pkt->udp_pkt.udp_dst_port == ntohs(card->wandev.udp_port)) &&
+		(x25_udp_pkt->wp_mgmt.request_reply == UDPMGMT_REQUEST)) {
+
+                        if(!strncmp(x25_udp_pkt->wp_mgmt.signature,
+                                UDPMGMT_XPIPE_SIGNATURE, 8)){
+                                return UDP_XPIPE_TYPE;
+			}else{
+				printk(KERN_INFO "%s: UDP Packet, Failed Signature !\n",
+					card->devname);
+			}
+	}
+
+        return UDP_INVALID_TYPE;
+}
+
+
+/*============================================================================
+ * Reply to UDP Management system.
+ * Return nothing.
+ */
+static int reply_udp( unsigned char *data, unsigned int mbox_len ) 
+{
+	unsigned short len, udp_length, temp, ip_length;
+	unsigned long ip_temp;
+	int even_bound = 0;
+
+  
+	x25_udp_pkt_t *x25_udp_pkt = (x25_udp_pkt_t *)data; 
+
+	/* Set length of packet */
+	len = sizeof(ip_pkt_t)+ 
+	      sizeof(udp_pkt_t)+
+	      sizeof(wp_mgmt_t)+
+	      sizeof(cblock_t)+
+	      mbox_len;
+ 
+
+	/* fill in UDP reply */
+	x25_udp_pkt->wp_mgmt.request_reply = UDPMGMT_REPLY;
+  
+	/* fill in UDP length */
+	udp_length = sizeof(udp_pkt_t)+ 
+		     sizeof(wp_mgmt_t)+
+		     sizeof(cblock_t)+
+		     mbox_len; 
+
+
+	/* put it on an even boundary */
+	if ( udp_length & 0x0001 ) {
+		udp_length += 1;
+		len += 1;
+		even_bound = 1;
+	}
+
+	temp = (udp_length<<8)|(udp_length>>8);
+	x25_udp_pkt->udp_pkt.udp_length = temp;
+	 
+	/* swap UDP ports */
+	temp = x25_udp_pkt->udp_pkt.udp_src_port;
+	x25_udp_pkt->udp_pkt.udp_src_port = 
+			x25_udp_pkt->udp_pkt.udp_dst_port; 
+	x25_udp_pkt->udp_pkt.udp_dst_port = temp;
+
+
+
+	/* add UDP pseudo header */
+	temp = 0x1100;
+	*((unsigned short *)
+		(x25_udp_pkt->data+mbox_len+even_bound)) = temp;	
+	temp = (udp_length<<8)|(udp_length>>8);
+	*((unsigned short *)
+		(x25_udp_pkt->data+mbox_len+even_bound+2)) = temp;
+		 
+	/* calculate UDP checksum */
+	x25_udp_pkt->udp_pkt.udp_checksum = 0;
+
+	x25_udp_pkt->udp_pkt.udp_checksum = 
+		calc_checksum(&data[UDP_OFFSET], udp_length+UDP_OFFSET);
+
+	/* fill in IP length */
+	ip_length = len;
+	temp = (ip_length<<8)|(ip_length>>8);
+	x25_udp_pkt->ip_pkt.total_length = temp;
+  
+	/* swap IP addresses */
+	ip_temp = x25_udp_pkt->ip_pkt.ip_src_address;
+	x25_udp_pkt->ip_pkt.ip_src_address = 
+				x25_udp_pkt->ip_pkt.ip_dst_address;
+	x25_udp_pkt->ip_pkt.ip_dst_address = ip_temp;
+
+		 
+	/* fill in IP checksum */
+	x25_udp_pkt->ip_pkt.hdr_checksum = 0;
+	x25_udp_pkt->ip_pkt.hdr_checksum = calc_checksum(data, sizeof(ip_pkt_t));
+
+	return len;
+} /* reply_udp */
+
+unsigned short calc_checksum (char *data, int len)
+{
+	unsigned short temp; 
+	unsigned long sum=0;
+	int i;
+
+	for( i = 0; i <len; i+=2 ) {
+		memcpy(&temp,&data[i],2);
+		sum += (unsigned long)temp;
+	}
+
+	while (sum >> 16 ) {
+		sum = (sum & 0xffffUL) + (sum >> 16);
+	}
+
+	temp = (unsigned short)sum;
+	temp = ~temp;
+
+	if( temp == 0 ) 
+		temp = 0xffff;
+
+	return temp;	
+}
+
+/*=============================================================================
+ * Store a UDP management packet for later processing.
+ */
+
+static int store_udp_mgmt_pkt(int udp_type, char udp_pkt_src, sdla_t* card,
+			      struct net_device *dev, struct sk_buff *skb,
+			      int lcn)
+{
+        int udp_pkt_stored = 0;
+
+        if(!card->u.x.udp_pkt_lgth && (skb->len <= MAX_LGTH_UDP_MGNT_PKT)){
+                card->u.x.udp_pkt_lgth = skb->len;
+                card->u.x.udp_type = udp_type;
+                card->u.x.udp_pkt_src = udp_pkt_src;
+                card->u.x.udp_lcn = lcn;
+		card->u.x.udp_dev = dev;
+                memcpy(card->u.x.udp_pkt_data, skb->data, skb->len);
+                card->u.x.timer_int_enabled |= TMR_INT_ENABLED_UDP_PKT;
+                udp_pkt_stored = 1;
+
+        }else{
+                printk(KERN_INFO "%s: ERROR: UDP packet not stored for LCN %d\n", 
+							card->devname,lcn);
+	}
+
+        if(udp_pkt_src == UDP_PKT_FRM_STACK){
+                dev_kfree_skb_any(skb);
+	}else{
+                dev_kfree_skb_any(skb);
+	}
+
+        return(udp_pkt_stored);
+}
+
+
+
+/*=============================================================================
+ * Initial the ppp_private_area structure.
+ */
+static void init_x25_channel_struct( x25_channel_t *chan )
+{
+	memset(&chan->if_send_stat.if_send_entry,0,sizeof(if_send_stat_t));
+	memset(&chan->rx_intr_stat.rx_intr_no_socket,0,sizeof(rx_intr_stat_t));
+	memset(&chan->pipe_mgmt_stat.UDP_PIPE_mgmt_kmalloc_err,0,sizeof(pipe_mgmt_stat_t));
+}
+
+/*============================================================================
+ * Initialize Global Statistics
+ */
+static void init_global_statistics( sdla_t *card )
+{
+	memset(&card->statistics.isr_entry,0,sizeof(global_stats_t));
+}
+
+
+/*===============================================================
+ * SMP Support
+ * ==============================================================*/
+
+static void S508_S514_lock(sdla_t *card, unsigned long *smp_flags)
+{
+	spin_lock_irqsave(&card->wandev.lock, *smp_flags);
+}
+static void S508_S514_unlock(sdla_t *card, unsigned long *smp_flags)
+{
+	spin_unlock_irqrestore(&card->wandev.lock, *smp_flags);
+}
+
+/*===============================================================
+ * x25_timer_routine
+ *
+ * 	A more efficient polling routine.  Each half a second
+ * 	queue a polling task. We want to do the polling in a 
+ * 	task not timer, because timer runs in interrupt time.
+ *
+ * 	FIXME Polling should be rethinked.
+ *==============================================================*/
+
+static void x25_timer_routine(unsigned long data)
+{
+	sdla_t *card = (sdla_t*)data;
+
+	if (!card->wandev.dev){
+		printk(KERN_INFO "%s: Stopping the X25 Poll Timer: No Dev.\n",
+				card->devname);
+		return;
+	}
+
+	if (card->open_cnt != card->u.x.num_of_ch){
+		printk(KERN_INFO "%s: Stopping the X25 Poll Timer: Interface down.\n",
+				card->devname);
+		return;
+	}
+
+	if (test_bit(PERI_CRIT,&card->wandev.critical)){
+		printk(KERN_INFO "%s: Stopping the X25 Poll Timer: Shutting down.\n",
+				card->devname);
+		return;
+	}
+	
+	if (!test_and_set_bit(POLL_CRIT,&card->wandev.critical)){
+		trigger_x25_poll(card);
+	}
+	
+	card->u.x.x25_timer.expires=jiffies+(HZ>>1);
+	add_timer(&card->u.x.x25_timer);
+	return;
+}
+
+void disable_comm_shutdown(sdla_t *card)
+{
+	TX25Mbox* mbox = card->mbox;
+	int err;
+
+	/* Turn of interrutps */
+	mbox->data[0] = 0;
+	if (card->hw.fwid == SFID_X25_508){
+		mbox->data[1] = card->hw.irq;
+		mbox->data[2] = 2;
+		mbox->cmd.length = 3;
+	}else {
+	 	mbox->cmd.length  = 1;
+	}
+	mbox->cmd.command = X25_SET_INTERRUPT_MODE;
+	err = sdla_exec(mbox) ? mbox->cmd.result : CMD_TIMEOUT;
+	if (err)
+		printk(KERN_INFO "INTERRUPT OFF FAIED %x\n",err);
+
+	/* Bring down HDLC */
+	mbox->cmd.command = X25_HDLC_LINK_CLOSE;
+	mbox->cmd.length  = 0;
+	err = sdla_exec(mbox) ? mbox->cmd.result : CMD_TIMEOUT;
+	if (err)
+		printk(KERN_INFO "LINK CLOSED FAILED %x\n",err);
+
+
+	/* Brind down DTR */
+	mbox->data[0] = 0;
+	mbox->data[2] = 0;
+	mbox->data[1] = 0x01;
+	mbox->cmd.length  = 3;
+	mbox->cmd.command = X25_SET_GLOBAL_VARS;
+	err = sdla_exec(mbox) ? mbox->cmd.result : CMD_TIMEOUT;
+	if (err)
+		printk(KERN_INFO "DTR DOWN FAILED %x\n",err);
+
+}
+
+MODULE_LICENSE("GPL");
+
+/****** End *****************************************************************/
diff --git a/drivers/net/wan/sdladrv.c b/drivers/net/wan/sdladrv.c
new file mode 100644
index 0000000..c8bc6da
--- /dev/null
+++ b/drivers/net/wan/sdladrv.c
@@ -0,0 +1,2318 @@
+/*****************************************************************************
+* sdladrv.c	SDLA Support Module.  Main module.
+*
+*		This module is a library of common hardware-specific functions
+*		used by all Sangoma drivers.
+*
+* Author:	Gideon Hack	
+*
+* Copyright:	(c) 1995-2000 Sangoma Technologies Inc.
+*
+*		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.
+* ============================================================================
+* Mar 20, 2001  Nenad Corbic	Added the auto_pci_cfg filed, to support
+*                               the PCISLOT #0. 
+* Apr 04, 2000  Nenad Corbic	Fixed the auto memory detection code.
+*                               The memory test at address 0xC8000.
+* Mar 09, 2000  Nenad Corbic 	Added Gideon's Bug Fix: clear pci
+*                               interrupt flags on initial load.
+* Jun 02, 1999  Gideon Hack     Added support for the S514 adapter.
+*				Updates for Linux 2.2.X kernels.	
+* Sep 17, 1998	Jaspreet Singh	Updates for linux 2.2.X kernels
+* Dec 20, 1996	Gene Kozin	Version 3.0.0. Complete overhaul.
+* Jul 12, 1996	Gene Kozin	Changes for Linux 2.0 compatibility.
+* Jun 12, 1996	Gene Kozin 	Added support for S503 card.
+* Apr 30, 1996	Gene Kozin	SDLA hardware interrupt is acknowledged before
+*				calling protocolspecific ISR.
+*				Register I/O ports with Linux kernel.
+*				Miscellaneous bug fixes.
+* Dec 20, 1995	Gene Kozin	Fixed a bug in interrupt routine.
+* Oct 14, 1995	Gene Kozin	Initial version.
+*****************************************************************************/
+
+/*****************************************************************************
+ * Notes:
+ * ------
+ * 1. This code is ment to be system-independent (as much as possible).  To
+ *    achive this, various macros are used to hide system-specific interfaces.
+ *    To compile this code, one of the following constants must be defined:
+ *
+ *	Platform	Define
+ *	--------	------
+ *	Linux		_LINUX_
+ *	SCO Unix	_SCO_UNIX_
+ *
+ * 2. Supported adapter types:
+ *
+ *	S502A
+ *	ES502A (S502E)
+ *	S503
+ *	S507
+ *	S508 (S509)
+ *
+ * 3. S502A Notes:
+ *
+ *	There is no separate DPM window enable/disable control in S502A.  It
+ *	opens immediately after a window number it written to the HMCR
+ *	register.  To close the window, HMCR has to be written a value
+ *	????1111b (e.g. 0x0F or 0xFF).
+ *
+ *	S502A DPM window cannot be located at offset E000 (e.g. 0xAE000).
+ *
+ *	There should be a delay of ??? before reading back S502A status
+ *	register.
+ *
+ * 4. S502E Notes:
+ *
+ *	S502E has a h/w bug: although default IRQ line state is HIGH, enabling
+ *	interrupts by setting bit 1 of the control register (BASE) to '1'
+ *	causes it to go LOW! Therefore, disabling interrupts by setting that
+ *	bit to '0' causes low-to-high transition on IRQ line (ghosty
+ *	interrupt). The same occurs when disabling CPU by resetting bit 0 of
+ *	CPU control register (BASE+3) - see the next note.
+ *
+ *	S502E CPU and DPM control is limited:
+ *
+ *	o CPU cannot be stopped independently. Resetting bit 0 of the CPUi
+ *	  control register (BASE+3) shuts the board down entirely, including
+ *	  DPM;
+ *
+ *	o DPM access cannot be controlled dynamically. Ones CPU is started,
+ *	  bit 1 of the control register (BASE) is used to enable/disable IRQ,
+ *	  so that access to shared memory cannot be disabled while CPU is
+ *	  running.
+ ****************************************************************************/
+
+#define	_LINUX_
+
+#if	defined(_LINUX_)	/****** Linux *******************************/
+
+#include <linux/config.h>
+#include <linux/kernel.h>	/* printk(), and other useful stuff */
+#include <linux/stddef.h>	/* offsetof(), etc. */
+#include <linux/errno.h>	/* return codes */
+#include <linux/string.h>	/* inline memset(), etc. */
+#include <linux/module.h>	/* support for loadable modules */
+#include <linux/jiffies.h>	/* for jiffies, HZ, etc. */
+#include <linux/sdladrv.h>	/* API definitions */
+#include <linux/sdlasfm.h>	/* SDLA firmware module definitions */
+#include <linux/sdlapci.h>	/* SDLA PCI hardware definitions */
+#include <linux/pci.h>		/* PCI defines and function prototypes */
+#include <asm/io.h>		/* for inb(), outb(), etc. */
+
+#define _INB(port)		(inb(port))
+#define _OUTB(port, byte)	(outb((byte),(port)))
+#define	SYSTEM_TICK		jiffies
+
+#include <linux/init.h>
+
+
+#elif	defined(_SCO_UNIX_)	/****** SCO Unix ****************************/
+
+#if	!defined(INKERNEL)
+#error	This code MUST be compiled in kernel mode!
+#endif
+#include <sys/sdladrv.h>	/* API definitions */
+#include <sys/sdlasfm.h>	/* SDLA firmware module definitions */
+#include <sys/inline.h>		/* for inb(), outb(), etc. */
+#define _INB(port)		(inb(port))
+#define _OUTB(port, byte)	(outb((port),(byte)))
+#define	SYSTEM_TICK		lbolt
+
+#else
+#error	Unknown system type!
+#endif
+
+#define	MOD_VERSION	3
+#define	MOD_RELEASE	0
+
+#define	SDLA_IODELAY	100	/* I/O Rd/Wr delay, 10 works for 486DX2-66 */
+#define	EXEC_DELAY	20	/* shared memory access delay, mks */
+#define	EXEC_TIMEOUT	(HZ*2)	/* command timeout, in ticks */
+
+/* I/O port address range */
+#define S502A_IORANGE	3
+#define S502E_IORANGE	4
+#define S503_IORANGE	3
+#define S507_IORANGE	4
+#define S508_IORANGE	4
+
+/* Maximum amount of memory */
+#define S502_MAXMEM	0x10000L
+#define S503_MAXMEM	0x10000L
+#define S507_MAXMEM	0x40000L
+#define S508_MAXMEM	0x40000L
+
+/* Minimum amount of memory */
+#define S502_MINMEM	0x8000L
+#define S503_MINMEM	0x8000L
+#define S507_MINMEM	0x20000L
+#define S508_MINMEM	0x20000L
+#define NO_PORT         -1
+
+
+
+
+
+/****** Function Prototypes *************************************************/
+
+/* Hardware-specific functions */
+static int sdla_detect	(sdlahw_t* hw);
+static int sdla_autodpm	(sdlahw_t* hw);
+static int sdla_setdpm	(sdlahw_t* hw);
+static int sdla_load	(sdlahw_t* hw, sfm_t* sfm, unsigned len);
+static int sdla_init	(sdlahw_t* hw);
+static unsigned long sdla_memtest (sdlahw_t* hw);
+static int sdla_bootcfg	(sdlahw_t* hw, sfm_info_t* sfminfo);
+static unsigned char make_config_byte (sdlahw_t* hw);
+static int sdla_start	(sdlahw_t* hw, unsigned addr);
+
+static int init_s502a	(sdlahw_t* hw);
+static int init_s502e	(sdlahw_t* hw);
+static int init_s503	(sdlahw_t* hw);
+static int init_s507	(sdlahw_t* hw);
+static int init_s508	(sdlahw_t* hw);
+            
+static int detect_s502a	(int port);
+static int detect_s502e	(int port);
+static int detect_s503	(int port);
+static int detect_s507	(int port);
+static int detect_s508	(int port);
+static int detect_s514  (sdlahw_t* hw);
+static int find_s514_adapter(sdlahw_t* hw, char find_first_S514_card);
+
+/* Miscellaneous functions */
+static void peek_by_4 (unsigned long src, void* buf, unsigned len);
+static void poke_by_4 (unsigned long dest, void* buf, unsigned len);
+static int calibrate_delay (int mks);
+static int get_option_index (unsigned* optlist, unsigned optval);
+static unsigned check_memregion (void* ptr, unsigned len);
+static unsigned	test_memregion (void* ptr, unsigned len);
+static unsigned short checksum (unsigned char* buf, unsigned len);
+static int init_pci_slot(sdlahw_t *);
+
+static int pci_probe(sdlahw_t *hw);
+
+/****** Global Data **********************************************************
+ * Note: All data must be explicitly initialized!!!
+ */
+
+static struct pci_device_id sdladrv_pci_tbl[] = {
+	{ V3_VENDOR_ID, V3_DEVICE_ID, PCI_ANY_ID, PCI_ANY_ID, },
+	{ }			/* Terminating entry */
+};
+MODULE_DEVICE_TABLE(pci, sdladrv_pci_tbl);
+
+MODULE_LICENSE("GPL");
+
+/* private data */
+static char modname[]	= "sdladrv";
+static char fullname[]	= "SDLA Support Module";
+static char copyright[]	= "(c) 1995-1999 Sangoma Technologies Inc.";
+static unsigned	exec_idle;
+
+/* Hardware configuration options.
+ * These are arrays of configuration options used by verification routines.
+ * The first element of each array is its size (i.e. number of options).
+ */
+static unsigned	s502_port_options[] =
+	{ 4, 0x250, 0x300, 0x350, 0x360 }
+;
+static unsigned	s503_port_options[] =
+	{ 8, 0x250, 0x254, 0x300, 0x304, 0x350, 0x354, 0x360, 0x364 }
+;
+static unsigned	s508_port_options[] =
+	{ 8, 0x250, 0x270, 0x280, 0x300, 0x350, 0x360, 0x380, 0x390 }
+;
+
+static unsigned s502a_irq_options[] = { 0 };
+static unsigned s502e_irq_options[] = { 4, 2, 3, 5, 7 };
+static unsigned s503_irq_options[]  = { 5, 2, 3, 4, 5, 7 };
+static unsigned s508_irq_options[]  = { 8, 3, 4, 5, 7, 10, 11, 12, 15 };
+
+static unsigned s502a_dpmbase_options[] =
+{
+	28,
+	0xA0000, 0xA2000, 0xA4000, 0xA6000, 0xA8000, 0xAA000, 0xAC000,
+	0xC0000, 0xC2000, 0xC4000, 0xC6000, 0xC8000, 0xCA000, 0xCC000,
+	0xD0000, 0xD2000, 0xD4000, 0xD6000, 0xD8000, 0xDA000, 0xDC000,
+	0xE0000, 0xE2000, 0xE4000, 0xE6000, 0xE8000, 0xEA000, 0xEC000,
+};
+static unsigned s507_dpmbase_options[] =
+{
+	32,
+	0xA0000, 0xA2000, 0xA4000, 0xA6000, 0xA8000, 0xAA000, 0xAC000, 0xAE000,
+	0xB0000, 0xB2000, 0xB4000, 0xB6000, 0xB8000, 0xBA000, 0xBC000, 0xBE000,
+	0xC0000, 0xC2000, 0xC4000, 0xC6000, 0xC8000, 0xCA000, 0xCC000, 0xCE000,
+	0xE0000, 0xE2000, 0xE4000, 0xE6000, 0xE8000, 0xEA000, 0xEC000, 0xEE000,
+};
+static unsigned s508_dpmbase_options[] =	/* incl. S502E and S503 */
+{
+	32,
+	0xA0000, 0xA2000, 0xA4000, 0xA6000, 0xA8000, 0xAA000, 0xAC000, 0xAE000,
+	0xC0000, 0xC2000, 0xC4000, 0xC6000, 0xC8000, 0xCA000, 0xCC000, 0xCE000,
+	0xD0000, 0xD2000, 0xD4000, 0xD6000, 0xD8000, 0xDA000, 0xDC000, 0xDE000,
+	0xE0000, 0xE2000, 0xE4000, 0xE6000, 0xE8000, 0xEA000, 0xEC000, 0xEE000,
+};
+
+/*
+static unsigned	s502_dpmsize_options[] = { 2, 0x2000, 0x10000 };
+static unsigned	s507_dpmsize_options[] = { 2, 0x2000, 0x4000 };
+static unsigned	s508_dpmsize_options[] = { 1, 0x2000 };
+*/
+
+static unsigned	s502a_pclk_options[] = { 2, 3600, 7200 };
+static unsigned	s502e_pclk_options[] = { 5, 3600, 5000, 7200, 8000, 10000 };
+static unsigned	s503_pclk_options[]  = { 3, 7200, 8000, 10000 };
+static unsigned	s507_pclk_options[]  = { 1, 12288 };
+static unsigned	s508_pclk_options[]  = { 1, 16000 };
+
+/* Host memory control register masks */
+static unsigned char s502a_hmcr[] =
+{
+	0x10, 0x12, 0x14, 0x16, 0x18, 0x1A, 0x1C,	/* A0000 - AC000 */
+	0x20, 0x22, 0x24, 0x26, 0x28, 0x2A, 0x2C,	/* C0000 - CC000 */
+	0x00, 0x02, 0x04, 0x06, 0x08, 0x0A, 0x0C,	/* D0000 - DC000 */
+	0x30, 0x32, 0x34, 0x36, 0x38, 0x3A, 0x3C,	/* E0000 - EC000 */
+};
+static unsigned char s502e_hmcr[] =
+{
+	0x10, 0x12, 0x14, 0x16, 0x18, 0x1A, 0x1C, 0x1E,	/* A0000 - AE000 */
+	0x20, 0x22, 0x24, 0x26, 0x28, 0x2A, 0x2C, 0x2E,	/* C0000 - CE000 */
+	0x00, 0x02, 0x04, 0x06, 0x08, 0x0A, 0x0C, 0x0E,	/* D0000 - DE000 */
+	0x30, 0x32, 0x34, 0x36, 0x38, 0x3A, 0x3C, 0x3E,	/* E0000 - EE000 */
+};
+static unsigned char s507_hmcr[] =
+{
+	0x00, 0x02, 0x04, 0x06, 0x08, 0x0A, 0x0C, 0x0E,	/* A0000 - AE000 */
+	0x40, 0x42, 0x44, 0x46, 0x48, 0x4A, 0x4C, 0x4E,	/* B0000 - BE000 */
+	0x80, 0x82, 0x84, 0x86, 0x88, 0x8A, 0x8C, 0x8E,	/* C0000 - CE000 */
+	0xC0, 0xC2, 0xC4, 0xC6, 0xC8, 0xCA, 0xCC, 0xCE,	/* E0000 - EE000 */
+};
+static unsigned char s508_hmcr[] =
+{
+	0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,	/* A0000 - AE000 */
+	0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17,	/* C0000 - CE000 */
+	0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F,	/* D0000 - DE000 */
+	0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F,	/* E0000 - EE000 */
+};
+
+static unsigned char s507_irqmask[] =
+{
+	0x00, 0x20, 0x40, 0x60, 0x80, 0xA0, 0xC0, 0xE0
+};
+
+static int pci_slot_ar[MAX_S514_CARDS];
+
+/******* Kernel Loadable Module Entry Points ********************************/
+
+/*============================================================================
+ * Module 'insert' entry point.
+ * o print announcement
+ * o initialize static data
+ * o calibrate SDLA shared memory access delay.
+ *
+ * Return:	0	Ok
+ *		< 0	error.
+ * Context:	process
+ */
+
+static int __init sdladrv_init(void)
+{
+	int i=0;
+
+	printk(KERN_INFO "%s v%u.%u %s\n",
+		fullname, MOD_VERSION, MOD_RELEASE, copyright);
+	exec_idle = calibrate_delay(EXEC_DELAY);
+#ifdef WANDEBUG	
+	printk(KERN_DEBUG "%s: exec_idle = %d\n", modname, exec_idle);
+#endif	
+
+	/* Initialize the PCI Card array, which
+         * will store flags, used to mark 
+         * card initialization state */
+	for (i=0; i<MAX_S514_CARDS; i++)
+		pci_slot_ar[i] = 0xFF;
+
+	return 0;
+}
+
+/*============================================================================
+ * Module 'remove' entry point.
+ * o release all remaining system resources
+ */
+static void __exit sdladrv_cleanup(void)
+{
+}
+
+module_init(sdladrv_init);
+module_exit(sdladrv_cleanup);
+
+/******* Kernel APIs ********************************************************/
+
+/*============================================================================
+ * Set up adapter.
+ * o detect adapter type
+ * o verify hardware configuration options
+ * o check for hardware conflicts
+ * o set up adapter shared memory
+ * o test adapter memory
+ * o load firmware
+ * Return:	0	ok.
+ *		< 0	error
+ */
+
+EXPORT_SYMBOL(sdla_setup);
+
+int sdla_setup (sdlahw_t* hw, void* sfm, unsigned len)
+{
+	unsigned* irq_opt	= NULL;	/* IRQ options */
+	unsigned* dpmbase_opt	= NULL;	/* DPM window base options */
+	unsigned* pclk_opt	= NULL;	/* CPU clock rate options */
+	int err=0;
+
+	if (sdla_detect(hw)) {
+                if(hw->type != SDLA_S514)
+                        printk(KERN_INFO "%s: no SDLA card found at port 0x%X\n",
+                        modname, hw->port);
+		return -EINVAL;
+	}
+
+	if(hw->type != SDLA_S514) {
+                printk(KERN_INFO "%s: found S%04u card at port 0x%X.\n",
+                modname, hw->type, hw->port);
+
+                hw->dpmsize = SDLA_WINDOWSIZE;
+                switch (hw->type) {
+                case SDLA_S502A:
+                        hw->io_range    = S502A_IORANGE;
+                        irq_opt         = s502a_irq_options;
+                        dpmbase_opt     = s502a_dpmbase_options;
+                        pclk_opt        = s502a_pclk_options;
+                        break;
+
+                case SDLA_S502E:
+                        hw->io_range    = S502E_IORANGE;
+                        irq_opt         = s502e_irq_options;
+                        dpmbase_opt     = s508_dpmbase_options;
+                        pclk_opt        = s502e_pclk_options;
+                        break;
+
+                case SDLA_S503:
+                        hw->io_range    = S503_IORANGE;
+                        irq_opt         = s503_irq_options;
+                        dpmbase_opt     = s508_dpmbase_options;
+                        pclk_opt        = s503_pclk_options;
+                        break;
+
+                case SDLA_S507:
+                        hw->io_range    = S507_IORANGE;
+                        irq_opt         = s508_irq_options;
+                        dpmbase_opt     = s507_dpmbase_options;
+                        pclk_opt        = s507_pclk_options;
+                        break;
+
+                case SDLA_S508:
+                        hw->io_range    = S508_IORANGE;
+                        irq_opt         = s508_irq_options;
+                        dpmbase_opt     = s508_dpmbase_options;
+                        pclk_opt        = s508_pclk_options;
+                        break;
+                }
+
+                /* Verify IRQ configuration options */
+                if (!get_option_index(irq_opt, hw->irq)) {
+                        printk(KERN_INFO "%s: IRQ %d is invalid!\n",
+                        	modname, hw->irq);
+                      return -EINVAL;
+                } 
+
+                /* Verify CPU clock rate configuration options */
+                if (hw->pclk == 0)
+                        hw->pclk = pclk_opt[1];  /* use default */
+        
+                else if (!get_option_index(pclk_opt, hw->pclk)) {
+                        printk(KERN_INFO "%s: CPU clock %u is invalid!\n",
+				modname, hw->pclk);
+                        return -EINVAL;
+                } 
+                printk(KERN_INFO "%s: assuming CPU clock rate of %u kHz.\n",
+			modname, hw->pclk);
+
+                /* Setup adapter dual-port memory window and test memory */
+                if (hw->dpmbase == 0) {
+                        err = sdla_autodpm(hw);
+                        if (err) {
+                                printk(KERN_INFO
+				"%s: can't find available memory region!\n",
+					modname);
+                                return err;
+                        }
+                }
+                else if (!get_option_index(dpmbase_opt,
+			virt_to_phys(hw->dpmbase))) {
+                        printk(KERN_INFO
+				"%s: memory address 0x%lX is invalid!\n",
+				modname, virt_to_phys(hw->dpmbase));
+                        return -EINVAL;
+                }               
+                else if (sdla_setdpm(hw)) {
+                        printk(KERN_INFO
+			"%s: 8K memory region at 0x%lX is not available!\n",
+				modname, virt_to_phys(hw->dpmbase));
+                        return -EINVAL;
+                } 
+                printk(KERN_INFO
+			"%s: dual-port memory window is set at 0x%lX.\n",
+				modname, virt_to_phys(hw->dpmbase));
+
+
+		/* If we find memory in 0xE**** Memory region, 
+                 * warn the user to disable the SHADOW RAM.  
+                 * Since memory corruption can occur if SHADOW is
+                 * enabled. This can causes random crashes ! */
+		if (virt_to_phys(hw->dpmbase) >= 0xE0000){
+			printk(KERN_WARNING "\n%s: !!!!!!!!  WARNING !!!!!!!!\n",modname);
+			printk(KERN_WARNING "%s: WANPIPE is using 0x%lX memory region !!!\n",
+						modname, virt_to_phys(hw->dpmbase));
+			printk(KERN_WARNING "         Please disable the SHADOW RAM, otherwise\n");
+			printk(KERN_WARNING "         your system might crash randomly from time to time !\n");
+			printk(KERN_WARNING "%s: !!!!!!!!  WARNING !!!!!!!!\n\n",modname);
+		}
+        }
+
+	else {
+		hw->memory = test_memregion((void*)hw->dpmbase, 
+			MAX_SIZEOF_S514_MEMORY);
+		if(hw->memory < (256 * 1024)) {
+			printk(KERN_INFO
+				"%s: error in testing S514 memory (0x%lX)\n",
+				modname, hw->memory);
+			sdla_down(hw);
+			return -EINVAL;
+		}
+	}
+    
+	printk(KERN_INFO "%s: found %luK bytes of on-board memory\n",
+		modname, hw->memory / 1024);
+
+	/* Load firmware. If loader fails then shut down adapter */
+	err = sdla_load(hw, sfm, len);
+	if (err) sdla_down(hw);		/* shutdown adapter */
+
+	return err;
+} 
+
+/*============================================================================
+ * Shut down SDLA: disable shared memory access and interrupts, stop CPU, etc.
+ */
+
+EXPORT_SYMBOL(sdla_down);
+
+int sdla_down (sdlahw_t* hw)
+{
+	unsigned port = hw->port;
+	int i;
+        unsigned char CPU_no;
+        u32 int_config, int_status;
+
+        if(!port && (hw->type != SDLA_S514))
+                return -EFAULT;
+
+	switch (hw->type) {
+	case SDLA_S502A:
+		_OUTB(port, 0x08);		/* halt CPU */
+		_OUTB(port, 0x08);
+		_OUTB(port, 0x08);
+		hw->regs[0] = 0x08;
+		_OUTB(port + 1, 0xFF);		/* close memory window */
+		hw->regs[1] = 0xFF;
+		break;
+
+	case SDLA_S502E:
+		_OUTB(port + 3, 0);		/* stop CPU */
+		_OUTB(port, 0);			/* reset board */
+		for (i = 0; i < S502E_IORANGE; ++i)
+			hw->regs[i] = 0
+		;
+		break;
+
+	case SDLA_S503:
+	case SDLA_S507:
+	case SDLA_S508:
+		_OUTB(port, 0);			/* reset board logic */
+		hw->regs[0] = 0;
+		break;
+
+	case SDLA_S514:
+		/* halt the adapter */
+                *(char *)hw->vector = S514_CPU_HALT;
+        	CPU_no = hw->S514_cpu_no[0];
+
+		/* disable the PCI IRQ and disable memory access */
+                pci_read_config_dword(hw->pci_dev, PCI_INT_CONFIG, &int_config);
+	        int_config &= (CPU_no == S514_CPU_A) ? ~PCI_DISABLE_IRQ_CPU_A :	~PCI_DISABLE_IRQ_CPU_B;
+                pci_write_config_dword(hw->pci_dev, PCI_INT_CONFIG, int_config);
+		read_S514_int_stat(hw, &int_status);
+		S514_intack(hw, int_status);
+		if(CPU_no == S514_CPU_A)
+                        pci_write_config_dword(hw->pci_dev, PCI_MAP0_DWORD,
+				PCI_CPU_A_MEM_DISABLE);
+		else
+                        pci_write_config_dword(hw->pci_dev, PCI_MAP1_DWORD,
+				PCI_CPU_B_MEM_DISABLE);
+
+		/* free up the allocated virtual memory */
+ 		iounmap((void *)hw->dpmbase);
+        	iounmap((void *)hw->vector);
+ 		break;
+
+
+	default:
+		return -EINVAL;
+	}
+	return 0;
+}
+
+/*============================================================================
+ * Map shared memory window into SDLA address space.
+ */
+
+EXPORT_SYMBOL(sdla_mapmem);
+
+int sdla_mapmem (sdlahw_t* hw, unsigned long addr)
+{
+	unsigned port = hw->port;
+	register int tmp;
+
+	switch (hw->type) {
+	case SDLA_S502A:
+	case SDLA_S502E:
+		if (addr < S502_MAXMEM)	{ /* verify parameter */
+			tmp = addr >> 13;	/* convert to register mask */
+			_OUTB(port + 2, tmp);
+			hw->regs[2] = tmp;
+		}
+		else return -EINVAL;
+		break;
+
+	case SDLA_S503:
+		if (addr < S503_MAXMEM)	{ /* verify parameter */
+			tmp = (hw->regs[0] & 0x8F) | ((addr >> 9) & 0x70);
+			_OUTB(port, tmp);
+			hw->regs[0] = tmp;
+		}
+		else return -EINVAL;
+		break;
+
+	case SDLA_S507:
+		if (addr < S507_MAXMEM) {
+			if (!(_INB(port) & 0x02))
+				return -EIO;
+			tmp = addr >> 13;	/* convert to register mask */
+			_OUTB(port + 2, tmp);
+			hw->regs[2] = tmp;
+		}
+		else return -EINVAL;
+		break;
+
+	case SDLA_S508:
+		if (addr < S508_MAXMEM) {
+			tmp = addr >> 13;	/* convert to register mask */
+			_OUTB(port + 2, tmp);
+			hw->regs[2] = tmp;
+		}
+		else return -EINVAL;
+		break;
+
+	case SDLA_S514:
+		return 0;
+
+ 	default:
+		return -EINVAL;
+	}
+	hw->vector = addr & 0xFFFFE000L;
+	return 0;
+}
+
+/*============================================================================
+ * Enable interrupt generation.
+ */
+
+EXPORT_SYMBOL(sdla_inten);
+
+int sdla_inten (sdlahw_t* hw)
+{
+	unsigned port = hw->port;
+	int tmp, i;
+
+	switch (hw->type) {
+	case SDLA_S502E:
+		/* Note thar interrupt control operations on S502E are allowed
+		 * only if CPU is enabled (bit 0 of status register is set).
+		 */
+		if (_INB(port) & 0x01) {
+			_OUTB(port, 0x02);	/* bit1 = 1, bit2 = 0 */
+			_OUTB(port, 0x06);	/* bit1 = 1, bit2 = 1 */
+			hw->regs[0] = 0x06;
+		}
+		else return -EIO;
+		break;
+
+	case SDLA_S503:
+		tmp = hw->regs[0] | 0x04;
+		_OUTB(port, tmp);
+		hw->regs[0] = tmp;		/* update mirror */
+		for (i = 0; i < SDLA_IODELAY; ++i);	/* delay */
+		if (!(_INB(port) & 0x02))		/* verify */
+			return -EIO;
+		break;
+
+	case SDLA_S508:
+		tmp = hw->regs[0] | 0x10;
+		_OUTB(port, tmp);
+		hw->regs[0] = tmp;		/* update mirror */
+		for (i = 0; i < SDLA_IODELAY; ++i);	/* delay */
+		if (!(_INB(port + 1) & 0x10))		/* verify */
+			return -EIO;
+		break;
+
+	case SDLA_S502A:
+	case SDLA_S507:
+		break;
+
+        case SDLA_S514:
+                break;
+
+	default:
+		return -EINVAL;
+
+	}
+	return 0;
+}
+
+/*============================================================================
+ * Disable interrupt generation.
+ */
+
+EXPORT_SYMBOL(sdla_intde);
+
+int sdla_intde (sdlahw_t* hw)
+{
+	unsigned port = hw->port;
+	int tmp, i;
+
+	switch (hw->type) {
+	case SDLA_S502E:
+		/* Notes:
+		 *  1) interrupt control operations are allowed only if CPU is
+		 *     enabled (bit 0 of status register is set).
+		 *  2) disabling interrupts using bit 1 of control register
+		 *     causes IRQ line go high, therefore we are going to use
+		 *     0x04 instead: lower it to inhibit interrupts to PC.
+		 */
+		if (_INB(port) & 0x01) {
+			_OUTB(port, hw->regs[0] & ~0x04);
+			hw->regs[0] &= ~0x04;
+		}
+		else return -EIO;
+		break;
+
+	case SDLA_S503:
+		tmp = hw->regs[0] & ~0x04;
+		_OUTB(port, tmp);
+		hw->regs[0] = tmp;			/* update mirror */
+		for (i = 0; i < SDLA_IODELAY; ++i);	/* delay */
+		if (_INB(port) & 0x02)			/* verify */
+			return -EIO;
+		break;
+
+	case SDLA_S508:
+		tmp = hw->regs[0] & ~0x10;
+		_OUTB(port, tmp);
+		hw->regs[0] = tmp;			/* update mirror */
+		for (i = 0; i < SDLA_IODELAY; ++i);	/* delay */
+		if (_INB(port) & 0x10)			/* verify */
+			return -EIO;
+		break;
+
+	case SDLA_S502A:
+	case SDLA_S507:
+		break;
+
+	default:
+		return -EINVAL;
+	}
+	return 0;
+}
+
+/*============================================================================
+ * Acknowledge SDLA hardware interrupt.
+ */
+
+EXPORT_SYMBOL(sdla_intack);
+
+int sdla_intack (sdlahw_t* hw)
+{
+	unsigned port = hw->port;
+	int tmp;
+
+	switch (hw->type) {
+	case SDLA_S502E:
+		/* To acknoledge hardware interrupt we have to toggle bit 3 of
+		 * control register: \_/
+		 * Note that interrupt control operations on S502E are allowed
+		 * only if CPU is enabled (bit 1 of status register is set).
+		 */
+		if (_INB(port) & 0x01) {
+			tmp = hw->regs[0] & ~0x04;
+			_OUTB(port, tmp);
+			tmp |= 0x04;
+			_OUTB(port, tmp);
+			hw->regs[0] = tmp;
+		}
+		else return -EIO;
+		break;
+
+	case SDLA_S503:
+		if (_INB(port) & 0x04) {
+			tmp = hw->regs[0] & ~0x08;
+			_OUTB(port, tmp);
+			tmp |= 0x08;
+			_OUTB(port, tmp);
+			hw->regs[0] = tmp;
+		}
+		break;
+
+	case SDLA_S502A:
+	case SDLA_S507:
+	case SDLA_S508:
+	break;
+
+	default:
+		return -EINVAL;
+	}
+	return 0;
+}
+
+
+/*============================================================================
+ * Acknowledge S514 hardware interrupt.
+ */
+
+EXPORT_SYMBOL(S514_intack);
+
+void S514_intack (sdlahw_t* hw, u32 int_status)
+{
+        pci_write_config_dword(hw->pci_dev, PCI_INT_STATUS, int_status);
+}
+
+
+/*============================================================================
+ * Read the S514 hardware interrupt status.
+ */
+
+EXPORT_SYMBOL(read_S514_int_stat);
+
+void read_S514_int_stat (sdlahw_t* hw, u32* int_status)
+{
+	pci_read_config_dword(hw->pci_dev, PCI_INT_STATUS, int_status);
+}
+
+
+/*============================================================================
+ * Generate an interrupt to adapter's CPU.
+ */
+
+EXPORT_SYMBOL(sdla_intr);
+
+int sdla_intr (sdlahw_t* hw)
+{
+	unsigned port = hw->port;
+
+	switch (hw->type) {
+	case SDLA_S502A:
+		if (!(_INB(port) & 0x40)) {
+			_OUTB(port, 0x10);		/* issue NMI to CPU */
+			hw->regs[0] = 0x10;
+		}
+		else return -EIO;
+		break;
+
+	case SDLA_S507:
+		if ((_INB(port) & 0x06) == 0x06) {
+			_OUTB(port + 3, 0);
+		}
+		else return -EIO;
+		break;
+
+	case SDLA_S508:
+		if (_INB(port + 1) & 0x02) {
+			_OUTB(port, 0x08);
+		}
+		else return -EIO;
+		break;
+
+	case SDLA_S502E:
+	case SDLA_S503:
+	default:
+		return -EINVAL;
+	}
+	return 0;
+}
+
+/*============================================================================
+ * Execute Adapter Command.
+ * o Set exec flag.
+ * o Busy-wait until flag is reset.
+ * o Return number of loops made, or 0 if command timed out.
+ */
+
+EXPORT_SYMBOL(sdla_exec);
+
+int sdla_exec (void* opflag)
+{
+	volatile unsigned char* flag = opflag;
+	unsigned long tstop;
+	int nloops;
+
+	if(readb(flag) != 0x00) {
+		printk(KERN_INFO
+			"WANPIPE: opp flag set on entry to sdla_exec\n");
+		return 0;
+	}
+	
+	writeb(0x01, flag);
+
+	tstop = SYSTEM_TICK + EXEC_TIMEOUT;
+
+	for (nloops = 1; (readb(flag) == 0x01); ++ nloops) {
+		unsigned delay = exec_idle;
+		while (-- delay);			/* delay */
+		if (SYSTEM_TICK > tstop) return 0;	/* time is up! */
+	}
+	return nloops;
+}
+
+/*============================================================================
+ * Read absolute adapter memory.
+ * Transfer data from adapter's memory to data buffer.
+ *
+ * Note:
+ * Care should be taken when crossing dual-port memory window boundary.
+ * This function is not atomic, so caller must disable interrupt if
+ * interrupt routines are accessing adapter shared memory.
+ */
+
+EXPORT_SYMBOL(sdla_peek);
+
+int sdla_peek (sdlahw_t* hw, unsigned long addr, void* buf, unsigned len)
+{
+
+	if (addr + len > hw->memory)	/* verify arguments */
+		return -EINVAL;
+
+        if(hw->type == SDLA_S514) {	/* copy data for the S514 adapter */
+                peek_by_4 ((unsigned long)hw->dpmbase + addr, buf, len);
+                return 0;
+	}
+
+        else {				/* copy data for the S508 adapter */
+	        unsigned long oldvec = hw->vector;
+        	unsigned winsize = hw->dpmsize;
+	        unsigned curpos, curlen;   /* current offset and block size */
+        	unsigned long curvec;      /* current DPM window vector */
+	        int err = 0;
+
+                while (len && !err) {
+                        curpos = addr % winsize;  /* current window offset */
+                        curvec = addr - curpos;   /* current window vector */
+                        curlen = (len > (winsize - curpos)) ?
+				(winsize - curpos) : len;
+                        /* Relocate window and copy block of data */
+                        err = sdla_mapmem(hw, curvec);
+                        peek_by_4 ((unsigned long)hw->dpmbase + curpos, buf,
+				curlen);
+                        addr       += curlen;
+                        buf         = (char*)buf + curlen;
+                        len        -= curlen;
+                }
+
+                /* Restore DPM window position */
+                sdla_mapmem(hw, oldvec);
+                return err;
+        }
+}
+
+
+/*============================================================================
+ * Read data from adapter's memory to a data buffer in 4-byte chunks.
+ * Note that we ensure that the SDLA memory address is on a 4-byte boundary
+ * before we begin moving the data in 4-byte chunks.
+*/
+
+static void peek_by_4 (unsigned long src, void* buf, unsigned len)
+{
+
+        /* byte copy data until we get to a 4-byte boundary */
+        while (len && (src & 0x03)) {
+                *(char *)buf ++ = readb(src ++);
+                len --;
+        }
+
+        /* copy data in 4-byte chunks */
+        while (len >= 4) {
+                *(unsigned long *)buf = readl(src);
+                buf += 4;
+                src += 4;
+                len -= 4;
+        }
+
+        /* byte copy any remaining data */
+        while (len) {
+                *(char *)buf ++ = readb(src ++);
+                len --;
+        }
+}
+
+
+/*============================================================================
+ * Write Absolute Adapter Memory.
+ * Transfer data from data buffer to adapter's memory.
+ *
+ * Note:
+ * Care should be taken when crossing dual-port memory window boundary.
+ * This function is not atomic, so caller must disable interrupt if
+ * interrupt routines are accessing adapter shared memory.
+ */
+
+EXPORT_SYMBOL(sdla_poke);
+ 
+int sdla_poke (sdlahw_t* hw, unsigned long addr, void* buf, unsigned len)
+{
+
+	if (addr + len > hw->memory)	/* verify arguments */
+		return -EINVAL;
+   
+        if(hw->type == SDLA_S514) {	/* copy data for the S514 adapter */
+                poke_by_4 ((unsigned long)hw->dpmbase + addr, buf, len);
+                return 0;
+	}
+	
+	else {				/* copy data for the S508 adapter */
+    		unsigned long oldvec = hw->vector;
+	        unsigned winsize = hw->dpmsize;
+        	unsigned curpos, curlen;     /* current offset and block size */
+        	unsigned long curvec;        /* current DPM window vector */
+        	int err = 0;
+
+		while (len && !err) {
+                        curpos = addr % winsize;    /* current window offset */
+                        curvec = addr - curpos;     /* current window vector */
+                        curlen = (len > (winsize - curpos)) ?
+				(winsize - curpos) : len;
+                        /* Relocate window and copy block of data */
+                        sdla_mapmem(hw, curvec);
+                        poke_by_4 ((unsigned long)hw->dpmbase + curpos, buf,
+				curlen);
+	                addr       += curlen;
+                        buf         = (char*)buf + curlen;
+                        len        -= curlen;
+                }
+
+                /* Restore DPM window position */
+                sdla_mapmem(hw, oldvec);
+                return err;
+        }
+}
+
+
+/*============================================================================
+ * Write from a data buffer to adapter's memory in 4-byte chunks.
+ * Note that we ensure that the SDLA memory address is on a 4-byte boundary
+ * before we begin moving the data in 4-byte chunks.
+*/
+
+static void poke_by_4 (unsigned long dest, void* buf, unsigned len)
+{
+
+        /* byte copy data until we get to a 4-byte boundary */
+        while (len && (dest & 0x03)) {
+                writeb (*(char *)buf ++, dest ++);
+                len --;
+        }
+
+        /* copy data in 4-byte chunks */
+        while (len >= 4) {
+                writel (*(unsigned long *)buf, dest);
+                dest += 4;
+                buf += 4;
+                len -= 4;
+        }
+
+        /* byte copy any remaining data */
+        while (len) {
+                writeb (*(char *)buf ++ , dest ++);
+                len --;
+        }
+}
+
+
+#ifdef	DONT_COMPIPLE_THIS
+#endif	/* DONT_COMPIPLE_THIS */
+
+/****** Hardware-Specific Functions *****************************************/
+
+/*============================================================================
+ * Detect adapter type.
+ * o if adapter type is specified then call detection routine for that adapter
+ *   type.  Otherwise call detection routines for every adapter types until
+ *   adapter is detected.
+ *
+ * Notes:
+ * 1) Detection tests are destructive! Adapter will be left in shutdown state
+ *    after the test.
+ */
+static int sdla_detect (sdlahw_t* hw)
+{
+	unsigned port = hw->port;
+	int err = 0;
+
+	if (!port && (hw->type != SDLA_S514))
+		return -EFAULT;
+
+    	switch (hw->type) {
+	case SDLA_S502A:
+		if (!detect_s502a(port)) err = -ENODEV;
+		break;
+
+	case SDLA_S502E:
+		if (!detect_s502e(port)) err = -ENODEV;
+		break;
+
+	case SDLA_S503:
+		if (!detect_s503(port)) err = -ENODEV;
+		break;
+
+	case SDLA_S507:
+		if (!detect_s507(port)) err = -ENODEV;
+		break;
+
+	case SDLA_S508:
+		if (!detect_s508(port)) err = -ENODEV;
+		break;
+
+	case SDLA_S514:
+                if (!detect_s514(hw)) err = -ENODEV;
+		break;
+
+	default:
+		if (detect_s502a(port))
+			hw->type = SDLA_S502A;
+		else if (detect_s502e(port))
+			hw->type = SDLA_S502E;
+		else if (detect_s503(port))
+			hw->type = SDLA_S503;
+		else if (detect_s507(port))
+			hw->type = SDLA_S507;
+		else if (detect_s508(port))
+			hw->type = SDLA_S508;
+		else err = -ENODEV;
+	}
+	return err;
+}
+
+/*============================================================================
+ * Autoselect memory region. 
+ * o try all available DMP address options from the top down until success.
+ */
+static int sdla_autodpm (sdlahw_t* hw)
+{
+	int i, err = -EINVAL;
+	unsigned* opt;
+
+	switch (hw->type) {
+	case SDLA_S502A:
+		opt = s502a_dpmbase_options;
+		break;
+
+	case SDLA_S502E:
+	case SDLA_S503:
+	case SDLA_S508:
+		opt = s508_dpmbase_options;
+		break;
+
+	case SDLA_S507:
+		opt = s507_dpmbase_options;
+		break;
+
+	default:
+		return -EINVAL;
+	}
+
+	/* Start testing from 8th position, address
+         * 0xC8000 from the 508 address table. 
+         * We don't want to test A**** addresses, since
+         * they are usually used for Video */
+	for (i = 8; i <= opt[0] && err; i++) {
+		hw->dpmbase = phys_to_virt(opt[i]);
+		err = sdla_setdpm(hw);
+	}
+	return err;
+}
+
+/*============================================================================
+ * Set up adapter dual-port memory window. 
+ * o shut down adapter
+ * o make sure that no physical memory exists in this region, i.e entire
+ *   region reads 0xFF and is not writable when adapter is shut down.
+ * o initialize adapter hardware
+ * o make sure that region is usable with SDLA card, i.e. we can write to it
+ *   when adapter is configured.
+ */
+static int sdla_setdpm (sdlahw_t* hw)
+{
+	int err;
+
+	/* Shut down card and verify memory region */
+	sdla_down(hw);
+	if (check_memregion(hw->dpmbase, hw->dpmsize))
+		return -EINVAL;
+
+	/* Initialize adapter and test on-board memory segment by segment.
+	 * If memory size appears to be less than shared memory window size,
+	 * assume that memory region is unusable.
+	 */
+	err = sdla_init(hw);
+	if (err) return err;
+
+	if (sdla_memtest(hw) < hw->dpmsize) {	/* less than window size */
+		sdla_down(hw);
+		return -EIO;
+	}
+	sdla_mapmem(hw, 0L);	/* set window vector at bottom */
+	return 0;
+}
+
+/*============================================================================
+ * Load adapter from the memory image of the SDLA firmware module. 
+ * o verify firmware integrity and compatibility
+ * o start adapter up
+ */
+static int sdla_load (sdlahw_t* hw, sfm_t* sfm, unsigned len)
+{
+
+	int i;
+
+	/* Verify firmware signature */
+	if (strcmp(sfm->signature, SFM_SIGNATURE)) {
+		printk(KERN_INFO "%s: not SDLA firmware!\n",
+			modname);
+		return -EINVAL;
+	}
+
+	/* Verify firmware module format version */
+	if (sfm->version != SFM_VERSION) {
+		printk(KERN_INFO
+			"%s: firmware format %u rejected! Expecting %u.\n",
+			modname, sfm->version, SFM_VERSION);
+		return -EINVAL;
+	}
+
+	/* Verify firmware module length and checksum */
+	if ((len - offsetof(sfm_t, image) != sfm->info.codesize) ||
+		(checksum((void*)&sfm->info,
+		sizeof(sfm_info_t) + sfm->info.codesize) != sfm->checksum)) {
+		printk(KERN_INFO "%s: firmware corrupted!\n", modname);
+		return -EINVAL;
+	}
+
+	/* Announce */
+	printk(KERN_INFO "%s: loading %s (ID=%u)...\n", modname,
+		(sfm->descr[0] != '\0') ? sfm->descr : "unknown firmware",
+		sfm->info.codeid);
+
+	if(hw->type == SDLA_S514)
+		printk(KERN_INFO "%s: loading S514 adapter, CPU %c\n",
+			modname, hw->S514_cpu_no[0]);
+
+	/* Scan through the list of compatible adapters and make sure our
+	 * adapter type is listed.
+	 */
+	for (i = 0;
+	     (i < SFM_MAX_SDLA) && (sfm->info.adapter[i] != hw->type);
+	     ++i);
+	
+	if (i == SFM_MAX_SDLA) {
+		printk(KERN_INFO "%s: firmware is not compatible with S%u!\n",
+			modname, hw->type);
+		return -EINVAL;
+	}
+
+
+	/* Make sure there is enough on-board memory */
+	if (hw->memory < sfm->info.memsize) {
+		printk(KERN_INFO
+			"%s: firmware needs %lu bytes of on-board memory!\n",
+			modname, sfm->info.memsize);
+		return -EINVAL;
+	}
+
+	/* Move code onto adapter */
+	if (sdla_poke(hw, sfm->info.codeoffs, sfm->image, sfm->info.codesize)) {
+		printk(KERN_INFO "%s: failed to load code segment!\n",
+			modname);
+		return -EIO;
+	}
+
+	/* Prepare boot-time configuration data and kick-off CPU */
+	sdla_bootcfg(hw, &sfm->info);
+	if (sdla_start(hw, sfm->info.startoffs)) {
+		printk(KERN_INFO "%s: Damn... Adapter won't start!\n",
+			modname);
+		return -EIO;
+	}
+
+	/* position DPM window over the mailbox and enable interrupts */
+        if (sdla_mapmem(hw, sfm->info.winoffs) || sdla_inten(hw)) {
+		printk(KERN_INFO "%s: adapter hardware failure!\n",
+			modname);
+		return -EIO;
+	}
+	hw->fwid = sfm->info.codeid;		/* set firmware ID */
+	return 0;
+}
+
+/*============================================================================
+ * Initialize SDLA hardware: setup memory window, IRQ, etc.
+ */
+static int sdla_init (sdlahw_t* hw)
+{
+	int i;
+
+	for (i = 0; i < SDLA_MAXIORANGE; ++i)
+		hw->regs[i] = 0;
+
+	switch (hw->type) {
+	case SDLA_S502A: return init_s502a(hw);
+	case SDLA_S502E: return init_s502e(hw);
+	case SDLA_S503:  return init_s503(hw);
+	case SDLA_S507:  return init_s507(hw);
+	case SDLA_S508:  return init_s508(hw);
+	}
+	return -EINVAL;
+}
+
+/*============================================================================
+ * Test adapter on-board memory.
+ * o slide DPM window from the bottom up and test adapter memory segment by
+ *   segment.
+ * Return adapter memory size.
+ */
+static unsigned long sdla_memtest (sdlahw_t* hw)
+{
+	unsigned long memsize;
+	unsigned winsize;
+
+	for (memsize = 0, winsize = hw->dpmsize;
+	     !sdla_mapmem(hw, memsize) &&
+		(test_memregion(hw->dpmbase, winsize) == winsize)
+	     ;
+	     memsize += winsize)
+	;
+	hw->memory = memsize;
+	return memsize;
+}
+
+/*============================================================================
+ * Prepare boot-time firmware configuration data.
+ * o position DPM window
+ * o initialize configuration data area
+ */
+static int sdla_bootcfg (sdlahw_t* hw, sfm_info_t* sfminfo)
+{
+	unsigned char* data;
+
+	if (!sfminfo->datasize) return 0;	/* nothing to do */
+
+	if (sdla_mapmem(hw, sfminfo->dataoffs) != 0)
+		return -EIO;
+
+	if(hw->type == SDLA_S514)
+                data = (void*)(hw->dpmbase + sfminfo->dataoffs);
+        else
+                data = (void*)((u8 *)hw->dpmbase +
+                        (sfminfo->dataoffs - hw->vector));
+
+	memset_io (data, 0, sfminfo->datasize);
+
+	writeb (make_config_byte(hw), &data[0x00]);
+
+	switch (sfminfo->codeid) {
+	case SFID_X25_502:
+	case SFID_X25_508:
+                writeb (3, &data[0x01]);        /* T1 timer */
+                writeb (10, &data[0x03]);       /* N2 */
+                writeb (7, &data[0x06]);        /* HDLC window size */
+                writeb (1, &data[0x0B]);        /* DTE */
+                writeb (2, &data[0x0C]);        /* X.25 packet window size */
+                writew (128, &data[0x0D]);	/* default X.25 data size */
+                writew (128, &data[0x0F]);	/* maximum X.25 data size */
+		break;
+	}
+	return 0;
+}
+
+/*============================================================================
+ * Prepare configuration byte identifying adapter type and CPU clock rate.
+ */
+static unsigned char make_config_byte (sdlahw_t* hw)
+{
+	unsigned char byte = 0;
+
+	switch (hw->pclk) {
+		case 5000:  byte = 0x01; break;
+		case 7200:  byte = 0x02; break;
+		case 8000:  byte = 0x03; break;
+		case 10000: byte = 0x04; break;
+		case 16000: byte = 0x05; break;
+	}
+
+	switch (hw->type) {
+		case SDLA_S502E: byte |= 0x80; break;
+		case SDLA_S503:  byte |= 0x40; break;
+	}
+	return byte;
+}
+
+/*============================================================================
+ * Start adapter's CPU.
+ * o calculate a pointer to adapter's cold boot entry point
+ * o position DPM window
+ * o place boot instruction (jp addr) at cold boot entry point
+ * o start CPU
+ */
+static int sdla_start (sdlahw_t* hw, unsigned addr)
+{
+	unsigned port = hw->port;
+	unsigned char *bootp;
+	int err, tmp, i;
+
+	if (!port && (hw->type != SDLA_S514)) return -EFAULT;
+
+ 	switch (hw->type) {
+	case SDLA_S502A:
+		bootp = hw->dpmbase;
+		bootp += 0x66;
+		break;
+
+	case SDLA_S502E:
+	case SDLA_S503:
+	case SDLA_S507:
+	case SDLA_S508:
+	case SDLA_S514:
+		bootp = hw->dpmbase;
+		break;
+
+	default:
+		return -EINVAL;
+	}
+
+	err = sdla_mapmem(hw, 0);
+	if (err) return err;
+
+      	writeb (0xC3, bootp);   /* Z80: 'jp' opcode */
+	bootp ++;
+	writew (addr, bootp);
+
+	switch (hw->type) {
+	case SDLA_S502A:
+		_OUTB(port, 0x10);		/* issue NMI to CPU */
+		hw->regs[0] = 0x10;
+		break;
+
+	case SDLA_S502E:
+		_OUTB(port + 3, 0x01);		/* start CPU */
+		hw->regs[3] = 0x01;
+		for (i = 0; i < SDLA_IODELAY; ++i);
+		if (_INB(port) & 0x01) {	/* verify */
+			/*
+			 * Enabling CPU changes functionality of the
+			 * control register, so we have to reset its
+			 * mirror.
+			 */
+			_OUTB(port, 0);		/* disable interrupts */
+			hw->regs[0] = 0;
+		}
+		else return -EIO;
+		break;
+
+	case SDLA_S503:
+		tmp = hw->regs[0] | 0x09;	/* set bits 0 and 3 */
+		_OUTB(port, tmp);
+		hw->regs[0] = tmp;		/* update mirror */
+		for (i = 0; i < SDLA_IODELAY; ++i);
+		if (!(_INB(port) & 0x01))	/* verify */
+			return -EIO;
+		break;
+
+	case SDLA_S507:
+		tmp = hw->regs[0] | 0x02;
+		_OUTB(port, tmp);
+		hw->regs[0] = tmp;		/* update mirror */
+		for (i = 0; i < SDLA_IODELAY; ++i);
+		if (!(_INB(port) & 0x04))	/* verify */
+			return -EIO;
+		break;
+
+	case SDLA_S508:
+		tmp = hw->regs[0] | 0x02;
+		_OUTB(port, tmp);
+		hw->regs[0] = tmp;	/* update mirror */
+		for (i = 0; i < SDLA_IODELAY; ++i);
+		if (!(_INB(port + 1) & 0x02))	/* verify */
+			return -EIO;
+		break;
+
+	case SDLA_S514:
+		writeb (S514_CPU_START, hw->vector);
+		break;
+
+	default:
+		return -EINVAL;
+	}
+	return 0;
+}
+
+/*============================================================================
+ * Initialize S502A adapter.
+ */
+static int init_s502a (sdlahw_t* hw)
+{
+	unsigned port = hw->port;
+	int tmp, i;
+
+	if (!detect_s502a(port))
+		return -ENODEV;
+
+	hw->regs[0] = 0x08;
+	hw->regs[1] = 0xFF;
+
+	/* Verify configuration options */
+	i = get_option_index(s502a_dpmbase_options, virt_to_phys(hw->dpmbase));
+	if (i == 0)
+		return -EINVAL;
+
+	tmp = s502a_hmcr[i - 1];
+	switch (hw->dpmsize) {
+	case 0x2000:
+		tmp |= 0x01;
+		break;
+
+	case 0x10000L:
+		break;
+
+	default:
+		return -EINVAL;
+	}
+
+	/* Setup dual-port memory window (this also enables memory access) */
+	_OUTB(port + 1, tmp);
+	hw->regs[1] = tmp;
+	hw->regs[0] = 0x08;
+	return 0;
+}
+
+/*============================================================================
+ * Initialize S502E adapter.
+ */
+static int init_s502e (sdlahw_t* hw)
+{
+	unsigned port = hw->port;
+	int tmp, i;
+
+	if (!detect_s502e(port))
+		return -ENODEV;
+
+	/* Verify configuration options */
+	i = get_option_index(s508_dpmbase_options, virt_to_phys(hw->dpmbase));
+	if (i == 0)
+		return -EINVAL;
+
+	tmp = s502e_hmcr[i - 1];
+	switch (hw->dpmsize) {
+	case 0x2000:
+		tmp |= 0x01;
+		break;
+
+	case 0x10000L:
+		break;
+
+	default:
+		return -EINVAL;
+	}
+
+	/* Setup dual-port memory window */
+	_OUTB(port + 1, tmp);
+	hw->regs[1] = tmp;
+
+	/* Enable memory access */
+	_OUTB(port, 0x02);
+	hw->regs[0] = 0x02;
+	for (i = 0; i < SDLA_IODELAY; ++i);	/* delay */
+	return (_INB(port) & 0x02) ? 0 : -EIO;
+}
+
+/*============================================================================
+ * Initialize S503 adapter.
+ * ---------------------------------------------------------------------------
+ */
+static int init_s503 (sdlahw_t* hw)
+{
+	unsigned port = hw->port;
+	int tmp, i;
+
+	if (!detect_s503(port))
+		return -ENODEV;
+
+	/* Verify configuration options */
+	i = get_option_index(s508_dpmbase_options, virt_to_phys(hw->dpmbase));
+	if (i == 0)
+		return -EINVAL;
+
+	tmp = s502e_hmcr[i - 1];
+	switch (hw->dpmsize) {
+	case 0x2000:
+		tmp |= 0x01;
+		break;
+
+	case 0x10000L:
+		break;
+
+	default:
+		return -EINVAL;
+	}
+
+	/* Setup dual-port memory window */
+	_OUTB(port + 1, tmp);
+	hw->regs[1] = tmp;
+
+	/* Enable memory access */
+	_OUTB(port, 0x02);
+	hw->regs[0] = 0x02;	/* update mirror */
+	return 0;
+}
+
+/*============================================================================
+ * Initialize S507 adapter.
+ */
+static int init_s507 (sdlahw_t* hw)
+{
+	unsigned port = hw->port;
+	int tmp, i;
+
+	if (!detect_s507(port))
+		return -ENODEV;
+
+	/* Verify configuration options */
+	i = get_option_index(s507_dpmbase_options, virt_to_phys(hw->dpmbase));
+	if (i == 0)
+		return -EINVAL;
+
+	tmp = s507_hmcr[i - 1];
+	switch (hw->dpmsize) {
+	case 0x2000:
+		tmp |= 0x01;
+		break;
+
+	case 0x10000L:
+		break;
+
+	default:
+		return -EINVAL;
+	}
+
+	/* Enable adapter's logic */
+	_OUTB(port, 0x01);
+	hw->regs[0] = 0x01;
+	for (i = 0; i < SDLA_IODELAY; ++i);	/* delay */
+	if (!(_INB(port) & 0x20))
+		return -EIO;
+
+	/* Setup dual-port memory window */
+	_OUTB(port + 1, tmp);
+	hw->regs[1] = tmp;
+
+	/* Enable memory access */
+	tmp = hw->regs[0] | 0x04;
+	if (hw->irq) {
+		i = get_option_index(s508_irq_options, hw->irq);
+		if (i) tmp |= s507_irqmask[i - 1];
+	}
+	_OUTB(port, tmp);
+	hw->regs[0] = tmp;		/* update mirror */
+	for (i = 0; i < SDLA_IODELAY; ++i);	/* delay */
+	return (_INB(port) & 0x08) ? 0 : -EIO;
+}
+
+/*============================================================================
+ * Initialize S508 adapter.
+ */
+static int init_s508 (sdlahw_t* hw)
+{
+	unsigned port = hw->port;
+	int tmp, i;
+
+	if (!detect_s508(port))
+		return -ENODEV;
+
+	/* Verify configuration options */
+	i = get_option_index(s508_dpmbase_options, virt_to_phys(hw->dpmbase));
+	if (i == 0)
+		return -EINVAL;
+
+	/* Setup memory configuration */
+	tmp = s508_hmcr[i - 1];
+	_OUTB(port + 1, tmp);
+	hw->regs[1] = tmp;
+
+	/* Enable memory access */
+	_OUTB(port, 0x04);
+	hw->regs[0] = 0x04;		/* update mirror */
+	for (i = 0; i < SDLA_IODELAY; ++i);	/* delay */
+	return (_INB(port + 1) & 0x04) ? 0 : -EIO;
+}
+
+/*============================================================================
+ * Detect S502A adapter.
+ *	Following tests are used to detect S502A adapter:
+ *	1. All registers other than status (BASE) should read 0xFF
+ *	2. After writing 00001000b to control register, status register should
+ *	   read 01000000b.
+ *	3. After writing 0 to control register, status register should still
+ *	   read  01000000b.
+ *	4. After writing 00000100b to control register, status register should
+ *	   read 01000100b.
+ *	Return 1 if detected o.k. or 0 if failed.
+ *	Note:	This test is destructive! Adapter will be left in shutdown
+ *		state after the test.
+ */
+static int detect_s502a (int port)
+{
+	int i, j;
+
+	if (!get_option_index(s502_port_options, port))
+		return 0;
+	
+	for (j = 1; j < SDLA_MAXIORANGE; ++j) {
+		if (_INB(port + j) != 0xFF)
+			return 0;
+		for (i = 0; i < SDLA_IODELAY; ++i);	/* delay */
+	}
+
+	_OUTB(port, 0x08);			/* halt CPU */
+	_OUTB(port, 0x08);
+	_OUTB(port, 0x08);
+	for (i = 0; i < SDLA_IODELAY; ++i);	/* delay */
+	if (_INB(port) != 0x40)
+		return 0;
+	_OUTB(port, 0x00);
+	for (i = 0; i < SDLA_IODELAY; ++i);	/* delay */
+	if (_INB(port) != 0x40)
+		return 0;
+	_OUTB(port, 0x04);
+	for (i = 0; i < SDLA_IODELAY; ++i);	/* delay */
+	if (_INB(port) != 0x44)
+		return 0;
+
+	/* Reset adapter */
+	_OUTB(port, 0x08);
+	_OUTB(port, 0x08);
+	_OUTB(port, 0x08);
+	_OUTB(port + 1, 0xFF);
+	return 1;
+}
+
+/*============================================================================
+ * Detect S502E adapter.
+ *	Following tests are used to verify adapter presence:
+ *	1. All registers other than status (BASE) should read 0xFF.
+ *	2. After writing 0 to CPU control register (BASE+3), status register
+ *	   (BASE) should read 11111000b.
+ *	3. After writing 00000100b to port BASE (set bit 2), status register
+ *	   (BASE) should read 11111100b.
+ *	Return 1 if detected o.k. or 0 if failed.
+ *	Note:	This test is destructive! Adapter will be left in shutdown
+ *		state after the test.
+ */
+static int detect_s502e (int port)
+{
+	int i, j;
+
+	if (!get_option_index(s502_port_options, port))
+		return 0;
+	for (j = 1; j < SDLA_MAXIORANGE; ++j) {
+		if (_INB(port + j) != 0xFF)
+			return 0;
+		for (i = 0; i < SDLA_IODELAY; ++i);	/* delay */
+	}
+
+	_OUTB(port + 3, 0);			/* CPU control reg. */
+	for (i = 0; i < SDLA_IODELAY; ++i);	/* delay */
+	if (_INB(port) != 0xF8)			/* read status */
+		return 0;
+	_OUTB(port, 0x04);			/* set bit 2 */
+	for (i = 0; i < SDLA_IODELAY; ++i);	/* delay */
+	if (_INB(port) != 0xFC)			/* verify */
+		return 0;
+
+	/* Reset adapter */
+	_OUTB(port, 0);
+	return 1;
+}
+
+/*============================================================================
+ * Detect s503 adapter.
+ *	Following tests are used to verify adapter presence:
+ *	1. All registers other than status (BASE) should read 0xFF.
+ *	2. After writing 0 to control register (BASE), status register (BASE)
+ *	   should read 11110000b.
+ *	3. After writing 00000100b (set bit 2) to control register (BASE),
+ *	   status register should read 11110010b.
+ *	Return 1 if detected o.k. or 0 if failed.
+ *	Note:	This test is destructive! Adapter will be left in shutdown
+ *		state after the test.
+ */
+static int detect_s503 (int port)
+{
+	int i, j;
+
+	if (!get_option_index(s503_port_options, port))
+		return 0;
+	for (j = 1; j < SDLA_MAXIORANGE; ++j) {
+		if (_INB(port + j) != 0xFF)
+			return 0;
+		for (i = 0; i < SDLA_IODELAY; ++i);	/* delay */
+	}
+
+	_OUTB(port, 0);				/* reset control reg.*/
+	for (i = 0; i < SDLA_IODELAY; ++i);	/* delay */
+	if (_INB(port) != 0xF0)			/* read status */
+		return 0;
+	_OUTB(port, 0x04);			/* set bit 2 */
+	for (i = 0; i < SDLA_IODELAY; ++i);	/* delay */
+	if (_INB(port) != 0xF2)			/* verify */
+		return 0;
+
+	/* Reset adapter */
+	_OUTB(port, 0);
+	return 1;
+}
+
+/*============================================================================
+ * Detect s507 adapter.
+ *	Following tests are used to detect s507 adapter:
+ *	1. All ports should read the same value.
+ *	2. After writing 0x00 to control register, status register should read
+ *	   ?011000?b.
+ *	3. After writing 0x01 to control register, status register should read
+ *	   ?011001?b.
+ *	Return 1 if detected o.k. or 0 if failed.
+ *	Note:	This test is destructive! Adapter will be left in shutdown
+ *		state after the test.
+ */
+static int detect_s507 (int port)
+{
+	int tmp, i, j;
+
+	if (!get_option_index(s508_port_options, port))
+		return 0;
+	tmp = _INB(port);
+	for (j = 1; j < S507_IORANGE; ++j) {
+		if (_INB(port + j) != tmp)
+			return 0;
+		for (i = 0; i < SDLA_IODELAY; ++i);	/* delay */
+	}
+
+	_OUTB(port, 0x00);
+	for (i = 0; i < SDLA_IODELAY; ++i);	/* delay */
+	if ((_INB(port) & 0x7E) != 0x30)
+		return 0;
+	_OUTB(port, 0x01);
+	for (i = 0; i < SDLA_IODELAY; ++i);	/* delay */
+	if ((_INB(port) & 0x7E) != 0x32)
+		return 0;
+
+	/* Reset adapter */
+	_OUTB(port, 0x00);
+	return 1;
+}
+
+/*============================================================================
+ * Detect s508 adapter.
+ *	Following tests are used to detect s508 adapter:
+ *	1. After writing 0x00 to control register, status register should read
+ *	   ??000000b.
+ *	2. After writing 0x10 to control register, status register should read
+ *	   ??010000b
+ *	Return 1 if detected o.k. or 0 if failed.
+ *	Note:	This test is destructive! Adapter will be left in shutdown
+ *		state after the test.
+ */
+static int detect_s508 (int port)
+{
+	int i;
+
+	if (!get_option_index(s508_port_options, port))
+		return 0;
+	_OUTB(port, 0x00);
+	for (i = 0; i < SDLA_IODELAY; ++i);	/* delay */
+	if ((_INB(port + 1) & 0x3F) != 0x00)
+		return 0;
+	_OUTB(port, 0x10);
+	for (i = 0; i < SDLA_IODELAY; ++i);	/* delay */
+	if ((_INB(port + 1) & 0x3F) != 0x10)
+		return 0;
+
+	/* Reset adapter */
+	_OUTB(port, 0x00);
+	return 1;
+}
+
+/*============================================================================
+ * Detect s514 PCI adapter.
+ *      Return 1 if detected o.k. or 0 if failed.
+ *      Note:   This test is destructive! Adapter will be left in shutdown
+ *              state after the test.
+ */
+static int detect_s514 (sdlahw_t* hw)
+{
+	unsigned char CPU_no, slot_no, auto_slot_cfg;
+	int number_S514_cards = 0;
+	u32 S514_mem_base_addr = 0;
+	u32 ut_u32;
+	struct pci_dev *pci_dev;
+
+
+#ifndef CONFIG_PCI
+        printk(KERN_INFO "%s: Linux not compiled for PCI usage!\n", modname);
+        return 0;
+#endif
+
+	/*
+	The 'setup()' procedure in 'sdlamain.c' passes the CPU number and the
+	slot number defined in 'router.conf' via the 'port' definition.
+	*/
+	CPU_no = hw->S514_cpu_no[0];
+	slot_no = hw->S514_slot_no;
+	auto_slot_cfg = hw->auto_pci_cfg;
+
+	if (auto_slot_cfg){
+		printk(KERN_INFO "%s: srch... S514 card, CPU %c, Slot=Auto\n",
+		modname, CPU_no);
+
+	}else{
+		printk(KERN_INFO "%s: srch... S514 card, CPU %c, Slot #%d\n",
+		modname, CPU_no, slot_no);
+	}
+	
+	/* check to see that CPU A or B has been selected in 'router.conf' */
+	switch(CPU_no) {
+		case S514_CPU_A:
+		case S514_CPU_B:
+			break;
+	
+		default:
+			printk(KERN_INFO "%s: S514 CPU definition invalid.\n", 
+				modname);
+			printk(KERN_INFO "Must be 'A' or 'B'\n");
+			return 0;
+	}
+
+	number_S514_cards = find_s514_adapter(hw, 0);
+	if(!number_S514_cards)
+		return 0;
+
+	/* we are using a single S514 adapter with a slot of 0 so re-read the */	
+	/* location of this adapter */
+	if((number_S514_cards == 1) && auto_slot_cfg) {	
+        	number_S514_cards = find_s514_adapter(hw, 1);
+		if(!number_S514_cards) {
+			printk(KERN_INFO "%s: Error finding PCI card\n",
+				modname);
+			return 0;
+		}
+	}
+
+	pci_dev = hw->pci_dev;
+	/* read the physical memory base address */
+	S514_mem_base_addr = (CPU_no == S514_CPU_A) ? 
+		(pci_dev->resource[1].start) :
+		(pci_dev->resource[2].start);
+	
+	printk(KERN_INFO "%s: S514 PCI memory at 0x%X\n",
+		modname, S514_mem_base_addr);
+	if(!S514_mem_base_addr) {
+		if(CPU_no == S514_CPU_B)
+			printk(KERN_INFO "%s: CPU #B not present on the card\n", 				modname);
+		else
+			printk(KERN_INFO "%s: No PCI memory allocated to card\n",				modname);
+		return 0;
+	}
+
+	/* enable the PCI memory */
+	pci_read_config_dword(pci_dev, 
+		(CPU_no == S514_CPU_A) ? PCI_MAP0_DWORD : PCI_MAP1_DWORD,
+		&ut_u32);
+	pci_write_config_dword(pci_dev,
+		(CPU_no == S514_CPU_A) ? PCI_MAP0_DWORD : PCI_MAP1_DWORD,
+		(ut_u32 | PCI_MEMORY_ENABLE));
+
+	/* check the IRQ allocated and enable IRQ usage */
+	if(!(hw->irq = pci_dev->irq)) {
+		printk(KERN_INFO "%s: IRQ not allocated to S514 adapter\n",
+			modname);
+                return 0;
+	}
+
+	/* BUG FIX : Mar 6 2000
+ 	 * On a initial loading of the card, we must check
+         * and clear PCI interrupt bits, due to a reset
+         * problem on some other boards.  i.e. An interrupt
+         * might be pending, even after system bootup, 
+         * in which case, when starting wanrouter the machine
+         * would crash. 
+	 */
+	if (init_pci_slot(hw))
+		return 0;
+
+        pci_read_config_dword(pci_dev, PCI_INT_CONFIG, &ut_u32);
+        ut_u32 |= (CPU_no == S514_CPU_A) ?
+                PCI_ENABLE_IRQ_CPU_A : PCI_ENABLE_IRQ_CPU_B;
+        pci_write_config_dword(pci_dev, PCI_INT_CONFIG, ut_u32);
+
+	printk(KERN_INFO "%s: IRQ %d allocated to the S514 card\n",
+		modname, hw->irq);
+
+	/* map the physical PCI memory to virtual memory */
+	(void *)hw->dpmbase = ioremap((unsigned long)S514_mem_base_addr,
+		(unsigned long)MAX_SIZEOF_S514_MEMORY);
+    	/* map the physical control register memory to virtual memory */
+	hw->vector = (unsigned long)ioremap(
+		(unsigned long)(S514_mem_base_addr + S514_CTRL_REG_BYTE),
+		(unsigned long)16);
+     
+        if(!hw->dpmbase || !hw->vector) {
+		printk(KERN_INFO "%s: PCI virtual memory allocation failed\n",
+			modname);
+                return 0;
+	}
+
+	/* halt the adapter */
+	writeb (S514_CPU_HALT, hw->vector);	
+
+	return 1;
+}
+
+/*============================================================================
+ * Find the S514 PCI adapter in the PCI bus.
+ *      Return the number of S514 adapters found (0 if no adapter found).
+ */
+static int find_s514_adapter(sdlahw_t* hw, char find_first_S514_card)
+{
+        unsigned char slot_no;
+        int number_S514_cards = 0;
+	char S514_found_in_slot = 0;
+        u16 PCI_subsys_vendor;
+
+        struct pci_dev *pci_dev = NULL;
+ 
+       slot_no = hw->S514_slot_no;
+  
+	while ((pci_dev = pci_find_device(V3_VENDOR_ID, V3_DEVICE_ID, pci_dev))
+        	!= NULL) {
+                
+		pci_read_config_word(pci_dev, PCI_SUBSYS_VENDOR_WORD,
+                        &PCI_subsys_vendor);
+                
+		if(PCI_subsys_vendor != SANGOMA_SUBSYS_VENDOR)
+                	continue;
+        	
+		hw->pci_dev = pci_dev;
+		
+		if(find_first_S514_card)
+			return(1);
+		
+                number_S514_cards ++;
+                
+		printk(KERN_INFO
+			"%s: S514 card found, slot #%d (devfn 0x%X)\n",
+                        modname, ((pci_dev->devfn >> 3) & PCI_DEV_SLOT_MASK),
+			pci_dev->devfn);
+		
+		if (hw->auto_pci_cfg){
+			hw->S514_slot_no = ((pci_dev->devfn >> 3) & PCI_DEV_SLOT_MASK);
+			slot_no = hw->S514_slot_no;
+			
+		}else if (((pci_dev->devfn >> 3) & PCI_DEV_SLOT_MASK) == slot_no){
+                        S514_found_in_slot = 1;
+                        break;
+                }
+        }
+
+	/* if no S514 adapter has been found, then exit */
+        if (!number_S514_cards) {
+                printk(KERN_INFO "%s: Error, no S514 adapters found\n", modname);
+                return 0;
+        }
+        /* if more than one S514 card has been found, then the user must have */        /* defined a slot number so that the correct adapter is used */
+        else if ((number_S514_cards > 1) && hw->auto_pci_cfg) {
+                printk(KERN_INFO "%s: Error, PCI Slot autodetect Failed! \n"
+				 "%s:        More than one S514 adapter found.\n"
+				 "%s:        Disable the Autodetect feature and supply\n"
+				 "%s:        the PCISLOT numbers for each card.\n",
+                        modname,modname,modname,modname);
+                return 0;
+        }
+        /* if the user has specified a slot number and the S514 adapter has */
+        /* not been found in that slot, then exit */
+        else if (!hw->auto_pci_cfg && !S514_found_in_slot) {
+                printk(KERN_INFO
+			"%s: Error, S514 card not found in specified slot #%d\n",
+                        modname, slot_no);
+                return 0;
+        }
+
+	return (number_S514_cards);
+}
+
+
+
+/******* Miscellaneous ******************************************************/
+
+/*============================================================================
+ * Calibrate SDLA memory access delay.
+ * Count number of idle loops made within 1 second and then calculate the
+ * number of loops that should be made to achive desired delay.
+ */
+static int calibrate_delay (int mks)
+{
+	unsigned int delay;
+	unsigned long stop;
+
+	for (delay = 0, stop = SYSTEM_TICK + HZ; SYSTEM_TICK < stop; ++delay);
+	return (delay/(1000000L/mks) + 1);
+}
+
+/*============================================================================
+ * Get option's index into the options list.
+ *	Return option's index (1 .. N) or zero if option is invalid.
+ */
+static int get_option_index (unsigned* optlist, unsigned optval)
+{
+	int i;
+
+	for (i = 1; i <= optlist[0]; ++i)
+		if ( optlist[i] == optval)
+			return i;
+	return 0;
+}
+
+/*============================================================================
+ * Check memory region to see if it's available. 
+ * Return:	0	ok.
+ */
+static unsigned check_memregion (void* ptr, unsigned len)
+{
+	volatile unsigned char* p = ptr;
+
+        for (; len && (readb (p) == 0xFF); --len, ++p) {
+                writeb (0, p);          /* attempt to write 0 */
+                if (readb(p) != 0xFF) { /* still has to read 0xFF */
+                        writeb (0xFF, p);/* restore original value */
+                        break;          /* not good */
+                }
+        }
+
+	return len;
+}
+
+/*============================================================================
+ * Test memory region.
+ * Return:	size of the region that passed the test.
+ * Note:	Region size must be multiple of 2 !
+ */
+static unsigned test_memregion (void* ptr, unsigned len)
+{
+	volatile unsigned short* w_ptr;
+	unsigned len_w = len >> 1;	/* region len in words */
+	unsigned i;
+
+        for (i = 0, w_ptr = ptr; i < len_w; ++i, ++w_ptr)
+                writew (0xAA55, w_ptr);
+        
+	for (i = 0, w_ptr = ptr; i < len_w; ++i, ++w_ptr)
+                if (readw (w_ptr) != 0xAA55) {
+                        len_w = i;
+                        break;
+                }
+
+        for (i = 0, w_ptr = ptr; i < len_w; ++i, ++w_ptr)
+                writew (0x55AA, w_ptr);
+        
+        for (i = 0, w_ptr = ptr; i < len_w; ++i, ++w_ptr)
+                if (readw(w_ptr) != 0x55AA) {
+                        len_w = i;
+                        break;
+                }
+        
+        for (i = 0, w_ptr = ptr; i < len_w; ++i, ++w_ptr)
+		writew (0, w_ptr);
+
+        return len_w << 1;
+}
+
+/*============================================================================
+ * Calculate 16-bit CRC using CCITT polynomial.
+ */
+static unsigned short checksum (unsigned char* buf, unsigned len)
+{
+	unsigned short crc = 0;
+	unsigned mask, flag;
+
+	for (; len; --len, ++buf) {
+		for (mask = 0x80; mask; mask >>= 1) {
+			flag = (crc & 0x8000);
+			crc <<= 1;
+			crc |= ((*buf & mask) ? 1 : 0);
+			if (flag) crc ^= 0x1021;
+		}
+	}
+	return crc;
+}
+
+static int init_pci_slot(sdlahw_t *hw)
+{
+
+	u32 int_status;
+	int volatile found=0;
+	int i=0;
+
+	/* Check if this is a very first load for a specific
+         * pci card. If it is, clear the interrput bits, and
+         * set the flag indicating that this card was initialized.
+	 */
+	
+	for (i=0; (i<MAX_S514_CARDS) && !found; i++){
+		if (pci_slot_ar[i] == hw->S514_slot_no){
+			found=1;
+			break;
+		}
+		if (pci_slot_ar[i] == 0xFF){
+			break;
+		}
+	}
+
+	if (!found){
+		read_S514_int_stat(hw,&int_status);
+		S514_intack(hw,int_status);
+		if (i == MAX_S514_CARDS){
+			printk(KERN_INFO "%s: Critical Error !!!\n",modname);
+			printk(KERN_INFO 
+				"%s: Number of Sangoma PCI cards exceeded maximum limit.\n",
+					modname);
+			printk(KERN_INFO "Please contact Sangoma Technologies\n");
+			return 1;
+		}
+		pci_slot_ar[i] = hw->S514_slot_no;
+	}
+	return 0;
+}
+
+static int pci_probe(sdlahw_t *hw)
+{
+
+        unsigned char slot_no;
+        int number_S514_cards = 0;
+        u16 PCI_subsys_vendor;
+	u16 PCI_card_type;
+
+        struct pci_dev *pci_dev = NULL;
+	struct pci_bus *bus = NULL;
+ 
+       slot_no = 0;
+  
+	while ((pci_dev = pci_find_device(V3_VENDOR_ID, V3_DEVICE_ID, pci_dev))
+        	!= NULL) {
+		
+                pci_read_config_word(pci_dev, PCI_SUBSYS_VENDOR_WORD,
+                        &PCI_subsys_vendor);
+		
+                if(PCI_subsys_vendor != SANGOMA_SUBSYS_VENDOR)
+                	continue;
+
+		pci_read_config_word(pci_dev, PCI_CARD_TYPE,
+                        &PCI_card_type);
+	
+		bus = pci_dev->bus;
+		
+		/* A dual cpu card can support up to 4 physical connections,
+		 * where a single cpu card can support up to 2 physical
+		 * connections.  The FT1 card can only support a single 
+		 * connection, however we cannot distinguish between a Single
+		 * CPU card and an FT1 card. */
+		if (PCI_card_type == S514_DUAL_CPU){
+                	number_S514_cards += 4;
+			 printk(KERN_INFO
+				"wanpipe: S514-PCI card found, cpu(s) 2, bus #%d, slot #%d, irq #%d\n",
+                        	bus->number,((pci_dev->devfn >> 3) & PCI_DEV_SLOT_MASK),
+				pci_dev->irq);
+		}else{
+			number_S514_cards += 2;
+			printk(KERN_INFO
+				"wanpipe: S514-PCI card found, cpu(s) 1, bus #%d, slot #%d, irq #%d\n",
+                        	bus->number,((pci_dev->devfn >> 3) & PCI_DEV_SLOT_MASK),
+				pci_dev->irq);
+		}
+        }
+
+	return number_S514_cards;
+
+}
+
+
+
+EXPORT_SYMBOL(wanpipe_hw_probe);
+
+unsigned wanpipe_hw_probe(void)
+{
+	sdlahw_t hw;
+	unsigned* opt = s508_port_options; 
+	unsigned cardno=0;
+	int i;
+	
+	memset(&hw, 0, sizeof(hw));
+	
+	for (i = 1; i <= opt[0]; i++) {
+		if (detect_s508(opt[i])){
+			/* S508 card can support up to two physical links */
+			cardno+=2;
+			printk(KERN_INFO "wanpipe: S508-ISA card found, port 0x%x\n",opt[i]);
+		}
+	}
+
+      #ifdef CONFIG_PCI
+	hw.S514_slot_no = 0;
+	cardno += pci_probe(&hw);
+      #else
+	printk(KERN_INFO "wanpipe: Warning, Kernel not compiled for PCI support!\n");
+	printk(KERN_INFO "wanpipe: PCI Hardware Probe Failed!\n");
+      #endif
+
+	return cardno;
+}
+
+/****** End *****************************************************************/
diff --git a/drivers/net/wan/sdlamain.c b/drivers/net/wan/sdlamain.c
new file mode 100644
index 0000000..74e151a
--- /dev/null
+++ b/drivers/net/wan/sdlamain.c
@@ -0,0 +1,1341 @@
+/****************************************************************************
+* sdlamain.c	WANPIPE(tm) Multiprotocol WAN Link Driver.  Main module.
+*
+* Author:	Nenad Corbic	<ncorbic@sangoma.com>
+*		Gideon Hack	
+*
+* Copyright:	(c) 1995-2000 Sangoma Technologies Inc.
+*
+*		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.
+* ============================================================================
+* Dec 22, 2000  Nenad Corbic	Updated for 2.4.X kernels.
+* 				Removed the polling routine.
+* Nov 13, 2000  Nenad Corbic	Added hw probing on module load and dynamic
+* 				device allocation. 
+* Nov 7,  2000  Nenad Corbic	Fixed the Multi-Port PPP for kernels
+*                               2.2.16 and above.
+* Aug 2,  2000  Nenad Corbic	Block the Multi-Port PPP from running on
+*  			        kernels 2.2.16 or greater.  The SyncPPP 
+*  			        has changed.
+* Jul 25, 2000  Nenad Corbic	Updated the Piggiback support for MultPPPP.
+* Jul 13, 2000	Nenad Corbic	Added Multi-PPP support.
+* Feb 02, 2000  Nenad Corbic    Fixed up piggyback probing and selection.
+* Sep 23, 1999  Nenad Corbic    Added support for SMP
+* Sep 13, 1999  Nenad Corbic	Each port is treated as a separate device.
+* Jun 02, 1999  Gideon Hack     Added support for the S514 adapter.
+*				Updates for Linux 2.2.X kernels.
+* Sep 17, 1998	Jaspreet Singh	Updated for 2.1.121+ kernel
+* Nov 28, 1997	Jaspreet Singh	Changed DRV_RELEASE to 1
+* Nov 10, 1997	Jaspreet Singh	Changed sti() to restore_flags();
+* Nov 06, 1997 	Jaspreet Singh	Changed DRV_VERSION to 4 and DRV_RELEASE to 0
+* Oct 20, 1997 	Jaspreet Singh	Modified sdla_isr routine so that card->in_isr
+*				assignments are taken out and placed in the
+*				sdla_ppp.c, sdla_fr.c and sdla_x25.c isr
+*				routines. Took out 'wandev->tx_int_enabled' and
+*				replaced it with 'wandev->enable_tx_int'. 
+* May 29, 1997	Jaspreet Singh	Flow Control Problem
+*				added "wandev->tx_int_enabled=1" line in the
+*				init module. This line initializes the flag for 
+*				preventing Interrupt disabled with device set to
+*				busy
+* Jan 15, 1997	Gene Kozin	Version 3.1.0
+*				 o added UDP management stuff
+* Jan 02, 1997	Gene Kozin	Initial version.
+*****************************************************************************/
+
+#include <linux/config.h>	/* OS configuration options */
+#include <linux/stddef.h>	/* offsetof(), etc. */
+#include <linux/errno.h>	/* return codes */
+#include <linux/string.h>	/* inline memset(), etc. */
+#include <linux/init.h>
+#include <linux/slab.h>	/* kmalloc(), kfree() */
+#include <linux/kernel.h>	/* printk(), and other useful stuff */
+#include <linux/module.h>	/* support for loadable modules */
+#include <linux/ioport.h>	/* request_region(), release_region() */
+#include <linux/wanrouter.h>	/* WAN router definitions */
+#include <linux/wanpipe.h>	/* WANPIPE common user API definitions */
+
+#include <linux/in.h>
+#include <asm/io.h>		/* phys_to_virt() */
+#include <linux/pci.h>
+#include <linux/sdlapci.h>
+#include <linux/if_wanpipe_common.h>
+
+#include <asm/uaccess.h>	/* kernel <-> user copy */
+#include <linux/inetdevice.h>
+
+#include <linux/ip.h>
+#include <net/route.h>
+ 
+#define KMEM_SAFETYZONE 8
+
+
+#ifndef CONFIG_WANPIPE_FR
+  #define wpf_init(a,b) (-EPROTONOSUPPORT) 
+#endif
+
+#ifndef CONFIG_WANPIPE_CHDLC
+ #define wpc_init(a,b) (-EPROTONOSUPPORT) 
+#endif
+
+#ifndef CONFIG_WANPIPE_X25
+ #define wpx_init(a,b) (-EPROTONOSUPPORT) 
+#endif
+ 
+#ifndef CONFIG_WANPIPE_PPP
+ #define wpp_init(a,b) (-EPROTONOSUPPORT) 
+#endif
+
+#ifndef CONFIG_WANPIPE_MULTPPP 
+ #define wsppp_init(a,b) (-EPROTONOSUPPORT) 
+#endif
+ 
+ 
+/***********FOR DEBUGGING PURPOSES*********************************************
+static void * dbg_kmalloc(unsigned int size, int prio, int line) {
+	int i = 0;
+	void * v = kmalloc(size+sizeof(unsigned int)+2*KMEM_SAFETYZONE*8,prio);
+	char * c1 = v;	
+	c1 += sizeof(unsigned int);
+	*((unsigned int *)v) = size;
+
+	for (i = 0; i < KMEM_SAFETYZONE; i++) {
+		c1[0] = 'D'; c1[1] = 'E'; c1[2] = 'A'; c1[3] = 'D';
+		c1[4] = 'B'; c1[5] = 'E'; c1[6] = 'E'; c1[7] = 'F';
+		c1 += 8;
+	}
+	c1 += size;
+	for (i = 0; i < KMEM_SAFETYZONE; i++) {
+		c1[0] = 'M'; c1[1] = 'U'; c1[2] = 'N'; c1[3] = 'G';
+		c1[4] = 'W'; c1[5] = 'A'; c1[6] = 'L'; c1[7] = 'L';
+		c1 += 8;
+	}
+	v = ((char *)v) + sizeof(unsigned int) + KMEM_SAFETYZONE*8;
+	printk(KERN_INFO "line %d  kmalloc(%d,%d) = %p\n",line,size,prio,v);
+	return v;
+}
+static void dbg_kfree(void * v, int line) {
+	unsigned int * sp = (unsigned int *)(((char *)v) - (sizeof(unsigned int) + KMEM_SAFETYZONE*8));
+	unsigned int size = *sp;
+	char * c1 = ((char *)v) - KMEM_SAFETYZONE*8;
+	int i = 0;
+	for (i = 0; i < KMEM_SAFETYZONE; i++) {
+		if (   c1[0] != 'D' || c1[1] != 'E' || c1[2] != 'A' || c1[3] != 'D'
+		    || c1[4] != 'B' || c1[5] != 'E' || c1[6] != 'E' || c1[7] != 'F') {
+			printk(KERN_INFO "kmalloced block at %p has been corrupted (underrun)!\n",v);
+			printk(KERN_INFO " %4x: %2x %2x %2x %2x %2x %2x %2x %2x\n", i*8,
+			                c1[0],c1[1],c1[2],c1[3],c1[4],c1[5],c1[6],c1[7] );
+		}
+		c1 += 8;
+	}
+	c1 += size;
+	for (i = 0; i < KMEM_SAFETYZONE; i++) {
+		if (   c1[0] != 'M' || c1[1] != 'U' || c1[2] != 'N' || c1[3] != 'G'
+		    || c1[4] != 'W' || c1[5] != 'A' || c1[6] != 'L' || c1[7] != 'L'
+		   ) {
+			printk(KERN_INFO "kmalloced block at %p has been corrupted (overrun):\n",v);
+			printk(KERN_INFO " %4x: %2x %2x %2x %2x %2x %2x %2x %2x\n", i*8,
+			                c1[0],c1[1],c1[2],c1[3],c1[4],c1[5],c1[6],c1[7] );
+		}
+		c1 += 8;
+	}
+	printk(KERN_INFO "line %d  kfree(%p)\n",line,v);
+	v = ((char *)v) - (sizeof(unsigned int) + KMEM_SAFETYZONE*8);
+	kfree(v);
+}
+
+#define kmalloc(x,y) dbg_kmalloc(x,y,__LINE__)
+#define kfree(x) dbg_kfree(x,__LINE__)
+******************************************************************************/
+
+
+
+/****** Defines & Macros ****************************************************/
+
+#ifdef	_DEBUG_
+#define	STATIC
+#else
+#define	STATIC		static
+#endif
+
+#define	DRV_VERSION	5		/* version number */
+#define	DRV_RELEASE	0		/* release (minor version) number */
+#define	MAX_CARDS	16		/* max number of adapters */
+
+#ifndef	CONFIG_WANPIPE_CARDS		/* configurable option */
+#define	CONFIG_WANPIPE_CARDS 1
+#endif
+
+#define	CMD_OK		0		/* normal firmware return code */
+#define	CMD_TIMEOUT	0xFF		/* firmware command timed out */
+#define	MAX_CMD_RETRY	10		/* max number of firmware retries */
+/****** Function Prototypes *************************************************/
+
+extern void disable_irq(unsigned int);
+extern void enable_irq(unsigned int);
+ 
+/* WAN link driver entry points */
+static int setup(struct wan_device* wandev, wandev_conf_t* conf);
+static int shutdown(struct wan_device* wandev);
+static int ioctl(struct wan_device* wandev, unsigned cmd, unsigned long arg);
+
+/* IOCTL handlers */
+static int ioctl_dump	(sdla_t* card, sdla_dump_t* u_dump);
+static int ioctl_exec	(sdla_t* card, sdla_exec_t* u_exec, int);
+
+/* Miscellaneous functions */
+STATIC irqreturn_t sdla_isr	(int irq, void* dev_id, struct pt_regs *regs);
+static void release_hw  (sdla_t *card);
+
+static int check_s508_conflicts (sdla_t* card,wandev_conf_t* conf, int*);
+static int check_s514_conflicts (sdla_t* card,wandev_conf_t* conf, int*);
+
+
+/****** Global Data **********************************************************
+ * Note: All data must be explicitly initialized!!!
+ */
+
+/* private data */
+static char drvname[]	= "wanpipe";
+static char fullname[]	= "WANPIPE(tm) Multiprotocol Driver";
+static char copyright[]	= "(c) 1995-2000 Sangoma Technologies Inc.";
+static int ncards; 
+static sdla_t* card_array;		/* adapter data space */
+
+/* Wanpipe's own workqueue, used for all API's.
+ * All protocol specific tasks will be inserted
+ * into the "wanpipe_wq" workqueue. 
+
+ * The kernel workqueue mechanism will execute
+ * all pending tasks in the "wanpipe_wq" workqueue.
+ */
+
+struct workqueue_struct *wanpipe_wq;
+DECLARE_WORK(wanpipe_work, NULL, NULL);
+
+static int wanpipe_bh_critical;
+
+/******* Kernel Loadable Module Entry Points ********************************/
+
+/*============================================================================
+ * Module 'insert' entry point.
+ * o print announcement
+ * o allocate adapter data space
+ * o initialize static data
+ * o register all cards with WAN router
+ * o calibrate SDLA shared memory access delay.
+ *
+ * Return:	0	Ok
+ *		< 0	error.
+ * Context:	process
+ */
+ 
+static int __init wanpipe_init(void)
+{
+	int cnt, err = 0;
+
+	printk(KERN_INFO "%s v%u.%u %s\n",
+		fullname, DRV_VERSION, DRV_RELEASE, copyright);
+
+	wanpipe_wq = create_workqueue("wanpipe_wq");
+	if (!wanpipe_wq)
+		return -ENOMEM;
+
+	/* Probe for wanpipe cards and return the number found */
+	printk(KERN_INFO "wanpipe: Probing for WANPIPE hardware.\n");
+	ncards = wanpipe_hw_probe();
+	if (ncards){
+		printk(KERN_INFO "wanpipe: Allocating maximum %i devices: wanpipe%i - wanpipe%i.\n",ncards,1,ncards);
+	}else{
+		printk(KERN_INFO "wanpipe: No S514/S508 cards found, unloading modules!\n");
+		destroy_workqueue(wanpipe_wq);
+		return -ENODEV;
+	}
+	
+	/* Verify number of cards and allocate adapter data space */
+	card_array = kmalloc(sizeof(sdla_t) * ncards, GFP_KERNEL);
+	if (card_array == NULL) {
+		destroy_workqueue(wanpipe_wq);
+		return -ENOMEM;
+	}
+
+	memset(card_array, 0, sizeof(sdla_t) * ncards);
+
+	/* Register adapters with WAN router */
+	for (cnt = 0; cnt < ncards; ++ cnt) {
+		sdla_t* card = &card_array[cnt];
+		struct wan_device* wandev = &card->wandev;
+
+		card->next = NULL;
+		sprintf(card->devname, "%s%d", drvname, cnt + 1);
+		wandev->magic    = ROUTER_MAGIC;
+		wandev->name     = card->devname;
+		wandev->private  = card;
+		wandev->enable_tx_int = 0;
+		wandev->setup    = &setup;
+		wandev->shutdown = &shutdown;
+		wandev->ioctl    = &ioctl;
+		err = register_wan_device(wandev);
+		if (err) {
+			printk(KERN_INFO
+				"%s: %s registration failed with error %d!\n",
+				drvname, card->devname, err);
+			break;
+		}
+	}
+	if (cnt){
+		ncards = cnt;	/* adjust actual number of cards */
+	}else {
+		kfree(card_array);
+		destroy_workqueue(wanpipe_wq);
+		printk(KERN_INFO "IN Init Module: NO Cards registered\n");
+		err = -ENODEV;
+	}
+
+	return err;
+}
+
+/*============================================================================
+ * Module 'remove' entry point.
+ * o unregister all adapters from the WAN router
+ * o release all remaining system resources
+ */
+static void __exit wanpipe_cleanup(void)
+{
+	int i;
+
+	if (!ncards)
+		return;
+		
+	for (i = 0; i < ncards; ++i) {
+		sdla_t* card = &card_array[i];
+		unregister_wan_device(card->devname);
+	}
+	destroy_workqueue(wanpipe_wq);
+	kfree(card_array);
+
+	printk(KERN_INFO "\nwanpipe: WANPIPE Modules Unloaded.\n");
+}
+
+module_init(wanpipe_init);
+module_exit(wanpipe_cleanup);
+
+/******* WAN Device Driver Entry Points *************************************/
+
+/*============================================================================
+ * Setup/configure WAN link driver.
+ * o check adapter state
+ * o make sure firmware is present in configuration
+ * o make sure I/O port and IRQ are specified
+ * o make sure I/O region is available
+ * o allocate interrupt vector
+ * o setup SDLA hardware
+ * o call appropriate routine to perform protocol-specific initialization
+ * o mark I/O region as used
+ * o if this is the first active card, then schedule background task
+ *
+ * This function is called when router handles ROUTER_SETUP IOCTL. The
+ * configuration structure is in kernel memory (including extended data, if
+ * any).
+ */
+ 
+static int setup(struct wan_device* wandev, wandev_conf_t* conf)
+{
+	sdla_t* card;
+	int err = 0;
+	int irq=0;
+
+	/* Sanity checks */
+	if ((wandev == NULL) || (wandev->private == NULL) || (conf == NULL)){
+		printk(KERN_INFO 
+		      "%s: Failed Sdlamain Setup wandev %u, card %u, conf %u !\n",
+		      wandev->name,
+		      (unsigned int)wandev,(unsigned int)wandev->private,
+		      (unsigned int)conf); 
+		return -EFAULT;
+	}
+
+	printk(KERN_INFO "%s: Starting WAN Setup\n", wandev->name);
+
+	card = wandev->private;
+	if (wandev->state != WAN_UNCONFIGURED){
+		printk(KERN_INFO "%s: failed sdlamain setup, busy!\n",
+			wandev->name);
+		return -EBUSY;		/* already configured */
+	}
+
+	printk(KERN_INFO "\nProcessing WAN device %s...\n", wandev->name);
+
+	/* Initialize the counters for each wandev 
+	 * Used for counting number of times new_if and 
+         * del_if get called.
+	 */
+	wandev->del_if_cnt = 0;
+	wandev->new_if_cnt = 0;
+	wandev->config_id  = conf->config_id;
+
+	if (!conf->data_size || (conf->data == NULL)) {
+		printk(KERN_INFO
+			"%s: firmware not found in configuration data!\n",
+			wandev->name);
+		return -EINVAL;
+	}
+
+	/* Check for resource conflicts and setup the
+	 * card for piggibacking if necessary */
+	if(!conf->S514_CPU_no[0]) {
+		if ((err=check_s508_conflicts(card,conf,&irq)) != 0){
+			return err;
+		}
+	}else {
+		if ((err=check_s514_conflicts(card,conf,&irq)) != 0){
+			return err;
+		}
+	}
+
+	/* If the current card has already been configured
+         * or it's a piggyback card, do not try to allocate
+         * resources.
+	 */
+	if (!card->wandev.piggyback && !card->configured){
+
+		/* Configure hardware, load firmware, etc. */
+		memset(&card->hw, 0, sizeof(sdlahw_t));
+
+		/* for an S514 adapter, pass the CPU number and the slot number read */
+		/* from 'router.conf' to the 'sdla_setup()' function via the 'port' */
+		/* parameter */
+		if (conf->S514_CPU_no[0]){
+
+			card->hw.S514_cpu_no[0] = conf->S514_CPU_no[0];
+			card->hw.S514_slot_no = conf->PCI_slot_no;
+			card->hw.auto_pci_cfg = conf->auto_pci_cfg;
+
+			if (card->hw.auto_pci_cfg == WANOPT_YES){
+				printk(KERN_INFO "%s: Setting CPU to %c and Slot to Auto\n",
+				card->devname, card->hw.S514_cpu_no[0]);
+			}else{
+				printk(KERN_INFO "%s: Setting CPU to %c and Slot to %i\n",
+				card->devname, card->hw.S514_cpu_no[0], card->hw.S514_slot_no);
+			}
+
+		}else{
+			/* 508 Card io port and irq initialization */
+			card->hw.port = conf->ioport;
+			card->hw.irq = (conf->irq == 9) ? 2 : conf->irq;
+		}
+
+
+		/* Compute the virtual address of the card in kernel space */
+		if(conf->maddr){
+			card->hw.dpmbase = phys_to_virt(conf->maddr);
+		}else{	
+			card->hw.dpmbase = (void *)conf->maddr;
+		}
+			
+		card->hw.dpmsize = SDLA_WINDOWSIZE;
+		
+		/* set the adapter type if using an S514 adapter */
+		card->hw.type = (conf->S514_CPU_no[0]) ? SDLA_S514 : conf->hw_opt[0]; 
+		card->hw.pclk = conf->hw_opt[1];
+
+		err = sdla_setup(&card->hw, conf->data, conf->data_size);
+		if (err){
+			printk(KERN_INFO "%s: Hardware setup Failed %i\n",
+					card->devname,err);
+			return err;
+		}
+
+	        if(card->hw.type != SDLA_S514)
+			irq = (conf->irq == 2) ? 9 : conf->irq; /* IRQ2 -> IRQ9 */
+		else
+			irq = card->hw.irq;
+
+		/* request an interrupt vector - note that interrupts may be shared */
+		/* when using the S514 PCI adapter */
+		
+       		if(request_irq(irq, sdla_isr, 
+		      (card->hw.type == SDLA_S514) ? SA_SHIRQ : 0, 
+		       wandev->name, card)){
+
+			printk(KERN_INFO "%s: Can't reserve IRQ %d!\n", wandev->name, irq);
+			return -EINVAL;
+		}
+
+	}else{
+		printk(KERN_INFO "%s: Card Configured %lu or Piggybacking %i!\n",
+			wandev->name,card->configured,card->wandev.piggyback);
+	} 
+
+
+	if (!card->configured){
+
+		/* Initialize the Spin lock */
+		printk(KERN_INFO "%s: Initializing for SMP\n",wandev->name);
+
+		/* Piggyback spin lock has already been initialized,
+		 * in check_s514/s508_conflicts() */
+		if (!card->wandev.piggyback){
+			spin_lock_init(&card->wandev.lock);
+		}
+		
+		/* Intialize WAN device data space */
+		wandev->irq       = irq;
+		wandev->dma       = 0;
+		if(card->hw.type != SDLA_S514){ 
+			wandev->ioport = card->hw.port;
+		}else{
+			wandev->S514_cpu_no[0] = card->hw.S514_cpu_no[0];
+			wandev->S514_slot_no = card->hw.S514_slot_no;
+		}
+		wandev->maddr     = (unsigned long)card->hw.dpmbase;
+		wandev->msize     = card->hw.dpmsize;
+		wandev->hw_opt[0] = card->hw.type;
+		wandev->hw_opt[1] = card->hw.pclk;
+		wandev->hw_opt[2] = card->hw.memory;
+		wandev->hw_opt[3] = card->hw.fwid;
+	}
+
+	/* Protocol-specific initialization */
+	switch (card->hw.fwid) {
+
+	case SFID_X25_502:
+	case SFID_X25_508:
+		printk(KERN_INFO "%s: Starting X.25 Protocol Init.\n",
+				card->devname);
+		err = wpx_init(card, conf);
+		break;
+	case SFID_FR502:
+	case SFID_FR508:
+		printk(KERN_INFO "%s: Starting Frame Relay Protocol Init.\n",
+				card->devname);
+		err = wpf_init(card, conf);
+		break;
+	case SFID_PPP502:
+	case SFID_PPP508:
+		printk(KERN_INFO "%s: Starting PPP Protocol Init.\n",
+				card->devname);
+		err = wpp_init(card, conf);
+		break;
+		
+	case SFID_CHDLC508:
+	case SFID_CHDLC514:
+		if (conf->ft1){		
+			printk(KERN_INFO "%s: Starting FT1 CSU/DSU Config Driver.\n",
+				card->devname);
+			err = wpft1_init(card, conf);
+			break;
+			
+		}else if (conf->config_id == WANCONFIG_MPPP){
+			printk(KERN_INFO "%s: Starting Multi-Port PPP Protocol Init.\n",
+					card->devname);
+			err = wsppp_init(card,conf);
+			break;
+
+		}else{
+			printk(KERN_INFO "%s: Starting CHDLC Protocol Init.\n",
+					card->devname);
+			err = wpc_init(card, conf);
+			break;
+		}
+	default:
+		printk(KERN_INFO "%s: Error, Firmware is not supported %X %X!\n",
+			wandev->name,card->hw.fwid,SFID_CHDLC508);
+		err = -EPROTONOSUPPORT;
+	}
+
+	if (err != 0){
+		if (err == -EPROTONOSUPPORT){
+			printk(KERN_INFO 
+				"%s: Error, Protocol selected has not been compiled!\n",
+					card->devname);
+			printk(KERN_INFO 
+				"%s:        Re-configure the kernel and re-build the modules!\n",
+					card->devname);
+		}
+		
+		release_hw(card);
+		wandev->state = WAN_UNCONFIGURED;
+		return err;
+	}
+
+
+  	/* Reserve I/O region and schedule background task */
+        if(card->hw.type != SDLA_S514 && !card->wandev.piggyback)
+		if (!request_region(card->hw.port, card->hw.io_range, 
+				wandev->name)) {
+			printk(KERN_WARNING "port 0x%04x busy\n", card->hw.port);
+			release_hw(card);
+			wandev->state = WAN_UNCONFIGURED;
+			return -EBUSY;
+	  }
+
+	/* Only use the polling routine for the X25 protocol */
+	
+	card->wandev.critical=0;
+	return 0;
+}
+
+/*================================================================== 
+ * configure_s508_card
+ * 
+ * For a S508 adapter, check for a possible configuration error in that
+ * we are loading an adapter in the same IO port as a previously loaded S508
+ * card.
+ */ 
+
+static int check_s508_conflicts (sdla_t* card,wandev_conf_t* conf, int *irq)
+{
+	unsigned long smp_flags;
+	int i;
+	
+	if (conf->ioport <= 0) {
+		printk(KERN_INFO
+		"%s: can't configure without I/O port address!\n",
+		card->wandev.name);
+		return -EINVAL;
+	}
+
+	if (conf->irq <= 0) {
+		printk(KERN_INFO "%s: can't configure without IRQ!\n",
+		card->wandev.name);
+		return -EINVAL;
+	}
+
+	if (test_bit(0,&card->configured))
+		return 0;
+
+
+	/* Check for already loaded card with the same IO port and IRQ 
+	 * If found, copy its hardware configuration and use its
+	 * resources (i.e. piggybacking)
+	 */
+	
+	for (i = 0; i < ncards; i++) {
+		sdla_t *nxt_card = &card_array[i];
+
+		/* Skip the current card ptr */
+		if (nxt_card == card)	
+			continue;
+
+
+		/* Find a card that is already configured with the
+		 * same IO Port */
+		if ((nxt_card->hw.type == SDLA_S508) &&
+		    (nxt_card->hw.port == conf->ioport) && 
+		    (nxt_card->next == NULL)){
+			
+			/* We found a card the card that has same configuration
+			 * as us. This means, that we must setup this card in 
+			 * piggibacking mode. However, only CHDLC and MPPP protocol
+			 * support this setup */
+		
+			if ((conf->config_id == WANCONFIG_CHDLC || 
+			     conf->config_id == WANCONFIG_MPPP) &&
+			    (nxt_card->wandev.config_id == WANCONFIG_CHDLC || 
+			     nxt_card->wandev.config_id == WANCONFIG_MPPP)){ 
+				
+				*irq = nxt_card->hw.irq;
+				memcpy(&card->hw, &nxt_card->hw, sizeof(sdlahw_t));
+			
+				/* The master could already be running, we must
+				 * set this as a critical area */
+				lock_adapter_irq(&nxt_card->wandev.lock, &smp_flags);
+
+				nxt_card->next = card;
+				card->next = nxt_card;
+
+				card->wandev.piggyback = WANOPT_YES;
+
+				/* We must initialise the piggiback spin lock here
+				 * since isr will try to lock card->next if it
+				 * exists */
+				spin_lock_init(&card->wandev.lock);
+				
+				unlock_adapter_irq(&nxt_card->wandev.lock, &smp_flags);
+				break;
+			}else{
+				/* Trying to run piggibacking with a wrong protocol */
+				printk(KERN_INFO "%s: ERROR: Resource busy, ioport: 0x%x\n"
+						 "%s:        This protocol doesn't support\n"
+						 "%s:        multi-port operation!\n",
+						 card->devname,nxt_card->hw.port,
+						 card->devname,card->devname);
+				return -EEXIST;
+			}
+		}
+	}
+	
+
+	/* Make sure I/O port region is available only if we are the
+	 * master device.  If we are running in piggybacking mode, 
+	 * we will use the resources of the master card. */
+	if (!card->wandev.piggyback) {
+		struct resource *rr =
+			request_region(conf->ioport, SDLA_MAXIORANGE, "sdlamain");
+		release_region(conf->ioport, SDLA_MAXIORANGE);
+
+		if (!rr) {
+			printk(KERN_INFO
+				"%s: I/O region 0x%X - 0x%X is in use!\n",
+				card->wandev.name, conf->ioport,
+				conf->ioport + SDLA_MAXIORANGE - 1);
+			return -EINVAL;
+		}
+	}
+
+	return 0;
+}
+
+/*================================================================== 
+ * configure_s514_card
+ * 
+ * For a S514 adapter, check for a possible configuration error in that
+ * we are loading an adapter in the same slot as a previously loaded S514
+ * card.
+ */ 
+
+
+static int check_s514_conflicts(sdla_t* card,wandev_conf_t* conf, int *irq)
+{
+	unsigned long smp_flags;
+	int i;
+	
+	if (test_bit(0,&card->configured))
+		return 0;
+
+	
+	/* Check for already loaded card with the same IO port and IRQ 
+	 * If found, copy its hardware configuration and use its
+	 * resources (i.e. piggybacking)
+	 */
+
+	for (i = 0; i < ncards; i ++) {
+	
+		sdla_t* nxt_card = &card_array[i];
+		if(nxt_card == card)
+			continue;
+		
+		if((nxt_card->hw.type == SDLA_S514) &&
+		   (nxt_card->hw.S514_slot_no == conf->PCI_slot_no) &&
+		   (nxt_card->hw.S514_cpu_no[0] == conf->S514_CPU_no[0])&&
+		   (nxt_card->next == NULL)){
+
+
+			if ((conf->config_id == WANCONFIG_CHDLC || 
+			     conf->config_id == WANCONFIG_MPPP) &&
+			    (nxt_card->wandev.config_id == WANCONFIG_CHDLC || 
+			     nxt_card->wandev.config_id == WANCONFIG_MPPP)){ 
+				
+				*irq = nxt_card->hw.irq;
+				memcpy(&card->hw, &nxt_card->hw, sizeof(sdlahw_t));
+	
+				/* The master could already be running, we must
+				 * set this as a critical area */
+				lock_adapter_irq(&nxt_card->wandev.lock,&smp_flags);
+				nxt_card->next = card;
+				card->next = nxt_card;
+
+				card->wandev.piggyback = WANOPT_YES;
+
+				/* We must initialise the piggiback spin lock here
+				 * since isr will try to lock card->next if it
+				 * exists */
+				spin_lock_init(&card->wandev.lock);
+
+				unlock_adapter_irq(&nxt_card->wandev.lock,&smp_flags);
+
+			}else{
+				/* Trying to run piggibacking with a wrong protocol */
+				printk(KERN_INFO "%s: ERROR: Resource busy: CPU %c PCISLOT %i\n"
+						 "%s:        This protocol doesn't support\n"
+						 "%s:        multi-port operation!\n",
+						 card->devname,
+						 conf->S514_CPU_no[0],conf->PCI_slot_no,
+						 card->devname,card->devname);
+				return -EEXIST;
+			}
+		}
+	}
+
+	return 0;
+}
+
+
+
+/*============================================================================
+ * Shut down WAN link driver. 
+ * o shut down adapter hardware
+ * o release system resources.
+ *
+ * This function is called by the router when device is being unregistered or
+ * when it handles ROUTER_DOWN IOCTL.
+ */
+static int shutdown(struct wan_device* wandev)
+{
+	sdla_t *card;
+	int err=0;
+	
+	/* sanity checks */
+	if ((wandev == NULL) || (wandev->private == NULL)){
+		return -EFAULT;
+	}
+		
+	if (wandev->state == WAN_UNCONFIGURED){
+		return 0;
+	}
+
+	card = wandev->private;
+
+	if (card->tty_opt){
+		if (card->tty_open){
+			printk(KERN_INFO 
+				"%s: Shutdown Failed: TTY is still open\n",
+				  card->devname);
+			return -EBUSY;
+		}
+	}
+	
+	wandev->state = WAN_UNCONFIGURED;
+
+	set_bit(PERI_CRIT,(void*)&wandev->critical);
+	
+	/* In case of piggibacking, make sure that 
+         * we never try to shutdown both devices at the same
+         * time, because they depend on one another */
+	
+	if (card->disable_comm){
+		card->disable_comm(card);
+	}
+
+	/* Release Resources */
+	release_hw(card);
+
+        /* only free the allocated I/O range if not an S514 adapter */
+	if (wandev->hw_opt[0] != SDLA_S514 && !card->configured){
+              	release_region(card->hw.port, card->hw.io_range);
+	}
+
+	if (!card->configured){
+		memset(&card->hw, 0, sizeof(sdlahw_t));
+	      	if (card->next){
+			memset(&card->next->hw, 0, sizeof(sdlahw_t));
+		}
+	}
+	
+
+	clear_bit(PERI_CRIT,(void*)&wandev->critical);
+	return err;
+}
+
+static void release_hw (sdla_t *card)
+{
+	sdla_t *nxt_card;
+
+	
+	/* Check if next device exists */
+	if (card->next){
+		nxt_card = card->next;
+		/* If next device is down then release resources */
+		if (nxt_card->wandev.state == WAN_UNCONFIGURED){
+			if (card->wandev.piggyback){
+				/* If this device is piggyback then use
+                                 * information of the master device 
+				 */
+				printk(KERN_INFO "%s: Piggyback shutting down\n",card->devname);
+				sdla_down(&card->next->hw);
+       				free_irq(card->wandev.irq, card->next);
+				card->configured = 0;
+				card->next->configured = 0;
+				card->wandev.piggyback = 0;
+			}else{
+				/* Master device shutting down */
+				printk(KERN_INFO "%s: Master shutting down\n",card->devname);
+				sdla_down(&card->hw);
+				free_irq(card->wandev.irq, card);
+				card->configured = 0;
+				card->next->configured = 0;
+			}
+		}else{
+			printk(KERN_INFO "%s: Device still running %i\n",
+				nxt_card->devname,nxt_card->wandev.state);
+
+			card->configured = 1;
+		}
+	}else{
+		printk(KERN_INFO "%s: Master shutting down\n",card->devname);
+		sdla_down(&card->hw);
+       		free_irq(card->wandev.irq, card);
+		card->configured = 0;
+	}
+	return;
+}
+
+
+/*============================================================================
+ * Driver I/O control. 
+ * o verify arguments
+ * o perform requested action
+ *
+ * This function is called when router handles one of the reserved user
+ * IOCTLs.  Note that 'arg' stil points to user address space.
+ */
+static int ioctl(struct wan_device* wandev, unsigned cmd, unsigned long arg)
+{
+	sdla_t* card;
+	int err;
+
+	/* sanity checks */
+	if ((wandev == NULL) || (wandev->private == NULL))
+		return -EFAULT;
+	if (wandev->state == WAN_UNCONFIGURED)
+		return -ENODEV;
+
+	card = wandev->private;
+
+	if(card->hw.type != SDLA_S514){
+		disable_irq(card->hw.irq);
+	}
+
+	if (test_bit(SEND_CRIT, (void*)&wandev->critical)) {
+		return -EAGAIN;
+	}
+	
+	switch (cmd) {
+	case WANPIPE_DUMP:
+		err = ioctl_dump(wandev->private, (void*)arg);
+		break;
+
+	case WANPIPE_EXEC:
+		err = ioctl_exec(wandev->private, (void*)arg, cmd);
+		break;
+	default:
+		err = -EINVAL;
+	}
+ 
+	return err;
+}
+
+/****** Driver IOCTL Handlers ***********************************************/
+
+/*============================================================================
+ * Dump adapter memory to user buffer.
+ * o verify request structure
+ * o copy request structure to kernel data space
+ * o verify length/offset
+ * o verify user buffer
+ * o copy adapter memory image to user buffer
+ *
+ * Note: when dumping memory, this routine switches curent dual-port memory
+ *	 vector, so care must be taken to avoid racing conditions.
+ */
+static int ioctl_dump (sdla_t* card, sdla_dump_t* u_dump)
+{
+	sdla_dump_t dump;
+	unsigned winsize;
+	unsigned long oldvec;	/* DPM window vector */
+	unsigned long smp_flags;
+	int err = 0;
+
+	if(copy_from_user((void*)&dump, (void*)u_dump, sizeof(sdla_dump_t)))
+		return -EFAULT;
+		
+	if ((dump.magic != WANPIPE_MAGIC) ||
+	    (dump.offset + dump.length > card->hw.memory))
+		return -EINVAL;
+	
+	winsize = card->hw.dpmsize;
+
+	if(card->hw.type != SDLA_S514) {
+
+		lock_adapter_irq(&card->wandev.lock, &smp_flags);
+		
+                oldvec = card->hw.vector;
+                while (dump.length) {
+			/* current offset */				
+                        unsigned pos = dump.offset % winsize;
+			/* current vector */
+                        unsigned long vec = dump.offset - pos;
+                        unsigned len = (dump.length > (winsize - pos)) ?
+                        	(winsize - pos) : dump.length;
+			/* relocate window */
+                        if (sdla_mapmem(&card->hw, vec) != 0) {
+                                err = -EIO;
+                                break;
+                        }
+			
+                        if(copy_to_user((void *)dump.ptr,
+                                (u8 *)card->hw.dpmbase + pos, len)){ 
+				
+				unlock_adapter_irq(&card->wandev.lock, &smp_flags);
+				return -EFAULT;
+			}
+
+                        dump.length     -= len;
+                        dump.offset     += len;
+                        dump.ptr         = (char*)dump.ptr + len;
+                }
+		
+                sdla_mapmem(&card->hw, oldvec);/* restore DPM window position */
+		unlock_adapter_irq(&card->wandev.lock, &smp_flags);
+        
+	}else {
+
+               if(copy_to_user((void *)dump.ptr,
+			       (u8 *)card->hw.dpmbase + dump.offset, dump.length)){
+			return -EFAULT;
+		}
+	}
+
+	return err;
+}
+
+/*============================================================================
+ * Execute adapter firmware command.
+ * o verify request structure
+ * o copy request structure to kernel data space
+ * o call protocol-specific 'exec' function
+ */
+static int ioctl_exec (sdla_t* card, sdla_exec_t* u_exec, int cmd)
+{
+	sdla_exec_t exec;
+	int err=0;
+
+	if (card->exec == NULL && cmd == WANPIPE_EXEC){
+		return -ENODEV;
+	}
+
+	if(copy_from_user((void*)&exec, (void*)u_exec, sizeof(sdla_exec_t)))
+		return -EFAULT;
+
+	if ((exec.magic != WANPIPE_MAGIC) || (exec.cmd == NULL))
+		return -EINVAL;
+
+	switch (cmd) {
+		case WANPIPE_EXEC:	
+			err = card->exec(card, exec.cmd, exec.data);
+			break;
+	}	
+	return err;
+}
+
+/******* Miscellaneous ******************************************************/
+
+/*============================================================================
+ * SDLA Interrupt Service Routine.
+ * o acknowledge SDLA hardware interrupt.
+ * o call protocol-specific interrupt service routine, if any.
+ */
+STATIC irqreturn_t sdla_isr (int irq, void* dev_id, struct pt_regs *regs)
+{
+#define	card	((sdla_t*)dev_id)
+
+	if(card->hw.type == SDLA_S514) {	/* handle interrrupt on S514 */
+                u32 int_status;
+                unsigned char CPU_no = card->hw.S514_cpu_no[0];
+                unsigned char card_found_for_IRQ;
+		u8 IRQ_count = 0;
+
+		for(;;) {
+
+			read_S514_int_stat(&card->hw, &int_status);
+
+			/* check if the interrupt is for this device */
+ 			if(!((unsigned char)int_status &
+				(IRQ_CPU_A | IRQ_CPU_B)))
+                	        return IRQ_HANDLED;
+
+			/* if the IRQ is for both CPUs on the same adapter, */
+			/* then alter the interrupt status so as to handle */
+			/* one CPU at a time */
+			if(((unsigned char)int_status & (IRQ_CPU_A | IRQ_CPU_B))
+				== (IRQ_CPU_A | IRQ_CPU_B)) {
+				int_status &= (CPU_no == S514_CPU_A) ?
+					~IRQ_CPU_B : ~IRQ_CPU_A;
+			}
+ 
+			card_found_for_IRQ = 0;
+
+	             	/* check to see that the CPU number for this device */
+			/* corresponds to the interrupt status read */
+                	switch (CPU_no) {
+                        	case S514_CPU_A:
+                                	if((unsigned char)int_status &
+						IRQ_CPU_A)
+                                        card_found_for_IRQ = 1;
+                                break;
+
+	                        case S514_CPU_B:
+        	                        if((unsigned char)int_status &
+						IRQ_CPU_B)
+                                        card_found_for_IRQ = 1;
+                                break;
+                	}
+
+			/* exit if the interrupt is for another CPU on the */
+			/* same IRQ */
+			if(!card_found_for_IRQ)
+				return IRQ_HANDLED;
+
+       	 		if (!card || 
+			   (card->wandev.state == WAN_UNCONFIGURED && !card->configured)){
+					printk(KERN_INFO
+						"Received IRQ %d for CPU #%c\n",
+						irq, CPU_no);
+					printk(KERN_INFO
+						"IRQ for unconfigured adapter\n");
+					S514_intack(&card->hw, int_status);
+					return IRQ_HANDLED;
+       			}
+
+	        	if (card->in_isr) {
+        	       		printk(KERN_INFO
+					"%s: interrupt re-entrancy on IRQ %d\n",
+                       			card->devname, card->wandev.irq);
+				S514_intack(&card->hw, int_status);
+ 				return IRQ_HANDLED;
+       			}
+
+			spin_lock(&card->wandev.lock);
+			if (card->next){
+				spin_lock(&card->next->wandev.lock);
+			}
+				
+	               	S514_intack(&card->hw, int_status);
+       			if (card->isr)
+				card->isr(card);
+
+			if (card->next){
+				spin_unlock(&card->next->wandev.lock);
+			}
+			spin_unlock(&card->wandev.lock);
+
+			/* handle a maximum of two interrupts (one for each */
+			/* CPU on the adapter) before returning */  
+			if((++ IRQ_count) == 2)
+				return IRQ_HANDLED;
+		}
+	}
+
+	else {			/* handle interrupt on S508 adapter */
+
+		if (!card || ((card->wandev.state == WAN_UNCONFIGURED) && !card->configured))
+			return IRQ_HANDLED;
+
+		if (card->in_isr) {
+			printk(KERN_INFO
+				"%s: interrupt re-entrancy on IRQ %d!\n",
+				card->devname, card->wandev.irq);
+			return IRQ_HANDLED;
+		}
+
+		spin_lock(&card->wandev.lock);
+		if (card->next){
+			spin_lock(&card->next->wandev.lock);
+		}
+	
+		sdla_intack(&card->hw);
+		if (card->isr)
+			card->isr(card);
+		
+		if (card->next){
+			spin_unlock(&card->next->wandev.lock);
+		}
+		spin_unlock(&card->wandev.lock);
+
+	}
+        return IRQ_HANDLED;
+#undef	card
+}
+
+/*============================================================================
+ * This routine is called by the protocol-specific modules when network
+ * interface is being open.  The only reason we need this, is because we
+ * have to call MOD_INC_USE_COUNT, but cannot include 'module.h' where it's
+ * defined more than once into the same kernel module.
+ */
+void wanpipe_open (sdla_t* card)
+{
+	++card->open_cnt;
+}
+
+/*============================================================================
+ * This routine is called by the protocol-specific modules when network
+ * interface is being closed.  The only reason we need this, is because we
+ * have to call MOD_DEC_USE_COUNT, but cannot include 'module.h' where it's
+ * defined more than once into the same kernel module.
+ */
+void wanpipe_close (sdla_t* card)
+{
+	--card->open_cnt;
+}
+
+/*============================================================================
+ * Set WAN device state.
+ */
+void wanpipe_set_state (sdla_t* card, int state)
+{
+	if (card->wandev.state != state) {
+		switch (state) {
+		case WAN_CONNECTED:
+			printk (KERN_INFO "%s: link connected!\n",
+				card->devname);
+			break;
+
+		case WAN_CONNECTING:
+			printk (KERN_INFO "%s: link connecting...\n",
+				card->devname);
+			break;
+
+		case WAN_DISCONNECTED:
+			printk (KERN_INFO "%s: link disconnected!\n",
+				card->devname);
+			break;
+		}
+		card->wandev.state = state;
+	}
+	card->state_tick = jiffies;
+}
+
+sdla_t * wanpipe_find_card (char *name)
+{
+	int cnt;
+	for (cnt = 0; cnt < ncards; ++ cnt) {
+		sdla_t* card = &card_array[cnt];
+		if (!strcmp(card->devname,name))
+			return card;
+	}
+	return NULL;
+}
+
+sdla_t * wanpipe_find_card_num (int num)
+{
+	if (num < 1 || num > ncards)
+		return NULL;	
+	num--;
+	return &card_array[num];
+}
+
+/*
+ * @work_pointer:	work_struct to be done;
+ * 			should already have PREPARE_WORK() or
+ * 			  INIT_WORK() done on it by caller;
+ */
+void wanpipe_queue_work (struct work_struct *work_pointer)
+{
+	if (test_and_set_bit(1, (void*)&wanpipe_bh_critical))
+		printk(KERN_INFO "CRITICAL IN QUEUING WORK\n");
+
+	queue_work(wanpipe_wq, work_pointer);
+	clear_bit(1,(void*)&wanpipe_bh_critical);
+}
+
+void wakeup_sk_bh(struct net_device *dev)
+{
+	wanpipe_common_t *chan = dev->priv;
+
+	if (test_bit(0,&chan->common_critical))
+		return;
+	
+	if (chan->sk && chan->tx_timer){
+		chan->tx_timer->expires=jiffies+1;
+		add_timer(chan->tx_timer);
+	}
+}
+
+int change_dev_flags(struct net_device *dev, unsigned flags)
+{
+	struct ifreq if_info;
+	mm_segment_t fs = get_fs();
+	int err;
+
+	memset(&if_info, 0, sizeof(if_info));
+	strcpy(if_info.ifr_name, dev->name);
+	if_info.ifr_flags = flags;	
+
+	set_fs(get_ds());     /* get user space block */ 
+	err = devinet_ioctl(SIOCSIFFLAGS, &if_info);
+	set_fs(fs);
+
+	return err;
+}
+
+unsigned long get_ip_address(struct net_device *dev, int option)
+{
+	
+	struct in_ifaddr *ifaddr;
+	struct in_device *in_dev;
+
+	if ((in_dev = __in_dev_get(dev)) == NULL){
+		return 0;
+	}
+
+	if ((ifaddr = in_dev->ifa_list)== NULL ){
+		return 0;
+	}
+	
+	switch (option){
+
+	case WAN_LOCAL_IP:
+		return ifaddr->ifa_local;
+		break;
+	
+	case WAN_POINTOPOINT_IP:
+		return ifaddr->ifa_address;
+		break;	
+
+	case WAN_NETMASK_IP:
+		return ifaddr->ifa_mask;
+		break;
+
+	case WAN_BROADCAST_IP:
+		return ifaddr->ifa_broadcast;
+		break;
+	default:
+		return 0;
+	}
+
+	return 0;
+}	
+
+void add_gateway(sdla_t *card, struct net_device *dev)
+{
+	mm_segment_t oldfs;
+	struct rtentry route;
+	int res;
+
+	memset((char*)&route,0,sizeof(struct rtentry));
+
+	((struct sockaddr_in *)
+		&(route.rt_dst))->sin_addr.s_addr = 0;
+	((struct sockaddr_in *)
+		&(route.rt_dst))->sin_family = AF_INET;
+
+	((struct sockaddr_in *)
+		&(route.rt_genmask))->sin_addr.s_addr = 0;
+	((struct sockaddr_in *) 
+		&(route.rt_genmask)) ->sin_family = AF_INET;
+
+
+	route.rt_flags = 0;  
+	route.rt_dev = dev->name;
+
+	oldfs = get_fs();
+	set_fs(get_ds());
+	res = ip_rt_ioctl(SIOCADDRT,&route);
+	set_fs(oldfs);
+
+	if (res == 0){
+		printk(KERN_INFO "%s: Gateway added for %s\n",
+			card->devname,dev->name);
+	}
+
+	return;
+}
+
+MODULE_LICENSE("GPL");
+
+/****** End *********************************************************/
diff --git a/drivers/net/wan/sealevel.c b/drivers/net/wan/sealevel.c
new file mode 100644
index 0000000..5380ddf
--- /dev/null
+++ b/drivers/net/wan/sealevel.c
@@ -0,0 +1,469 @@
+/*
+ *	Sealevel Systems 4021 driver.
+ *
+ *	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.
+ *
+ *	(c) Copyright 1999, 2001 Alan Cox
+ *	(c) Copyright 2001 Red Hat Inc.
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/mm.h>
+#include <linux/net.h>
+#include <linux/skbuff.h>
+#include <linux/netdevice.h>
+#include <linux/if_arp.h>
+#include <linux/delay.h>
+#include <linux/ioport.h>
+#include <linux/init.h>
+#include <net/arp.h>
+
+#include <asm/io.h>
+#include <asm/dma.h>
+#include <asm/byteorder.h>
+#include <net/syncppp.h>
+#include "z85230.h"
+
+
+struct slvl_device
+{
+	void *if_ptr;	/* General purpose pointer (used by SPPP) */
+	struct z8530_channel *chan;
+	struct ppp_device pppdev;
+	int channel;
+};
+
+
+struct slvl_board
+{
+	struct slvl_device *dev[2];
+	struct z8530_dev board;
+	int iobase;
+};
+
+/*
+ *	Network driver support routines
+ */
+
+/*
+ *	Frame receive. Simple for our card as we do sync ppp and there
+ *	is no funny garbage involved
+ */
+ 
+static void sealevel_input(struct z8530_channel *c, struct sk_buff *skb)
+{
+	/* Drop the CRC - it's not a good idea to try and negotiate it ;) */
+	skb_trim(skb, skb->len-2);
+	skb->protocol=htons(ETH_P_WAN_PPP);
+	skb->mac.raw=skb->data;
+	skb->dev=c->netdevice;
+	/*
+	 *	Send it to the PPP layer. We don't have time to process
+	 *	it right now.
+	 */
+	netif_rx(skb);
+	c->netdevice->last_rx = jiffies;
+}
+ 
+/*
+ *	We've been placed in the UP state
+ */ 
+ 
+static int sealevel_open(struct net_device *d)
+{
+	struct slvl_device *slvl=d->priv;
+	int err = -1;
+	int unit = slvl->channel;
+	
+	/*
+	 *	Link layer up. 
+	 */
+
+	switch(unit)
+	{
+		case 0:
+			err=z8530_sync_dma_open(d, slvl->chan);
+			break;
+		case 1:
+			err=z8530_sync_open(d, slvl->chan);
+			break;
+	}
+	
+	if(err)
+		return err;
+	/*
+	 *	Begin PPP
+	 */
+	err=sppp_open(d);
+	if(err)
+	{
+		switch(unit)
+		{
+			case 0:
+				z8530_sync_dma_close(d, slvl->chan);
+				break;
+			case 1:
+				z8530_sync_close(d, slvl->chan);
+				break;
+		}				
+		return err;
+	}
+	
+	slvl->chan->rx_function=sealevel_input;
+	
+	/*
+	 *	Go go go
+	 */
+	netif_start_queue(d);
+	return 0;
+}
+
+static int sealevel_close(struct net_device *d)
+{
+	struct slvl_device *slvl=d->priv;
+	int unit = slvl->channel;
+	
+	/*
+	 *	Discard new frames
+	 */
+	
+	slvl->chan->rx_function=z8530_null_rx;
+		
+	/*
+	 *	PPP off
+	 */
+	sppp_close(d);
+	/*
+	 *	Link layer down
+	 */
+
+	netif_stop_queue(d);
+		
+	switch(unit)
+	{
+		case 0:
+			z8530_sync_dma_close(d, slvl->chan);
+			break;
+		case 1:
+			z8530_sync_close(d, slvl->chan);
+			break;
+	}
+	return 0;
+}
+
+static int sealevel_ioctl(struct net_device *d, struct ifreq *ifr, int cmd)
+{
+	/* struct slvl_device *slvl=d->priv;
+	   z8530_ioctl(d,&slvl->sync.chanA,ifr,cmd) */
+	return sppp_do_ioctl(d, ifr,cmd);
+}
+
+static struct net_device_stats *sealevel_get_stats(struct net_device *d)
+{
+	struct slvl_device *slvl=d->priv;
+	if(slvl)
+		return z8530_get_stats(slvl->chan);
+	else
+		return NULL;
+}
+
+/*
+ *	Passed PPP frames, fire them downwind.
+ */
+ 
+static int sealevel_queue_xmit(struct sk_buff *skb, struct net_device *d)
+{
+	struct slvl_device *slvl=d->priv;
+	return z8530_queue_xmit(slvl->chan, skb);
+}
+
+static int sealevel_neigh_setup(struct neighbour *n)
+{
+	if (n->nud_state == NUD_NONE) {
+		n->ops = &arp_broken_ops;
+		n->output = n->ops->output;
+	}
+	return 0;
+}
+
+static int sealevel_neigh_setup_dev(struct net_device *dev, struct neigh_parms *p)
+{
+	if (p->tbl->family == AF_INET) {
+		p->neigh_setup = sealevel_neigh_setup;
+		p->ucast_probes = 0;
+		p->mcast_probes = 0;
+	}
+	return 0;
+}
+
+static int sealevel_attach(struct net_device *dev)
+{
+	struct slvl_device *sv = dev->priv;
+	sppp_attach(&sv->pppdev);
+	return 0;
+}
+
+static void sealevel_detach(struct net_device *dev)
+{
+	sppp_detach(dev);
+}
+		
+static void slvl_setup(struct net_device *d)
+{
+	d->open = sealevel_open;
+	d->stop = sealevel_close;
+	d->init = sealevel_attach;
+	d->uninit = sealevel_detach;
+	d->hard_start_xmit = sealevel_queue_xmit;
+	d->get_stats = sealevel_get_stats;
+	d->set_multicast_list = NULL;
+	d->do_ioctl = sealevel_ioctl;
+	d->neigh_setup = sealevel_neigh_setup_dev;
+	d->set_mac_address = NULL;
+
+}
+
+static inline struct slvl_device *slvl_alloc(int iobase, int irq)
+{
+	struct net_device *d;
+	struct slvl_device *sv;
+
+	d = alloc_netdev(sizeof(struct slvl_device), "hdlc%d",
+			 slvl_setup);
+
+	if (!d) 
+		return NULL;
+
+	sv = d->priv;
+	sv->if_ptr = &sv->pppdev;
+	sv->pppdev.dev = d;
+	d->base_addr = iobase;
+	d->irq = irq;
+		
+	return sv;
+}
+
+
+/*
+ *	Allocate and setup Sealevel board.
+ */
+ 
+static __init struct slvl_board *slvl_init(int iobase, int irq, 
+					   int txdma, int rxdma, int slow)
+{
+	struct z8530_dev *dev;
+	struct slvl_board *b;
+	
+	/*
+	 *	Get the needed I/O space
+	 */
+
+	if(!request_region(iobase, 8, "Sealevel 4021")) 
+	{	
+		printk(KERN_WARNING "sealevel: I/O 0x%X already in use.\n", iobase);
+		return NULL;
+	}
+	
+	b = kmalloc(sizeof(struct slvl_board), GFP_KERNEL);
+	if(!b)
+		goto fail3;
+
+	memset(b, 0, sizeof(*b));
+	if (!(b->dev[0]= slvl_alloc(iobase, irq)))
+		goto fail2;
+
+	b->dev[0]->chan = &b->board.chanA;	
+	b->dev[0]->channel = 0;
+	
+	if (!(b->dev[1] = slvl_alloc(iobase, irq)))
+		goto fail1_0;
+
+	b->dev[1]->chan = &b->board.chanB;
+	b->dev[1]->channel = 1;
+
+	dev = &b->board;
+	
+	/*
+	 *	Stuff in the I/O addressing
+	 */
+	 
+	dev->active = 0;
+
+	b->iobase = iobase;
+	
+	/*
+	 *	Select 8530 delays for the old board
+	 */
+	 
+	if(slow)
+		iobase |= Z8530_PORT_SLEEP;
+		
+	dev->chanA.ctrlio=iobase+1;
+	dev->chanA.dataio=iobase;
+	dev->chanB.ctrlio=iobase+3;
+	dev->chanB.dataio=iobase+2;
+	
+	dev->chanA.irqs=&z8530_nop;
+	dev->chanB.irqs=&z8530_nop;
+	
+	/*
+	 *	Assert DTR enable DMA
+	 */
+	 
+	outb(3|(1<<7), b->iobase+4);	
+	
+
+	/* We want a fast IRQ for this device. Actually we'd like an even faster
+	   IRQ ;) - This is one driver RtLinux is made for */
+   
+	if(request_irq(irq, &z8530_interrupt, SA_INTERRUPT, "SeaLevel", dev)<0)
+	{
+		printk(KERN_WARNING "sealevel: IRQ %d already in use.\n", irq);
+		goto fail1_1;
+	}
+	
+	dev->irq=irq;
+	dev->chanA.private=&b->dev[0];
+	dev->chanB.private=&b->dev[1];
+	dev->chanA.netdevice=b->dev[0]->pppdev.dev;
+	dev->chanB.netdevice=b->dev[1]->pppdev.dev;
+	dev->chanA.dev=dev;
+	dev->chanB.dev=dev;
+
+	dev->chanA.txdma=3;
+	dev->chanA.rxdma=1;
+	if(request_dma(dev->chanA.txdma, "SeaLevel (TX)")!=0)
+		goto fail;
+		
+	if(request_dma(dev->chanA.rxdma, "SeaLevel (RX)")!=0)
+		goto dmafail;
+	
+	disable_irq(irq);
+		
+	/*
+	 *	Begin normal initialise
+	 */
+	 
+	if(z8530_init(dev)!=0)
+	{
+		printk(KERN_ERR "Z8530 series device not found.\n");
+		enable_irq(irq);
+		goto dmafail2;
+	}
+	if(dev->type==Z85C30)
+	{
+		z8530_channel_load(&dev->chanA, z8530_hdlc_kilostream);
+		z8530_channel_load(&dev->chanB, z8530_hdlc_kilostream);
+	}
+	else
+	{
+		z8530_channel_load(&dev->chanA, z8530_hdlc_kilostream_85230);
+		z8530_channel_load(&dev->chanB, z8530_hdlc_kilostream_85230);
+	}
+
+	/*
+	 *	Now we can take the IRQ
+	 */
+	
+	enable_irq(irq);
+
+	if (register_netdev(b->dev[0]->pppdev.dev)) 
+		goto dmafail2;
+		
+	if (register_netdev(b->dev[1]->pppdev.dev)) 
+		goto fail_unit;
+
+	z8530_describe(dev, "I/O", iobase);
+	dev->active=1;
+	return b;
+
+fail_unit:
+	unregister_netdev(b->dev[0]->pppdev.dev);
+	
+dmafail2:
+	free_dma(dev->chanA.rxdma);
+dmafail:
+	free_dma(dev->chanA.txdma);
+fail:
+	free_irq(irq, dev);
+fail1_1:
+	free_netdev(b->dev[1]->pppdev.dev);
+fail1_0:
+	free_netdev(b->dev[0]->pppdev.dev);
+fail2:
+	kfree(b);
+fail3:
+	release_region(iobase,8);
+	return NULL;
+}
+
+static void __exit slvl_shutdown(struct slvl_board *b)
+{
+	int u;
+
+	z8530_shutdown(&b->board);
+	
+	for(u=0; u<2; u++)
+	{
+		struct net_device *d = b->dev[u]->pppdev.dev;
+		unregister_netdev(d);
+		free_netdev(d);
+	}
+	
+	free_irq(b->board.irq, &b->board);
+	free_dma(b->board.chanA.rxdma);
+	free_dma(b->board.chanA.txdma);
+	/* DMA off on the card, drop DTR */
+	outb(0, b->iobase);
+	release_region(b->iobase, 8);
+	kfree(b);
+}
+
+
+static int io=0x238;
+static int txdma=1;
+static int rxdma=3;
+static int irq=5;
+static int slow=0;
+
+module_param(io, int, 0);
+MODULE_PARM_DESC(io, "The I/O base of the Sealevel card");
+module_param(txdma, int, 0);
+MODULE_PARM_DESC(txdma, "Transmit DMA channel");
+module_param(rxdma, int, 0);
+MODULE_PARM_DESC(rxdma, "Receive DMA channel");
+module_param(irq, int, 0);
+MODULE_PARM_DESC(irq, "The interrupt line setting for the SeaLevel card");
+module_param(slow, bool, 0);
+MODULE_PARM_DESC(slow, "Set this for an older Sealevel card such as the 4012");
+
+MODULE_AUTHOR("Alan Cox");
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("Modular driver for the SeaLevel 4021");
+
+static struct slvl_board *slvl_unit;
+
+static int __init slvl_init_module(void)
+{
+#ifdef MODULE
+	printk(KERN_INFO "SeaLevel Z85230 Synchronous Driver v 0.02.\n");
+	printk(KERN_INFO "(c) Copyright 1998, Building Number Three Ltd.\n");
+#endif
+	slvl_unit = slvl_init(io, irq, txdma, rxdma, slow);
+
+	return slvl_unit ? 0 : -ENODEV;
+}
+
+static void __exit slvl_cleanup_module(void)
+{
+	if(slvl_unit)
+		slvl_shutdown(slvl_unit);
+}
+
+module_init(slvl_init_module);
+module_exit(slvl_cleanup_module);
diff --git a/drivers/net/wan/syncppp.c b/drivers/net/wan/syncppp.c
new file mode 100644
index 0000000..84b65c6
--- /dev/null
+++ b/drivers/net/wan/syncppp.c
@@ -0,0 +1,1488 @@
+/*
+ *	NET3:	A (fairly minimal) implementation of synchronous PPP for Linux
+ *		as well as a CISCO HDLC implementation. See the copyright 
+ *		message below for the original source.
+ *
+ *	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.
+ *
+ *	Note however. This code is also used in a different form by FreeBSD.
+ *	Therefore when making any non OS specific change please consider
+ *	contributing it back to the original author under the terms
+ *	below in addition.
+ *		-- Alan
+ *
+ *	Port for Linux-2.1 by Jan "Yenya" Kasprzak <kas@fi.muni.cz>
+ */
+
+/*
+ * Synchronous PPP/Cisco link level subroutines.
+ * Keepalive protocol implemented in both Cisco and PPP modes.
+ *
+ * Copyright (C) 1994 Cronyx Ltd.
+ * Author: Serge Vakulenko, <vak@zebub.msk.su>
+ *
+ * This software is distributed with NO WARRANTIES, not even the implied
+ * warranties for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ *
+ * Authors grant any other persons or organisations permission to use
+ * or modify this software as long as this message is kept with the software,
+ * all derivative works or modified versions.
+ *
+ * Version 1.9, Wed Oct  4 18:58:15 MSK 1995
+ *
+ * $Id: syncppp.c,v 1.18 2000/04/11 05:25:31 asj Exp $
+ */
+#undef DEBUG
+
+#include <linux/config.h>
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/errno.h>
+#include <linux/init.h>
+#include <linux/if_arp.h>
+#include <linux/skbuff.h>
+#include <linux/route.h>
+#include <linux/netdevice.h>
+#include <linux/inetdevice.h>
+#include <linux/random.h>
+#include <linux/pkt_sched.h>
+#include <linux/spinlock.h>
+#include <linux/rcupdate.h>
+
+#include <net/syncppp.h>
+
+#include <asm/byteorder.h>
+#include <asm/uaccess.h>
+
+#define MAXALIVECNT     6               /* max. alive packets */
+
+#define PPP_ALLSTATIONS 0xff            /* All-Stations broadcast address */
+#define PPP_UI          0x03            /* Unnumbered Information */
+#define PPP_IP          0x0021          /* Internet Protocol */
+#define PPP_ISO         0x0023          /* ISO OSI Protocol */
+#define PPP_XNS         0x0025          /* Xerox NS Protocol */
+#define PPP_IPX         0x002b          /* Novell IPX Protocol */
+#define PPP_LCP         0xc021          /* Link Control Protocol */
+#define PPP_IPCP        0x8021          /* Internet Protocol Control Protocol */
+
+#define LCP_CONF_REQ    1               /* PPP LCP configure request */
+#define LCP_CONF_ACK    2               /* PPP LCP configure acknowledge */
+#define LCP_CONF_NAK    3               /* PPP LCP configure negative ack */
+#define LCP_CONF_REJ    4               /* PPP LCP configure reject */
+#define LCP_TERM_REQ    5               /* PPP LCP terminate request */
+#define LCP_TERM_ACK    6               /* PPP LCP terminate acknowledge */
+#define LCP_CODE_REJ    7               /* PPP LCP code reject */
+#define LCP_PROTO_REJ   8               /* PPP LCP protocol reject */
+#define LCP_ECHO_REQ    9               /* PPP LCP echo request */
+#define LCP_ECHO_REPLY  10              /* PPP LCP echo reply */
+#define LCP_DISC_REQ    11              /* PPP LCP discard request */
+
+#define LCP_OPT_MRU             1       /* maximum receive unit */
+#define LCP_OPT_ASYNC_MAP       2       /* async control character map */
+#define LCP_OPT_AUTH_PROTO      3       /* authentication protocol */
+#define LCP_OPT_QUAL_PROTO      4       /* quality protocol */
+#define LCP_OPT_MAGIC           5       /* magic number */
+#define LCP_OPT_RESERVED        6       /* reserved */
+#define LCP_OPT_PROTO_COMP      7       /* protocol field compression */
+#define LCP_OPT_ADDR_COMP       8       /* address/control field compression */
+
+#define IPCP_CONF_REQ   LCP_CONF_REQ    /* PPP IPCP configure request */
+#define IPCP_CONF_ACK   LCP_CONF_ACK    /* PPP IPCP configure acknowledge */
+#define IPCP_CONF_NAK   LCP_CONF_NAK    /* PPP IPCP configure negative ack */
+#define IPCP_CONF_REJ   LCP_CONF_REJ    /* PPP IPCP configure reject */
+#define IPCP_TERM_REQ   LCP_TERM_REQ    /* PPP IPCP terminate request */
+#define IPCP_TERM_ACK   LCP_TERM_ACK    /* PPP IPCP terminate acknowledge */
+#define IPCP_CODE_REJ   LCP_CODE_REJ    /* PPP IPCP code reject */
+
+#define CISCO_MULTICAST         0x8f    /* Cisco multicast address */
+#define CISCO_UNICAST           0x0f    /* Cisco unicast address */
+#define CISCO_KEEPALIVE         0x8035  /* Cisco keepalive protocol */
+#define CISCO_ADDR_REQ          0       /* Cisco address request */
+#define CISCO_ADDR_REPLY        1       /* Cisco address reply */
+#define CISCO_KEEPALIVE_REQ     2       /* Cisco keepalive request */
+
+struct ppp_header {
+	u8 address;
+	u8 control;
+	u16 protocol;
+};
+#define PPP_HEADER_LEN          sizeof (struct ppp_header)
+
+struct lcp_header {
+	u8 type;
+	u8 ident;
+	u16 len;
+};
+#define LCP_HEADER_LEN          sizeof (struct lcp_header)
+
+struct cisco_packet {
+	u32 type;
+	u32 par1;
+	u32 par2;
+	u16 rel;
+	u16 time0;
+	u16 time1;
+};
+#define CISCO_PACKET_LEN 18
+#define CISCO_BIG_PACKET_LEN 20
+
+static struct sppp *spppq;
+static struct timer_list sppp_keepalive_timer;
+static DEFINE_SPINLOCK(spppq_lock);
+
+/* global xmit queue for sending packets while spinlock is held */
+static struct sk_buff_head tx_queue;
+
+static void sppp_keepalive (unsigned long dummy);
+static void sppp_cp_send (struct sppp *sp, u16 proto, u8 type,
+	u8 ident, u16 len, void *data);
+static void sppp_cisco_send (struct sppp *sp, int type, long par1, long par2);
+static void sppp_lcp_input (struct sppp *sp, struct sk_buff *m);
+static void sppp_cisco_input (struct sppp *sp, struct sk_buff *m);
+static void sppp_ipcp_input (struct sppp *sp, struct sk_buff *m);
+static void sppp_lcp_open (struct sppp *sp);
+static void sppp_ipcp_open (struct sppp *sp);
+static int sppp_lcp_conf_parse_options (struct sppp *sp, struct lcp_header *h,
+	int len, u32 *magic);
+static void sppp_cp_timeout (unsigned long arg);
+static char *sppp_lcp_type_name (u8 type);
+static char *sppp_ipcp_type_name (u8 type);
+static void sppp_print_bytes (u8 *p, u16 len);
+
+static int debug;
+
+/* Flush global outgoing packet queue to dev_queue_xmit().
+ *
+ * dev_queue_xmit() must be called with interrupts enabled
+ * which means it can't be called with spinlocks held.
+ * If a packet needs to be sent while a spinlock is held,
+ * then put the packet into tx_queue, and call sppp_flush_xmit()
+ * after spinlock is released.
+ */
+static void sppp_flush_xmit(void)
+{
+	struct sk_buff *skb;
+	while ((skb = skb_dequeue(&tx_queue)) != NULL)
+		dev_queue_xmit(skb);
+}
+
+/*
+ *	Interface down stub
+ */	
+
+static void if_down(struct net_device *dev)
+{
+	struct sppp *sp = (struct sppp *)sppp_of(dev);
+
+	sp->pp_link_state=SPPP_LINK_DOWN;
+}
+
+/*
+ * Timeout routine activations.
+ */
+
+static void sppp_set_timeout(struct sppp *p,int s) 
+{
+	if (! (p->pp_flags & PP_TIMO)) 
+	{
+		init_timer(&p->pp_timer);
+		p->pp_timer.function=sppp_cp_timeout;
+		p->pp_timer.expires=jiffies+s*HZ;
+		p->pp_timer.data=(unsigned long)p;
+		p->pp_flags |= PP_TIMO;
+		add_timer(&p->pp_timer);
+	}
+}
+
+static void sppp_clear_timeout(struct sppp *p)
+{
+	if (p->pp_flags & PP_TIMO) 
+	{
+		del_timer(&p->pp_timer);
+		p->pp_flags &= ~PP_TIMO; 
+	}
+}
+
+/**
+ *	sppp_input -	receive and process a WAN PPP frame
+ *	@skb:	The buffer to process
+ *	@dev:	The device it arrived on
+ *
+ *	This can be called directly by cards that do not have
+ *	timing constraints but is normally called from the network layer
+ *	after interrupt servicing to process frames queued via netif_rx().
+ *
+ *	We process the options in the card. If the frame is destined for
+ *	the protocol stacks then it requeues the frame for the upper level
+ *	protocol. If it is a control from it is processed and discarded
+ *	here.
+ */
+ 
+void sppp_input (struct net_device *dev, struct sk_buff *skb)
+{
+	struct ppp_header *h;
+	struct sppp *sp = (struct sppp *)sppp_of(dev);
+	unsigned long flags;
+
+	skb->dev=dev;
+	skb->mac.raw=skb->data;
+
+	if (dev->flags & IFF_RUNNING)
+	{
+		/* Count received bytes, add FCS and one flag */
+		sp->ibytes+= skb->len + 3;
+		sp->ipkts++;
+	}
+
+	if (!pskb_may_pull(skb, PPP_HEADER_LEN)) {
+		/* Too small packet, drop it. */
+		if (sp->pp_flags & PP_DEBUG)
+			printk (KERN_DEBUG "%s: input packet is too small, %d bytes\n",
+				dev->name, skb->len);
+		kfree_skb(skb);
+		return;
+	}
+
+	/* Get PPP header. */
+	h = (struct ppp_header *)skb->data;
+	skb_pull(skb,sizeof(struct ppp_header));
+
+	spin_lock_irqsave(&sp->lock, flags);
+	
+	switch (h->address) {
+	default:        /* Invalid PPP packet. */
+		goto invalid;
+	case PPP_ALLSTATIONS:
+		if (h->control != PPP_UI)
+			goto invalid;
+		if (sp->pp_flags & PP_CISCO) {
+			if (sp->pp_flags & PP_DEBUG)
+				printk (KERN_WARNING "%s: PPP packet in Cisco mode <0x%x 0x%x 0x%x>\n",
+					dev->name,
+					h->address, h->control, ntohs (h->protocol));
+			goto drop;
+		}
+		switch (ntohs (h->protocol)) {
+		default:
+			if (sp->lcp.state == LCP_STATE_OPENED)
+				sppp_cp_send (sp, PPP_LCP, LCP_PROTO_REJ,
+					++sp->pp_seq, skb->len + 2,
+					&h->protocol);
+			if (sp->pp_flags & PP_DEBUG)
+				printk (KERN_WARNING "%s: invalid input protocol <0x%x 0x%x 0x%x>\n",
+					dev->name,
+					h->address, h->control, ntohs (h->protocol));
+			goto drop;
+		case PPP_LCP:
+			sppp_lcp_input (sp, skb);
+			goto drop;
+		case PPP_IPCP:
+			if (sp->lcp.state == LCP_STATE_OPENED)
+				sppp_ipcp_input (sp, skb);
+			else
+				printk(KERN_DEBUG "IPCP when still waiting LCP finish.\n");
+			goto drop;
+		case PPP_IP:
+			if (sp->ipcp.state == IPCP_STATE_OPENED) {
+				if(sp->pp_flags&PP_DEBUG)
+					printk(KERN_DEBUG "Yow an IP frame.\n");
+				skb->protocol=htons(ETH_P_IP);
+				netif_rx(skb);
+				dev->last_rx = jiffies;
+				goto done;
+			}
+			break;
+#ifdef IPX
+		case PPP_IPX:
+			/* IPX IPXCP not implemented yet */
+			if (sp->lcp.state == LCP_STATE_OPENED) {
+				skb->protocol=htons(ETH_P_IPX);
+				netif_rx(skb);
+				dev->last_rx = jiffies;
+				goto done;
+			}
+			break;
+#endif
+		}
+		break;
+	case CISCO_MULTICAST:
+	case CISCO_UNICAST:
+		/* Don't check the control field here (RFC 1547). */
+		if (! (sp->pp_flags & PP_CISCO)) {
+			if (sp->pp_flags & PP_DEBUG)
+				printk (KERN_WARNING "%s: Cisco packet in PPP mode <0x%x 0x%x 0x%x>\n",
+					dev->name,
+					h->address, h->control, ntohs (h->protocol));
+			goto drop;
+		}
+		switch (ntohs (h->protocol)) {
+		default:
+			goto invalid;
+		case CISCO_KEEPALIVE:
+			sppp_cisco_input (sp, skb);
+			goto drop;
+#ifdef CONFIG_INET
+		case ETH_P_IP:
+			skb->protocol=htons(ETH_P_IP);
+			netif_rx(skb);
+			dev->last_rx = jiffies;
+			goto done;
+#endif
+#ifdef CONFIG_IPX
+		case ETH_P_IPX:
+			skb->protocol=htons(ETH_P_IPX);
+			netif_rx(skb);
+			dev->last_rx = jiffies;
+			goto done;
+#endif
+		}
+		break;
+	}
+	goto drop;
+
+invalid:
+	if (sp->pp_flags & PP_DEBUG)
+		printk (KERN_WARNING "%s: invalid input packet <0x%x 0x%x 0x%x>\n",
+			dev->name, h->address, h->control, ntohs (h->protocol));
+drop:
+	kfree_skb(skb);
+done:
+	spin_unlock_irqrestore(&sp->lock, flags);
+	sppp_flush_xmit();
+	return;
+}
+
+EXPORT_SYMBOL(sppp_input);
+
+/*
+ *	Handle transmit packets.
+ */
+ 
+static int sppp_hard_header(struct sk_buff *skb, struct net_device *dev, __u16 type,
+		void *daddr, void *saddr, unsigned int len)
+{
+	struct sppp *sp = (struct sppp *)sppp_of(dev);
+	struct ppp_header *h;
+	skb_push(skb,sizeof(struct ppp_header));
+	h=(struct ppp_header *)skb->data;
+	if(sp->pp_flags&PP_CISCO)
+	{
+		h->address = CISCO_UNICAST;
+		h->control = 0;
+	}
+	else
+	{
+		h->address = PPP_ALLSTATIONS;
+		h->control = PPP_UI;
+	}
+	if(sp->pp_flags & PP_CISCO)
+	{
+		h->protocol = htons(type);
+	}
+	else switch(type)
+	{
+		case ETH_P_IP:
+			h->protocol = htons(PPP_IP);
+			break;
+		case ETH_P_IPX:
+			h->protocol = htons(PPP_IPX);
+			break;
+	}
+	return sizeof(struct ppp_header);
+}
+
+static int sppp_rebuild_header(struct sk_buff *skb)
+{
+	return 0;
+}
+
+/*
+ * Send keepalive packets, every 10 seconds.
+ */
+
+static void sppp_keepalive (unsigned long dummy)
+{
+	struct sppp *sp;
+	unsigned long flags;
+
+	spin_lock_irqsave(&spppq_lock, flags);
+
+	for (sp=spppq; sp; sp=sp->pp_next) 
+	{
+		struct net_device *dev = sp->pp_if;
+
+		/* Keepalive mode disabled or channel down? */
+		if (! (sp->pp_flags & PP_KEEPALIVE) ||
+		    ! (dev->flags & IFF_UP))
+			continue;
+
+		spin_lock(&sp->lock);
+
+		/* No keepalive in PPP mode if LCP not opened yet. */
+		if (! (sp->pp_flags & PP_CISCO) &&
+		    sp->lcp.state != LCP_STATE_OPENED) {
+			spin_unlock(&sp->lock);
+			continue;
+		}
+
+		if (sp->pp_alivecnt == MAXALIVECNT) {
+			/* No keepalive packets got.  Stop the interface. */
+			printk (KERN_WARNING "%s: protocol down\n", dev->name);
+			if_down (dev);
+			if (! (sp->pp_flags & PP_CISCO)) {
+				/* Shut down the PPP link. */
+				sp->lcp.magic = jiffies;
+				sp->lcp.state = LCP_STATE_CLOSED;
+				sp->ipcp.state = IPCP_STATE_CLOSED;
+				sppp_clear_timeout (sp);
+				/* Initiate negotiation. */
+				sppp_lcp_open (sp);
+			}
+		}
+		if (sp->pp_alivecnt <= MAXALIVECNT)
+			++sp->pp_alivecnt;
+		if (sp->pp_flags & PP_CISCO)
+			sppp_cisco_send (sp, CISCO_KEEPALIVE_REQ, ++sp->pp_seq,
+				sp->pp_rseq);
+		else if (sp->lcp.state == LCP_STATE_OPENED) {
+			long nmagic = htonl (sp->lcp.magic);
+			sp->lcp.echoid = ++sp->pp_seq;
+			sppp_cp_send (sp, PPP_LCP, LCP_ECHO_REQ,
+				sp->lcp.echoid, 4, &nmagic);
+		}
+
+		spin_unlock(&sp->lock);
+	}
+	spin_unlock_irqrestore(&spppq_lock, flags);
+	sppp_flush_xmit();
+	sppp_keepalive_timer.expires=jiffies+10*HZ;
+	add_timer(&sppp_keepalive_timer);
+}
+
+/*
+ * Handle incoming PPP Link Control Protocol packets.
+ */
+ 
+static void sppp_lcp_input (struct sppp *sp, struct sk_buff *skb)
+{
+	struct lcp_header *h;
+	struct net_device *dev = sp->pp_if;
+	int len = skb->len;
+	u8 *p, opt[6];
+	u32 rmagic;
+
+	if (!pskb_may_pull(skb, sizeof(struct lcp_header))) {
+		if (sp->pp_flags & PP_DEBUG)
+			printk (KERN_WARNING "%s: invalid lcp packet length: %d bytes\n",
+				dev->name, len);
+		return;
+	}
+	h = (struct lcp_header *)skb->data;
+	skb_pull(skb,sizeof(struct lcp_header *));
+	
+	if (sp->pp_flags & PP_DEBUG) 
+	{
+		char state = '?';
+		switch (sp->lcp.state) {
+		case LCP_STATE_CLOSED:   state = 'C'; break;
+		case LCP_STATE_ACK_RCVD: state = 'R'; break;
+		case LCP_STATE_ACK_SENT: state = 'S'; break;
+		case LCP_STATE_OPENED:   state = 'O'; break;
+		}
+		printk (KERN_WARNING "%s: lcp input(%c): %d bytes <%s id=%xh len=%xh",
+			dev->name, state, len,
+			sppp_lcp_type_name (h->type), h->ident, ntohs (h->len));
+		if (len > 4)
+			sppp_print_bytes ((u8*) (h+1), len-4);
+		printk (">\n");
+	}
+	if (len > ntohs (h->len))
+		len = ntohs (h->len);
+	switch (h->type) {
+	default:
+		/* Unknown packet type -- send Code-Reject packet. */
+		sppp_cp_send (sp, PPP_LCP, LCP_CODE_REJ, ++sp->pp_seq,
+			skb->len, h);
+		break;
+	case LCP_CONF_REQ:
+		if (len < 4) {
+			if (sp->pp_flags & PP_DEBUG)
+				printk (KERN_DEBUG"%s: invalid lcp configure request packet length: %d bytes\n",
+					dev->name, len);
+			break;
+		}
+		if (len>4 && !sppp_lcp_conf_parse_options (sp, h, len, &rmagic))
+			goto badreq;
+		if (rmagic == sp->lcp.magic) {
+			/* Local and remote magics equal -- loopback? */
+			if (sp->pp_loopcnt >= MAXALIVECNT*5) {
+				printk (KERN_WARNING "%s: loopback\n",
+					dev->name);
+				sp->pp_loopcnt = 0;
+				if (dev->flags & IFF_UP) {
+					if_down (dev);
+				}
+			} else if (sp->pp_flags & PP_DEBUG)
+				printk (KERN_DEBUG "%s: conf req: magic glitch\n",
+					dev->name);
+			++sp->pp_loopcnt;
+
+			/* MUST send Conf-Nack packet. */
+			rmagic = ~sp->lcp.magic;
+			opt[0] = LCP_OPT_MAGIC;
+			opt[1] = sizeof (opt);
+			opt[2] = rmagic >> 24;
+			opt[3] = rmagic >> 16;
+			opt[4] = rmagic >> 8;
+			opt[5] = rmagic;
+			sppp_cp_send (sp, PPP_LCP, LCP_CONF_NAK,
+				h->ident, sizeof (opt), &opt);
+badreq:
+			switch (sp->lcp.state) {
+			case LCP_STATE_OPENED:
+				/* Initiate renegotiation. */
+				sppp_lcp_open (sp);
+				/* fall through... */
+			case LCP_STATE_ACK_SENT:
+				/* Go to closed state. */
+				sp->lcp.state = LCP_STATE_CLOSED;
+				sp->ipcp.state = IPCP_STATE_CLOSED;
+			}
+			break;
+		}
+		/* Send Configure-Ack packet. */
+		sp->pp_loopcnt = 0;
+		if (sp->lcp.state != LCP_STATE_OPENED) {
+			sppp_cp_send (sp, PPP_LCP, LCP_CONF_ACK,
+					h->ident, len-4, h+1);
+		}
+		/* Change the state. */
+		switch (sp->lcp.state) {
+		case LCP_STATE_CLOSED:
+			sp->lcp.state = LCP_STATE_ACK_SENT;
+			break;
+		case LCP_STATE_ACK_RCVD:
+			sp->lcp.state = LCP_STATE_OPENED;
+			sppp_ipcp_open (sp);
+			break;
+		case LCP_STATE_OPENED:
+			/* Remote magic changed -- close session. */
+			sp->lcp.state = LCP_STATE_CLOSED;
+			sp->ipcp.state = IPCP_STATE_CLOSED;
+			/* Initiate renegotiation. */
+			sppp_lcp_open (sp);
+			/* Send ACK after our REQ in attempt to break loop */
+			sppp_cp_send (sp, PPP_LCP, LCP_CONF_ACK,
+					h->ident, len-4, h+1);
+			sp->lcp.state = LCP_STATE_ACK_SENT;
+			break;
+		}
+		break;
+	case LCP_CONF_ACK:
+		if (h->ident != sp->lcp.confid)
+			break;
+		sppp_clear_timeout (sp);
+		if ((sp->pp_link_state != SPPP_LINK_UP) &&
+		    (dev->flags & IFF_UP)) {
+			/* Coming out of loopback mode. */
+			sp->pp_link_state=SPPP_LINK_UP;
+			printk (KERN_INFO "%s: protocol up\n", dev->name);
+		}
+		switch (sp->lcp.state) {
+		case LCP_STATE_CLOSED:
+			sp->lcp.state = LCP_STATE_ACK_RCVD;
+			sppp_set_timeout (sp, 5);
+			break;
+		case LCP_STATE_ACK_SENT:
+			sp->lcp.state = LCP_STATE_OPENED;
+			sppp_ipcp_open (sp);
+			break;
+		}
+		break;
+	case LCP_CONF_NAK:
+		if (h->ident != sp->lcp.confid)
+			break;
+		p = (u8*) (h+1);
+		if (len>=10 && p[0] == LCP_OPT_MAGIC && p[1] >= 4) {
+			rmagic = (u32)p[2] << 24 |
+				(u32)p[3] << 16 | p[4] << 8 | p[5];
+			if (rmagic == ~sp->lcp.magic) {
+				int newmagic;
+				if (sp->pp_flags & PP_DEBUG)
+					printk (KERN_DEBUG "%s: conf nak: magic glitch\n",
+						dev->name);
+				get_random_bytes(&newmagic, sizeof(newmagic));
+				sp->lcp.magic += newmagic;
+			} else
+				sp->lcp.magic = rmagic;
+			}
+		if (sp->lcp.state != LCP_STATE_ACK_SENT) {
+			/* Go to closed state. */
+			sp->lcp.state = LCP_STATE_CLOSED;
+			sp->ipcp.state = IPCP_STATE_CLOSED;
+		}
+		/* The link will be renegotiated after timeout,
+		 * to avoid endless req-nack loop. */
+		sppp_clear_timeout (sp);
+		sppp_set_timeout (sp, 2);
+		break;
+	case LCP_CONF_REJ:
+		if (h->ident != sp->lcp.confid)
+			break;
+		sppp_clear_timeout (sp);
+		/* Initiate renegotiation. */
+		sppp_lcp_open (sp);
+		if (sp->lcp.state != LCP_STATE_ACK_SENT) {
+			/* Go to closed state. */
+			sp->lcp.state = LCP_STATE_CLOSED;
+			sp->ipcp.state = IPCP_STATE_CLOSED;
+		}
+		break;
+	case LCP_TERM_REQ:
+		sppp_clear_timeout (sp);
+		/* Send Terminate-Ack packet. */
+		sppp_cp_send (sp, PPP_LCP, LCP_TERM_ACK, h->ident, 0, NULL);
+		/* Go to closed state. */
+		sp->lcp.state = LCP_STATE_CLOSED;
+		sp->ipcp.state = IPCP_STATE_CLOSED;
+		/* Initiate renegotiation. */
+		sppp_lcp_open (sp);
+		break;
+	case LCP_TERM_ACK:
+	case LCP_CODE_REJ:
+	case LCP_PROTO_REJ:
+		/* Ignore for now. */
+		break;
+	case LCP_DISC_REQ:
+		/* Discard the packet. */
+		break;
+	case LCP_ECHO_REQ:
+		if (sp->lcp.state != LCP_STATE_OPENED)
+			break;
+		if (len < 8) {
+			if (sp->pp_flags & PP_DEBUG)
+				printk (KERN_WARNING "%s: invalid lcp echo request packet length: %d bytes\n",
+					dev->name, len);
+			break;
+		}
+		if (ntohl (*(long*)(h+1)) == sp->lcp.magic) {
+			/* Line loopback mode detected. */
+			printk (KERN_WARNING "%s: loopback\n", dev->name);
+			if_down (dev);
+
+			/* Shut down the PPP link. */
+			sp->lcp.state = LCP_STATE_CLOSED;
+			sp->ipcp.state = IPCP_STATE_CLOSED;
+			sppp_clear_timeout (sp);
+			/* Initiate negotiation. */
+			sppp_lcp_open (sp);
+			break;
+		}
+		*(long*)(h+1) = htonl (sp->lcp.magic);
+		sppp_cp_send (sp, PPP_LCP, LCP_ECHO_REPLY, h->ident, len-4, h+1);
+		break;
+	case LCP_ECHO_REPLY:
+		if (h->ident != sp->lcp.echoid)
+			break;
+		if (len < 8) {
+			if (sp->pp_flags & PP_DEBUG)
+				printk (KERN_WARNING "%s: invalid lcp echo reply packet length: %d bytes\n",
+					dev->name, len);
+			break;
+		}
+		if (ntohl (*(long*)(h+1)) != sp->lcp.magic)
+		sp->pp_alivecnt = 0;
+		break;
+	}
+}
+
+/*
+ * Handle incoming Cisco keepalive protocol packets.
+ */
+
+static void sppp_cisco_input (struct sppp *sp, struct sk_buff *skb)
+{
+	struct cisco_packet *h;
+	struct net_device *dev = sp->pp_if;
+
+	if (!pskb_may_pull(skb, sizeof(struct cisco_packet))
+	    || (skb->len != CISCO_PACKET_LEN
+		&& skb->len != CISCO_BIG_PACKET_LEN)) {
+		if (sp->pp_flags & PP_DEBUG)
+			printk (KERN_WARNING "%s: invalid cisco packet length: %d bytes\n",
+				dev->name,  skb->len);
+		return;
+	}
+	h = (struct cisco_packet *)skb->data;
+	skb_pull(skb, sizeof(struct cisco_packet*));
+	if (sp->pp_flags & PP_DEBUG)
+		printk (KERN_WARNING "%s: cisco input: %d bytes <%xh %xh %xh %xh %xh-%xh>\n",
+			dev->name,  skb->len,
+			ntohl (h->type), h->par1, h->par2, h->rel,
+			h->time0, h->time1);
+	switch (ntohl (h->type)) {
+	default:
+		if (sp->pp_flags & PP_DEBUG)
+			printk (KERN_WARNING "%s: unknown cisco packet type: 0x%x\n",
+				dev->name,  ntohl (h->type));
+		break;
+	case CISCO_ADDR_REPLY:
+		/* Reply on address request, ignore */
+		break;
+	case CISCO_KEEPALIVE_REQ:
+		sp->pp_alivecnt = 0;
+		sp->pp_rseq = ntohl (h->par1);
+		if (sp->pp_seq == sp->pp_rseq) {
+			/* Local and remote sequence numbers are equal.
+			 * Probably, the line is in loopback mode. */
+			int newseq;
+			if (sp->pp_loopcnt >= MAXALIVECNT) {
+				printk (KERN_WARNING "%s: loopback\n",
+					dev->name);
+				sp->pp_loopcnt = 0;
+				if (dev->flags & IFF_UP) {
+					if_down (dev);
+				}
+			}
+			++sp->pp_loopcnt;
+
+			/* Generate new local sequence number */
+			get_random_bytes(&newseq, sizeof(newseq));
+			sp->pp_seq ^= newseq;
+			break;
+		}
+		sp->pp_loopcnt = 0;
+		if (sp->pp_link_state==SPPP_LINK_DOWN &&
+		    (dev->flags & IFF_UP)) {
+			sp->pp_link_state=SPPP_LINK_UP;
+			printk (KERN_INFO "%s: protocol up\n", dev->name);
+		}
+		break;
+	case CISCO_ADDR_REQ:
+		/* Stolen from net/ipv4/devinet.c -- SIOCGIFADDR ioctl */
+		{
+		struct in_device *in_dev;
+		struct in_ifaddr *ifa;
+		u32 addr = 0, mask = ~0; /* FIXME: is the mask correct? */
+#ifdef CONFIG_INET
+		rcu_read_lock();
+		if ((in_dev = __in_dev_get(dev)) != NULL)
+		{
+			for (ifa=in_dev->ifa_list; ifa != NULL;
+				ifa=ifa->ifa_next) {
+				if (strcmp(dev->name, ifa->ifa_label) == 0) 
+				{
+					addr = ifa->ifa_local;
+					mask = ifa->ifa_mask;
+					break;
+				}
+			}
+		}
+		rcu_read_unlock();
+#endif		
+		/* I hope both addr and mask are in the net order */
+		sppp_cisco_send (sp, CISCO_ADDR_REPLY, addr, mask);
+		break;
+		}
+	}
+}
+
+
+/*
+ * Send PPP LCP packet.
+ */
+
+static void sppp_cp_send (struct sppp *sp, u16 proto, u8 type,
+	u8 ident, u16 len, void *data)
+{
+	struct ppp_header *h;
+	struct lcp_header *lh;
+	struct sk_buff *skb;
+	struct net_device *dev = sp->pp_if;
+
+	skb=alloc_skb(dev->hard_header_len+PPP_HEADER_LEN+LCP_HEADER_LEN+len,
+		GFP_ATOMIC);
+	if (skb==NULL)
+		return;
+
+	skb_reserve(skb,dev->hard_header_len);
+	
+	h = (struct ppp_header *)skb_put(skb, sizeof(struct ppp_header));
+	h->address = PPP_ALLSTATIONS;        /* broadcast address */
+	h->control = PPP_UI;                 /* Unnumbered Info */
+	h->protocol = htons (proto);         /* Link Control Protocol */
+
+	lh = (struct lcp_header *)skb_put(skb, sizeof(struct lcp_header));
+	lh->type = type;
+	lh->ident = ident;
+	lh->len = htons (LCP_HEADER_LEN + len);
+
+	if (len)
+		memcpy(skb_put(skb,len),data, len);
+
+	if (sp->pp_flags & PP_DEBUG) {
+		printk (KERN_WARNING "%s: %s output <%s id=%xh len=%xh",
+			dev->name, 
+			proto==PPP_LCP ? "lcp" : "ipcp",
+			proto==PPP_LCP ? sppp_lcp_type_name (lh->type) :
+			sppp_ipcp_type_name (lh->type), lh->ident,
+			ntohs (lh->len));
+		if (len)
+			sppp_print_bytes ((u8*) (lh+1), len);
+		printk (">\n");
+	}
+	sp->obytes += skb->len;
+	/* Control is high priority so it doesn't get queued behind data */
+	skb->priority=TC_PRIO_CONTROL;
+	skb->dev = dev;
+	skb_queue_tail(&tx_queue, skb);
+}
+
+/*
+ * Send Cisco keepalive packet.
+ */
+
+static void sppp_cisco_send (struct sppp *sp, int type, long par1, long par2)
+{
+	struct ppp_header *h;
+	struct cisco_packet *ch;
+	struct sk_buff *skb;
+	struct net_device *dev = sp->pp_if;
+	u32 t = jiffies * 1000/HZ;
+
+	skb=alloc_skb(dev->hard_header_len+PPP_HEADER_LEN+CISCO_PACKET_LEN,
+		GFP_ATOMIC);
+
+	if(skb==NULL)
+		return;
+		
+	skb_reserve(skb, dev->hard_header_len);
+	h = (struct ppp_header *)skb_put (skb, sizeof(struct ppp_header));
+	h->address = CISCO_MULTICAST;
+	h->control = 0;
+	h->protocol = htons (CISCO_KEEPALIVE);
+
+	ch = (struct cisco_packet*)skb_put(skb, CISCO_PACKET_LEN);
+	ch->type = htonl (type);
+	ch->par1 = htonl (par1);
+	ch->par2 = htonl (par2);
+	ch->rel = -1;
+	ch->time0 = htons ((u16) (t >> 16));
+	ch->time1 = htons ((u16) t);
+
+	if (sp->pp_flags & PP_DEBUG)
+		printk (KERN_WARNING "%s: cisco output: <%xh %xh %xh %xh %xh-%xh>\n",
+			dev->name,  ntohl (ch->type), ch->par1,
+			ch->par2, ch->rel, ch->time0, ch->time1);
+	sp->obytes += skb->len;
+	skb->priority=TC_PRIO_CONTROL;
+	skb->dev = dev;
+	skb_queue_tail(&tx_queue, skb);
+}
+
+/**
+ *	sppp_close - close down a synchronous PPP or Cisco HDLC link
+ *	@dev: The network device to drop the link of
+ *
+ *	This drops the logical interface to the channel. It is not
+ *	done politely as we assume we will also be dropping DTR. Any
+ *	timeouts are killed.
+ */
+
+int sppp_close (struct net_device *dev)
+{
+	struct sppp *sp = (struct sppp *)sppp_of(dev);
+	unsigned long flags;
+
+	spin_lock_irqsave(&sp->lock, flags);
+	sp->pp_link_state = SPPP_LINK_DOWN;
+	sp->lcp.state = LCP_STATE_CLOSED;
+	sp->ipcp.state = IPCP_STATE_CLOSED;
+	sppp_clear_timeout (sp);
+	spin_unlock_irqrestore(&sp->lock, flags);
+
+	return 0;
+}
+
+EXPORT_SYMBOL(sppp_close);
+
+/**
+ *	sppp_open - open a synchronous PPP or Cisco HDLC link
+ *	@dev:	Network device to activate
+ *	
+ *	Close down any existing synchronous session and commence
+ *	from scratch. In the PPP case this means negotiating LCP/IPCP
+ *	and friends, while for Cisco HDLC we simply need to start sending
+ *	keepalives
+ */
+
+int sppp_open (struct net_device *dev)
+{
+	struct sppp *sp = (struct sppp *)sppp_of(dev);
+	unsigned long flags;
+
+	sppp_close(dev);
+
+	spin_lock_irqsave(&sp->lock, flags);
+	if (!(sp->pp_flags & PP_CISCO)) {
+		sppp_lcp_open (sp);
+	}
+	sp->pp_link_state = SPPP_LINK_DOWN;
+	spin_unlock_irqrestore(&sp->lock, flags);
+	sppp_flush_xmit();
+
+	return 0;
+}
+
+EXPORT_SYMBOL(sppp_open);
+
+/**
+ *	sppp_reopen - notify of physical link loss
+ *	@dev: Device that lost the link
+ *
+ *	This function informs the synchronous protocol code that
+ *	the underlying link died (for example a carrier drop on X.21)
+ *
+ *	We increment the magic numbers to ensure that if the other end
+ *	failed to notice we will correctly start a new session. It happens
+ *	do to the nature of telco circuits is that you can lose carrier on
+ *	one endonly.
+ *
+ *	Having done this we go back to negotiating. This function may
+ *	be called from an interrupt context.
+ */
+ 
+int sppp_reopen (struct net_device *dev)
+{
+	struct sppp *sp = (struct sppp *)sppp_of(dev);
+	unsigned long flags;
+
+	sppp_close(dev);
+
+	spin_lock_irqsave(&sp->lock, flags);
+	if (!(sp->pp_flags & PP_CISCO))
+	{
+		sp->lcp.magic = jiffies;
+		++sp->pp_seq;
+		sp->lcp.state = LCP_STATE_CLOSED;
+		sp->ipcp.state = IPCP_STATE_CLOSED;
+		/* Give it a moment for the line to settle then go */
+		sppp_set_timeout (sp, 1);
+	} 
+	sp->pp_link_state=SPPP_LINK_DOWN;
+	spin_unlock_irqrestore(&sp->lock, flags);
+
+	return 0;
+}
+
+EXPORT_SYMBOL(sppp_reopen);
+
+/**
+ *	sppp_change_mtu - Change the link MTU
+ *	@dev:	Device to change MTU on
+ *	@new_mtu: New MTU
+ *
+ *	Change the MTU on the link. This can only be called with
+ *	the link down. It returns an error if the link is up or
+ *	the mtu is out of range.
+ */
+ 
+int sppp_change_mtu(struct net_device *dev, int new_mtu)
+{
+	if(new_mtu<128||new_mtu>PPP_MTU||(dev->flags&IFF_UP))
+		return -EINVAL;
+	dev->mtu=new_mtu;
+	return 0;
+}
+
+EXPORT_SYMBOL(sppp_change_mtu);
+
+/**
+ *	sppp_do_ioctl - Ioctl handler for ppp/hdlc
+ *	@dev: Device subject to ioctl
+ *	@ifr: Interface request block from the user
+ *	@cmd: Command that is being issued
+ *	
+ *	This function handles the ioctls that may be issued by the user
+ *	to control the settings of a PPP/HDLC link. It does both busy
+ *	and security checks. This function is intended to be wrapped by
+ *	callers who wish to add additional ioctl calls of their own.
+ */
+ 
+int sppp_do_ioctl(struct net_device *dev, struct ifreq *ifr, int cmd)
+{
+	struct sppp *sp = (struct sppp *)sppp_of(dev);
+
+	if(dev->flags&IFF_UP)
+		return -EBUSY;
+		
+	if(!capable(CAP_NET_ADMIN))
+		return -EPERM;
+	
+	switch(cmd)
+	{
+		case SPPPIOCCISCO:
+			sp->pp_flags|=PP_CISCO;
+			dev->type = ARPHRD_HDLC;
+			break;
+		case SPPPIOCPPP:
+			sp->pp_flags&=~PP_CISCO;
+			dev->type = ARPHRD_PPP;
+			break;
+		case SPPPIOCDEBUG:
+			sp->pp_flags&=~PP_DEBUG;
+			if(ifr->ifr_flags)
+				sp->pp_flags|=PP_DEBUG;
+			break;
+		case SPPPIOCGFLAGS:
+			if(copy_to_user(ifr->ifr_data, &sp->pp_flags, sizeof(sp->pp_flags)))
+				return -EFAULT;
+			break;
+		case SPPPIOCSFLAGS:
+			if(copy_from_user(&sp->pp_flags, ifr->ifr_data, sizeof(sp->pp_flags)))
+				return -EFAULT;
+			break;
+		default:
+			return -EINVAL;
+	}
+	return 0;
+}
+
+EXPORT_SYMBOL(sppp_do_ioctl);
+
+/**
+ *	sppp_attach - attach synchronous PPP/HDLC to a device
+ *	@pd:	PPP device to initialise
+ *
+ *	This initialises the PPP/HDLC support on an interface. At the
+ *	time of calling the dev element must point to the network device
+ *	that this interface is attached to. The interface should not yet
+ *	be registered. 
+ */
+ 
+void sppp_attach(struct ppp_device *pd)
+{
+	struct net_device *dev = pd->dev;
+	struct sppp *sp = &pd->sppp;
+	unsigned long flags;
+
+	/* Make sure embedding is safe for sppp_of */
+	BUG_ON(sppp_of(dev) != sp);
+
+	spin_lock_irqsave(&spppq_lock, flags);
+	/* Initialize keepalive handler. */
+	if (! spppq)
+	{
+		init_timer(&sppp_keepalive_timer);
+		sppp_keepalive_timer.expires=jiffies+10*HZ;
+		sppp_keepalive_timer.function=sppp_keepalive;
+		add_timer(&sppp_keepalive_timer);
+	}
+	/* Insert new entry into the keepalive list. */
+	sp->pp_next = spppq;
+	spppq = sp;
+	spin_unlock_irqrestore(&spppq_lock, flags);
+
+	sp->pp_loopcnt = 0;
+	sp->pp_alivecnt = 0;
+	sp->pp_seq = 0;
+	sp->pp_rseq = 0;
+	sp->pp_flags = PP_KEEPALIVE|PP_CISCO|debug;/*PP_DEBUG;*/
+	sp->lcp.magic = 0;
+	sp->lcp.state = LCP_STATE_CLOSED;
+	sp->ipcp.state = IPCP_STATE_CLOSED;
+	sp->pp_if = dev;
+	spin_lock_init(&sp->lock);
+	
+	/* 
+	 *	Device specific setup. All but interrupt handler and
+	 *	hard_start_xmit.
+	 */
+	 
+	dev->hard_header = sppp_hard_header;
+	dev->rebuild_header = sppp_rebuild_header;
+	dev->tx_queue_len = 10;
+	dev->type = ARPHRD_HDLC;
+	dev->addr_len = 0;
+	dev->hard_header_len = sizeof(struct ppp_header);
+	dev->mtu = PPP_MTU;
+	/*
+	 *	These 4 are callers but MUST also call sppp_ functions
+	 */
+	dev->do_ioctl = sppp_do_ioctl;
+#if 0
+	dev->get_stats = NULL;		/* Let the driver override these */
+	dev->open = sppp_open;
+	dev->stop = sppp_close;
+#endif	
+	dev->change_mtu = sppp_change_mtu;
+	dev->hard_header_cache = NULL;
+	dev->header_cache_update = NULL;
+	dev->flags = IFF_MULTICAST|IFF_POINTOPOINT|IFF_NOARP;
+}
+
+EXPORT_SYMBOL(sppp_attach);
+
+/**
+ *	sppp_detach - release PPP resources from a device
+ *	@dev:	Network device to release
+ *
+ *	Stop and free up any PPP/HDLC resources used by this
+ *	interface. This must be called before the device is
+ *	freed.
+ */
+ 
+void sppp_detach (struct net_device *dev)
+{
+	struct sppp **q, *p, *sp = (struct sppp *)sppp_of(dev);
+	unsigned long flags;
+
+	spin_lock_irqsave(&spppq_lock, flags);
+	/* Remove the entry from the keepalive list. */
+	for (q = &spppq; (p = *q); q = &p->pp_next)
+		if (p == sp) {
+			*q = p->pp_next;
+			break;
+		}
+
+	/* Stop keepalive handler. */
+	if (! spppq)
+		del_timer(&sppp_keepalive_timer);
+	sppp_clear_timeout (sp);
+	spin_unlock_irqrestore(&spppq_lock, flags);
+}
+
+EXPORT_SYMBOL(sppp_detach);
+
+/*
+ * Analyze the LCP Configure-Request options list
+ * for the presence of unknown options.
+ * If the request contains unknown options, build and
+ * send Configure-reject packet, containing only unknown options.
+ */
+static int
+sppp_lcp_conf_parse_options (struct sppp *sp, struct lcp_header *h,
+	int len, u32 *magic)
+{
+	u8 *buf, *r, *p;
+	int rlen;
+
+	len -= 4;
+	buf = r = kmalloc (len, GFP_ATOMIC);
+	if (! buf)
+		return (0);
+
+	p = (void*) (h+1);
+	for (rlen=0; len>1 && p[1]; len-=p[1], p+=p[1]) {
+		switch (*p) {
+		case LCP_OPT_MAGIC:
+			/* Magic number -- extract. */
+			if (len >= 6 && p[1] == 6) {
+				*magic = (u32)p[2] << 24 |
+					(u32)p[3] << 16 | p[4] << 8 | p[5];
+				continue;
+			}
+			break;
+		case LCP_OPT_ASYNC_MAP:
+			/* Async control character map -- check to be zero. */
+			if (len >= 6 && p[1] == 6 && ! p[2] && ! p[3] &&
+			    ! p[4] && ! p[5])
+				continue;
+			break;
+		case LCP_OPT_MRU:
+			/* Maximum receive unit -- always OK. */
+			continue;
+		default:
+			/* Others not supported. */
+			break;
+		}
+		/* Add the option to rejected list. */
+		memcpy(r, p, p[1]);
+		r += p[1];
+		rlen += p[1];
+	}
+	if (rlen)
+		sppp_cp_send (sp, PPP_LCP, LCP_CONF_REJ, h->ident, rlen, buf);
+	kfree(buf);
+	return (rlen == 0);
+}
+
+static void sppp_ipcp_input (struct sppp *sp, struct sk_buff *skb)
+{
+	struct lcp_header *h;
+	struct net_device *dev = sp->pp_if;
+	int len = skb->len;
+
+	if (!pskb_may_pull(skb, sizeof(struct lcp_header))) {
+		if (sp->pp_flags & PP_DEBUG)
+			printk (KERN_WARNING "%s: invalid ipcp packet length: %d bytes\n",
+				dev->name,  len);
+		return;
+	}
+	h = (struct lcp_header *)skb->data;
+	skb_pull(skb,sizeof(struct lcp_header));
+	if (sp->pp_flags & PP_DEBUG) {
+		printk (KERN_WARNING "%s: ipcp input: %d bytes <%s id=%xh len=%xh",
+			dev->name,  len,
+			sppp_ipcp_type_name (h->type), h->ident, ntohs (h->len));
+		if (len > 4)
+			sppp_print_bytes ((u8*) (h+1), len-4);
+		printk (">\n");
+	}
+	if (len > ntohs (h->len))
+		len = ntohs (h->len);
+	switch (h->type) {
+	default:
+		/* Unknown packet type -- send Code-Reject packet. */
+		sppp_cp_send (sp, PPP_IPCP, IPCP_CODE_REJ, ++sp->pp_seq, len, h);
+		break;
+	case IPCP_CONF_REQ:
+		if (len < 4) {
+			if (sp->pp_flags & PP_DEBUG)
+				printk (KERN_WARNING "%s: invalid ipcp configure request packet length: %d bytes\n",
+					dev->name, len);
+			return;
+		}
+		if (len > 4) {
+			sppp_cp_send (sp, PPP_IPCP, LCP_CONF_REJ, h->ident,
+				len-4, h+1);
+
+			switch (sp->ipcp.state) {
+			case IPCP_STATE_OPENED:
+				/* Initiate renegotiation. */
+				sppp_ipcp_open (sp);
+				/* fall through... */
+			case IPCP_STATE_ACK_SENT:
+				/* Go to closed state. */
+				sp->ipcp.state = IPCP_STATE_CLOSED;
+			}
+		} else {
+			/* Send Configure-Ack packet. */
+			sppp_cp_send (sp, PPP_IPCP, IPCP_CONF_ACK, h->ident,
+				0, NULL);
+			/* Change the state. */
+			if (sp->ipcp.state == IPCP_STATE_ACK_RCVD)
+				sp->ipcp.state = IPCP_STATE_OPENED;
+			else
+				sp->ipcp.state = IPCP_STATE_ACK_SENT;
+		}
+		break;
+	case IPCP_CONF_ACK:
+		if (h->ident != sp->ipcp.confid)
+			break;
+		sppp_clear_timeout (sp);
+		switch (sp->ipcp.state) {
+		case IPCP_STATE_CLOSED:
+			sp->ipcp.state = IPCP_STATE_ACK_RCVD;
+			sppp_set_timeout (sp, 5);
+			break;
+		case IPCP_STATE_ACK_SENT:
+			sp->ipcp.state = IPCP_STATE_OPENED;
+			break;
+		}
+		break;
+	case IPCP_CONF_NAK:
+	case IPCP_CONF_REJ:
+		if (h->ident != sp->ipcp.confid)
+			break;
+		sppp_clear_timeout (sp);
+			/* Initiate renegotiation. */
+		sppp_ipcp_open (sp);
+		if (sp->ipcp.state != IPCP_STATE_ACK_SENT)
+			/* Go to closed state. */
+			sp->ipcp.state = IPCP_STATE_CLOSED;
+		break;
+	case IPCP_TERM_REQ:
+		/* Send Terminate-Ack packet. */
+		sppp_cp_send (sp, PPP_IPCP, IPCP_TERM_ACK, h->ident, 0, NULL);
+		/* Go to closed state. */
+		sp->ipcp.state = IPCP_STATE_CLOSED;
+		/* Initiate renegotiation. */
+		sppp_ipcp_open (sp);
+		break;
+	case IPCP_TERM_ACK:
+		/* Ignore for now. */
+	case IPCP_CODE_REJ:
+		/* Ignore for now. */
+		break;
+	}
+}
+
+static void sppp_lcp_open (struct sppp *sp)
+{
+	char opt[6];
+
+	if (! sp->lcp.magic)
+		sp->lcp.magic = jiffies;
+	opt[0] = LCP_OPT_MAGIC;
+	opt[1] = sizeof (opt);
+	opt[2] = sp->lcp.magic >> 24;
+	opt[3] = sp->lcp.magic >> 16;
+	opt[4] = sp->lcp.magic >> 8;
+	opt[5] = sp->lcp.magic;
+	sp->lcp.confid = ++sp->pp_seq;
+	sppp_cp_send (sp, PPP_LCP, LCP_CONF_REQ, sp->lcp.confid,
+		sizeof (opt), &opt);
+	sppp_set_timeout (sp, 2);
+}
+
+static void sppp_ipcp_open (struct sppp *sp)
+{
+	sp->ipcp.confid = ++sp->pp_seq;
+	sppp_cp_send (sp, PPP_IPCP, IPCP_CONF_REQ, sp->ipcp.confid, 0, NULL);
+	sppp_set_timeout (sp, 2);
+}
+
+/*
+ * Process PPP control protocol timeouts.
+ */
+ 
+static void sppp_cp_timeout (unsigned long arg)
+{
+	struct sppp *sp = (struct sppp*) arg;
+	unsigned long flags;
+
+	spin_lock_irqsave(&sp->lock, flags);
+
+	sp->pp_flags &= ~PP_TIMO;
+	if (! (sp->pp_if->flags & IFF_UP) || (sp->pp_flags & PP_CISCO)) {
+		spin_unlock_irqrestore(&sp->lock, flags);
+		return;
+	}
+	switch (sp->lcp.state) {
+	case LCP_STATE_CLOSED:
+		/* No ACK for Configure-Request, retry. */
+		sppp_lcp_open (sp);
+		break;
+	case LCP_STATE_ACK_RCVD:
+		/* ACK got, but no Configure-Request for peer, retry. */
+		sppp_lcp_open (sp);
+		sp->lcp.state = LCP_STATE_CLOSED;
+		break;
+	case LCP_STATE_ACK_SENT:
+		/* ACK sent but no ACK for Configure-Request, retry. */
+		sppp_lcp_open (sp);
+		break;
+	case LCP_STATE_OPENED:
+		/* LCP is already OK, try IPCP. */
+		switch (sp->ipcp.state) {
+		case IPCP_STATE_CLOSED:
+			/* No ACK for Configure-Request, retry. */
+			sppp_ipcp_open (sp);
+			break;
+		case IPCP_STATE_ACK_RCVD:
+			/* ACK got, but no Configure-Request for peer, retry. */
+			sppp_ipcp_open (sp);
+			sp->ipcp.state = IPCP_STATE_CLOSED;
+			break;
+		case IPCP_STATE_ACK_SENT:
+			/* ACK sent but no ACK for Configure-Request, retry. */
+			sppp_ipcp_open (sp);
+			break;
+		case IPCP_STATE_OPENED:
+			/* IPCP is OK. */
+			break;
+		}
+		break;
+	}
+	spin_unlock_irqrestore(&sp->lock, flags);
+	sppp_flush_xmit();
+}
+
+static char *sppp_lcp_type_name (u8 type)
+{
+	static char buf [8];
+	switch (type) {
+	case LCP_CONF_REQ:   return ("conf-req");
+	case LCP_CONF_ACK:   return ("conf-ack");
+	case LCP_CONF_NAK:   return ("conf-nack");
+	case LCP_CONF_REJ:   return ("conf-rej");
+	case LCP_TERM_REQ:   return ("term-req");
+	case LCP_TERM_ACK:   return ("term-ack");
+	case LCP_CODE_REJ:   return ("code-rej");
+	case LCP_PROTO_REJ:  return ("proto-rej");
+	case LCP_ECHO_REQ:   return ("echo-req");
+	case LCP_ECHO_REPLY: return ("echo-reply");
+	case LCP_DISC_REQ:   return ("discard-req");
+	}
+	sprintf (buf, "%xh", type);
+	return (buf);
+}
+
+static char *sppp_ipcp_type_name (u8 type)
+{
+	static char buf [8];
+	switch (type) {
+	case IPCP_CONF_REQ:   return ("conf-req");
+	case IPCP_CONF_ACK:   return ("conf-ack");
+	case IPCP_CONF_NAK:   return ("conf-nack");
+	case IPCP_CONF_REJ:   return ("conf-rej");
+	case IPCP_TERM_REQ:   return ("term-req");
+	case IPCP_TERM_ACK:   return ("term-ack");
+	case IPCP_CODE_REJ:   return ("code-rej");
+	}
+	sprintf (buf, "%xh", type);
+	return (buf);
+}
+
+static void sppp_print_bytes (u_char *p, u16 len)
+{
+	printk (" %x", *p++);
+	while (--len > 0)
+		printk ("-%x", *p++);
+}
+
+/**
+ *	sppp_rcv -	receive and process a WAN PPP frame
+ *	@skb:	The buffer to process
+ *	@dev:	The device it arrived on
+ *	@p: Unused
+ *
+ *	Protocol glue. This drives the deferred processing mode the poorer
+ *	cards use. This can be called directly by cards that do not have
+ *	timing constraints but is normally called from the network layer
+ *	after interrupt servicing to process frames queued via netif_rx.
+ */
+
+static int sppp_rcv(struct sk_buff *skb, struct net_device *dev, struct packet_type *p)
+{
+	if ((skb = skb_share_check(skb, GFP_ATOMIC)) == NULL)
+		return NET_RX_DROP;
+	sppp_input(dev,skb);
+	return 0;
+}
+
+struct packet_type sppp_packet_type = {
+	.type	= __constant_htons(ETH_P_WAN_PPP),
+	.func	= sppp_rcv,
+};
+
+static char banner[] __initdata = 
+	KERN_INFO "Cronyx Ltd, Synchronous PPP and CISCO HDLC (c) 1994\n"
+	KERN_INFO "Linux port (c) 1998 Building Number Three Ltd & "
+		  "Jan \"Yenya\" Kasprzak.\n";
+
+static int __init sync_ppp_init(void)
+{
+	if(debug)
+		debug=PP_DEBUG;
+	printk(banner);
+	skb_queue_head_init(&tx_queue);
+	dev_add_pack(&sppp_packet_type);
+	return 0;
+}
+
+
+static void __exit sync_ppp_cleanup(void)
+{
+	dev_remove_pack(&sppp_packet_type);
+}
+
+module_init(sync_ppp_init);
+module_exit(sync_ppp_cleanup);
+module_param(debug, int, 0);
+MODULE_LICENSE("GPL");
+
diff --git a/drivers/net/wan/wanpipe_multppp.c b/drivers/net/wan/wanpipe_multppp.c
new file mode 100644
index 0000000..6aa6987
--- /dev/null
+++ b/drivers/net/wan/wanpipe_multppp.c
@@ -0,0 +1,2357 @@
+/*****************************************************************************
+* wanpipe_multppp.c Multi-Port PPP driver module.
+*
+* Authors: 	Nenad Corbic <ncorbic@sangoma.com>
+*
+* Copyright:	(c) 1995-2001 Sangoma Technologies Inc.
+*
+*		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.
+* ============================================================================
+* Dec 15 2000   Updated for 2.4.X kernel
+* Nov 15 2000   Fixed the SyncPPP support for kernels 2.2.16 and higher.
+*   		The pppstruct has changed.
+* Jul 13 2000	Using the kernel Syncppp module on top of RAW Wanpipe CHDLC
+*  		module.
+*****************************************************************************/
+
+#include <linux/module.h>
+#include <linux/kernel.h>	/* printk(), and other useful stuff */
+#include <linux/stddef.h>	/* offsetof(), etc. */
+#include <linux/errno.h>	/* return codes */
+#include <linux/string.h>	/* inline memset(), etc. */
+#include <linux/slab.h>	/* kmalloc(), kfree() */
+#include <linux/wanrouter.h>	/* WAN router definitions */
+#include <linux/wanpipe.h>	/* WANPIPE common user API definitions */
+#include <linux/if_arp.h>	/* ARPHRD_* defines */
+
+#include <linux/in.h>		/* sockaddr_in */
+#include <linux/inet.h>	
+#include <linux/if.h>
+#include <asm/byteorder.h>	/* htons(), etc. */
+#include <linux/sdlapci.h>
+#include <asm/io.h>
+
+#include <linux/sdla_chdlc.h>		/* CHDLC firmware API definitions */
+#include <linux/sdla_asy.h>           	/* CHDLC (async) API definitions */
+
+#include <linux/if_wanpipe_common.h>    /* Socket Driver common area */
+#include <linux/if_wanpipe.h>		
+
+
+#include <linux/inetdevice.h>
+#include <asm/uaccess.h>
+
+#include <net/syncppp.h>
+
+
+/****** Defines & Macros ****************************************************/
+
+#ifdef	_DEBUG_
+#define	STATIC
+#else
+#define	STATIC		static
+#endif
+
+/* reasons for enabling the timer interrupt on the adapter */
+#define TMR_INT_ENABLED_UDP   	0x01
+#define TMR_INT_ENABLED_UPDATE	0x02
+#define TMR_INT_ENABLED_CONFIG  0x04
+ 
+#define	CHDLC_DFLT_DATA_LEN	1500		/* default MTU */
+#define CHDLC_HDR_LEN		1
+
+#define IFF_POINTTOPOINT 0x10
+
+#define CHDLC_API 0x01
+
+#define PORT(x)   (x == 0 ? "PRIMARY" : "SECONDARY" )
+#define MAX_BH_BUFF	10
+
+#define CRC_LENGTH 	2 
+#define PPP_HEADER_LEN 	4
+ 
+/******Data Structures*****************************************************/
+
+/* This structure is placed in the private data area of the device structure.
+ * The card structure used to occupy the private area but now the following 
+ * structure will incorporate the card structure along with CHDLC specific data
+ */
+
+typedef struct chdlc_private_area
+{
+	void *if_ptr;				/* General Pointer used by SPPP */
+	wanpipe_common_t common;
+	sdla_t		*card;
+	int 		TracingEnabled;		/* For enabling Tracing */
+	unsigned long 	curr_trace_addr;	/* Used for Tracing */
+	unsigned long 	start_trace_addr;
+	unsigned long 	end_trace_addr;
+	unsigned long 	base_addr_trace_buffer;
+	unsigned long 	end_addr_trace_buffer;
+	unsigned short 	number_trace_elements;
+	unsigned  	available_buffer_space;
+	unsigned long 	router_start_time;
+	unsigned char 	route_status;
+	unsigned char 	route_removed;
+	unsigned long 	tick_counter;		/* For 5s timeout counter */
+	unsigned long 	router_up_time;
+        u32             IP_address;		/* IP addressing */
+        u32             IP_netmask;
+	unsigned char  mc;			/* Mulitcast support on/off */
+	unsigned short udp_pkt_lgth;		/* udp packet processing */
+	char udp_pkt_src;
+	char udp_pkt_data[MAX_LGTH_UDP_MGNT_PKT];
+	unsigned short timer_int_enabled;
+	char update_comms_stats;		/* updating comms stats */
+
+	//FIXME: add driver stats as per frame relay!
+
+} chdlc_private_area_t;
+
+/* Route Status options */
+#define NO_ROUTE	0x00
+#define ADD_ROUTE	0x01
+#define ROUTE_ADDED	0x02
+#define REMOVE_ROUTE	0x03
+
+
+/* variable for keeping track of enabling/disabling FT1 monitor status */
+static int rCount = 0;
+
+/* variable for tracking how many interfaces to open for WANPIPE on the
+   two ports */
+
+extern void disable_irq(unsigned int);
+extern void enable_irq(unsigned int);
+
+/****** Function Prototypes *************************************************/
+/* WAN link driver entry points. These are called by the WAN router module. */
+static int update(struct wan_device* wandev);
+static int new_if(struct wan_device* wandev, struct net_device* dev,
+		  wanif_conf_t* conf);
+static int del_if(struct wan_device* wandev, struct net_device* dev);
+
+/* Network device interface */
+static int if_init(struct net_device* dev);
+static int if_open(struct net_device* dev);
+static int if_close(struct net_device* dev);
+static int if_send(struct sk_buff* skb, struct net_device* dev);
+static struct net_device_stats* if_stats(struct net_device* dev);
+
+static void if_tx_timeout(struct net_device *dev);
+
+/* CHDLC Firmware interface functions */
+static int chdlc_configure 	(sdla_t* card, void* data);
+static int chdlc_comm_enable 	(sdla_t* card);
+static int chdlc_comm_disable 	(sdla_t* card);
+static int chdlc_read_version 	(sdla_t* card, char* str);
+static int chdlc_set_intr_mode 	(sdla_t* card, unsigned mode);
+static int chdlc_send (sdla_t* card, void* data, unsigned len);
+static int chdlc_read_comm_err_stats (sdla_t* card);
+static int chdlc_read_op_stats (sdla_t* card);
+static int config_chdlc (sdla_t *card);
+
+
+/* Miscellaneous CHDLC Functions */
+static int set_chdlc_config (sdla_t* card);
+static void init_chdlc_tx_rx_buff(sdla_t* card, struct net_device *dev);
+static int chdlc_error (sdla_t *card, int err, CHDLC_MAILBOX_STRUCT *mb);
+static int process_chdlc_exception(sdla_t *card);
+static int process_global_exception(sdla_t *card);
+static int update_comms_stats(sdla_t* card,
+        chdlc_private_area_t* chdlc_priv_area);
+static void port_set_state (sdla_t *card, int);
+
+/* Interrupt handlers */
+static void wsppp_isr (sdla_t* card);
+static void rx_intr (sdla_t* card);
+static void timer_intr(sdla_t *);
+
+/* Miscellaneous functions */
+static int reply_udp( unsigned char *data, unsigned int mbox_len );
+static int intr_test( sdla_t* card);
+static int udp_pkt_type( struct sk_buff *skb , sdla_t* card);
+static int store_udp_mgmt_pkt(char udp_pkt_src, sdla_t* card,
+			      struct sk_buff *skb, struct net_device* dev,
+			      chdlc_private_area_t* chdlc_priv_area);
+static int process_udp_mgmt_pkt(sdla_t* card, struct net_device* dev,  
+				chdlc_private_area_t* chdlc_priv_area);
+static unsigned short calc_checksum (char *, int);
+static void s508_lock (sdla_t *card, unsigned long *smp_flags);
+static void s508_unlock (sdla_t *card, unsigned long *smp_flags);
+static void send_ppp_term_request(struct net_device *dev);
+
+
+static int  Intr_test_counter;
+/****** Public Functions ****************************************************/
+
+/*============================================================================
+ * Cisco HDLC protocol initialization routine.
+ *
+ * This routine is called by the main WANPIPE module during setup.  At this
+ * point adapter is completely initialized and firmware is running.
+ *  o read firmware version (to make sure it's alive)
+ *  o configure adapter
+ *  o initialize protocol-specific fields of the adapter data space.
+ *
+ * Return:	0	o.k.
+ *		< 0	failure.
+ */
+int wsppp_init (sdla_t* card, wandev_conf_t* conf)
+{
+	unsigned char port_num;
+	int err;
+	unsigned long max_permitted_baud = 0;
+	SHARED_MEMORY_INFO_STRUCT *flags;
+
+	union
+		{
+		char str[80];
+		} u;
+	volatile CHDLC_MAILBOX_STRUCT* mb;
+	CHDLC_MAILBOX_STRUCT* mb1;
+	unsigned long timeout;
+
+	/* Verify configuration ID */
+	if (conf->config_id != WANCONFIG_MPPP) {
+		printk(KERN_INFO "%s: invalid configuration ID %u!\n",
+				  card->devname, conf->config_id);
+		return -EINVAL;
+	}
+
+	/* Find out which Port to use */
+	if ((conf->comm_port == WANOPT_PRI) || (conf->comm_port == WANOPT_SEC)){
+		if (card->next){
+
+			if (conf->comm_port != card->next->u.c.comm_port){
+				card->u.c.comm_port = conf->comm_port;
+			}else{
+				printk(KERN_ERR "%s: ERROR - %s port used!\n",
+        		        	card->wandev.name, PORT(conf->comm_port));
+				return -EINVAL;
+			}
+		}else{
+			card->u.c.comm_port = conf->comm_port;
+		}
+	}else{
+		printk(KERN_ERR "%s: ERROR - Invalid Port Selected!\n",
+                			card->wandev.name);
+		return -EINVAL;
+	}
+	
+
+	/* Initialize protocol-specific fields */
+	if(card->hw.type != SDLA_S514){
+
+		if (card->u.c.comm_port == WANOPT_PRI){	
+			card->mbox  = (void *) card->hw.dpmbase;
+		}else{
+			card->mbox  = (void *) card->hw.dpmbase + 
+				SEC_BASE_ADDR_MB_STRUCT - PRI_BASE_ADDR_MB_STRUCT;
+		}	
+	}else{ 
+		/* for a S514 adapter, set a pointer to the actual mailbox in the */
+		/* allocated virtual memory area */
+		if (card->u.c.comm_port == WANOPT_PRI){
+			card->mbox = (void *) card->hw.dpmbase + PRI_BASE_ADDR_MB_STRUCT;
+		}else{
+			card->mbox = (void *) card->hw.dpmbase + SEC_BASE_ADDR_MB_STRUCT;
+		}	
+	}
+
+	mb = mb1 = card->mbox;
+
+	if (!card->configured){
+
+		/* The board will place an 'I' in the return code to indicate that it is
+	   	ready to accept commands.  We expect this to be completed in less
+           	than 1 second. */
+
+		timeout = jiffies;
+		while (mb->return_code != 'I')	/* Wait 1s for board to initialize */
+			if ((jiffies - timeout) > 1*HZ) break;
+
+		if (mb->return_code != 'I') {
+			printk(KERN_INFO
+				"%s: Initialization not completed by adapter\n",
+				card->devname);
+			printk(KERN_INFO "Please contact Sangoma representative.\n");
+			return -EIO;
+		}
+	}
+
+	/* Read firmware version.  Note that when adapter initializes, it
+	 * clears the mailbox, so it may appear that the first command was
+	 * executed successfully when in fact it was merely erased. To work
+	 * around this, we execute the first command twice.
+	 */
+
+	if (chdlc_read_version(card, u.str))
+		return -EIO;
+
+	printk(KERN_INFO "%s: Running Raw CHDLC firmware v%s\n" 
+			 "%s: for Multi-Port PPP protocol.\n",
+			card->devname,u.str,card->devname); 
+
+	card->isr			= &wsppp_isr;
+	card->poll			= NULL;
+	card->exec			= NULL;
+	card->wandev.update		= &update;
+ 	card->wandev.new_if		= &new_if;
+	card->wandev.del_if		= &del_if;
+	card->wandev.udp_port   	= conf->udp_port;
+
+	card->wandev.new_if_cnt = 0;
+
+	/* reset the number of times the 'update()' proc has been called */
+	card->u.c.update_call_count = 0;
+	
+	card->wandev.ttl = conf->ttl;
+	card->wandev.interface = conf->interface; 
+
+	if ((card->u.c.comm_port == WANOPT_SEC && conf->interface == WANOPT_V35)&&
+	    card->hw.type != SDLA_S514){
+		printk(KERN_INFO "%s: ERROR - V35 Interface not supported on S508 %s port \n",
+			card->devname, PORT(card->u.c.comm_port));
+		return -EIO;
+	}
+
+
+	card->wandev.clocking = conf->clocking;
+
+	port_num = card->u.c.comm_port;
+
+	/* Setup Port Bps */
+
+	if(card->wandev.clocking) {
+		if((port_num == WANOPT_PRI) || card->u.c.receive_only) {
+			/* For Primary Port 0 */
+               		max_permitted_baud =
+				(card->hw.type == SDLA_S514) ?
+				PRI_MAX_BAUD_RATE_S514 : 
+				PRI_MAX_BAUD_RATE_S508;
+		}
+		else if(port_num == WANOPT_SEC) {
+			/* For Secondary Port 1 */
+                        max_permitted_baud =
+                               (card->hw.type == SDLA_S514) ?
+                                SEC_MAX_BAUD_RATE_S514 :
+                                SEC_MAX_BAUD_RATE_S508;
+                        }
+  
+			if(conf->bps > max_permitted_baud) {
+				conf->bps = max_permitted_baud;
+				printk(KERN_INFO "%s: Baud too high!\n",
+					card->wandev.name);
+ 				printk(KERN_INFO "%s: Baud rate set to %lu bps\n", 
+					card->wandev.name, max_permitted_baud);
+			}
+                             
+			card->wandev.bps = conf->bps;
+	}else{
+        	card->wandev.bps = 0;
+  	}
+
+	/* Setup the Port MTU */
+	if((port_num == WANOPT_PRI) || card->u.c.receive_only) {
+
+		/* For Primary Port 0 */
+		card->wandev.mtu =
+			(conf->mtu >= MIN_LGTH_CHDLC_DATA_CFG) ?
+			min_t(unsigned int, conf->mtu, PRI_MAX_NO_DATA_BYTES_IN_FRAME) :
+			CHDLC_DFLT_DATA_LEN;
+	} else if(port_num == WANOPT_SEC) { 
+		/* For Secondary Port 1 */
+		card->wandev.mtu =
+			(conf->mtu >= MIN_LGTH_CHDLC_DATA_CFG) ?
+			min_t(unsigned int, conf->mtu, SEC_MAX_NO_DATA_BYTES_IN_FRAME) :
+			CHDLC_DFLT_DATA_LEN;
+	}
+
+	/* Add on a PPP Header */
+	card->wandev.mtu += PPP_HEADER_LEN;
+
+	/* Set up the interrupt status area */
+	/* Read the CHDLC Configuration and obtain: 
+	 *	Ptr to shared memory infor struct
+         * Use this pointer to calculate the value of card->u.c.flags !
+ 	 */
+	mb1->buffer_length = 0;
+	mb1->command = READ_CHDLC_CONFIGURATION;
+	err = sdla_exec(mb1) ? mb1->return_code : CMD_TIMEOUT;
+	if(err != COMMAND_OK) {
+		clear_bit(1, (void*)&card->wandev.critical);
+
+                if(card->hw.type != SDLA_S514)
+                	enable_irq(card->hw.irq);
+
+		chdlc_error(card, err, mb1);
+		return -EIO;
+	}
+
+	if(card->hw.type == SDLA_S514){
+               	card->u.c.flags = (void *)(card->hw.dpmbase +
+               		(((CHDLC_CONFIGURATION_STRUCT *)mb1->data)->
+			ptr_shared_mem_info_struct));
+        }else{
+                card->u.c.flags = (void *)(card->hw.dpmbase +
+                        (((CHDLC_CONFIGURATION_STRUCT *)mb1->data)->
+			ptr_shared_mem_info_struct % SDLA_WINDOWSIZE));
+	}
+	
+	flags = card->u.c.flags;
+	
+	/* This is for the ports link state */
+	card->wandev.state = WAN_DUALPORT;
+	card->u.c.state = WAN_DISCONNECTED;
+
+
+	if (!card->wandev.piggyback){
+		err = intr_test(card);
+
+		if(err || (Intr_test_counter < MAX_INTR_TEST_COUNTER)) { 
+			printk(KERN_ERR "%s: Interrupt test failed (%i)\n",
+					card->devname, Intr_test_counter);
+			printk(KERN_ERR "%s: Please choose another interrupt\n",
+					card->devname);
+			return  -EIO;
+		}
+			
+		printk(KERN_INFO "%s: Interrupt test passed (%i)\n", 
+				card->devname, Intr_test_counter);
+	}
+
+
+	if (chdlc_set_intr_mode(card, APP_INT_ON_TIMER)){
+		printk (KERN_INFO "%s: Failed to set interrupt triggers!\n",
+				card->devname);
+		return -EIO;	
+        }
+	
+	/* Mask the Timer interrupt */
+	flags->interrupt_info_struct.interrupt_permission &= 
+		~APP_INT_ON_TIMER;
+
+	printk(KERN_INFO "\n");
+
+	return 0;
+}
+
+/******* WAN Device Driver Entry Points *************************************/
+
+/*============================================================================
+ * Update device status & statistics
+ * This procedure is called when updating the PROC file system and returns
+ * various communications statistics. These statistics are accumulated from 3 
+ * different locations:
+ * 	1) The 'if_stats' recorded for the device.
+ * 	2) Communication error statistics on the adapter.
+ *      3) CHDLC operational statistics on the adapter.
+ * The board level statistics are read during a timer interrupt. Note that we 
+ * read the error and operational statistics during consecitive timer ticks so
+ * as to minimize the time that we are inside the interrupt handler.
+ *
+ */
+static int update(struct wan_device* wandev)
+{
+	sdla_t* card = wandev->private;
+ 	struct net_device* dev;
+        volatile chdlc_private_area_t* chdlc_priv_area;
+        SHARED_MEMORY_INFO_STRUCT *flags;
+	unsigned long timeout;
+
+	/* sanity checks */
+	if((wandev == NULL) || (wandev->private == NULL))
+		return -EFAULT;
+	
+	if(wandev->state == WAN_UNCONFIGURED)
+		return -ENODEV;
+
+	/* more sanity checks */
+        if(!card->u.c.flags)
+                return -ENODEV;
+
+	if((dev=card->wandev.dev) == NULL)
+		return -ENODEV;
+
+	if((chdlc_priv_area=dev->priv) == NULL)
+		return -ENODEV;
+
+      	flags = card->u.c.flags;
+
+       	if(chdlc_priv_area->update_comms_stats){
+		return -EAGAIN;
+	}
+			
+	/* we will need 2 timer interrupts to complete the */
+	/* reading of the statistics */
+	chdlc_priv_area->update_comms_stats = 2;
+       	flags->interrupt_info_struct.interrupt_permission |= APP_INT_ON_TIMER;
+	chdlc_priv_area->timer_int_enabled = TMR_INT_ENABLED_UPDATE;
+  
+	/* wait a maximum of 1 second for the statistics to be updated */ 
+        timeout = jiffies;
+        for(;;) {
+		if(chdlc_priv_area->update_comms_stats == 0)
+			break;
+                if ((jiffies - timeout) > (1 * HZ)){
+    			chdlc_priv_area->update_comms_stats = 0;
+ 			chdlc_priv_area->timer_int_enabled &=
+				~TMR_INT_ENABLED_UPDATE; 
+ 			return -EAGAIN;
+		}
+        }
+
+	return 0;
+}
+
+
+/*============================================================================
+ * Create new logical channel.
+ * This routine is called by the router when ROUTER_IFNEW IOCTL is being
+ * handled.
+ * o parse media- and hardware-specific configuration
+ * o make sure that a new channel can be created
+ * o allocate resources, if necessary
+ * o prepare network device structure for registaration.
+ *
+ * Return:	0	o.k.
+ *		< 0	failure (channel will not be created)
+ */
+static int new_if(struct wan_device* wandev, struct net_device* pdev,
+		  wanif_conf_t* conf)
+{
+
+	struct ppp_device *pppdev = (struct ppp_device *)pdev;
+	struct net_device *dev = NULL;
+	struct sppp *sp;
+	sdla_t* card = wandev->private;
+	chdlc_private_area_t* chdlc_priv_area;
+	
+	if ((conf->name[0] == '\0') || (strlen(conf->name) > WAN_IFNAME_SZ)) {
+		printk(KERN_INFO "%s: invalid interface name!\n",
+			card->devname);
+		return -EINVAL;
+	}
+		
+	/* allocate and initialize private data */
+	chdlc_priv_area = kmalloc(sizeof(chdlc_private_area_t), GFP_KERNEL);
+	
+	if(chdlc_priv_area == NULL) 
+		return -ENOMEM;
+
+	memset(chdlc_priv_area, 0, sizeof(chdlc_private_area_t));
+
+	chdlc_priv_area->card = card; 
+
+	/* initialize data */
+	strcpy(card->u.c.if_name, conf->name);
+
+	if(card->wandev.new_if_cnt > 0) {
+                kfree(chdlc_priv_area);
+		return -EEXIST;
+	}
+
+	card->wandev.new_if_cnt++;
+
+	chdlc_priv_area->TracingEnabled = 0;
+
+	//We don't need this any more
+	chdlc_priv_area->route_status = NO_ROUTE;
+	chdlc_priv_area->route_removed = 0;
+
+	printk(KERN_INFO "%s: Firmware running in HDLC STREAMING Mode\n",
+		wandev->name);
+	
+	/* Setup wanpipe as a router (WANPIPE) or as an API */
+	if( strcmp(conf->usedby, "WANPIPE") == 0) {
+		printk(KERN_INFO "%s: Driver running in WANPIPE mode!\n",
+			wandev->name);
+		card->u.c.usedby = WANPIPE;
+	} else {
+		printk(KERN_INFO 
+			"%s: API Mode is not supported for SyncPPP!\n",
+			wandev->name);
+		kfree(chdlc_priv_area);
+		return -EINVAL;
+	}
+
+	/* Get Multicast Information */
+	chdlc_priv_area->mc = conf->mc;
+
+
+	chdlc_priv_area->if_ptr = pppdev;
+
+	/* prepare network device data space for registration */
+
+	strcpy(dev->name,card->u.c.if_name);
+
+	/* Attach PPP protocol layer to pppdev
+	 * The sppp_attach() will initilize the dev structure
+         * and setup ppp layer protocols.
+         * All we have to do is to bind in:
+         *        if_open(), if_close(), if_send() and get_stats() functions.
+         */
+	sppp_attach(pppdev);
+	dev = pppdev->dev;
+	sp = &pppdev->sppp;
+	
+	/* Enable PPP Debugging */
+	// FIXME Fix this up somehow
+	//sp->pp_flags |= PP_DEBUG; 	
+	sp->pp_flags &= ~PP_CISCO;
+
+	dev->init = &if_init;
+	dev->priv = chdlc_priv_area;
+	
+	return 0;
+}
+
+
+
+
+/*============================================================================
+ * Delete logical channel.
+ */
+static int del_if(struct wan_device* wandev, struct net_device* dev)
+{
+	chdlc_private_area_t *chdlc_priv_area = dev->priv;
+	sdla_t *card = chdlc_priv_area->card;
+	unsigned long smp_lock;
+	
+	/* Detach the PPP layer */
+	printk(KERN_INFO "%s: Detaching SyncPPP Module from %s\n",
+			wandev->name,dev->name);
+
+	lock_adapter_irq(&wandev->lock,&smp_lock);
+
+	sppp_detach(dev);
+	chdlc_priv_area->if_ptr=NULL;
+	
+	chdlc_set_intr_mode(card, 0);
+	if (card->u.c.comm_enabled)
+		chdlc_comm_disable(card);
+	unlock_adapter_irq(&wandev->lock,&smp_lock);
+	
+	port_set_state(card, WAN_DISCONNECTED);
+
+	return 0;
+}
+
+
+/****** Network Device Interface ********************************************/
+
+/*============================================================================
+ * Initialize Linux network interface.
+ *
+ * This routine is called only once for each interface, during Linux network
+ * interface registration.  Returning anything but zero will fail interface
+ * registration.
+ */
+static int if_init(struct net_device* dev)
+{
+	chdlc_private_area_t* chdlc_priv_area = dev->priv;
+	sdla_t* card = chdlc_priv_area->card;
+	struct wan_device* wandev = &card->wandev;
+	
+	/* NOTE: Most of the dev initialization was
+         *       done in sppp_attach(), called by new_if() 
+         *       function. All we have to do here is
+         *       to link four major routines below. 
+         */
+
+	/* Initialize device driver entry points */
+	dev->open		= &if_open;
+	dev->stop		= &if_close;
+	dev->hard_start_xmit	= &if_send;
+	dev->get_stats		= &if_stats;
+	dev->tx_timeout		= &if_tx_timeout;
+	dev->watchdog_timeo	= TX_TIMEOUT;
+
+
+	/* Initialize hardware parameters */
+	dev->irq	= wandev->irq;
+	dev->dma	= wandev->dma;
+	dev->base_addr	= wandev->ioport;
+	dev->mem_start	= wandev->maddr;
+	dev->mem_end	= wandev->maddr + wandev->msize - 1;
+
+	/* Set transmit buffer queue length 
+         * If we over fill this queue the packets will
+         * be droped by the kernel.
+         * sppp_attach() sets this to 10, but
+         * 100 will give us more room at low speeds.
+	 */
+        dev->tx_queue_len = 100;
+   
+	return 0;
+}
+
+
+/*============================================================================
+ * Handle transmit timeout event from netif watchdog
+ */
+static void if_tx_timeout(struct net_device *dev)
+{
+    	chdlc_private_area_t* chan = dev->priv;
+	sdla_t *card = chan->card;
+	
+	/* If our device stays busy for at least 5 seconds then we will
+	 * kick start the device by making dev->tbusy = 0.  We expect
+	 * that our device never stays busy more than 5 seconds. So this                 
+	 * is only used as a last resort.
+	 */
+
+	++card->wandev.stats.collisions;
+
+	printk (KERN_INFO "%s: Transmit timed out on %s\n", card->devname,dev->name);
+	netif_wake_queue (dev);
+}
+
+
+/*============================================================================
+ * Open network interface.
+ * o enable communications and interrupts.
+ * o prevent module from unloading by incrementing use count
+ *
+ * Return 0 if O.k. or errno.
+ */
+static int if_open(struct net_device* dev)
+{
+	chdlc_private_area_t* chdlc_priv_area = dev->priv;
+	sdla_t* card = chdlc_priv_area->card;
+	struct timeval tv;
+	SHARED_MEMORY_INFO_STRUCT *flags = card->u.c.flags;
+
+	/* Only one open per interface is allowed */
+	if (netif_running(dev))
+		return -EBUSY;
+
+	/* Start PPP Layer */
+	if (sppp_open(dev)){
+		return -EIO;
+	}
+
+	do_gettimeofday(&tv);
+	chdlc_priv_area->router_start_time = tv.tv_sec;
+ 
+	netif_start_queue(dev);
+	
+	wanpipe_open(card);
+
+	chdlc_priv_area->timer_int_enabled |= TMR_INT_ENABLED_CONFIG;
+	flags->interrupt_info_struct.interrupt_permission |= APP_INT_ON_TIMER;
+	return 0;
+}
+
+/*============================================================================
+ * Close network interface.
+ * o if this is the last close, then disable communications and interrupts.
+ * o reset flags.
+ */
+static int if_close(struct net_device* dev)
+{
+	chdlc_private_area_t* chdlc_priv_area = dev->priv;
+	sdla_t* card = chdlc_priv_area->card;
+
+	/* Stop the PPP Layer */
+	sppp_close(dev);
+	netif_stop_queue(dev);
+
+	wanpipe_close(card);
+	
+	return 0;
+}
+
+/*============================================================================
+ * Send a packet on a network interface.
+ * o set tbusy flag (marks start of the transmission) to block a timer-based
+ *   transmit from overlapping.
+ * o check link state. If link is not up, then drop the packet.
+ * o execute adapter send command.
+ * o free socket buffer
+ *
+ * Return:	0	complete (socket buffer must be freed)
+ *		non-0	packet may be re-transmitted (tbusy must be set)
+ *
+ * Notes:
+ * 1. This routine is called either by the protocol stack or by the "net
+ *    bottom half" (with interrupts enabled).
+ * 2. Setting tbusy flag will inhibit further transmit requests from the
+ *    protocol stack and can be used for flow control with protocol layer.
+ */
+static int if_send(struct sk_buff* skb, struct net_device* dev)
+{
+	chdlc_private_area_t *chdlc_priv_area = dev->priv;
+	sdla_t *card = chdlc_priv_area->card;
+	SHARED_MEMORY_INFO_STRUCT *flags = card->u.c.flags;
+	INTERRUPT_INFORMATION_STRUCT *chdlc_int = &flags->interrupt_info_struct;
+	int udp_type = 0;
+	unsigned long smp_flags;
+	int err=0;
+
+	netif_stop_queue(dev);
+
+	
+	if (skb == NULL){
+		/* If we get here, some higher layer thinks we've missed an
+		 * tx-done interrupt.
+		 */
+		printk(KERN_INFO "%s: Received NULL skb buffer! interface %s got kicked!\n",
+			card->devname, dev->name);
+
+		netif_wake_queue(dev);
+		return 0;
+	}
+
+   	if (ntohs(skb->protocol) != htons(PVC_PROT)){
+		/* check the udp packet type */
+		
+		udp_type = udp_pkt_type(skb, card);
+		if (udp_type == UDP_CPIPE_TYPE){
+                        if(store_udp_mgmt_pkt(UDP_PKT_FRM_STACK, card, skb, dev,
+                                chdlc_priv_area)){
+				chdlc_int->interrupt_permission |=
+					APP_INT_ON_TIMER;
+			}
+			netif_start_queue(dev);
+			return 0;
+		}
+        }
+
+	/* Lock the 508 Card: SMP is supported */
+      	if(card->hw.type != SDLA_S514){
+		s508_lock(card,&smp_flags);
+	} 
+
+    	if (test_and_set_bit(SEND_CRIT, (void*)&card->wandev.critical)){
+	
+		printk(KERN_INFO "%s: Critical in if_send: %lx\n",
+					card->wandev.name,card->wandev.critical);
+                ++card->wandev.stats.tx_dropped;
+		netif_start_queue(dev);
+		goto if_send_crit_exit;
+	}
+
+	if (card->wandev.state != WAN_CONNECTED){
+		++card->wandev.stats.tx_dropped;
+		netif_start_queue(dev);
+		goto if_send_crit_exit;
+	}
+	
+	if (chdlc_send(card, skb->data, skb->len)){
+		netif_stop_queue(dev);
+
+	}else{
+		++card->wandev.stats.tx_packets;
+       		card->wandev.stats.tx_bytes += skb->len;
+		dev->trans_start = jiffies;
+		netif_start_queue(dev);
+	}	
+
+if_send_crit_exit:
+	if (!(err=netif_queue_stopped(dev))){
+                dev_kfree_skb_any(skb);
+	}else{
+		chdlc_priv_area->tick_counter = jiffies;
+		chdlc_int->interrupt_permission |= APP_INT_ON_TX_FRAME;
+	}
+
+	clear_bit(SEND_CRIT, (void*)&card->wandev.critical);
+	if(card->hw.type != SDLA_S514){
+		s508_unlock(card,&smp_flags);
+	}
+
+	return err;
+}
+
+
+/*============================================================================
+ * Reply to UDP Management system.
+ * Return length of reply.
+ */
+static int reply_udp( unsigned char *data, unsigned int mbox_len )
+{
+
+	unsigned short len, udp_length, temp, ip_length;
+	unsigned long ip_temp;
+	int even_bound = 0;
+  	chdlc_udp_pkt_t *c_udp_pkt = (chdlc_udp_pkt_t *)data;
+	 
+	/* Set length of packet */
+	len = sizeof(ip_pkt_t)+ 
+	      sizeof(udp_pkt_t)+
+	      sizeof(wp_mgmt_t)+
+	      sizeof(cblock_t)+
+	      sizeof(trace_info_t)+ 
+	      mbox_len;
+
+	/* fill in UDP reply */
+	c_udp_pkt->wp_mgmt.request_reply = UDPMGMT_REPLY;
+   
+	/* fill in UDP length */
+	udp_length = sizeof(udp_pkt_t)+ 
+		     sizeof(wp_mgmt_t)+
+		     sizeof(cblock_t)+
+	             sizeof(trace_info_t)+
+		     mbox_len; 
+
+ 	/* put it on an even boundary */
+	if ( udp_length & 0x0001 ) {
+		udp_length += 1;
+		len += 1;
+		even_bound = 1;
+	}  
+
+	temp = (udp_length<<8)|(udp_length>>8);
+	c_udp_pkt->udp_pkt.udp_length = temp;
+		 
+	/* swap UDP ports */
+	temp = c_udp_pkt->udp_pkt.udp_src_port;
+	c_udp_pkt->udp_pkt.udp_src_port = 
+			c_udp_pkt->udp_pkt.udp_dst_port; 
+	c_udp_pkt->udp_pkt.udp_dst_port = temp;
+
+	/* add UDP pseudo header */
+	temp = 0x1100;
+	*((unsigned short *)(c_udp_pkt->data+mbox_len+even_bound)) = temp;	
+	temp = (udp_length<<8)|(udp_length>>8);
+	*((unsigned short *)(c_udp_pkt->data+mbox_len+even_bound+2)) = temp;
+
+		 
+	/* calculate UDP checksum */
+	c_udp_pkt->udp_pkt.udp_checksum = 0;
+	c_udp_pkt->udp_pkt.udp_checksum = calc_checksum(&data[UDP_OFFSET],udp_length+UDP_OFFSET);
+
+	/* fill in IP length */
+	ip_length = len;
+	temp = (ip_length<<8)|(ip_length>>8);
+	c_udp_pkt->ip_pkt.total_length = temp;
+  
+	/* swap IP addresses */
+	ip_temp = c_udp_pkt->ip_pkt.ip_src_address;
+	c_udp_pkt->ip_pkt.ip_src_address = c_udp_pkt->ip_pkt.ip_dst_address;
+	c_udp_pkt->ip_pkt.ip_dst_address = ip_temp;
+
+	/* fill in IP checksum */
+	c_udp_pkt->ip_pkt.hdr_checksum = 0;
+	c_udp_pkt->ip_pkt.hdr_checksum = calc_checksum(data,sizeof(ip_pkt_t));
+
+	return len;
+
+} /* reply_udp */
+
+unsigned short calc_checksum (char *data, int len)
+{
+	unsigned short temp; 
+	unsigned long sum=0;
+	int i;
+
+	for( i = 0; i <len; i+=2 ) {
+		memcpy(&temp,&data[i],2);
+		sum += (unsigned long)temp;
+	}
+
+	while (sum >> 16 ) {
+		sum = (sum & 0xffffUL) + (sum >> 16);
+	}
+
+	temp = (unsigned short)sum;
+	temp = ~temp;
+
+	if( temp == 0 ) 
+		temp = 0xffff;
+
+	return temp;	
+}
+
+
+/*============================================================================
+ * Get ethernet-style interface statistics.
+ * Return a pointer to struct enet_statistics.
+ */
+static struct net_device_stats* if_stats(struct net_device* dev)
+{
+	sdla_t *my_card;
+	chdlc_private_area_t* chdlc_priv_area;
+
+	/* Shutdown bug fix. In del_if() we kill
+         * dev->priv pointer. This function, gets
+         * called after del_if(), thus check
+         * if pointer has been deleted */
+	if ((chdlc_priv_area=dev->priv) == NULL)
+		return NULL;
+
+	my_card = chdlc_priv_area->card;
+	return &my_card->wandev.stats; 
+}
+
+
+/****** Cisco HDLC Firmware Interface Functions *******************************/
+
+/*============================================================================
+ * Read firmware code version.
+ *	Put code version as ASCII string in str. 
+ */
+static int chdlc_read_version (sdla_t* card, char* str)
+{
+	CHDLC_MAILBOX_STRUCT* mb = card->mbox;
+	int len;
+	char err;
+	mb->buffer_length = 0;
+	mb->command = READ_CHDLC_CODE_VERSION;
+	err = sdla_exec(mb) ? mb->return_code : CMD_TIMEOUT;
+
+	if(err != COMMAND_OK) {
+		chdlc_error(card,err,mb);
+	}
+	else if (str) {  /* is not null */
+		len = mb->buffer_length;
+		memcpy(str, mb->data, len);
+		str[len] = '\0';
+	}
+	return (err);
+}
+
+/*-----------------------------------------------------------------------------
+ *  Configure CHDLC firmware.
+ */
+static int chdlc_configure (sdla_t* card, void* data)
+{
+	int err;
+	CHDLC_MAILBOX_STRUCT *mailbox = card->mbox;
+	int data_length = sizeof(CHDLC_CONFIGURATION_STRUCT);
+	
+	mailbox->buffer_length = data_length;  
+	memcpy(mailbox->data, data, data_length);
+	mailbox->command = SET_CHDLC_CONFIGURATION;
+	err = sdla_exec(mailbox) ? mailbox->return_code : CMD_TIMEOUT;
+	
+	if (err != COMMAND_OK) chdlc_error (card, err, mailbox);
+                           
+	return err;
+}
+
+
+/*============================================================================
+ * Set interrupt mode -- HDLC Version.
+ */
+
+static int chdlc_set_intr_mode (sdla_t* card, unsigned mode)
+{
+	CHDLC_MAILBOX_STRUCT* mb = card->mbox;
+	CHDLC_INT_TRIGGERS_STRUCT* int_data =
+		 (CHDLC_INT_TRIGGERS_STRUCT *)mb->data;
+	int err;
+
+	int_data->CHDLC_interrupt_triggers 	= mode;
+	int_data->IRQ				= card->hw.irq;
+	int_data->interrupt_timer               = 1;
+   
+	mb->buffer_length = sizeof(CHDLC_INT_TRIGGERS_STRUCT);
+	mb->command = SET_CHDLC_INTERRUPT_TRIGGERS;
+	err = sdla_exec(mb) ? mb->return_code : CMD_TIMEOUT;
+	if (err != COMMAND_OK)
+		chdlc_error (card, err, mb);
+	return err;
+}
+
+
+/*============================================================================
+ * Enable communications.
+ */
+
+static int chdlc_comm_enable (sdla_t* card)
+{
+	int err;
+	CHDLC_MAILBOX_STRUCT* mb = card->mbox;
+
+	mb->buffer_length = 0;
+	mb->command = ENABLE_CHDLC_COMMUNICATIONS;
+	err = sdla_exec(mb) ? mb->return_code : CMD_TIMEOUT;
+	if (err != COMMAND_OK)
+		chdlc_error(card, err, mb);
+	else
+		card->u.c.comm_enabled=1;
+
+	return err;
+}
+
+/*============================================================================
+ * Disable communications and Drop the Modem lines (DCD and RTS).
+ */
+static int chdlc_comm_disable (sdla_t* card)
+{
+	int err;
+	CHDLC_MAILBOX_STRUCT* mb = card->mbox;
+
+	mb->buffer_length = 0;
+	mb->command = DISABLE_CHDLC_COMMUNICATIONS;
+	err = sdla_exec(mb) ? mb->return_code : CMD_TIMEOUT;
+	if (err != COMMAND_OK)
+		chdlc_error(card,err,mb);
+
+	return err;
+}
+
+/*============================================================================
+ * Read communication error statistics.
+ */
+static int chdlc_read_comm_err_stats (sdla_t* card)
+{
+        int err;
+        CHDLC_MAILBOX_STRUCT* mb = card->mbox;
+
+        mb->buffer_length = 0;
+        mb->command = READ_COMMS_ERROR_STATS;
+        err = sdla_exec(mb) ? mb->return_code : CMD_TIMEOUT;
+        if (err != COMMAND_OK)
+                chdlc_error(card,err,mb);
+        return err;
+}
+
+
+/*============================================================================
+ * Read CHDLC operational statistics.
+ */
+static int chdlc_read_op_stats (sdla_t* card)
+{
+        int err;
+        CHDLC_MAILBOX_STRUCT* mb = card->mbox;
+
+        mb->buffer_length = 0;
+        mb->command = READ_CHDLC_OPERATIONAL_STATS;
+        err = sdla_exec(mb) ? mb->return_code : CMD_TIMEOUT;
+        if (err != COMMAND_OK)
+                chdlc_error(card,err,mb);
+        return err;
+}
+
+
+/*============================================================================
+ * Update communications error and general packet statistics.
+ */
+static int update_comms_stats(sdla_t* card,
+	chdlc_private_area_t* chdlc_priv_area)
+{
+        CHDLC_MAILBOX_STRUCT* mb = card->mbox;
+  	COMMS_ERROR_STATS_STRUCT* err_stats;
+        CHDLC_OPERATIONAL_STATS_STRUCT *op_stats;
+
+	/* on the first timer interrupt, read the comms error statistics */
+	if(chdlc_priv_area->update_comms_stats == 2) {
+		if(chdlc_read_comm_err_stats(card))
+			return 1;
+		err_stats = (COMMS_ERROR_STATS_STRUCT *)mb->data;
+		card->wandev.stats.rx_over_errors = 
+				err_stats->Rx_overrun_err_count;
+		card->wandev.stats.rx_crc_errors = 
+				err_stats->CRC_err_count;
+		card->wandev.stats.rx_frame_errors = 
+				err_stats->Rx_abort_count;
+		card->wandev.stats.rx_fifo_errors = 
+				err_stats->Rx_dis_pri_bfrs_full_count; 
+		card->wandev.stats.rx_missed_errors =
+				card->wandev.stats.rx_fifo_errors;
+		card->wandev.stats.tx_aborted_errors =
+				err_stats->sec_Tx_abort_count;
+	}
+
+        /* on the second timer interrupt, read the operational statistics */
+	else {
+        	if(chdlc_read_op_stats(card))
+                	return 1;
+		op_stats = (CHDLC_OPERATIONAL_STATS_STRUCT *)mb->data;
+		card->wandev.stats.rx_length_errors =
+			(op_stats->Rx_Data_discard_short_count +
+			op_stats->Rx_Data_discard_long_count);
+	}
+
+	return 0;
+}
+
+/*============================================================================
+ * Send packet.
+ *	Return:	0 - o.k.
+ *		1 - no transmit buffers available
+ */
+static int chdlc_send (sdla_t* card, void* data, unsigned len)
+{
+	CHDLC_DATA_TX_STATUS_EL_STRUCT *txbuf = card->u.c.txbuf;
+
+	if (txbuf->opp_flag)
+		return 1;
+	
+	sdla_poke(&card->hw, txbuf->ptr_data_bfr, data, len);
+
+	txbuf->frame_length = len;
+	txbuf->opp_flag = 1;		/* start transmission */
+	
+	/* Update transmit buffer control fields */
+	card->u.c.txbuf = ++txbuf;
+
+	if ((void*)txbuf > card->u.c.txbuf_last)
+		card->u.c.txbuf = card->u.c.txbuf_base;
+
+	return 0;
+}
+
+/****** Firmware Error Handler **********************************************/
+
+/*============================================================================
+ * Firmware error handler.
+ *	This routine is called whenever firmware command returns non-zero
+ *	return code.
+ *
+ * Return zero if previous command has to be cancelled.
+ */
+static int chdlc_error (sdla_t *card, int err, CHDLC_MAILBOX_STRUCT *mb)
+{
+	unsigned cmd = mb->command;
+
+	switch (err) {
+
+	case CMD_TIMEOUT:
+		printk(KERN_ERR "%s: command 0x%02X timed out!\n",
+			card->devname, cmd);
+		break;
+
+	case S514_BOTH_PORTS_SAME_CLK_MODE:
+		if(cmd == SET_CHDLC_CONFIGURATION) {
+			printk(KERN_INFO
+			 "%s: Configure both ports for the same clock source\n",
+				card->devname);
+			break;
+		}
+
+	default:
+		printk(KERN_INFO "%s: command 0x%02X returned 0x%02X!\n",
+			card->devname, cmd, err);
+	}
+
+	return 0;
+}
+
+/****** Interrupt Handlers **************************************************/
+
+/*============================================================================
+ * Cisco HDLC interrupt service routine.
+ */
+STATIC void wsppp_isr (sdla_t* card)
+{
+	struct net_device* dev;
+	SHARED_MEMORY_INFO_STRUCT* flags = NULL;
+	int i;
+	sdla_t *my_card;
+
+
+	/* Check for which port the interrupt has been generated
+	 * Since Secondary Port is piggybacking on the Primary
+         * the check must be done here. 
+	 */
+
+	flags = card->u.c.flags;
+	if (!flags->interrupt_info_struct.interrupt_type){
+		/* Check for a second port (piggybacking) */
+		if((my_card = card->next)){
+			flags = my_card->u.c.flags;
+			if (flags->interrupt_info_struct.interrupt_type){
+				card = my_card;
+				card->isr(card);
+				return;
+			}
+		}
+	}
+
+	dev = card->wandev.dev;
+	card->in_isr = 1;
+	flags = card->u.c.flags;
+		
+	/* If we get an interrupt with no network device, stop the interrupts
+	 * and issue an error */
+	if ((!dev || !dev->priv) && flags->interrupt_info_struct.interrupt_type != 
+	    	COMMAND_COMPLETE_APP_INT_PEND){
+		goto isr_done;
+	}
+
+	
+	/* if critical due to peripheral operations
+	 * ie. update() or getstats() then reset the interrupt and
+	 * wait for the board to retrigger.
+	 */
+	if(test_bit(PERI_CRIT, (void*)&card->wandev.critical)) {
+		flags->interrupt_info_struct.
+					interrupt_type = 0;
+		goto isr_done;
+	}
+
+
+	/* On a 508 Card, if critical due to if_send 
+         * Major Error !!!
+	 */
+	if(card->hw.type != SDLA_S514) {
+		if(test_bit(0, (void*)&card->wandev.critical)) {
+			printk(KERN_INFO "%s: Critical while in ISR: %lx\n",
+				card->devname, card->wandev.critical);
+			goto isr_done;
+		}
+	}
+
+	switch(flags->interrupt_info_struct.interrupt_type) {
+
+		case RX_APP_INT_PEND:	/* 0x01: receive interrupt */
+			rx_intr(card);
+			break;
+
+		case TX_APP_INT_PEND:	/* 0x02: transmit interrupt */
+			flags->interrupt_info_struct.interrupt_permission &=
+				 ~APP_INT_ON_TX_FRAME;
+
+			netif_wake_queue(dev);
+			break;
+
+		case COMMAND_COMPLETE_APP_INT_PEND:/* 0x04: cmd cplt */
+			++ Intr_test_counter;
+			break;
+
+		case CHDLC_EXCEP_COND_APP_INT_PEND:	/* 0x20 */
+			process_chdlc_exception(card);
+			break;
+
+		case GLOBAL_EXCEP_COND_APP_INT_PEND:
+			process_global_exception(card);
+			break;
+
+		case TIMER_APP_INT_PEND:
+			timer_intr(card);
+			break;
+
+		default:
+			printk(KERN_INFO "%s: spurious interrupt 0x%02X!\n", 
+				card->devname,
+				flags->interrupt_info_struct.interrupt_type);
+			printk(KERN_INFO "Code name: ");
+			for(i = 0; i < 4; i ++)
+				printk(KERN_INFO "%c",
+					flags->global_info_struct.codename[i]); 
+			printk(KERN_INFO "\nCode version: ");
+			for(i = 0; i < 4; i ++)
+				printk(KERN_INFO "%c", 
+					flags->global_info_struct.codeversion[i]); 
+			printk(KERN_INFO "\n");	
+			break;
+	}
+
+isr_done:
+	card->in_isr = 0;
+	flags->interrupt_info_struct.interrupt_type = 0;
+}
+
+/*============================================================================
+ * Receive interrupt handler.
+ */
+static void rx_intr (sdla_t* card)
+{
+	struct net_device *dev;
+	chdlc_private_area_t *chdlc_priv_area;
+	SHARED_MEMORY_INFO_STRUCT *flags = card->u.c.flags;
+	CHDLC_DATA_RX_STATUS_EL_STRUCT *rxbuf = card->u.c.rxmb;
+	struct sk_buff *skb;
+	unsigned len;
+	unsigned addr = rxbuf->ptr_data_bfr;
+	void *buf;
+	int i,udp_type;
+	
+	if (rxbuf->opp_flag != 0x01) {
+		printk(KERN_INFO 
+			"%s: corrupted Rx buffer @ 0x%X, flag = 0x%02X!\n", 
+			card->devname, (unsigned)rxbuf, rxbuf->opp_flag);
+                printk(KERN_INFO "Code name: ");
+                for(i = 0; i < 4; i ++)
+                        printk(KERN_INFO "%c",
+                                flags->global_info_struct.codename[i]);
+                printk(KERN_INFO "\nCode version: ");
+                for(i = 0; i < 4; i ++)
+                        printk(KERN_INFO "%c",
+                                flags->global_info_struct.codeversion[i]);
+                printk(KERN_INFO "\n");
+
+
+		/* Bug Fix: Mar 6 2000
+                 * If we get a corrupted mailbox, it measn that driver 
+                 * is out of sync with the firmware. There is no recovery.
+                 * If we don't turn off all interrupts for this card
+                 * the machine will crash. 
+                 */
+		printk(KERN_INFO "%s: Critical router failure ...!!!\n", card->devname);
+		printk(KERN_INFO "Please contact Sangoma Technologies !\n");
+		chdlc_set_intr_mode(card,0);	
+		return;
+	}
+
+	dev = card->wandev.dev;
+
+	if (!dev){ 
+		goto rx_exit;
+	}
+	
+	if (!netif_running(dev)){
+		goto rx_exit;
+	}
+
+	chdlc_priv_area = dev->priv;
+
+	if (rxbuf->error_flag){	
+		goto rx_exit;
+	}
+	/* Take off two CRC bytes */
+
+	if (rxbuf->frame_length < 7 || rxbuf->frame_length > 1506 ){
+		goto rx_exit;
+	}	
+
+	len = rxbuf->frame_length - CRC_LENGTH;
+
+	/* Allocate socket buffer */
+	skb = dev_alloc_skb(len);
+
+	if (skb == NULL) {
+		if (net_ratelimit()){
+			printk(KERN_INFO "%s: no socket buffers available!\n",
+						card->devname);
+		}
+		++card->wandev.stats.rx_dropped;
+		goto rx_exit;
+	}
+
+	/* Copy data to the socket buffer */
+	if((addr + len) > card->u.c.rx_top + 1) {
+		unsigned tmp = card->u.c.rx_top - addr + 1;
+		buf = skb_put(skb, tmp);
+		sdla_peek(&card->hw, addr, buf, tmp);
+		addr = card->u.c.rx_base;
+		len -= tmp;
+	}
+		
+	buf = skb_put(skb, len);
+	sdla_peek(&card->hw, addr, buf, len);
+
+	skb->protocol = htons(ETH_P_WAN_PPP);
+
+	card->wandev.stats.rx_packets ++;
+	card->wandev.stats.rx_bytes += skb->len;
+	udp_type = udp_pkt_type( skb, card );
+
+	if(udp_type == UDP_CPIPE_TYPE) {
+		if(store_udp_mgmt_pkt(UDP_PKT_FRM_NETWORK,
+   				      card, skb, dev, chdlc_priv_area)) {
+     		        flags->interrupt_info_struct.
+						interrupt_permission |= 
+							APP_INT_ON_TIMER; 
+		}
+	}else{
+               	/* Pass it up the protocol stack */
+                skb->dev = dev;
+                skb->mac.raw  = skb->data;
+                netif_rx(skb);
+                dev->last_rx = jiffies;
+	}
+
+rx_exit:
+	/* Release buffer element and calculate a pointer to the next one */
+	rxbuf->opp_flag = 0x00;
+	card->u.c.rxmb = ++ rxbuf;
+	if((void*)rxbuf > card->u.c.rxbuf_last){
+		card->u.c.rxmb = card->u.c.rxbuf_base;
+	}
+}
+
+/*============================================================================
+ * Timer interrupt handler.
+ * The timer interrupt is used for two purposes:
+ *    1) Processing udp calls from 'cpipemon'.
+ *    2) Reading board-level statistics for updating the proc file system.
+ */
+void timer_intr(sdla_t *card)
+{
+        struct net_device* dev;
+        chdlc_private_area_t* chdlc_priv_area = NULL;
+        SHARED_MEMORY_INFO_STRUCT* flags = NULL;
+
+        dev = card->wandev.dev; 
+        chdlc_priv_area = dev->priv;
+
+	if (chdlc_priv_area->timer_int_enabled & TMR_INT_ENABLED_CONFIG) {
+		if (!config_chdlc(card)){
+			chdlc_priv_area->timer_int_enabled &= ~TMR_INT_ENABLED_CONFIG;
+		}
+	}
+	
+	/* process a udp call if pending */
+       	if(chdlc_priv_area->timer_int_enabled & TMR_INT_ENABLED_UDP) {
+               	process_udp_mgmt_pkt(card, dev,
+                       chdlc_priv_area);
+		chdlc_priv_area->timer_int_enabled &= ~TMR_INT_ENABLED_UDP;
+        }
+	
+
+	/* read the communications statistics if required */
+	if(chdlc_priv_area->timer_int_enabled & TMR_INT_ENABLED_UPDATE) {
+		update_comms_stats(card, chdlc_priv_area);
+                if(!(-- chdlc_priv_area->update_comms_stats)) {
+			chdlc_priv_area->timer_int_enabled &= 
+				~TMR_INT_ENABLED_UPDATE;
+		}
+        }
+
+	/* only disable the timer interrupt if there are no udp or statistic */
+	/* updates pending */
+        if(!chdlc_priv_area->timer_int_enabled) {
+                flags = card->u.c.flags;
+                flags->interrupt_info_struct.interrupt_permission &=
+                        ~APP_INT_ON_TIMER;
+        }
+}
+
+/*------------------------------------------------------------------------------
+  Miscellaneous Functions
+	- set_chdlc_config() used to set configuration options on the board
+------------------------------------------------------------------------------*/
+
+static int set_chdlc_config(sdla_t* card)
+{
+
+	CHDLC_CONFIGURATION_STRUCT cfg;
+
+	memset(&cfg, 0, sizeof(CHDLC_CONFIGURATION_STRUCT));
+
+	if(card->wandev.clocking)
+		cfg.baud_rate = card->wandev.bps;
+
+	cfg.line_config_options = (card->wandev.interface == WANOPT_RS232) ?
+		INTERFACE_LEVEL_RS232 : INTERFACE_LEVEL_V35;
+
+	cfg.modem_config_options	= 0;
+	//API OPTIONS
+	cfg.CHDLC_API_options		= DISCARD_RX_ERROR_FRAMES;
+	cfg.modem_status_timer		= 100;
+	cfg.CHDLC_protocol_options	= HDLC_STREAMING_MODE;
+	cfg.percent_data_buffer_for_Tx  = 50;
+	cfg.CHDLC_statistics_options	= (CHDLC_TX_DATA_BYTE_COUNT_STAT |
+		CHDLC_RX_DATA_BYTE_COUNT_STAT);
+	cfg.max_CHDLC_data_field_length	= card->wandev.mtu;
+
+	cfg.transmit_keepalive_timer	= 0;
+	cfg.receive_keepalive_timer	= 0;
+	cfg.keepalive_error_tolerance	= 0;
+	cfg.SLARP_request_timer		= 0;
+
+	cfg.IP_address		= 0;
+	cfg.IP_netmask		= 0;
+	
+	return chdlc_configure(card, &cfg);
+}
+
+/*============================================================================
+ * Process global exception condition
+ */
+static int process_global_exception(sdla_t *card)
+{
+	CHDLC_MAILBOX_STRUCT* mbox = card->mbox;
+	int err;
+
+	mbox->buffer_length = 0;
+	mbox->command = READ_GLOBAL_EXCEPTION_CONDITION;
+	err = sdla_exec(mbox) ? mbox->return_code : CMD_TIMEOUT;
+
+	if(err != CMD_TIMEOUT ){
+	
+		switch(mbox->return_code) {
+         
+	      	case EXCEP_MODEM_STATUS_CHANGE:
+
+			printk(KERN_INFO "%s: Modem status change\n",
+				card->devname);
+
+			switch(mbox->data[0] & (DCD_HIGH | CTS_HIGH)) {
+				case (DCD_HIGH):
+					printk(KERN_INFO "%s: DCD high, CTS low\n",card->devname);
+					break;
+				case (CTS_HIGH):
+                                        printk(KERN_INFO "%s: DCD low, CTS high\n",card->devname);
+                                        break;
+                                case ((DCD_HIGH | CTS_HIGH)):
+                                        printk(KERN_INFO "%s: DCD high, CTS high\n",card->devname);
+                                        break;
+				default:
+                                        printk(KERN_INFO "%s: DCD low, CTS low\n",card->devname);
+                                        break;
+			}
+
+			if (!(mbox->data[0] & DCD_HIGH) || !(mbox->data[0] & DCD_HIGH)){
+				//printk(KERN_INFO "Sending TERM Request Manually !\n");
+				send_ppp_term_request(card->wandev.dev);
+			}	
+			break;
+
+                case EXCEP_TRC_DISABLED:
+                        printk(KERN_INFO "%s: Line trace disabled\n",
+				card->devname);
+                        break;
+
+		case EXCEP_IRQ_TIMEOUT:
+			printk(KERN_INFO "%s: IRQ timeout occurred\n",
+				card->devname); 
+			break;
+
+                default:
+                        printk(KERN_INFO "%s: Global exception %x\n",
+				card->devname, mbox->return_code);
+                        break;
+                }
+	}
+	return 0;
+}
+
+
+/*============================================================================
+ * Process chdlc exception condition
+ */
+static int process_chdlc_exception(sdla_t *card)
+{
+	CHDLC_MAILBOX_STRUCT* mb = card->mbox;
+	int err;
+
+	mb->buffer_length = 0;
+	mb->command = READ_CHDLC_EXCEPTION_CONDITION;
+	err = sdla_exec(mb) ? mb->return_code : CMD_TIMEOUT;
+	if(err != CMD_TIMEOUT) {
+	
+		switch (err) {
+
+		case EXCEP_LINK_ACTIVE:
+			port_set_state(card, WAN_CONNECTED);
+			break;
+
+		case EXCEP_LINK_INACTIVE_MODEM:
+			port_set_state(card, WAN_DISCONNECTED);
+			break;
+
+		case EXCEP_LOOPBACK_CONDITION:
+			printk(KERN_INFO "%s: Loopback Condition Detected.\n",
+						card->devname);
+			break;
+
+		case NO_CHDLC_EXCEP_COND_TO_REPORT:
+			printk(KERN_INFO "%s: No exceptions reported.\n",
+						card->devname);
+			break;
+		default:
+			printk(KERN_INFO "%s: Exception Condition %x!\n",
+					card->devname,err);
+			break;
+		}
+
+	}
+	return 0;
+}
+
+
+/*=============================================================================
+ * Store a UDP management packet for later processing.
+ */
+
+static int store_udp_mgmt_pkt(char udp_pkt_src, sdla_t* card,
+			      struct sk_buff *skb, struct net_device* dev,
+			      chdlc_private_area_t* chdlc_priv_area )
+{
+	int udp_pkt_stored = 0;
+
+	if(!chdlc_priv_area->udp_pkt_lgth &&
+	  (skb->len <= MAX_LGTH_UDP_MGNT_PKT)) {
+        	chdlc_priv_area->udp_pkt_lgth = skb->len;
+		chdlc_priv_area->udp_pkt_src = udp_pkt_src;
+       		memcpy(chdlc_priv_area->udp_pkt_data, skb->data, skb->len);
+		chdlc_priv_area->timer_int_enabled = TMR_INT_ENABLED_UDP;
+		udp_pkt_stored = 1;
+	}
+
+	if(udp_pkt_src == UDP_PKT_FRM_STACK)
+		dev_kfree_skb_any(skb);
+	else
+                dev_kfree_skb_any(skb);
+	
+	return(udp_pkt_stored);
+}
+
+
+/*=============================================================================
+ * Process UDP management packet.
+ */
+
+static int process_udp_mgmt_pkt(sdla_t* card, struct net_device* dev,
+				chdlc_private_area_t* chdlc_priv_area ) 
+{
+	unsigned char *buf;
+	unsigned int frames, len;
+	struct sk_buff *new_skb;
+	unsigned short buffer_length, real_len;
+	unsigned long data_ptr;
+	unsigned data_length;
+	int udp_mgmt_req_valid = 1;
+	CHDLC_MAILBOX_STRUCT *mb = card->mbox;
+	SHARED_MEMORY_INFO_STRUCT *flags = card->u.c.flags;
+	chdlc_udp_pkt_t *chdlc_udp_pkt;
+	struct timeval tv;
+	int err;
+	char ut_char;
+
+	chdlc_udp_pkt = (chdlc_udp_pkt_t *) chdlc_priv_area->udp_pkt_data;
+
+	if(chdlc_priv_area->udp_pkt_src == UDP_PKT_FRM_NETWORK) {
+
+		switch(chdlc_udp_pkt->cblock.command) {
+			case READ_GLOBAL_STATISTICS:
+			case READ_MODEM_STATUS:  
+			case READ_CHDLC_LINK_STATUS:
+			case CPIPE_ROUTER_UP_TIME:
+			case READ_COMMS_ERROR_STATS:
+			case READ_CHDLC_OPERATIONAL_STATS:
+
+			/* These two commands are executed for
+			 * each request */
+			case READ_CHDLC_CONFIGURATION:
+			case READ_CHDLC_CODE_VERSION:
+				udp_mgmt_req_valid = 1;
+				break;
+			default:
+				udp_mgmt_req_valid = 0;
+				break;
+		} 
+	}
+	
+  	if(!udp_mgmt_req_valid) {
+
+		/* set length to 0 */
+		chdlc_udp_pkt->cblock.buffer_length = 0;
+
+    		/* set return code */
+		chdlc_udp_pkt->cblock.return_code = 0xCD;
+
+		if (net_ratelimit()){	
+			printk(KERN_INFO 
+			"%s: Warning, Illegal UDP command attempted from network: %x\n",
+			card->devname,chdlc_udp_pkt->cblock.command);
+		}
+
+   	} else {
+	   	unsigned long trace_status_cfg_addr = 0;
+		TRACE_STATUS_EL_CFG_STRUCT trace_cfg_struct;
+		TRACE_STATUS_ELEMENT_STRUCT trace_element_struct;
+
+		switch(chdlc_udp_pkt->cblock.command) {
+
+		case CPIPE_ENABLE_TRACING:
+		     if (!chdlc_priv_area->TracingEnabled) {
+
+			/* OPERATE_DATALINE_MONITOR */
+
+			mb->buffer_length = sizeof(LINE_TRACE_CONFIG_STRUCT);
+			mb->command = SET_TRACE_CONFIGURATION;
+
+    			((LINE_TRACE_CONFIG_STRUCT *)mb->data)->
+				trace_config = TRACE_ACTIVE;
+			/* Trace delay mode is not used because it slows
+			   down transfer and results in a standoff situation
+			   when there is a lot of data */
+
+			/* Configure the Trace based on user inputs */
+			((LINE_TRACE_CONFIG_STRUCT *)mb->data)->trace_config |= 
+					chdlc_udp_pkt->data[0];
+
+			((LINE_TRACE_CONFIG_STRUCT *)mb->data)->
+			   trace_deactivation_timer = 4000;
+
+
+			err = sdla_exec(mb) ? mb->return_code : CMD_TIMEOUT;
+			if (err != COMMAND_OK) {
+				chdlc_error(card,err,mb);
+				card->TracingEnabled = 0;
+				chdlc_udp_pkt->cblock.return_code = err;
+				mb->buffer_length = 0;
+				break;
+	    		} 
+
+			/* Get the base address of the trace element list */
+			mb->buffer_length = 0;
+			mb->command = READ_TRACE_CONFIGURATION;
+			err = sdla_exec(mb) ? mb->return_code : CMD_TIMEOUT;
+
+			if (err != COMMAND_OK) {
+				chdlc_error(card,err,mb);
+				chdlc_priv_area->TracingEnabled = 0;
+				chdlc_udp_pkt->cblock.return_code = err;
+				mb->buffer_length = 0;
+				break;
+	    		} 	
+
+	   		trace_status_cfg_addr =((LINE_TRACE_CONFIG_STRUCT *)
+				mb->data) -> ptr_trace_stat_el_cfg_struct;
+
+			sdla_peek(&card->hw, trace_status_cfg_addr,
+				 &trace_cfg_struct, sizeof(trace_cfg_struct));
+		    
+			chdlc_priv_area->start_trace_addr = trace_cfg_struct.
+				base_addr_trace_status_elements;
+
+			chdlc_priv_area->number_trace_elements = 
+					trace_cfg_struct.number_trace_status_elements;
+
+			chdlc_priv_area->end_trace_addr = (unsigned long)
+					((TRACE_STATUS_ELEMENT_STRUCT *)
+					 chdlc_priv_area->start_trace_addr + 
+					 (chdlc_priv_area->number_trace_elements - 1));
+
+			chdlc_priv_area->base_addr_trace_buffer = 
+					trace_cfg_struct.base_addr_trace_buffer;
+
+			chdlc_priv_area->end_addr_trace_buffer = 
+					trace_cfg_struct.end_addr_trace_buffer;
+
+		    	chdlc_priv_area->curr_trace_addr = 
+					trace_cfg_struct.next_trace_element_to_use;
+
+	    		chdlc_priv_area->available_buffer_space = 2000 - 
+								  sizeof(ip_pkt_t) -
+								  sizeof(udp_pkt_t) -
+							      	  sizeof(wp_mgmt_t) -
+								  sizeof(cblock_t) -
+							          sizeof(trace_info_t);	
+	       	     }
+		     chdlc_udp_pkt->cblock.return_code = COMMAND_OK;
+		     mb->buffer_length = 0;
+	       	     chdlc_priv_area->TracingEnabled = 1;
+	       	     break;
+	   
+
+		case CPIPE_DISABLE_TRACING:
+		     if (chdlc_priv_area->TracingEnabled) {
+
+			/* OPERATE_DATALINE_MONITOR */
+			mb->buffer_length = sizeof(LINE_TRACE_CONFIG_STRUCT);
+			mb->command = SET_TRACE_CONFIGURATION;
+    			((LINE_TRACE_CONFIG_STRUCT *)mb->data)->
+				trace_config = TRACE_INACTIVE;
+			err = sdla_exec(mb) ? mb->return_code : CMD_TIMEOUT;
+		     }		
+
+		     chdlc_priv_area->TracingEnabled = 0;
+		     chdlc_udp_pkt->cblock.return_code = COMMAND_OK;
+		     mb->buffer_length = 0;
+		     break;
+	   
+
+		case CPIPE_GET_TRACE_INFO:
+
+		     if (!chdlc_priv_area->TracingEnabled) {
+			chdlc_udp_pkt->cblock.return_code = 1;
+			mb->buffer_length = 0;
+			break;
+		     }
+
+  		     chdlc_udp_pkt->trace_info.ismoredata = 0x00;
+		     buffer_length = 0;	/* offset of packet already occupied */
+
+		     for (frames=0; frames < chdlc_priv_area->number_trace_elements; frames++){
+
+			trace_pkt_t *trace_pkt = (trace_pkt_t *)
+				&chdlc_udp_pkt->data[buffer_length];
+
+			sdla_peek(&card->hw, chdlc_priv_area->curr_trace_addr,
+			   	  (unsigned char *)&trace_element_struct,
+			   	  sizeof(TRACE_STATUS_ELEMENT_STRUCT));
+
+     			if (trace_element_struct.opp_flag == 0x00) {
+			 	break;
+			}
+
+			/* get pointer to real data */
+			data_ptr = trace_element_struct.ptr_data_bfr;
+
+			/* See if there is actual data on the trace buffer */
+			if (data_ptr){
+				data_length = trace_element_struct.trace_length;
+			}else{
+				data_length = 0;
+				chdlc_udp_pkt->trace_info.ismoredata = 0x01;
+			}
+	
+   			if( (chdlc_priv_area->available_buffer_space - buffer_length)
+				< ( sizeof(trace_pkt_t) + data_length) ) {
+
+                            /* indicate there are more frames on board & exit */
+				chdlc_udp_pkt->trace_info.ismoredata = 0x01;
+                               	break;
+                         }
+
+			trace_pkt->status = trace_element_struct.trace_type;
+
+			trace_pkt->time_stamp =
+				trace_element_struct.trace_time_stamp;
+
+			trace_pkt->real_length =
+				trace_element_struct.trace_length;
+
+			/* see if we can fit the frame into the user buffer */
+			real_len = trace_pkt->real_length;
+
+			if (data_ptr == 0) {
+			     	trace_pkt->data_avail = 0x00;
+			} else {
+				unsigned tmp = 0;
+
+				/* get the data from circular buffer
+				    must check for end of buffer */
+			        trace_pkt->data_avail = 0x01;
+
+				if ((data_ptr + real_len) >
+					     chdlc_priv_area->end_addr_trace_buffer + 1){
+
+				    	tmp = chdlc_priv_area->end_addr_trace_buffer - data_ptr + 1;
+				    	sdla_peek(&card->hw, data_ptr,
+					       	  trace_pkt->data,tmp);
+				    	data_ptr = chdlc_priv_area->base_addr_trace_buffer;
+				}
+	
+		        	sdla_peek(&card->hw, data_ptr,
+					  &trace_pkt->data[tmp], real_len - tmp);
+			}	
+
+			/* zero the opp flag to show we got the frame */
+			ut_char = 0x00;
+			sdla_poke(&card->hw, chdlc_priv_area->curr_trace_addr, &ut_char, 1);
+
+       			/* now move onto the next frame */
+       			chdlc_priv_area->curr_trace_addr += sizeof(TRACE_STATUS_ELEMENT_STRUCT);
+
+       			/* check if we went over the last address */
+			if ( chdlc_priv_area->curr_trace_addr > chdlc_priv_area->end_trace_addr ) {
+				chdlc_priv_area->curr_trace_addr = chdlc_priv_area->start_trace_addr;
+       			}
+
+            		if(trace_pkt->data_avail == 0x01) {
+				buffer_length += real_len - 1;
+			}
+	 
+	       	    	/* for the header */
+	            	buffer_length += sizeof(trace_pkt_t);
+
+		     }  /* For Loop */
+
+		     if (frames == chdlc_priv_area->number_trace_elements){
+			chdlc_udp_pkt->trace_info.ismoredata = 0x01;
+	             }
+ 		     chdlc_udp_pkt->trace_info.num_frames = frames;
+		 
+    		     mb->buffer_length = buffer_length;
+		     chdlc_udp_pkt->cblock.buffer_length = buffer_length; 
+		 
+		     chdlc_udp_pkt->cblock.return_code = COMMAND_OK; 
+		     
+		     break;
+
+
+		case CPIPE_FT1_READ_STATUS:
+			((unsigned char *)chdlc_udp_pkt->data )[0] =
+				flags->FT1_info_struct.parallel_port_A_input;
+
+			((unsigned char *)chdlc_udp_pkt->data )[1] =
+				flags->FT1_info_struct.parallel_port_B_input;
+				 
+			chdlc_udp_pkt->cblock.return_code = COMMAND_OK;
+			mb->buffer_length = 2;
+			break;
+		
+		case CPIPE_ROUTER_UP_TIME:
+			do_gettimeofday( &tv );
+			chdlc_priv_area->router_up_time = tv.tv_sec - 
+					chdlc_priv_area->router_start_time;
+			*(unsigned long *)&chdlc_udp_pkt->data = 
+					chdlc_priv_area->router_up_time;	
+			mb->buffer_length = sizeof(unsigned long);
+			break;
+
+   		case FT1_MONITOR_STATUS_CTRL:
+			/* Enable FT1 MONITOR STATUS */
+	        	if ((chdlc_udp_pkt->data[0] & ENABLE_READ_FT1_STATUS) ||  
+				(chdlc_udp_pkt->data[0] & ENABLE_READ_FT1_OP_STATS)) {
+			
+			     	if( rCount++ != 0 ) {
+					chdlc_udp_pkt->cblock.
+					return_code = COMMAND_OK;
+					mb->buffer_length = 1;
+		  			break;
+		    	     	}
+	      		}
+
+	      		/* Disable FT1 MONITOR STATUS */
+	      		if( chdlc_udp_pkt->data[0] == 0) {
+
+	      	   	     	if( --rCount != 0) {
+		  			chdlc_udp_pkt->cblock.
+					return_code = COMMAND_OK;
+					mb->buffer_length = 1;
+		  			break;
+	   	    	     	} 
+	      		} 	
+	
+		default:
+			/* it's a board command */
+			mb->command = chdlc_udp_pkt->cblock.command;
+			mb->buffer_length = chdlc_udp_pkt->cblock.buffer_length;
+			if (mb->buffer_length) {
+				memcpy(&mb->data, (unsigned char *) chdlc_udp_pkt->
+							data, mb->buffer_length);
+	      		} 
+			/* run the command on the board */
+			err = sdla_exec(mb) ? mb->return_code : CMD_TIMEOUT;
+			if (err != COMMAND_OK) {
+				break;
+			}
+
+			/* copy the result back to our buffer */
+	         	memcpy(&chdlc_udp_pkt->cblock, mb, sizeof(cblock_t)); 
+			
+			if (mb->buffer_length) {
+	         		memcpy(&chdlc_udp_pkt->data, &mb->data, 
+								mb->buffer_length); 
+	      		}
+
+		} /* end of switch */
+     	} /* end of else */
+
+     	/* Fill UDP TTL */
+	chdlc_udp_pkt->ip_pkt.ttl = card->wandev.ttl; 
+
+     	len = reply_udp(chdlc_priv_area->udp_pkt_data, mb->buffer_length);
+	
+     	if(chdlc_priv_area->udp_pkt_src == UDP_PKT_FRM_NETWORK) {
+		if(!chdlc_send(card, chdlc_priv_area->udp_pkt_data, len)) {
+			++ card->wandev.stats.tx_packets;
+			card->wandev.stats.tx_bytes += len;
+		}
+	} else {	
+	
+		/* Pass it up the stack
+    		   Allocate socket buffer */
+		if ((new_skb = dev_alloc_skb(len)) != NULL) {
+			/* copy data into new_skb */
+
+ 	    		buf = skb_put(new_skb, len);
+  	    		memcpy(buf, chdlc_priv_area->udp_pkt_data, len);
+
+            		/* Decapsulate pkt and pass it up the protocol stack */
+	    		new_skb->protocol = htons(ETH_P_IP);
+            		new_skb->dev = dev;
+	    		new_skb->mac.raw  = new_skb->data;
+	
+			netif_rx(new_skb);
+			dev->last_rx = jiffies;
+		} else {
+	    	
+			printk(KERN_INFO "%s: no socket buffers available!\n",
+					card->devname);
+  		}
+    	}
+ 
+	chdlc_priv_area->udp_pkt_lgth = 0;
+ 	
+	return 0;
+}
+
+/*============================================================================
+ * Initialize Receive and Transmit Buffers.
+ */
+
+static void init_chdlc_tx_rx_buff(sdla_t* card, struct net_device *dev)
+{
+	CHDLC_MAILBOX_STRUCT* mb = card->mbox;
+	CHDLC_TX_STATUS_EL_CFG_STRUCT *tx_config;
+	CHDLC_RX_STATUS_EL_CFG_STRUCT *rx_config;
+	char err;
+	
+	mb->buffer_length = 0;
+	mb->command = READ_CHDLC_CONFIGURATION;
+	err = sdla_exec(mb) ? mb->return_code : CMD_TIMEOUT;
+
+	if(err != COMMAND_OK) {
+		chdlc_error(card,err,mb);
+		return;
+	}
+
+	if(card->hw.type == SDLA_S514) {
+		tx_config = (CHDLC_TX_STATUS_EL_CFG_STRUCT *)(card->hw.dpmbase +
+                (((CHDLC_CONFIGURATION_STRUCT *)mb->data)->
+                            ptr_CHDLC_Tx_stat_el_cfg_struct));
+        	rx_config = (CHDLC_RX_STATUS_EL_CFG_STRUCT *)(card->hw.dpmbase +
+                (((CHDLC_CONFIGURATION_STRUCT *)mb->data)->
+                            ptr_CHDLC_Rx_stat_el_cfg_struct));
+
+       		/* Setup Head and Tails for buffers */
+        	card->u.c.txbuf_base = (void *)(card->hw.dpmbase +
+                tx_config->base_addr_Tx_status_elements);
+        	card->u.c.txbuf_last = 
+		(CHDLC_DATA_TX_STATUS_EL_STRUCT *)  
+                card->u.c.txbuf_base +
+		(tx_config->number_Tx_status_elements - 1);
+
+        	card->u.c.rxbuf_base = (void *)(card->hw.dpmbase +
+                rx_config->base_addr_Rx_status_elements);
+        	card->u.c.rxbuf_last =
+		(CHDLC_DATA_RX_STATUS_EL_STRUCT *)
+                card->u.c.rxbuf_base +
+		(rx_config->number_Rx_status_elements - 1);
+
+ 		/* Set up next pointer to be used */
+        	card->u.c.txbuf = (void *)(card->hw.dpmbase +
+                tx_config->next_Tx_status_element_to_use);
+        	card->u.c.rxmb = (void *)(card->hw.dpmbase +
+                rx_config->next_Rx_status_element_to_use);
+	}
+        else {
+                tx_config = (CHDLC_TX_STATUS_EL_CFG_STRUCT *)(card->hw.dpmbase +
+			(((CHDLC_CONFIGURATION_STRUCT *)mb->data)->
+			ptr_CHDLC_Tx_stat_el_cfg_struct % SDLA_WINDOWSIZE));
+
+                rx_config = (CHDLC_RX_STATUS_EL_CFG_STRUCT *)(card->hw.dpmbase +
+			(((CHDLC_CONFIGURATION_STRUCT *)mb->data)->
+			ptr_CHDLC_Rx_stat_el_cfg_struct % SDLA_WINDOWSIZE));
+
+                /* Setup Head and Tails for buffers */
+                card->u.c.txbuf_base = (void *)(card->hw.dpmbase +
+		(tx_config->base_addr_Tx_status_elements % SDLA_WINDOWSIZE));
+                card->u.c.txbuf_last =
+		(CHDLC_DATA_TX_STATUS_EL_STRUCT *)card->u.c.txbuf_base
+		+ (tx_config->number_Tx_status_elements - 1);
+                card->u.c.rxbuf_base = (void *)(card->hw.dpmbase +
+		(rx_config->base_addr_Rx_status_elements % SDLA_WINDOWSIZE));
+                card->u.c.rxbuf_last = 
+		(CHDLC_DATA_RX_STATUS_EL_STRUCT *)card->u.c.rxbuf_base
+		+ (rx_config->number_Rx_status_elements - 1);
+
+                 /* Set up next pointer to be used */
+                card->u.c.txbuf = (void *)(card->hw.dpmbase +
+		(tx_config->next_Tx_status_element_to_use % SDLA_WINDOWSIZE));
+                card->u.c.rxmb = (void *)(card->hw.dpmbase +
+		(rx_config->next_Rx_status_element_to_use % SDLA_WINDOWSIZE));
+        }
+
+        /* Setup Actual Buffer Start and end addresses */
+        card->u.c.rx_base = rx_config->base_addr_Rx_buffer;
+        card->u.c.rx_top  = rx_config->end_addr_Rx_buffer;
+
+}
+
+/*=============================================================================
+ * Perform Interrupt Test by running READ_CHDLC_CODE_VERSION command MAX_INTR
+ * _TEST_COUNTER times.
+ */
+static int intr_test( sdla_t* card)
+{
+	CHDLC_MAILBOX_STRUCT* mb = card->mbox;
+	int err,i;
+
+	Intr_test_counter = 0;
+
+	/* The critical flag is unset because during initialization (if_open) 
+	 * we want the interrupts to be enabled so that when the wpc_isr is
+	 * called it does not exit due to critical flag set.
+	 */ 
+
+	err = chdlc_set_intr_mode(card, APP_INT_ON_COMMAND_COMPLETE);
+
+	if (err == CMD_OK) { 
+		for (i = 0; i < MAX_INTR_TEST_COUNTER; i ++) {	
+			mb->buffer_length  = 0;
+			mb->command = READ_CHDLC_CODE_VERSION;
+			err = sdla_exec(mb) ? mb->return_code : CMD_TIMEOUT;
+		}
+	}
+	else {
+		return err;
+	}
+
+	err = chdlc_set_intr_mode(card, 0);
+
+	if (err != CMD_OK)
+		return err;
+
+	return 0;
+}
+
+/*==============================================================================
+ * Determine what type of UDP call it is. CPIPEAB ?
+ */
+static int udp_pkt_type(struct sk_buff *skb, sdla_t* card)
+{
+	 chdlc_udp_pkt_t *chdlc_udp_pkt = (chdlc_udp_pkt_t *)skb->data;
+
+	if (!strncmp(chdlc_udp_pkt->wp_mgmt.signature,UDPMGMT_SIGNATURE,8) &&
+	   (chdlc_udp_pkt->udp_pkt.udp_dst_port == ntohs(card->wandev.udp_port)) &&
+	   (chdlc_udp_pkt->ip_pkt.protocol == UDPMGMT_UDP_PROTOCOL) &&
+	   (chdlc_udp_pkt->wp_mgmt.request_reply == UDPMGMT_REQUEST)) {
+		return UDP_CPIPE_TYPE;
+	}
+	else return UDP_INVALID_TYPE;
+}
+
+/*============================================================================
+ * Set PORT state.
+ */
+static void port_set_state (sdla_t *card, int state)
+{
+	struct net_device *dev = card->wandev.dev;
+	chdlc_private_area_t *chdlc_priv_area = dev->priv;
+
+        if (card->u.c.state != state)
+        {
+                switch (state)
+                {
+                case WAN_CONNECTED:
+                        printk (KERN_INFO "%s: HDLC link connected!\n",
+                                card->devname);
+                      break;
+
+                case WAN_CONNECTING:
+                        printk (KERN_INFO "%s: HDLC link connecting...\n",
+                                card->devname);
+                        break;
+
+                case WAN_DISCONNECTED:
+                        printk (KERN_INFO "%s: HDLC link disconnected!\n",
+                                card->devname);
+                        break;
+                }
+
+                card->wandev.state = card->u.c.state = state;
+		chdlc_priv_area->common.state = state;
+        }
+}
+
+void s508_lock (sdla_t *card, unsigned long *smp_flags)
+{
+	spin_lock_irqsave(&card->wandev.lock, *smp_flags);
+        if (card->next){
+		/* It is ok to use spin_lock here, since we
+		 * already turned off interrupts */
+        	spin_lock(&card->next->wandev.lock);
+	}
+}
+
+void s508_unlock (sdla_t *card, unsigned long *smp_flags)
+{
+	if (card->next){
+		spin_unlock(&card->next->wandev.lock);
+	}
+	spin_unlock_irqrestore(&card->wandev.lock, *smp_flags);
+}
+
+
+
+/*===========================================================================
+ * config_chdlc
+ *
+ *	Configure the chdlc protocol and enable communications.		
+ *
+ *   	The if_open() function binds this function to the poll routine.
+ *      Therefore, this function will run every time the chdlc interface
+ *      is brought up. We cannot run this function from the if_open 
+ *      because if_open does not have access to the remote IP address.
+ *      
+ *	If the communications are not enabled, proceed to configure
+ *      the card and enable communications.
+ *
+ *      If the communications are enabled, it means that the interface
+ *      was shutdown by ether the user or driver. In this case, we 
+ *      have to check that the IP addresses have not changed.  If
+ *      the IP addresses have changed, we have to reconfigure the firmware
+ *      and update the changed IP addresses.  Otherwise, just exit.
+ *
+ */
+
+static int config_chdlc (sdla_t *card)
+{
+	struct net_device *dev = card->wandev.dev;
+	SHARED_MEMORY_INFO_STRUCT *flags = card->u.c.flags;
+
+	if (card->u.c.comm_enabled){
+		chdlc_comm_disable(card);
+		port_set_state(card, WAN_DISCONNECTED);
+	}
+
+	if (set_chdlc_config(card)) {
+		printk(KERN_INFO "%s: CHDLC Configuration Failed!\n",
+				card->devname);
+		return 0;
+	}
+	init_chdlc_tx_rx_buff(card, dev);
+
+	/* Set interrupt mode and mask */
+        if (chdlc_set_intr_mode(card, APP_INT_ON_RX_FRAME |
+                		APP_INT_ON_GLOBAL_EXCEP_COND |
+                		APP_INT_ON_TX_FRAME |
+                		APP_INT_ON_CHDLC_EXCEP_COND | APP_INT_ON_TIMER)){
+		printk (KERN_INFO "%s: Failed to set interrupt triggers!\n",
+				card->devname);
+		return 0;	
+        }
+	
+
+	/* Mask the Transmit and Timer interrupt */
+	flags->interrupt_info_struct.interrupt_permission &= 
+		~(APP_INT_ON_TX_FRAME | APP_INT_ON_TIMER);
+
+
+	if (chdlc_comm_enable(card) != 0) {
+		printk(KERN_INFO "%s: Failed to enable chdlc communications!\n",
+				card->devname);
+		flags->interrupt_info_struct.interrupt_permission = 0;
+		card->u.c.comm_enabled=0;
+		chdlc_set_intr_mode(card,0);
+		return 0;
+	}
+
+	/* Initialize Rx/Tx buffer control fields */
+	port_set_state(card, WAN_CONNECTING);
+	return 0; 
+}
+
+
+static void send_ppp_term_request(struct net_device *dev)
+{
+	struct sk_buff *new_skb;
+	unsigned char *buf;
+
+	if ((new_skb = dev_alloc_skb(8)) != NULL) {
+		/* copy data into new_skb */
+
+		buf = skb_put(new_skb, 8);
+		sprintf(buf,"%c%c%c%c%c%c%c%c", 0xFF,0x03,0xC0,0x21,0x05,0x98,0x00,0x07);
+
+		/* Decapsulate pkt and pass it up the protocol stack */
+		new_skb->protocol = htons(ETH_P_WAN_PPP);
+		new_skb->dev = dev;
+		new_skb->mac.raw  = new_skb->data;
+
+		netif_rx(new_skb);
+		dev->last_rx = jiffies;
+	}
+}
+
+
+MODULE_LICENSE("GPL");
+
+/****** End ****************************************************************/
diff --git a/drivers/net/wan/wanxl.c b/drivers/net/wan/wanxl.c
new file mode 100644
index 0000000..1e7b477
--- /dev/null
+++ b/drivers/net/wan/wanxl.c
@@ -0,0 +1,839 @@
+/*
+ * wanXL serial card driver for Linux
+ * host part
+ *
+ * Copyright (C) 2003 Krzysztof Halasa <khc@pm.waw.pl>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of version 2 of the GNU General Public License
+ * as published by the Free Software Foundation.
+ *
+ * Status:
+ *   - Only DTE (external clock) support with NRZ and NRZI encodings
+ *   - wanXL100 will require minor driver modifications, no access to hw
+ */
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include <linux/sched.h>
+#include <linux/types.h>
+#include <linux/fcntl.h>
+#include <linux/string.h>
+#include <linux/errno.h>
+#include <linux/init.h>
+#include <linux/ioport.h>
+#include <linux/netdevice.h>
+#include <linux/hdlc.h>
+#include <linux/pci.h>
+#include <asm/io.h>
+#include <asm/delay.h>
+
+#include "wanxl.h"
+
+static const char* version = "wanXL serial card driver version: 0.48";
+
+#define PLX_CTL_RESET   0x40000000 /* adapter reset */
+
+#undef DEBUG_PKT
+#undef DEBUG_PCI
+
+/* MAILBOX #1 - PUTS COMMANDS */
+#define MBX1_CMD_ABORTJ 0x85000000 /* Abort and Jump */
+#ifdef __LITTLE_ENDIAN
+#define MBX1_CMD_BSWAP  0x8C000001 /* little-endian Byte Swap Mode */
+#else
+#define MBX1_CMD_BSWAP  0x8C000000 /* big-endian Byte Swap Mode */
+#endif
+
+/* MAILBOX #2 - DRAM SIZE */
+#define MBX2_MEMSZ_MASK 0xFFFF0000 /* PUTS Memory Size Register mask */
+
+
+typedef struct {
+	struct net_device *dev;
+	struct card_t *card;
+	spinlock_t lock;	/* for wanxl_xmit */
+        int node;		/* physical port #0 - 3 */
+	unsigned int clock_type;
+	int tx_in, tx_out;
+	struct sk_buff *tx_skbs[TX_BUFFERS];
+}port_t;
+
+
+typedef struct {
+	desc_t rx_descs[RX_QUEUE_LENGTH];
+	port_status_t port_status[4];
+}card_status_t;
+
+
+typedef struct card_t {
+	int n_ports;		/* 1, 2 or 4 ports */
+	u8 irq;
+
+	u8 __iomem *plx;	/* PLX PCI9060 virtual base address */
+	struct pci_dev *pdev;	/* for pci_name(pdev) */
+	int rx_in;
+	struct sk_buff *rx_skbs[RX_QUEUE_LENGTH];
+	card_status_t *status;	/* shared between host and card */
+	dma_addr_t status_address;
+	port_t ports[0];	/* 1 - 4 port_t structures follow */
+}card_t;
+
+
+
+static inline port_t* dev_to_port(struct net_device *dev)
+{
+        return (port_t *)dev_to_hdlc(dev)->priv;
+}
+
+
+static inline port_status_t* get_status(port_t *port)
+{
+	return &port->card->status->port_status[port->node];
+}
+
+
+#ifdef DEBUG_PCI
+static inline dma_addr_t pci_map_single_debug(struct pci_dev *pdev, void *ptr,
+					      size_t size, int direction)
+{
+	dma_addr_t addr = pci_map_single(pdev, ptr, size, direction);
+	if (addr + size > 0x100000000LL)
+		printk(KERN_CRIT "wanXL %s: pci_map_single() returned memory"
+		       " at 0x%LX!\n", pci_name(pdev),
+		       (unsigned long long)addr);
+	return addr;
+}
+
+#undef pci_map_single
+#define pci_map_single pci_map_single_debug
+#endif
+
+
+/* Cable and/or personality module change interrupt service */
+static inline void wanxl_cable_intr(port_t *port)
+{
+	u32 value = get_status(port)->cable;
+	int valid = 1;
+	const char *cable, *pm, *dte = "", *dsr = "", *dcd = "";
+
+	switch(value & 0x7) {
+	case STATUS_CABLE_V35: cable = "V.35"; break;
+	case STATUS_CABLE_X21: cable = "X.21"; break;
+	case STATUS_CABLE_V24: cable = "V.24"; break;
+	case STATUS_CABLE_EIA530: cable = "EIA530"; break;
+	case STATUS_CABLE_NONE: cable = "no"; break;
+	default: cable = "invalid";
+	}
+
+	switch((value >> STATUS_CABLE_PM_SHIFT) & 0x7) {
+	case STATUS_CABLE_V35: pm = "V.35"; break;
+	case STATUS_CABLE_X21: pm = "X.21"; break;
+	case STATUS_CABLE_V24: pm = "V.24"; break;
+	case STATUS_CABLE_EIA530: pm = "EIA530"; break;
+	case STATUS_CABLE_NONE: pm = "no personality"; valid = 0; break;
+	default: pm = "invalid personality"; valid = 0;
+	}
+
+	if (valid) {
+		if ((value & 7) == ((value >> STATUS_CABLE_PM_SHIFT) & 7)) {
+			dsr = (value & STATUS_CABLE_DSR) ? ", DSR ON" :
+				", DSR off";
+			dcd = (value & STATUS_CABLE_DCD) ? ", carrier ON" :
+				", carrier off";
+		}
+		dte = (value & STATUS_CABLE_DCE) ? " DCE" : " DTE";
+	}
+	printk(KERN_INFO "%s: %s%s module, %s cable%s%s\n",
+	       port->dev->name, pm, dte, cable, dsr, dcd);
+
+	hdlc_set_carrier(value & STATUS_CABLE_DCD, port->dev);
+}
+
+
+
+/* Transmit complete interrupt service */
+static inline void wanxl_tx_intr(port_t *port)
+{
+	struct net_device *dev = port->dev;
+	struct net_device_stats *stats = hdlc_stats(dev);
+	while (1) {
+                desc_t *desc = &get_status(port)->tx_descs[port->tx_in];
+		struct sk_buff *skb = port->tx_skbs[port->tx_in];
+
+		switch (desc->stat) {
+		case PACKET_FULL:
+		case PACKET_EMPTY:
+			netif_wake_queue(dev);
+			return;
+
+		case PACKET_UNDERRUN:
+			stats->tx_errors++;
+			stats->tx_fifo_errors++;
+			break;
+
+		default:
+			stats->tx_packets++;
+			stats->tx_bytes += skb->len;
+		}
+                desc->stat = PACKET_EMPTY; /* Free descriptor */
+		pci_unmap_single(port->card->pdev, desc->address, skb->len,
+				 PCI_DMA_TODEVICE);
+		dev_kfree_skb_irq(skb);
+                port->tx_in = (port->tx_in + 1) % TX_BUFFERS;
+        }
+}
+
+
+
+/* Receive complete interrupt service */
+static inline void wanxl_rx_intr(card_t *card)
+{
+	desc_t *desc;
+	while (desc = &card->status->rx_descs[card->rx_in],
+	       desc->stat != PACKET_EMPTY) {
+		if ((desc->stat & PACKET_PORT_MASK) > card->n_ports)
+			printk(KERN_CRIT "wanXL %s: received packet for"
+			       " nonexistent port\n", pci_name(card->pdev));
+		else {
+			struct sk_buff *skb = card->rx_skbs[card->rx_in];
+			port_t *port = &card->ports[desc->stat &
+						    PACKET_PORT_MASK];
+			struct net_device *dev = port->dev;
+			struct net_device_stats *stats = hdlc_stats(dev);
+
+			if (!skb)
+				stats->rx_dropped++;
+			else {
+				pci_unmap_single(card->pdev, desc->address,
+						 BUFFER_LENGTH,
+						 PCI_DMA_FROMDEVICE);
+				skb_put(skb, desc->length);
+
+#ifdef DEBUG_PKT
+				printk(KERN_DEBUG "%s RX(%i):", dev->name,
+				       skb->len);
+				debug_frame(skb);
+#endif
+				stats->rx_packets++;
+				stats->rx_bytes += skb->len;
+				dev->last_rx = jiffies;
+				skb->protocol = hdlc_type_trans(skb, dev);
+				netif_rx(skb);
+				skb = NULL;
+			}
+
+			if (!skb) {
+				skb = dev_alloc_skb(BUFFER_LENGTH);
+				desc->address = skb ?
+					pci_map_single(card->pdev, skb->data,
+						       BUFFER_LENGTH,
+						       PCI_DMA_FROMDEVICE) : 0;
+				card->rx_skbs[card->rx_in] = skb;
+			}
+		}
+		desc->stat = PACKET_EMPTY; /* Free descriptor */
+		card->rx_in = (card->rx_in + 1) % RX_QUEUE_LENGTH;
+	}
+}
+
+
+
+static irqreturn_t wanxl_intr(int irq, void* dev_id, struct pt_regs *regs)
+{
+        card_t *card = dev_id;
+        int i;
+        u32 stat;
+        int handled = 0;
+
+
+        while((stat = readl(card->plx + PLX_DOORBELL_FROM_CARD)) != 0) {
+                handled = 1;
+		writel(stat, card->plx + PLX_DOORBELL_FROM_CARD);
+
+                for (i = 0; i < card->n_ports; i++) {
+			if (stat & (1 << (DOORBELL_FROM_CARD_TX_0 + i)))
+				wanxl_tx_intr(&card->ports[i]);
+			if (stat & (1 << (DOORBELL_FROM_CARD_CABLE_0 + i)))
+				wanxl_cable_intr(&card->ports[i]);
+		}
+		if (stat & (1 << DOORBELL_FROM_CARD_RX))
+			wanxl_rx_intr(card);
+        }
+
+        return IRQ_RETVAL(handled);
+}
+
+
+
+static int wanxl_xmit(struct sk_buff *skb, struct net_device *dev)
+{
+        port_t *port = dev_to_port(dev);
+	desc_t *desc;
+
+        spin_lock(&port->lock);
+
+	desc = &get_status(port)->tx_descs[port->tx_out];
+        if (desc->stat != PACKET_EMPTY) {
+                /* should never happen - previous xmit should stop queue */
+#ifdef DEBUG_PKT
+                printk(KERN_DEBUG "%s: transmitter buffer full\n", dev->name);
+#endif
+		netif_stop_queue(dev);
+		spin_unlock_irq(&port->lock);
+		return 1;       /* request packet to be queued */
+	}
+
+#ifdef DEBUG_PKT
+	printk(KERN_DEBUG "%s TX(%i):", dev->name, skb->len);
+	debug_frame(skb);
+#endif
+
+	port->tx_skbs[port->tx_out] = skb;
+	desc->address = pci_map_single(port->card->pdev, skb->data, skb->len,
+				       PCI_DMA_TODEVICE);
+	desc->length = skb->len;
+	desc->stat = PACKET_FULL;
+	writel(1 << (DOORBELL_TO_CARD_TX_0 + port->node),
+	       port->card->plx + PLX_DOORBELL_TO_CARD);
+	dev->trans_start = jiffies;
+
+	port->tx_out = (port->tx_out + 1) % TX_BUFFERS;
+
+	if (get_status(port)->tx_descs[port->tx_out].stat != PACKET_EMPTY) {
+		netif_stop_queue(dev);
+#ifdef DEBUG_PKT
+		printk(KERN_DEBUG "%s: transmitter buffer full\n", dev->name);
+#endif
+	}
+
+	spin_unlock(&port->lock);
+	return 0;
+}
+
+
+
+static int wanxl_attach(struct net_device *dev, unsigned short encoding,
+			unsigned short parity)
+{
+	port_t *port = dev_to_port(dev);
+
+	if (encoding != ENCODING_NRZ &&
+	    encoding != ENCODING_NRZI)
+		return -EINVAL;
+
+	if (parity != PARITY_NONE &&
+	    parity != PARITY_CRC32_PR1_CCITT &&
+	    parity != PARITY_CRC16_PR1_CCITT &&
+	    parity != PARITY_CRC32_PR0_CCITT &&
+	    parity != PARITY_CRC16_PR0_CCITT)
+		return -EINVAL;
+
+	get_status(port)->encoding = encoding;
+	get_status(port)->parity = parity;
+	return 0;
+}
+
+
+
+static int wanxl_ioctl(struct net_device *dev, struct ifreq *ifr, int cmd)
+{
+	const size_t size = sizeof(sync_serial_settings);
+	sync_serial_settings line;
+	port_t *port = dev_to_port(dev);
+
+	if (cmd != SIOCWANDEV)
+		return hdlc_ioctl(dev, ifr, cmd);
+
+	switch (ifr->ifr_settings.type) {
+	case IF_GET_IFACE:
+		ifr->ifr_settings.type = IF_IFACE_SYNC_SERIAL;
+		if (ifr->ifr_settings.size < size) {
+			ifr->ifr_settings.size = size; /* data size wanted */
+			return -ENOBUFS;
+		}
+		line.clock_type = get_status(port)->clocking;
+		line.clock_rate = 0;
+		line.loopback = 0;
+
+		if (copy_to_user(ifr->ifr_settings.ifs_ifsu.sync, &line, size))
+			return -EFAULT;
+		return 0;
+
+	case IF_IFACE_SYNC_SERIAL:
+		if (!capable(CAP_NET_ADMIN))
+			return -EPERM;
+		if (dev->flags & IFF_UP)
+			return -EBUSY;
+
+		if (copy_from_user(&line, ifr->ifr_settings.ifs_ifsu.sync,
+				   size))
+			return -EFAULT;
+
+		if (line.clock_type != CLOCK_EXT &&
+		    line.clock_type != CLOCK_TXFROMRX)
+			return -EINVAL; /* No such clock setting */
+
+		if (line.loopback != 0)
+			return -EINVAL;
+
+		get_status(port)->clocking = line.clock_type;
+		return 0;
+
+	default:
+		return hdlc_ioctl(dev, ifr, cmd);
+        }
+}
+
+
+
+static int wanxl_open(struct net_device *dev)
+{
+	port_t *port = dev_to_port(dev);
+	u8 __iomem *dbr = port->card->plx + PLX_DOORBELL_TO_CARD;
+	unsigned long timeout;
+	int i;
+
+	if (get_status(port)->open) {
+		printk(KERN_ERR "%s: port already open\n", dev->name);
+		return -EIO;
+	}
+	if ((i = hdlc_open(dev)) != 0)
+		return i;
+
+	port->tx_in = port->tx_out = 0;
+	for (i = 0; i < TX_BUFFERS; i++)
+		get_status(port)->tx_descs[i].stat = PACKET_EMPTY;
+	/* signal the card */
+	writel(1 << (DOORBELL_TO_CARD_OPEN_0 + port->node), dbr);
+
+	timeout = jiffies + HZ;
+	do
+		if (get_status(port)->open) {
+			netif_start_queue(dev);
+			return 0;
+		}
+	while (time_after(timeout, jiffies));
+
+	printk(KERN_ERR "%s: unable to open port\n", dev->name);
+	/* ask the card to close the port, should it be still alive */
+	writel(1 << (DOORBELL_TO_CARD_CLOSE_0 + port->node), dbr);
+	return -EFAULT;
+}
+
+
+
+static int wanxl_close(struct net_device *dev)
+{
+	port_t *port = dev_to_port(dev);
+	unsigned long timeout;
+	int i;
+
+	hdlc_close(dev);
+	/* signal the card */
+	writel(1 << (DOORBELL_TO_CARD_CLOSE_0 + port->node),
+	       port->card->plx + PLX_DOORBELL_TO_CARD);
+
+	timeout = jiffies + HZ;
+	do
+		if (!get_status(port)->open)
+			break;
+	while (time_after(timeout, jiffies));
+
+	if (get_status(port)->open)
+		printk(KERN_ERR "%s: unable to close port\n", dev->name);
+
+	netif_stop_queue(dev);
+
+	for (i = 0; i < TX_BUFFERS; i++) {
+		desc_t *desc = &get_status(port)->tx_descs[i];
+
+		if (desc->stat != PACKET_EMPTY) {
+			desc->stat = PACKET_EMPTY;
+			pci_unmap_single(port->card->pdev, desc->address,
+					 port->tx_skbs[i]->len,
+					 PCI_DMA_TODEVICE);
+			dev_kfree_skb(port->tx_skbs[i]);
+		}
+	}
+	return 0;
+}
+
+
+
+static struct net_device_stats *wanxl_get_stats(struct net_device *dev)
+{
+	struct net_device_stats *stats = hdlc_stats(dev);
+	port_t *port = dev_to_port(dev);
+
+	stats->rx_over_errors = get_status(port)->rx_overruns;
+	stats->rx_frame_errors = get_status(port)->rx_frame_errors;
+	stats->rx_errors = stats->rx_over_errors + stats->rx_frame_errors;
+        return stats;
+}
+
+
+
+static int wanxl_puts_command(card_t *card, u32 cmd)
+{
+	unsigned long timeout = jiffies + 5 * HZ;
+
+	writel(cmd, card->plx + PLX_MAILBOX_1);
+	do {
+		if (readl(card->plx + PLX_MAILBOX_1) == 0)
+			return 0;
+
+		schedule();
+	}while (time_after(timeout, jiffies));
+
+	return -1;
+}
+
+
+
+static void wanxl_reset(card_t *card)
+{
+	u32 old_value = readl(card->plx + PLX_CONTROL) & ~PLX_CTL_RESET;
+
+	writel(0x80, card->plx + PLX_MAILBOX_0);
+	writel(old_value | PLX_CTL_RESET, card->plx + PLX_CONTROL);
+	readl(card->plx + PLX_CONTROL); /* wait for posted write */
+	udelay(1);
+	writel(old_value, card->plx + PLX_CONTROL);
+	readl(card->plx + PLX_CONTROL); /* wait for posted write */
+}
+
+
+
+static void wanxl_pci_remove_one(struct pci_dev *pdev)
+{
+	card_t *card = pci_get_drvdata(pdev);
+	int i;
+
+	for (i = 0; i < card->n_ports; i++) {
+		unregister_hdlc_device(card->ports[i].dev);
+		free_netdev(card->ports[i].dev);
+	}
+
+	/* unregister and free all host resources */
+	if (card->irq)
+		free_irq(card->irq, card);
+
+	wanxl_reset(card);
+
+	for (i = 0; i < RX_QUEUE_LENGTH; i++)
+		if (card->rx_skbs[i]) {
+			pci_unmap_single(card->pdev,
+					 card->status->rx_descs[i].address,
+					 BUFFER_LENGTH, PCI_DMA_FROMDEVICE);
+			dev_kfree_skb(card->rx_skbs[i]);
+		}
+
+	if (card->plx)
+		iounmap(card->plx);
+
+	if (card->status)
+		pci_free_consistent(pdev, sizeof(card_status_t),
+				    card->status, card->status_address);
+
+	pci_release_regions(pdev);
+	pci_disable_device(pdev);
+	pci_set_drvdata(pdev, NULL);
+	kfree(card);
+}
+
+
+#include "wanxlfw.inc"
+
+static int __devinit wanxl_pci_init_one(struct pci_dev *pdev,
+					const struct pci_device_id *ent)
+{
+	card_t *card;
+	u32 ramsize, stat;
+	unsigned long timeout;
+	u32 plx_phy;		/* PLX PCI base address */
+	u32 mem_phy;		/* memory PCI base addr */
+	u8 __iomem *mem;	/* memory virtual base addr */
+	int i, ports, alloc_size;
+
+#ifndef MODULE
+	static int printed_version;
+	if (!printed_version) {
+		printed_version++;
+		printk(KERN_INFO "%s\n", version);
+	}
+#endif
+
+	i = pci_enable_device(pdev);
+	if (i)
+		return i;
+
+	/* QUICC can only access first 256 MB of host RAM directly,
+	   but PLX9060 DMA does 32-bits for actual packet data transfers */
+
+	/* FIXME when PCI/DMA subsystems are fixed.
+	   We set both dma_mask and consistent_dma_mask to 28 bits
+	   and pray pci_alloc_consistent() will use this info. It should
+	   work on most platforms */
+	if (pci_set_consistent_dma_mask(pdev, 0x0FFFFFFF) ||
+	    pci_set_dma_mask(pdev, 0x0FFFFFFF)) {
+		printk(KERN_ERR "wanXL: No usable DMA configuration\n");
+		return -EIO;
+	}
+
+	i = pci_request_regions(pdev, "wanXL");
+	if (i) {
+		pci_disable_device(pdev);
+		return i;
+	}
+
+	switch (pdev->device) {
+	case PCI_DEVICE_ID_SBE_WANXL100: ports = 1; break;
+	case PCI_DEVICE_ID_SBE_WANXL200: ports = 2; break;
+	default: ports = 4;
+	}
+
+	alloc_size = sizeof(card_t) + ports * sizeof(port_t);
+	card = kmalloc(alloc_size, GFP_KERNEL);
+	if (card == NULL) {
+		printk(KERN_ERR "wanXL %s: unable to allocate memory\n",
+		       pci_name(pdev));
+		pci_release_regions(pdev);
+		pci_disable_device(pdev);
+		return -ENOBUFS;
+	}
+	memset(card, 0, alloc_size);
+
+	pci_set_drvdata(pdev, card);
+	card->pdev = pdev;
+
+	card->status = pci_alloc_consistent(pdev, sizeof(card_status_t),
+					    &card->status_address);
+	if (card->status == NULL) {
+		wanxl_pci_remove_one(pdev);
+		return -ENOBUFS;
+	}
+
+#ifdef DEBUG_PCI
+	printk(KERN_DEBUG "wanXL %s: pci_alloc_consistent() returned memory"
+	       " at 0x%LX\n", pci_name(pdev),
+	       (unsigned long long)card->status_address);
+#endif
+
+	/* FIXME when PCI/DMA subsystems are fixed.
+	   We set both dma_mask and consistent_dma_mask back to 32 bits
+	   to indicate the card can do 32-bit DMA addressing */
+	if (pci_set_consistent_dma_mask(pdev, 0xFFFFFFFF) ||
+	    pci_set_dma_mask(pdev, 0xFFFFFFFF)) {
+		printk(KERN_ERR "wanXL: No usable DMA configuration\n");
+		wanxl_pci_remove_one(pdev);
+		return -EIO;
+	}
+
+	/* set up PLX mapping */
+	plx_phy = pci_resource_start(pdev, 0);
+	card->plx = ioremap_nocache(plx_phy, 0x70);
+
+#if RESET_WHILE_LOADING
+	wanxl_reset(card);
+#endif
+
+	timeout = jiffies + 20 * HZ;
+	while ((stat = readl(card->plx + PLX_MAILBOX_0)) != 0) {
+		if (time_before(timeout, jiffies)) {
+			printk(KERN_WARNING "wanXL %s: timeout waiting for"
+			       " PUTS to complete\n", pci_name(pdev));
+			wanxl_pci_remove_one(pdev);
+			return -ENODEV;
+		}
+
+		switch(stat & 0xC0) {
+		case 0x00:	/* hmm - PUTS completed with non-zero code? */
+		case 0x80:	/* PUTS still testing the hardware */
+			break;
+
+		default:
+			printk(KERN_WARNING "wanXL %s: PUTS test 0x%X"
+			       " failed\n", pci_name(pdev), stat & 0x30);
+			wanxl_pci_remove_one(pdev);
+			return -ENODEV;
+		}
+
+		schedule();
+	}
+
+	/* get on-board memory size (PUTS detects no more than 4 MB) */
+	ramsize = readl(card->plx + PLX_MAILBOX_2) & MBX2_MEMSZ_MASK;
+
+	/* set up on-board RAM mapping */
+	mem_phy = pci_resource_start(pdev, 2);
+
+
+	/* sanity check the board's reported memory size */
+	if (ramsize < BUFFERS_ADDR +
+	    (TX_BUFFERS + RX_BUFFERS) * BUFFER_LENGTH * ports) {
+		printk(KERN_WARNING "wanXL %s: no enough on-board RAM"
+		       " (%u bytes detected, %u bytes required)\n",
+		       pci_name(pdev), ramsize, BUFFERS_ADDR +
+		       (TX_BUFFERS + RX_BUFFERS) * BUFFER_LENGTH * ports);
+		wanxl_pci_remove_one(pdev);
+		return -ENODEV;
+	}
+
+	if (wanxl_puts_command(card, MBX1_CMD_BSWAP)) {
+		printk(KERN_WARNING "wanXL %s: unable to Set Byte Swap"
+		       " Mode\n", pci_name(pdev));
+		wanxl_pci_remove_one(pdev);
+		return -ENODEV;
+	}
+
+	for (i = 0; i < RX_QUEUE_LENGTH; i++) {
+		struct sk_buff *skb = dev_alloc_skb(BUFFER_LENGTH);
+		card->rx_skbs[i] = skb;
+		if (skb)
+			card->status->rx_descs[i].address =
+				pci_map_single(card->pdev, skb->data,
+					       BUFFER_LENGTH,
+					       PCI_DMA_FROMDEVICE);
+	}
+
+	mem = ioremap_nocache(mem_phy, PDM_OFFSET + sizeof(firmware));
+	for (i = 0; i < sizeof(firmware); i += 4)
+		writel(htonl(*(u32*)(firmware + i)), mem + PDM_OFFSET + i);
+
+	for (i = 0; i < ports; i++)
+		writel(card->status_address +
+		       (void *)&card->status->port_status[i] -
+		       (void *)card->status, mem + PDM_OFFSET + 4 + i * 4);
+	writel(card->status_address, mem + PDM_OFFSET + 20);
+	writel(PDM_OFFSET, mem);
+	iounmap(mem);
+
+	writel(0, card->plx + PLX_MAILBOX_5);
+
+	if (wanxl_puts_command(card, MBX1_CMD_ABORTJ)) {
+		printk(KERN_WARNING "wanXL %s: unable to Abort and Jump\n",
+		       pci_name(pdev));
+		wanxl_pci_remove_one(pdev);
+		return -ENODEV;
+	}
+
+	stat = 0;
+	timeout = jiffies + 5 * HZ;
+	do {
+		if ((stat = readl(card->plx + PLX_MAILBOX_5)) != 0)
+			break;
+		schedule();
+	}while (time_after(timeout, jiffies));
+
+	if (!stat) {
+		printk(KERN_WARNING "wanXL %s: timeout while initializing card"
+		       "firmware\n", pci_name(pdev));
+		wanxl_pci_remove_one(pdev);
+		return -ENODEV;
+	}
+
+#if DETECT_RAM
+	ramsize = stat;
+#endif
+
+	printk(KERN_INFO "wanXL %s: at 0x%X, %u KB of RAM at 0x%X, irq %u\n",
+	       pci_name(pdev), plx_phy, ramsize / 1024, mem_phy, pdev->irq);
+
+	/* Allocate IRQ */
+	if (request_irq(pdev->irq, wanxl_intr, SA_SHIRQ, "wanXL", card)) {
+		printk(KERN_WARNING "wanXL %s: could not allocate IRQ%i.\n",
+		       pci_name(pdev), pdev->irq);
+		wanxl_pci_remove_one(pdev);
+		return -EBUSY;
+	}
+	card->irq = pdev->irq;
+
+	for (i = 0; i < ports; i++) {
+		hdlc_device *hdlc;
+		port_t *port = &card->ports[i];
+		struct net_device *dev = alloc_hdlcdev(port);
+		if (!dev) {
+			printk(KERN_ERR "wanXL %s: unable to allocate"
+			       " memory\n", pci_name(pdev));
+			wanxl_pci_remove_one(pdev);
+			return -ENOMEM;
+		}
+
+		port->dev = dev;
+		hdlc = dev_to_hdlc(dev);
+		spin_lock_init(&port->lock);
+		SET_MODULE_OWNER(dev);
+		dev->tx_queue_len = 50;
+		dev->do_ioctl = wanxl_ioctl;
+		dev->open = wanxl_open;
+		dev->stop = wanxl_close;
+		hdlc->attach = wanxl_attach;
+		hdlc->xmit = wanxl_xmit;
+		dev->get_stats = wanxl_get_stats;
+		port->card = card;
+		port->node = i;
+		get_status(port)->clocking = CLOCK_EXT;
+		if (register_hdlc_device(dev)) {
+			printk(KERN_ERR "wanXL %s: unable to register hdlc"
+			       " device\n", pci_name(pdev));
+			free_netdev(dev);
+			wanxl_pci_remove_one(pdev);
+			return -ENOBUFS;
+		}
+		card->n_ports++;
+	}
+
+	printk(KERN_INFO "wanXL %s: port", pci_name(pdev));
+	for (i = 0; i < ports; i++)
+		printk("%s #%i: %s", i ? "," : "", i,
+		       card->ports[i].dev->name);
+	printk("\n");
+
+	for (i = 0; i < ports; i++)
+		wanxl_cable_intr(&card->ports[i]); /* get carrier status etc.*/
+
+	return 0;
+}
+
+static struct pci_device_id wanxl_pci_tbl[] __devinitdata = {
+	{ PCI_VENDOR_ID_SBE, PCI_DEVICE_ID_SBE_WANXL100, PCI_ANY_ID,
+	  PCI_ANY_ID, 0, 0, 0 },
+	{ PCI_VENDOR_ID_SBE, PCI_DEVICE_ID_SBE_WANXL200, PCI_ANY_ID,
+	  PCI_ANY_ID, 0, 0, 0 },
+	{ PCI_VENDOR_ID_SBE, PCI_DEVICE_ID_SBE_WANXL400, PCI_ANY_ID,
+	  PCI_ANY_ID, 0, 0, 0 },
+	{ 0, }
+};
+
+
+static struct pci_driver wanxl_pci_driver = {
+	.name		= "wanXL",
+	.id_table	= wanxl_pci_tbl,
+	.probe		= wanxl_pci_init_one,
+	.remove		= wanxl_pci_remove_one,
+};
+
+
+static int __init wanxl_init_module(void)
+{
+#ifdef MODULE
+	printk(KERN_INFO "%s\n", version);
+#endif
+	return pci_module_init(&wanxl_pci_driver);
+}
+
+static void __exit wanxl_cleanup_module(void)
+{
+	pci_unregister_driver(&wanxl_pci_driver);
+}
+
+
+MODULE_AUTHOR("Krzysztof Halasa <khc@pm.waw.pl>");
+MODULE_DESCRIPTION("SBE Inc. wanXL serial port driver");
+MODULE_LICENSE("GPL v2");
+MODULE_DEVICE_TABLE(pci, wanxl_pci_tbl);
+
+module_init(wanxl_init_module);
+module_exit(wanxl_cleanup_module);
diff --git a/drivers/net/wan/wanxl.h b/drivers/net/wan/wanxl.h
new file mode 100644
index 0000000..3f86558
--- /dev/null
+++ b/drivers/net/wan/wanxl.h
@@ -0,0 +1,152 @@
+/*
+ * wanXL serial card driver for Linux
+ * definitions common to host driver and card firmware
+ *
+ * Copyright (C) 2003 Krzysztof Halasa <khc@pm.waw.pl>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of version 2 of the GNU General Public License
+ * as published by the Free Software Foundation.
+ */
+
+#define RESET_WHILE_LOADING 0
+
+/* you must rebuild the firmware if any of the following is changed */
+#define DETECT_RAM 0		/* needed for > 4MB RAM, 16 MB maximum */
+#define QUICC_MEMCPY_USES_PLX 1	/* must be used if the host has > 256 MB RAM */
+
+
+#define STATUS_CABLE_V35	2
+#define STATUS_CABLE_X21	3
+#define STATUS_CABLE_V24	4
+#define STATUS_CABLE_EIA530	5
+#define STATUS_CABLE_INVALID	6
+#define STATUS_CABLE_NONE	7
+
+#define STATUS_CABLE_DCE	0x8000
+#define STATUS_CABLE_DSR	0x0010
+#define STATUS_CABLE_DCD	0x0008
+#define STATUS_CABLE_PM_SHIFT	5
+
+#define PDM_OFFSET 0x1000
+
+#define TX_BUFFERS 10		/* per port */
+#define RX_BUFFERS 30
+#define RX_QUEUE_LENGTH 40	/* card->host queue length - per card */
+
+#define PACKET_EMPTY		0x00
+#define PACKET_FULL		0x10
+#define PACKET_SENT		0x20 /* TX only */
+#define PACKET_UNDERRUN		0x30 /* TX only */
+#define PACKET_PORT_MASK	0x03 /* RX only */
+
+/* bit numbers in PLX9060 doorbell registers */
+#define DOORBELL_FROM_CARD_TX_0		0 /* packet sent by the card */
+#define DOORBELL_FROM_CARD_TX_1		1
+#define DOORBELL_FROM_CARD_TX_2		2
+#define DOORBELL_FROM_CARD_TX_3		3
+#define DOORBELL_FROM_CARD_RX		4
+#define DOORBELL_FROM_CARD_CABLE_0	5 /* cable/PM/etc. changed */
+#define DOORBELL_FROM_CARD_CABLE_1	6
+#define DOORBELL_FROM_CARD_CABLE_2	7
+#define DOORBELL_FROM_CARD_CABLE_3	8
+
+#define DOORBELL_TO_CARD_OPEN_0		0
+#define DOORBELL_TO_CARD_OPEN_1		1
+#define DOORBELL_TO_CARD_OPEN_2		2
+#define DOORBELL_TO_CARD_OPEN_3		3
+#define DOORBELL_TO_CARD_CLOSE_0	4
+#define DOORBELL_TO_CARD_CLOSE_1	5
+#define DOORBELL_TO_CARD_CLOSE_2	6
+#define DOORBELL_TO_CARD_CLOSE_3	7
+#define DOORBELL_TO_CARD_TX_0		8 /* outbound packet queued */
+#define DOORBELL_TO_CARD_TX_1		9
+#define DOORBELL_TO_CARD_TX_2		10
+#define DOORBELL_TO_CARD_TX_3		11
+
+/* firmware-only status bits, starting from last DOORBELL_TO_CARD + 1 */
+#define TASK_SCC_0			12
+#define TASK_SCC_1			13
+#define TASK_SCC_2			14
+#define TASK_SCC_3			15
+
+#define ALIGN32(x) (((x) + 3) & 0xFFFFFFFC)
+#define BUFFER_LENGTH	ALIGN32(HDLC_MAX_MRU + 4) /* 4 bytes for 32-bit CRC */
+
+/* Address of TX and RX buffers in 68360 address space */
+#define BUFFERS_ADDR	0x4000	/* 16 KB */
+
+#ifndef __ASSEMBLER__
+#define PLX_OFFSET		0
+#else
+#define PLX_OFFSET		PLX + 0x80
+#endif
+
+#define PLX_MAILBOX_0		(PLX_OFFSET + 0x40)
+#define PLX_MAILBOX_1		(PLX_OFFSET + 0x44)
+#define PLX_MAILBOX_2		(PLX_OFFSET + 0x48)
+#define PLX_MAILBOX_3		(PLX_OFFSET + 0x4C)
+#define PLX_MAILBOX_4		(PLX_OFFSET + 0x50)
+#define PLX_MAILBOX_5		(PLX_OFFSET + 0x54)
+#define PLX_MAILBOX_6		(PLX_OFFSET + 0x58)
+#define PLX_MAILBOX_7		(PLX_OFFSET + 0x5C)
+#define PLX_DOORBELL_TO_CARD	(PLX_OFFSET + 0x60)
+#define PLX_DOORBELL_FROM_CARD	(PLX_OFFSET + 0x64)
+#define PLX_INTERRUPT_CS	(PLX_OFFSET + 0x68)
+#define PLX_CONTROL		(PLX_OFFSET + 0x6C)
+
+#ifdef __ASSEMBLER__
+#define PLX_DMA_0_MODE		(PLX + 0x100)
+#define PLX_DMA_0_PCI		(PLX + 0x104)
+#define PLX_DMA_0_LOCAL		(PLX + 0x108)
+#define PLX_DMA_0_LENGTH	(PLX + 0x10C)
+#define PLX_DMA_0_DESC		(PLX + 0x110)
+#define PLX_DMA_1_MODE		(PLX + 0x114)
+#define PLX_DMA_1_PCI		(PLX + 0x118)
+#define PLX_DMA_1_LOCAL		(PLX + 0x11C)
+#define PLX_DMA_1_LENGTH	(PLX + 0x120)
+#define PLX_DMA_1_DESC		(PLX + 0x124)
+#define PLX_DMA_CMD_STS		(PLX + 0x128)
+#define PLX_DMA_ARBITR_0	(PLX + 0x12C)
+#define PLX_DMA_ARBITR_1	(PLX + 0x130)
+#endif
+
+#define DESC_LENGTH 12
+
+/* offsets from start of status_t */
+/* card to host */
+#define STATUS_OPEN		0
+#define STATUS_CABLE		(STATUS_OPEN + 4)
+#define STATUS_RX_OVERRUNS	(STATUS_CABLE + 4)
+#define STATUS_RX_FRAME_ERRORS	(STATUS_RX_OVERRUNS + 4)
+
+/* host to card */
+#define STATUS_PARITY		(STATUS_RX_FRAME_ERRORS + 4)
+#define STATUS_ENCODING		(STATUS_PARITY + 4)
+#define STATUS_CLOCKING		(STATUS_ENCODING + 4)
+#define STATUS_TX_DESCS		(STATUS_CLOCKING + 4)
+
+#ifndef __ASSEMBLER__
+
+typedef struct {
+	volatile u32 stat;
+	u32 address;		/* PCI address */
+	volatile u32 length;
+}desc_t;
+
+
+typedef struct {
+// Card to host
+	volatile u32 open;
+	volatile u32 cable;
+	volatile u32 rx_overruns;
+	volatile u32 rx_frame_errors;
+
+// Host to card
+	u32 parity;
+	u32 encoding;
+	u32 clocking;
+	desc_t tx_descs[TX_BUFFERS];
+}port_status_t;
+
+#endif /* __ASSEMBLER__ */
diff --git a/drivers/net/wan/wanxlfw.S b/drivers/net/wan/wanxlfw.S
new file mode 100644
index 0000000..73aae2b
--- /dev/null
+++ b/drivers/net/wan/wanxlfw.S
@@ -0,0 +1,895 @@
+.psize 0
+/*
+  wanXL serial card driver for Linux
+  card firmware part
+
+  Copyright (C) 2003 Krzysztof Halasa <khc@pm.waw.pl>
+
+  This program is free software; you can redistribute it and/or modify it
+  under the terms of version 2 of the GNU General Public License
+  as published by the Free Software Foundation.
+
+
+
+
+	DPRAM BDs:
+	0x000 - 0x050 TX#0	0x050 - 0x140 RX#0
+	0x140 - 0x190 TX#1	0x190 - 0x280 RX#1
+	0x280 - 0x2D0 TX#2	0x2D0 - 0x3C0 RX#2
+	0x3C0 - 0x410 TX#3	0x410 - 0x500 RX#3
+
+
+	000 5FF 1536 Bytes Dual-Port RAM User Data / BDs
+	600 6FF 256 Bytes Dual-Port RAM User Data / BDs
+	700 7FF 256 Bytes Dual-Port RAM User Data / BDs
+	C00 CBF 192 Bytes Dual-Port RAM Parameter RAM Page 1
+	D00 DBF 192 Bytes Dual-Port RAM Parameter RAM Page 2
+	E00 EBF 192 Bytes Dual-Port RAM Parameter RAM Page 3
+	F00 FBF 192 Bytes Dual-Port RAM Parameter RAM Page 4
+
+	local interrupts		    level
+	NMI					7
+	PIT timer, CPM (RX/TX complete)		4
+	PCI9060	DMA and PCI doorbells		3
+	Cable - not used			1
+*/
+
+#include <linux/hdlc.h>
+#include "wanxl.h"
+
+/* memory addresses and offsets */
+
+MAX_RAM_SIZE	= 16 * 1024 * 1024	// max RAM supported by hardware
+
+PCI9060_VECTOR	= 0x0000006C
+CPM_IRQ_BASE	= 0x40
+ERROR_VECTOR	= CPM_IRQ_BASE * 4
+SCC1_VECTOR	= (CPM_IRQ_BASE + 0x1E) * 4
+SCC2_VECTOR	= (CPM_IRQ_BASE + 0x1D) * 4
+SCC3_VECTOR	= (CPM_IRQ_BASE + 0x1C) * 4
+SCC4_VECTOR	= (CPM_IRQ_BASE + 0x1B) * 4
+CPM_IRQ_LEVEL	= 4
+TIMER_IRQ	= 128
+TIMER_IRQ_LEVEL = 4
+PITR_CONST	= 0x100 + 16		// 1 Hz timer
+
+MBAR		= 0x0003FF00
+
+VALUE_WINDOW	= 0x40000000
+ORDER_WINDOW	= 0xC0000000
+
+PLX		= 0xFFF90000
+
+CSRA		= 0xFFFB0000
+CSRB		= 0xFFFB0002
+CSRC		= 0xFFFB0004
+CSRD		= 0xFFFB0006
+STATUS_CABLE_LL		= 0x2000
+STATUS_CABLE_DTR	= 0x1000
+
+DPRBASE		= 0xFFFC0000
+
+SCC1_BASE	= DPRBASE + 0xC00
+MISC_BASE	= DPRBASE + 0xCB0
+SCC2_BASE	= DPRBASE + 0xD00
+SCC3_BASE	= DPRBASE + 0xE00
+SCC4_BASE	= DPRBASE + 0xF00
+
+// offset from SCCx_BASE
+// SCC_xBASE contain offsets from DPRBASE and must be divisible by 8
+SCC_RBASE	= 0		// 16-bit RxBD base address
+SCC_TBASE	= 2		// 16-bit TxBD base address
+SCC_RFCR	= 4		// 8-bit Rx function code
+SCC_TFCR	= 5		// 8-bit Tx function code
+SCC_MRBLR	= 6		// 16-bit maximum Rx buffer length
+SCC_C_MASK	= 0x34		// 32-bit CRC constant
+SCC_C_PRES	= 0x38		// 32-bit CRC preset
+SCC_MFLR	= 0x46		// 16-bit max Rx frame length (without flags)
+
+REGBASE		= DPRBASE + 0x1000
+PICR		= REGBASE + 0x026	// 16-bit periodic irq control
+PITR		= REGBASE + 0x02A	// 16-bit periodic irq timing
+OR1		= REGBASE + 0x064	// 32-bit RAM bank #1 options
+CICR		= REGBASE + 0x540	// 32(24)-bit CP interrupt config
+CIMR		= REGBASE + 0x548	// 32-bit CP interrupt mask
+CISR		= REGBASE + 0x54C	// 32-bit CP interrupts in-service
+PADIR		= REGBASE + 0x550	// 16-bit PortA data direction bitmap
+PAPAR		= REGBASE + 0x552	// 16-bit PortA pin assignment bitmap
+PAODR		= REGBASE + 0x554	// 16-bit PortA open drain bitmap
+PADAT		= REGBASE + 0x556	// 16-bit PortA data register
+
+PCDIR		= REGBASE + 0x560	// 16-bit PortC data direction bitmap
+PCPAR		= REGBASE + 0x562	// 16-bit PortC pin assignment bitmap
+PCSO		= REGBASE + 0x564	// 16-bit PortC special options
+PCDAT		= REGBASE + 0x566	// 16-bit PortC data register
+PCINT		= REGBASE + 0x568	// 16-bit PortC interrupt control
+CR		= REGBASE + 0x5C0	// 16-bit Command register
+
+SCC1_REGS	= REGBASE + 0x600
+SCC2_REGS	= REGBASE + 0x620
+SCC3_REGS	= REGBASE + 0x640
+SCC4_REGS	= REGBASE + 0x660
+SICR		= REGBASE + 0x6EC	// 32-bit SI clock route
+
+// offset from SCCx_REGS
+SCC_GSMR_L	= 0x00	// 32 bits
+SCC_GSMR_H	= 0x04	// 32 bits
+SCC_PSMR	= 0x08	// 16 bits
+SCC_TODR	= 0x0C	// 16 bits
+SCC_DSR		= 0x0E	// 16 bits
+SCC_SCCE	= 0x10	// 16 bits
+SCC_SCCM	= 0x14	// 16 bits
+SCC_SCCS	= 0x17	// 8 bits
+
+#if QUICC_MEMCPY_USES_PLX
+	.macro memcpy_from_pci src, dest, len // len must be < 8 MB
+	addl #3, \len
+	andl #0xFFFFFFFC, \len		// always copy n * 4 bytes
+	movel \src, PLX_DMA_0_PCI
+	movel \dest, PLX_DMA_0_LOCAL
+	movel \len, PLX_DMA_0_LENGTH
+	movel #0x0103, PLX_DMA_CMD_STS	// start channel 0 transfer
+	bsr memcpy_from_pci_run
+	.endm
+
+	.macro memcpy_to_pci src, dest, len
+	addl #3, \len
+	andl #0xFFFFFFFC, \len		// always copy n * 4 bytes
+	movel \src, PLX_DMA_1_LOCAL
+	movel \dest, PLX_DMA_1_PCI
+	movel \len, PLX_DMA_1_LENGTH
+	movel #0x0301, PLX_DMA_CMD_STS	// start channel 1 transfer
+	bsr memcpy_to_pci_run
+	.endm
+
+#else
+
+	.macro memcpy src, dest, len	// len must be < 65536 bytes
+	movel %d7, -(%sp)		// src and dest must be < 256 MB
+	movel \len, %d7			// bits 0 and 1
+	lsrl #2, \len
+	andl \len, \len
+	beq 99f				// only 0 - 3 bytes
+	subl #1, \len			// for dbf
+98:	movel (\src)+, (\dest)+
+	dbfw \len, 98b
+99:	movel %d7, \len
+	btstl #1, \len
+	beq 99f
+	movew (\src)+, (\dest)+
+99:	btstl #0, \len
+	beq 99f
+	moveb (\src)+, (\dest)+
+99:
+	movel (%sp)+, %d7
+	.endm
+
+	.macro memcpy_from_pci src, dest, len
+	addl #VALUE_WINDOW, \src
+	memcpy \src, \dest, \len
+	.endm
+
+	.macro memcpy_to_pci src, dest, len
+	addl #VALUE_WINDOW, \dest
+	memcpy \src, \dest, \len
+	.endm
+#endif
+
+
+	.macro wait_for_command
+99:	btstl #0, CR
+	bne 99b
+	.endm
+
+
+
+
+/****************************** card initialization *******************/
+	.text
+	.global _start
+_start:	bra init
+
+	.org _start + 4
+ch_status_addr:	.long 0, 0, 0, 0
+rx_descs_addr:	.long 0
+
+init:
+#if DETECT_RAM
+	movel OR1, %d0
+	andl #0xF00007FF, %d0		// mask AMxx bits
+	orl #0xFFFF800 & ~(MAX_RAM_SIZE - 1), %d0 // update RAM bank size
+	movel %d0, OR1
+#endif
+
+	addl #VALUE_WINDOW, rx_descs_addr // PCI addresses of shared data
+	clrl %d0			// D0 = 4 * port
+init_1:	tstl ch_status_addr(%d0)
+	beq init_2
+	addl #VALUE_WINDOW, ch_status_addr(%d0)
+init_2:	addl #4, %d0
+	cmpl #4 * 4, %d0
+	bne init_1
+
+	movel #pci9060_interrupt, PCI9060_VECTOR
+	movel #error_interrupt, ERROR_VECTOR
+	movel #port_interrupt_1, SCC1_VECTOR
+	movel #port_interrupt_2, SCC2_VECTOR
+	movel #port_interrupt_3, SCC3_VECTOR
+	movel #port_interrupt_4, SCC4_VECTOR
+	movel #timer_interrupt, TIMER_IRQ * 4
+
+	movel #0x78000000, CIMR		// only SCCx IRQs from CPM
+	movew #(TIMER_IRQ_LEVEL << 8) + TIMER_IRQ, PICR	// interrupt from PIT
+	movew #PITR_CONST, PITR
+
+	// SCC1=SCCa SCC2=SCCb SCC3=SCCc SCC4=SCCd prio=4 HP=-1 IRQ=64-79
+	movel #0xD41F40 + (CPM_IRQ_LEVEL << 13), CICR
+	movel #0x543, PLX_DMA_0_MODE	// 32-bit, Ready, Burst, IRQ
+	movel #0x543, PLX_DMA_1_MODE
+	movel #0x0, PLX_DMA_0_DESC	// from PCI to local
+	movel #0x8, PLX_DMA_1_DESC	// from local to PCI
+	movel #0x101, PLX_DMA_CMD_STS	// enable both DMA channels
+	// enable local IRQ, DMA, doorbells and PCI IRQ
+	orl #0x000F0300, PLX_INTERRUPT_CS
+
+#if DETECT_RAM
+	bsr ram_test
+#else
+	movel #1, PLX_MAILBOX_5		// non-zero value = init complete
+#endif
+	bsr check_csr
+
+	movew #0xFFFF, PAPAR		// all pins are clocks/data
+	clrw PADIR			// first function
+	clrw PCSO			// CD and CTS always active
+
+
+/****************************** main loop *****************************/
+
+main:	movel channel_stats, %d7	// D7 = doorbell + irq status
+	clrl channel_stats
+
+	tstl %d7
+	bne main_1
+	// nothing to do - wait for next event
+	stop #0x2200			// supervisor + IRQ level 2
+	movew #0x2700, %sr		// disable IRQs again
+	bra main
+
+main_1:	clrl %d0			// D0 = 4 * port
+	clrl %d6			// D6 = doorbell to host value
+
+main_l: btstl #DOORBELL_TO_CARD_CLOSE_0, %d7
+	beq main_op
+	bclrl #DOORBELL_TO_CARD_OPEN_0, %d7 // in case both bits are set
+	bsr close_port
+main_op:
+	btstl #DOORBELL_TO_CARD_OPEN_0, %d7
+	beq main_cl
+	bsr open_port
+main_cl:
+	btstl #DOORBELL_TO_CARD_TX_0, %d7
+	beq main_txend
+	bsr tx
+main_txend:
+	btstl #TASK_SCC_0, %d7
+	beq main_next
+	bsr tx_end
+	bsr rx
+
+main_next:
+	lsrl #1, %d7			// port status for next port
+	addl #4, %d0			// D0 = 4 * next port
+	cmpl #4 * 4, %d0
+	bne main_l
+	movel %d6, PLX_DOORBELL_FROM_CARD // signal the host
+	bra main
+
+
+/****************************** open port *****************************/
+
+open_port:				// D0 = 4 * port, D6 = doorbell to host
+	movel ch_status_addr(%d0), %a0	// A0 = port status address
+	tstl STATUS_OPEN(%a0)
+	bne open_port_ret		// port already open
+	movel #1, STATUS_OPEN(%a0)	// confirm the port is open
+// setup BDs
+	clrl tx_in(%d0)
+	clrl tx_out(%d0)
+	clrl tx_count(%d0)
+	clrl rx_in(%d0)
+
+	movel SICR, %d1			// D1 = clock settings in SICR
+	andl clocking_mask(%d0), %d1
+	cmpl #CLOCK_TXFROMRX, STATUS_CLOCKING(%a0)
+	bne open_port_clock_ext
+	orl clocking_txfromrx(%d0), %d1
+	bra open_port_set_clock
+
+open_port_clock_ext:
+	orl clocking_ext(%d0), %d1
+open_port_set_clock:
+	movel %d1, SICR			// update clock settings in SICR
+
+	orw #STATUS_CABLE_DTR, csr_output(%d0)	// DTR on
+	bsr check_csr			// call with disabled timer interrupt
+
+// Setup TX descriptors
+	movel first_buffer(%d0), %d1	// D1 = starting buffer address
+	movel tx_first_bd(%d0), %a1	// A1 = starting TX BD address
+	movel #TX_BUFFERS - 2, %d2	// D2 = TX_BUFFERS - 1 counter
+	movel #0x18000000, %d3		// D3 = initial TX BD flags: Int + Last
+	cmpl #PARITY_NONE, STATUS_PARITY(%a0)
+	beq open_port_tx_loop
+	bsetl #26, %d3			// TX BD flag: Transmit CRC
+open_port_tx_loop:
+	movel %d3, (%a1)+		// TX flags + length
+	movel %d1, (%a1)+		// buffer address
+	addl #BUFFER_LENGTH, %d1
+	dbfw %d2, open_port_tx_loop
+
+	bsetl #29, %d3			// TX BD flag: Wrap (last BD)
+	movel %d3, (%a1)+		// Final TX flags + length
+	movel %d1, (%a1)+		// buffer address
+
+// Setup RX descriptors			// A1 = starting RX BD address
+	movel #RX_BUFFERS - 2, %d2	// D2 = RX_BUFFERS - 1 counter
+open_port_rx_loop:
+	movel #0x90000000, (%a1)+	// RX flags + length
+	movel %d1, (%a1)+		// buffer address
+	addl #BUFFER_LENGTH, %d1
+	dbfw %d2, open_port_rx_loop
+
+	movel #0xB0000000, (%a1)+	// Final RX flags + length
+	movel %d1, (%a1)+		// buffer address
+
+// Setup port parameters
+	movel scc_base_addr(%d0), %a1	// A1 = SCC_BASE address
+	movel scc_reg_addr(%d0), %a2	// A2 = SCC_REGS address
+
+	movel #0xFFFF, SCC_SCCE(%a2)	// clear status bits
+	movel #0x0000, SCC_SCCM(%a2)	// interrupt mask
+
+	movel tx_first_bd(%d0), %d1
+	movew %d1, SCC_TBASE(%a1)	// D1 = offset of first TxBD
+	addl #TX_BUFFERS * 8, %d1
+	movew %d1, SCC_RBASE(%a1)	// D1 = offset of first RxBD
+	moveb #0x8, SCC_RFCR(%a1)	// Intel mode, 1000
+	moveb #0x8, SCC_TFCR(%a1)
+
+// Parity settings
+	cmpl #PARITY_CRC16_PR1_CCITT, STATUS_PARITY(%a0)
+	bne open_port_parity_1
+	clrw SCC_PSMR(%a2)		// CRC16-CCITT
+	movel #0xF0B8, SCC_C_MASK(%a1)
+	movel #0xFFFF, SCC_C_PRES(%a1)
+	movew #HDLC_MAX_MRU + 2, SCC_MFLR(%a1) // 2 bytes for CRC
+	movew #2, parity_bytes(%d0)
+	bra open_port_2
+
+open_port_parity_1:
+	cmpl #PARITY_CRC32_PR1_CCITT, STATUS_PARITY(%a0)
+	bne open_port_parity_2
+	movew #0x0800, SCC_PSMR(%a2)	// CRC32-CCITT
+	movel #0xDEBB20E3, SCC_C_MASK(%a1)
+	movel #0xFFFFFFFF, SCC_C_PRES(%a1)
+	movew #HDLC_MAX_MRU + 4, SCC_MFLR(%a1) // 4 bytes for CRC
+	movew #4, parity_bytes(%d0)
+	bra open_port_2
+
+open_port_parity_2:
+	cmpl #PARITY_CRC16_PR0_CCITT, STATUS_PARITY(%a0)
+	bne open_port_parity_3
+	clrw SCC_PSMR(%a2)		// CRC16-CCITT preset 0
+	movel #0xF0B8, SCC_C_MASK(%a1)
+	clrl SCC_C_PRES(%a1)
+	movew #HDLC_MAX_MRU + 2, SCC_MFLR(%a1) // 2 bytes for CRC
+	movew #2, parity_bytes(%d0)
+	bra open_port_2
+
+open_port_parity_3:
+	cmpl #PARITY_CRC32_PR0_CCITT, STATUS_PARITY(%a0)
+	bne open_port_parity_4
+	movew #0x0800, SCC_PSMR(%a2)	// CRC32-CCITT preset 0
+	movel #0xDEBB20E3, SCC_C_MASK(%a1)
+	clrl SCC_C_PRES(%a1)
+	movew #HDLC_MAX_MRU + 4, SCC_MFLR(%a1) // 4 bytes for CRC
+	movew #4, parity_bytes(%d0)
+	bra open_port_2
+
+open_port_parity_4:
+	clrw SCC_PSMR(%a2)		// no parity
+	movel #0xF0B8, SCC_C_MASK(%a1)
+	movel #0xFFFF, SCC_C_PRES(%a1)
+	movew #HDLC_MAX_MRU, SCC_MFLR(%a1) // 0 bytes for CRC
+	clrw parity_bytes(%d0)
+
+open_port_2:
+	movel #0x00000003, SCC_GSMR_H(%a2) // RTSM
+	cmpl #ENCODING_NRZI, STATUS_ENCODING(%a0)
+	bne open_port_nrz
+	movel #0x10040900, SCC_GSMR_L(%a2) // NRZI: TCI Tend RECN+TENC=1
+	bra open_port_3
+
+open_port_nrz:
+	movel #0x10040000, SCC_GSMR_L(%a2) // NRZ: TCI Tend RECN+TENC=0
+open_port_3:
+	movew #BUFFER_LENGTH, SCC_MRBLR(%a1)
+	movel %d0, %d1
+	lsll #4, %d1			// D1 bits 7 and 6 = port
+	orl #1, %d1
+	movew %d1, CR			// Init SCC RX and TX params
+	wait_for_command
+
+	// TCI Tend ENR ENT
+	movew #0x001F, SCC_SCCM(%a2)	// TXE RXF BSY TXB RXB interrupts
+	orl #0x00000030, SCC_GSMR_L(%a2) // enable SCC
+open_port_ret:
+	rts
+
+
+/****************************** close port ****************************/
+
+close_port:				// D0 = 4 * port, D6 = doorbell to host
+	movel scc_reg_addr(%d0), %a0	// A0 = SCC_REGS address
+	clrw SCC_SCCM(%a0)		// no SCC interrupts
+	andl #0xFFFFFFCF, SCC_GSMR_L(%a0) // Disable ENT and ENR
+
+	andw #~STATUS_CABLE_DTR, csr_output(%d0) // DTR off
+	bsr check_csr			// call with disabled timer interrupt
+
+	movel ch_status_addr(%d0), %d1
+	clrl STATUS_OPEN(%d1)		// confirm the port is closed
+	rts
+
+
+/****************************** transmit packet ***********************/
+// queue packets for transmission
+tx:					// D0 = 4 * port, D6 = doorbell to host
+	cmpl #TX_BUFFERS, tx_count(%d0)
+	beq tx_ret			// all DB's = descs in use
+
+	movel tx_out(%d0), %d1
+	movel %d1, %d2			// D1 = D2 = tx_out BD# = desc#
+	mulul #DESC_LENGTH, %d2		// D2 = TX desc offset
+	addl ch_status_addr(%d0), %d2
+	addl #STATUS_TX_DESCS, %d2	// D2 = TX desc address
+	cmpl #PACKET_FULL, (%d2)	// desc status
+	bne tx_ret
+
+// queue it
+	movel 4(%d2), %a0		// PCI address
+	lsll #3, %d1			// BD is 8-bytes long
+	addl tx_first_bd(%d0), %d1	// D1 = current tx_out BD addr
+
+	movel 4(%d1), %a1		// A1 = dest address
+	movel 8(%d2), %d2		// D2 = length
+	movew %d2, 2(%d1)		// length into BD
+	memcpy_from_pci %a0, %a1, %d2
+	bsetl #31, (%d1)		// CP go ahead
+
+// update tx_out and tx_count
+	movel tx_out(%d0), %d1
+	addl #1, %d1
+	cmpl #TX_BUFFERS, %d1
+	bne tx_1
+	clrl %d1
+tx_1:	movel %d1, tx_out(%d0)
+
+	addl #1, tx_count(%d0)
+	bra tx
+
+tx_ret: rts
+
+
+/****************************** packet received ***********************/
+
+// Service receive buffers		// D0 = 4 * port, D6 = doorbell to host
+rx:	movel rx_in(%d0), %d1		// D1 = rx_in BD#
+	lsll #3, %d1			// BD is 8-bytes long
+	addl rx_first_bd(%d0), %d1	// D1 = current rx_in BD address
+	movew (%d1), %d2		// D2 = RX BD flags
+	btstl #15, %d2
+	bne rx_ret			// BD still empty
+
+	btstl #1, %d2
+	bne rx_overrun
+
+	tstw parity_bytes(%d0)
+	bne rx_parity
+	bclrl #2, %d2			// do not test for CRC errors
+rx_parity:
+	andw #0x0CBC, %d2		// mask status bits
+	cmpw #0x0C00, %d2		// correct frame
+	bne rx_bad_frame
+	clrl %d3
+	movew 2(%d1), %d3
+	subw parity_bytes(%d0), %d3	// D3 = packet length
+	cmpw #HDLC_MAX_MRU, %d3
+	bgt rx_bad_frame
+
+rx_good_frame:
+	movel rx_out, %d2
+	mulul #DESC_LENGTH, %d2
+	addl rx_descs_addr, %d2		// D2 = RX desc address
+	cmpl #PACKET_EMPTY, (%d2)	// desc stat
+	bne rx_overrun
+
+	movel %d3, 8(%d2)
+	movel 4(%d1), %a0		// A0 = source address
+	movel 4(%d2), %a1
+	tstl %a1
+	beq rx_ignore_data
+	memcpy_to_pci %a0, %a1, %d3
+rx_ignore_data:
+	movel packet_full(%d0), (%d2)	// update desc stat
+
+// update D6 and rx_out
+	bsetl #DOORBELL_FROM_CARD_RX, %d6 // signal host that RX completed
+	movel rx_out, %d2
+	addl #1, %d2
+	cmpl #RX_QUEUE_LENGTH, %d2
+	bne rx_1
+	clrl %d2
+rx_1:	movel %d2, rx_out
+
+rx_free_bd:
+	andw #0xF000, (%d1)		// clear CM and error bits
+	bsetl #31, (%d1)		// free BD
+// update rx_in
+	movel rx_in(%d0), %d1
+	addl #1, %d1
+	cmpl #RX_BUFFERS, %d1
+	bne rx_2
+	clrl %d1
+rx_2:	movel %d1, rx_in(%d0)
+	bra rx
+
+rx_overrun:
+	movel ch_status_addr(%d0), %d2
+	addl #1, STATUS_RX_OVERRUNS(%d2)
+	bra rx_free_bd
+
+rx_bad_frame:
+	movel ch_status_addr(%d0), %d2
+	addl #1, STATUS_RX_FRAME_ERRORS(%d2)
+	bra rx_free_bd
+
+rx_ret: rts
+
+
+/****************************** packet transmitted ********************/
+
+// Service transmit buffers		// D0 = 4 * port, D6 = doorbell to host
+tx_end:	tstl tx_count(%d0)
+	beq tx_end_ret			// TX buffers already empty
+
+	movel tx_in(%d0), %d1
+	movel %d1, %d2			// D1 = D2 = tx_in BD# = desc#
+	lsll #3, %d1			// BD is 8-bytes long
+	addl tx_first_bd(%d0), %d1	// D1 = current tx_in BD address
+	movew (%d1), %d3		// D3 = TX BD flags
+	btstl #15, %d3
+	bne tx_end_ret			// BD still being transmitted
+
+// update D6, tx_in and tx_count
+	orl bell_tx(%d0), %d6		// signal host that TX desc freed
+	subl #1, tx_count(%d0)
+	movel tx_in(%d0), %d1
+	addl #1, %d1
+	cmpl #TX_BUFFERS, %d1
+	bne tx_end_1
+	clrl %d1
+tx_end_1:
+	movel %d1, tx_in(%d0)
+
+// free host's descriptor
+	mulul #DESC_LENGTH, %d2		// D2 = TX desc offset
+	addl ch_status_addr(%d0), %d2
+	addl #STATUS_TX_DESCS, %d2	// D2 = TX desc address
+	btstl #1, %d3
+	bne tx_end_underrun
+	movel #PACKET_SENT, (%d2)
+	bra tx_end
+
+tx_end_underrun:
+	movel #PACKET_UNDERRUN, (%d2)
+	bra tx_end
+
+tx_end_ret: rts
+
+
+/****************************** PLX PCI9060 DMA memcpy ****************/
+
+#if QUICC_MEMCPY_USES_PLX
+// called with interrupts disabled
+memcpy_from_pci_run:
+	movel %d0, -(%sp)
+	movew %sr, -(%sp)
+memcpy_1:
+	movel PLX_DMA_CMD_STS, %d0	// do not btst PLX register directly
+	btstl #4, %d0			// transfer done?
+	bne memcpy_end
+	stop #0x2200			// enable PCI9060 interrupts
+	movew #0x2700, %sr		// disable interrupts again
+	bra memcpy_1
+
+memcpy_to_pci_run:
+	movel %d0, -(%sp)
+	movew %sr, -(%sp)
+memcpy_2:
+	movel PLX_DMA_CMD_STS, %d0	// do not btst PLX register directly
+	btstl #12, %d0			// transfer done?
+	bne memcpy_end
+	stop #0x2200			// enable PCI9060 interrupts
+	movew #0x2700, %sr		// disable interrupts again
+	bra memcpy_2
+
+memcpy_end:
+	movew (%sp)+, %sr
+	movel (%sp)+, %d0
+	rts
+#endif
+
+
+
+
+
+
+/****************************** PLX PCI9060 interrupt *****************/
+
+pci9060_interrupt:
+	movel %d0, -(%sp)
+
+	movel PLX_DOORBELL_TO_CARD, %d0
+	movel %d0, PLX_DOORBELL_TO_CARD	// confirm all requests
+	orl %d0, channel_stats
+
+	movel #0x0909, PLX_DMA_CMD_STS	// clear DMA ch #0 and #1 interrupts
+
+	movel (%sp)+, %d0
+	rte
+
+/****************************** SCC interrupts ************************/
+
+port_interrupt_1:
+	orl #0, SCC1_REGS + SCC_SCCE; // confirm SCC events
+	orl #1 << TASK_SCC_0, channel_stats
+	movel #0x40000000, CISR
+	rte
+
+port_interrupt_2:
+	orl #0, SCC2_REGS + SCC_SCCE; // confirm SCC events
+	orl #1 << TASK_SCC_1, channel_stats
+	movel #0x20000000, CISR
+	rte
+
+port_interrupt_3:
+	orl #0, SCC3_REGS + SCC_SCCE; // confirm SCC events
+	orl #1 << TASK_SCC_2, channel_stats
+	movel #0x10000000, CISR
+	rte
+
+port_interrupt_4:
+	orl #0, SCC4_REGS + SCC_SCCE; // confirm SCC events
+	orl #1 << TASK_SCC_3, channel_stats
+	movel #0x08000000, CISR
+	rte
+
+error_interrupt:
+	rte
+
+
+/****************************** cable and PM routine ******************/
+// modified registers: none
+check_csr:
+	movel %d0, -(%sp)
+	movel %d1, -(%sp)
+	movel %d2, -(%sp)
+	movel %a0, -(%sp)
+	movel %a1, -(%sp)
+
+	clrl %d0			// D0 = 4 * port
+	movel #CSRA, %a0		// A0 = CSR address
+
+check_csr_loop:
+	movew (%a0), %d1		// D1 = CSR input bits
+	andl #0xE7, %d1			// PM and cable sense bits (no DCE bit)
+	cmpw #STATUS_CABLE_V35 * (1 + 1 << STATUS_CABLE_PM_SHIFT), %d1
+	bne check_csr_1
+	movew #0x0E08, %d1
+	bra check_csr_valid
+
+check_csr_1:
+	cmpw #STATUS_CABLE_X21 * (1 + 1 << STATUS_CABLE_PM_SHIFT), %d1
+	bne check_csr_2
+	movew #0x0408, %d1
+	bra check_csr_valid
+
+check_csr_2:
+	cmpw #STATUS_CABLE_V24 * (1 + 1 << STATUS_CABLE_PM_SHIFT), %d1
+	bne check_csr_3
+	movew #0x0208, %d1
+	bra check_csr_valid
+
+check_csr_3:
+	cmpw #STATUS_CABLE_EIA530 * (1 + 1 << STATUS_CABLE_PM_SHIFT), %d1
+	bne check_csr_disable
+	movew #0x0D08, %d1
+	bra check_csr_valid
+
+check_csr_disable:
+	movew #0x0008, %d1		// D1 = disable everything
+	movew #0x80E7, %d2		// D2 = input mask: ignore DSR
+	bra check_csr_write
+
+check_csr_valid:			// D1 = mode and IRQ bits
+	movew csr_output(%d0), %d2
+	andw #0x3000, %d2		// D2 = requested LL and DTR bits
+	orw %d2, %d1			// D1 = all requested output bits
+	movew #0x80FF, %d2		// D2 = input mask: include DSR
+
+check_csr_write:
+	cmpw old_csr_output(%d0), %d1
+	beq check_csr_input
+	movew %d1, old_csr_output(%d0)
+	movew %d1, (%a0)		// Write CSR output bits
+
+check_csr_input:
+	movew (PCDAT), %d1
+	andw dcd_mask(%d0), %d1
+	beq check_csr_dcd_on		// DCD and CTS signals are negated
+	movew (%a0), %d1		// D1 = CSR input bits
+	andw #~STATUS_CABLE_DCD, %d1	// DCD off
+	bra check_csr_previous
+
+check_csr_dcd_on:
+	movew (%a0), %d1		// D1 = CSR input bits
+	orw #STATUS_CABLE_DCD, %d1	// DCD on
+check_csr_previous:
+	andw %d2, %d1			// input mask
+	movel ch_status_addr(%d0), %a1
+	cmpl STATUS_CABLE(%a1), %d1	// check for change
+	beq check_csr_next
+	movel %d1, STATUS_CABLE(%a1)	// update status
+	movel bell_cable(%d0), PLX_DOORBELL_FROM_CARD	// signal the host
+
+check_csr_next:
+	addl #2, %a0			// next CSR register
+	addl #4, %d0			// D0 = 4 * next port
+	cmpl #4 * 4, %d0
+	bne check_csr_loop
+
+	movel (%sp)+, %a1
+	movel (%sp)+, %a0
+	movel (%sp)+, %d2
+	movel (%sp)+, %d1
+	movel (%sp)+, %d0
+	rts
+
+
+/****************************** timer interrupt ***********************/
+
+timer_interrupt:
+	bsr check_csr
+	rte
+
+
+/****************************** RAM sizing and test *******************/
+#if DETECT_RAM
+ram_test:
+	movel #0x12345678, %d1		// D1 = test value
+	movel %d1, (128 * 1024 - 4)
+	movel #128 * 1024, %d0		// D0 = RAM size tested
+ram_test_size:
+	cmpl #MAX_RAM_SIZE, %d0
+	beq ram_test_size_found
+	movel %d0, %a0
+	addl #128 * 1024 - 4, %a0
+	cmpl (%a0), %d1
+	beq ram_test_size_check
+ram_test_next_size:
+	lsll #1, %d0
+	bra ram_test_size
+
+ram_test_size_check:
+	eorl #0xFFFFFFFF, %d1
+	movel %d1, (128 * 1024 - 4)
+	cmpl (%a0), %d1
+	bne ram_test_next_size
+
+ram_test_size_found:			// D0 = RAM size
+	movel %d0, %a0			// A0 = fill ptr
+	subl #firmware_end + 4, %d0
+	lsrl #2, %d0
+	movel %d0, %d1			// D1 = DBf counter
+ram_test_fill:
+	movel %a0, -(%a0)
+	dbfw %d1, ram_test_fill
+	subl #0x10000, %d1
+	cmpl #0xFFFFFFFF, %d1
+	bne ram_test_fill
+
+ram_test_loop:				// D0 = DBf counter
+	cmpl (%a0)+, %a0
+	dbnew %d0, ram_test_loop
+	bne ram_test_found_bad
+	subl #0x10000, %d0
+	cmpl #0xFFFFFFFF, %d0
+	bne ram_test_loop
+	bra ram_test_all_ok
+
+ram_test_found_bad:
+	subl #4, %a0
+ram_test_all_ok:
+	movel %a0, PLX_MAILBOX_5
+	rts
+#endif
+
+
+/****************************** constants *****************************/
+
+scc_reg_addr:
+	.long SCC1_REGS, SCC2_REGS, SCC3_REGS, SCC4_REGS
+scc_base_addr:
+	.long SCC1_BASE, SCC2_BASE, SCC3_BASE, SCC4_BASE
+
+tx_first_bd:
+	.long DPRBASE
+	.long DPRBASE + (TX_BUFFERS + RX_BUFFERS) * 8
+	.long DPRBASE + (TX_BUFFERS + RX_BUFFERS) * 8 * 2
+	.long DPRBASE + (TX_BUFFERS + RX_BUFFERS) * 8 * 3
+
+rx_first_bd:
+	.long DPRBASE + TX_BUFFERS * 8
+	.long DPRBASE + TX_BUFFERS * 8 + (TX_BUFFERS + RX_BUFFERS) * 8
+	.long DPRBASE + TX_BUFFERS * 8 + (TX_BUFFERS + RX_BUFFERS) * 8 * 2
+	.long DPRBASE + TX_BUFFERS * 8 + (TX_BUFFERS + RX_BUFFERS) * 8 * 3
+
+first_buffer:
+	.long BUFFERS_ADDR
+	.long BUFFERS_ADDR + (TX_BUFFERS + RX_BUFFERS) * BUFFER_LENGTH
+	.long BUFFERS_ADDR + (TX_BUFFERS + RX_BUFFERS) * BUFFER_LENGTH * 2
+	.long BUFFERS_ADDR + (TX_BUFFERS + RX_BUFFERS) * BUFFER_LENGTH * 3
+
+bell_tx:
+	.long 1 << DOORBELL_FROM_CARD_TX_0, 1 << DOORBELL_FROM_CARD_TX_1
+	.long 1 << DOORBELL_FROM_CARD_TX_2, 1 << DOORBELL_FROM_CARD_TX_3
+
+bell_cable:
+	.long 1 << DOORBELL_FROM_CARD_CABLE_0, 1 << DOORBELL_FROM_CARD_CABLE_1
+	.long 1 << DOORBELL_FROM_CARD_CABLE_2, 1 << DOORBELL_FROM_CARD_CABLE_3
+
+packet_full:
+	.long PACKET_FULL, PACKET_FULL + 1, PACKET_FULL + 2, PACKET_FULL + 3
+
+clocking_ext:
+	.long 0x0000002C, 0x00003E00, 0x002C0000, 0x3E000000
+clocking_txfromrx:
+	.long 0x0000002D, 0x00003F00, 0x002D0000, 0x3F000000
+clocking_mask:
+	.long 0x000000FF, 0x0000FF00, 0x00FF0000, 0xFF000000
+dcd_mask:
+	.word 0x020, 0, 0x080, 0, 0x200, 0, 0x800
+
+	.ascii "wanXL firmware\n"
+	.asciz "Copyright (C) 2003 Krzysztof Halasa <khc@pm.waw.pl>\n"
+
+
+/****************************** variables *****************************/
+
+		.align 4
+channel_stats:	.long 0
+
+tx_in:		.long 0, 0, 0, 0	// transmitted
+tx_out:		.long 0, 0, 0, 0	// received from host for transmission
+tx_count:	.long 0, 0, 0, 0	// currently in transmit queue
+
+rx_in:		.long 0, 0, 0, 0	// received from port
+rx_out:		.long 0			// transmitted to host
+parity_bytes:	.word 0, 0, 0, 0, 0, 0, 0 // only 4 words are used
+
+csr_output:	.word 0
+old_csr_output:	.word 0, 0, 0, 0, 0, 0, 0
+		.align 4
+firmware_end:				// must be dword-aligned
diff --git a/drivers/net/wan/wanxlfw.inc_shipped b/drivers/net/wan/wanxlfw.inc_shipped
new file mode 100644
index 0000000..73da688
--- /dev/null
+++ b/drivers/net/wan/wanxlfw.inc_shipped
@@ -0,0 +1,158 @@
+static u8 firmware[]={
+0x60,0x00,0x00,0x16,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x06,0xB9,0x40,0x00,0x00,0x00,0x00,0x00,
+0x10,0x14,0x42,0x80,0x4A,0xB0,0x09,0xB0,0x00,0x00,0x10,0x04,0x67,0x00,0x00,0x0E,
+0x06,0xB0,0x40,0x00,0x00,0x00,0x09,0xB0,0x00,0x00,0x10,0x04,0x58,0x80,0x0C,0x80,
+0x00,0x00,0x00,0x10,0x66,0x00,0xFF,0xDE,0x21,0xFC,0x00,0x00,0x16,0xBC,0x00,0x6C,
+0x21,0xFC,0x00,0x00,0x17,0x5E,0x01,0x00,0x21,0xFC,0x00,0x00,0x16,0xDE,0x01,0x78,
+0x21,0xFC,0x00,0x00,0x16,0xFE,0x01,0x74,0x21,0xFC,0x00,0x00,0x17,0x1E,0x01,0x70,
+0x21,0xFC,0x00,0x00,0x17,0x3E,0x01,0x6C,0x21,0xFC,0x00,0x00,0x18,0x4C,0x02,0x00,
+0x23,0xFC,0x78,0x00,0x00,0x00,0xFF,0xFC,0x15,0x48,0x33,0xFC,0x04,0x80,0xFF,0xFC,
+0x10,0x26,0x33,0xFC,0x01,0x10,0xFF,0xFC,0x10,0x2A,0x23,0xFC,0x00,0xD4,0x9F,0x40,
+0xFF,0xFC,0x15,0x40,0x23,0xFC,0x00,0x00,0x05,0x43,0xFF,0xF9,0x01,0x00,0x23,0xFC,
+0x00,0x00,0x05,0x43,0xFF,0xF9,0x01,0x14,0x23,0xFC,0x00,0x00,0x00,0x00,0xFF,0xF9,
+0x01,0x10,0x23,0xFC,0x00,0x00,0x00,0x08,0xFF,0xF9,0x01,0x24,0x23,0xFC,0x00,0x00,
+0x01,0x01,0xFF,0xF9,0x01,0x28,0x00,0xB9,0x00,0x0F,0x03,0x00,0xFF,0xF9,0x00,0xE8,
+0x23,0xFC,0x00,0x00,0x00,0x01,0xFF,0xF9,0x00,0xD4,0x61,0x00,0x06,0x74,0x33,0xFC,
+0xFF,0xFF,0xFF,0xFC,0x15,0x52,0x42,0x79,0xFF,0xFC,0x15,0x50,0x42,0x79,0xFF,0xFC,
+0x15,0x64,0x2E,0x3A,0x08,0x50,0x42,0xB9,0x00,0x00,0x19,0x54,0x4A,0x87,0x66,0x00,
+0x00,0x0E,0x4E,0x72,0x22,0x00,0x46,0xFC,0x27,0x00,0x60,0x00,0xFF,0xE6,0x42,0x80,
+0x42,0x86,0x08,0x07,0x00,0x04,0x67,0x00,0x00,0x0A,0x08,0x87,0x00,0x00,0x61,0x00,
+0x02,0xA0,0x08,0x07,0x00,0x00,0x67,0x00,0x00,0x06,0x61,0x00,0x00,0x36,0x08,0x07,
+0x00,0x08,0x67,0x00,0x00,0x06,0x61,0x00,0x02,0xB8,0x08,0x07,0x00,0x0C,0x67,0x00,
+0x00,0x0A,0x61,0x00,0x04,0x94,0x61,0x00,0x03,0x60,0xE2,0x8F,0x58,0x80,0x0C,0x80,
+0x00,0x00,0x00,0x10,0x66,0x00,0xFF,0xBC,0x23,0xC6,0xFF,0xF9,0x00,0xE4,0x60,0x00,
+0xFF,0x92,0x20,0x70,0x09,0xB0,0x00,0x00,0x10,0x04,0x4A,0xA8,0x00,0x00,0x66,0x00,
+0x02,0x4E,0x21,0x7C,0x00,0x00,0x00,0x01,0x00,0x00,0x42,0xB0,0x09,0xB0,0x00,0x00,
+0x19,0x58,0x42,0xB0,0x09,0xB0,0x00,0x00,0x19,0x68,0x42,0xB0,0x09,0xB0,0x00,0x00,
+0x19,0x78,0x42,0xB0,0x09,0xB0,0x00,0x00,0x19,0x88,0x22,0x39,0xFF,0xFC,0x16,0xEC,
+0xC2,0xB0,0x09,0xB0,0x00,0x00,0x18,0xF2,0x0C,0xA8,0x00,0x00,0x00,0x04,0x00,0x18,
+0x66,0x00,0x00,0x0E,0x82,0xB0,0x09,0xB0,0x00,0x00,0x18,0xE2,0x60,0x00,0x00,0x0A,
+0x82,0xB0,0x09,0xB0,0x00,0x00,0x18,0xD2,0x23,0xC1,0xFF,0xFC,0x16,0xEC,0x00,0x70,
+0x10,0x00,0x09,0xB0,0x00,0x00,0x19,0xAA,0x61,0x00,0x05,0x76,0x22,0x30,0x09,0xB0,
+0x00,0x00,0x18,0x92,0x22,0x70,0x09,0xB0,0x00,0x00,0x18,0x72,0x74,0x08,0x26,0x3C,
+0x18,0x00,0x00,0x00,0x0C,0xA8,0x00,0x00,0x00,0x01,0x00,0x10,0x67,0x00,0x00,0x06,
+0x08,0xC3,0x00,0x1A,0x22,0xC3,0x22,0xC1,0x06,0x81,0x00,0x00,0x05,0xFC,0x51,0xCA,
+0xFF,0xF4,0x08,0xC3,0x00,0x1D,0x22,0xC3,0x22,0xC1,0x74,0x1C,0x22,0xFC,0x90,0x00,
+0x00,0x00,0x22,0xC1,0x06,0x81,0x00,0x00,0x05,0xFC,0x51,0xCA,0xFF,0xF0,0x22,0xFC,
+0xB0,0x00,0x00,0x00,0x22,0xC1,0x22,0x70,0x09,0xB0,0x00,0x00,0x18,0x62,0x24,0x70,
+0x09,0xB0,0x00,0x00,0x18,0x52,0x25,0x7C,0x00,0x00,0xFF,0xFF,0x00,0x10,0x25,0x7C,
+0x00,0x00,0x00,0x00,0x00,0x14,0x22,0x30,0x09,0xB0,0x00,0x00,0x18,0x72,0x33,0x41,
+0x00,0x02,0x06,0x81,0x00,0x00,0x00,0x50,0x33,0x41,0x00,0x00,0x13,0x7C,0x00,0x08,
+0x00,0x04,0x13,0x7C,0x00,0x08,0x00,0x05,0x0C,0xA8,0x00,0x00,0x00,0x05,0x00,0x10,
+0x66,0x00,0x00,0x2A,0x42,0x6A,0x00,0x08,0x23,0x7C,0x00,0x00,0xF0,0xB8,0x00,0x34,
+0x23,0x7C,0x00,0x00,0xFF,0xFF,0x00,0x38,0x33,0x7C,0x05,0xFA,0x00,0x46,0x31,0xBC,
+0x00,0x02,0x09,0xB0,0x00,0x00,0x19,0x9C,0x60,0x00,0x00,0xBC,0x0C,0xA8,0x00,0x00,
+0x00,0x07,0x00,0x10,0x66,0x00,0x00,0x2C,0x35,0x7C,0x08,0x00,0x00,0x08,0x23,0x7C,
+0xDE,0xBB,0x20,0xE3,0x00,0x34,0x23,0x7C,0xFF,0xFF,0xFF,0xFF,0x00,0x38,0x33,0x7C,
+0x05,0xFC,0x00,0x46,0x31,0xBC,0x00,0x04,0x09,0xB0,0x00,0x00,0x19,0x9C,0x60,0x00,
+0x00,0x86,0x0C,0xA8,0x00,0x00,0x00,0x04,0x00,0x10,0x66,0x00,0x00,0x26,0x42,0x6A,
+0x00,0x08,0x23,0x7C,0x00,0x00,0xF0,0xB8,0x00,0x34,0x42,0xA9,0x00,0x38,0x33,0x7C,
+0x05,0xFA,0x00,0x46,0x31,0xBC,0x00,0x02,0x09,0xB0,0x00,0x00,0x19,0x9C,0x60,0x00,
+0x00,0x56,0x0C,0xA8,0x00,0x00,0x00,0x06,0x00,0x10,0x66,0x00,0x00,0x28,0x35,0x7C,
+0x08,0x00,0x00,0x08,0x23,0x7C,0xDE,0xBB,0x20,0xE3,0x00,0x34,0x42,0xA9,0x00,0x38,
+0x33,0x7C,0x05,0xFC,0x00,0x46,0x31,0xBC,0x00,0x04,0x09,0xB0,0x00,0x00,0x19,0x9C,
+0x60,0x00,0x00,0x24,0x42,0x6A,0x00,0x08,0x23,0x7C,0x00,0x00,0xF0,0xB8,0x00,0x34,
+0x23,0x7C,0x00,0x00,0xFF,0xFF,0x00,0x38,0x33,0x7C,0x05,0xF8,0x00,0x46,0x42,0x70,
+0x09,0xB0,0x00,0x00,0x19,0x9C,0x25,0x7C,0x00,0x00,0x00,0x03,0x00,0x04,0x0C,0xA8,
+0x00,0x00,0x00,0x02,0x00,0x14,0x66,0x00,0x00,0x0E,0x25,0x7C,0x10,0x04,0x09,0x00,
+0x00,0x00,0x60,0x00,0x00,0x0A,0x25,0x7C,0x10,0x04,0x00,0x00,0x00,0x00,0x33,0x7C,
+0x05,0xFC,0x00,0x06,0x22,0x00,0xE9,0x89,0x00,0x81,0x00,0x00,0x00,0x01,0x33,0xC1,
+0xFF,0xFC,0x15,0xC0,0x08,0x39,0x00,0x00,0xFF,0xFC,0x15,0xC0,0x66,0x00,0xFF,0xF6,
+0x35,0x7C,0x00,0x1F,0x00,0x14,0x00,0xAA,0x00,0x00,0x00,0x30,0x00,0x00,0x4E,0x75,
+0x20,0x70,0x09,0xB0,0x00,0x00,0x18,0x52,0x42,0x68,0x00,0x14,0x02,0xA8,0xFF,0xFF,
+0xFF,0xCF,0x00,0x00,0x02,0x70,0xEF,0xFF,0x09,0xB0,0x00,0x00,0x19,0xAA,0x61,0x00,
+0x03,0x70,0x22,0x30,0x09,0xB0,0x00,0x00,0x10,0x04,0x42,0xB0,0x19,0x90,0x4E,0x75,
+0x0C,0xB0,0x00,0x00,0x00,0x0A,0x09,0xB0,0x00,0x00,0x19,0x78,0x67,0x00,0x00,0xA8,
+0x22,0x30,0x09,0xB0,0x00,0x00,0x19,0x68,0x24,0x01,0x4C,0x3C,0x20,0x00,0x00,0x00,
+0x00,0x0C,0xD4,0xB0,0x09,0xB0,0x00,0x00,0x10,0x04,0x06,0x82,0x00,0x00,0x00,0x1C,
+0x0C,0xB0,0x00,0x00,0x00,0x10,0x29,0x90,0x66,0x00,0x00,0x7C,0x20,0x70,0x29,0xA0,
+0x00,0x04,0xE7,0x89,0xD2,0xB0,0x09,0xB0,0x00,0x00,0x18,0x72,0x22,0x70,0x19,0xA0,
+0x00,0x04,0x24,0x30,0x29,0xA0,0x00,0x08,0x31,0x82,0x19,0xA0,0x00,0x02,0x56,0x82,
+0x02,0x82,0xFF,0xFF,0xFF,0xFC,0x23,0xC8,0xFF,0xF9,0x01,0x04,0x23,0xC9,0xFF,0xF9,
+0x01,0x08,0x23,0xC2,0xFF,0xF9,0x01,0x0C,0x23,0xFC,0x00,0x00,0x01,0x03,0xFF,0xF9,
+0x01,0x28,0x61,0x00,0x01,0xF6,0x08,0xF0,0x00,0x1F,0x19,0x90,0x22,0x30,0x09,0xB0,
+0x00,0x00,0x19,0x68,0x52,0x81,0x0C,0x81,0x00,0x00,0x00,0x0A,0x66,0x00,0x00,0x04,
+0x42,0x81,0x21,0x81,0x09,0xB0,0x00,0x00,0x19,0x68,0x52,0xB0,0x09,0xB0,0x00,0x00,
+0x19,0x78,0x60,0x00,0xFF,0x4C,0x4E,0x75,0x22,0x30,0x09,0xB0,0x00,0x00,0x19,0x88,
+0xE7,0x89,0xD2,0xB0,0x09,0xB0,0x00,0x00,0x18,0x82,0x34,0x30,0x19,0x90,0x08,0x02,
+0x00,0x0F,0x66,0x00,0x01,0x12,0x08,0x02,0x00,0x01,0x66,0x00,0x00,0xE6,0x4A,0x70,
+0x09,0xB0,0x00,0x00,0x19,0x9C,0x66,0x00,0x00,0x06,0x08,0x82,0x00,0x02,0x02,0x42,
+0x0C,0xBC,0x0C,0x42,0x0C,0x00,0x66,0x00,0x00,0xDC,0x42,0x83,0x36,0x30,0x19,0xA0,
+0x00,0x02,0x96,0x70,0x09,0xB0,0x00,0x00,0x19,0x9C,0x0C,0x43,0x05,0xF8,0x6E,0x00,
+0x00,0xC4,0x24,0x3A,0x04,0x84,0x4C,0x3C,0x20,0x00,0x00,0x00,0x00,0x0C,0xD4,0xBA,
+0xFA,0xF4,0x0C,0xB0,0x00,0x00,0x00,0x00,0x29,0x90,0x66,0x00,0x00,0x96,0x21,0x83,
+0x29,0xA0,0x00,0x08,0x20,0x70,0x19,0xA0,0x00,0x04,0x22,0x70,0x29,0xA0,0x00,0x04,
+0x4A,0x89,0x67,0x00,0x00,0x2A,0x56,0x83,0x02,0x83,0xFF,0xFF,0xFF,0xFC,0x23,0xC8,
+0xFF,0xF9,0x01,0x1C,0x23,0xC9,0xFF,0xF9,0x01,0x18,0x23,0xC3,0xFF,0xF9,0x01,0x20,
+0x23,0xFC,0x00,0x00,0x03,0x01,0xFF,0xF9,0x01,0x28,0x61,0x00,0x01,0x2C,0x21,0xB0,
+0x09,0xB0,0x00,0x00,0x18,0xC2,0x29,0x90,0x08,0xC6,0x00,0x04,0x24,0x3A,0x04,0x1A,
+0x52,0x82,0x0C,0x82,0x00,0x00,0x00,0x28,0x66,0x00,0x00,0x04,0x42,0x82,0x23,0xC2,
+0x00,0x00,0x19,0x98,0x02,0x70,0xF0,0x00,0x19,0x90,0x08,0xF0,0x00,0x1F,0x19,0x90,
+0x22,0x30,0x09,0xB0,0x00,0x00,0x19,0x88,0x52,0x81,0x0C,0x81,0x00,0x00,0x00,0x1E,
+0x66,0x00,0x00,0x04,0x42,0x81,0x21,0x81,0x09,0xB0,0x00,0x00,0x19,0x88,0x60,0x00,
+0xFE,0xF8,0x24,0x30,0x09,0xB0,0x00,0x00,0x10,0x04,0x52,0xB0,0x29,0xA0,0x00,0x08,
+0x60,0x00,0xFF,0xC2,0x24,0x30,0x09,0xB0,0x00,0x00,0x10,0x04,0x52,0xB0,0x29,0xA0,
+0x00,0x0C,0x60,0x00,0xFF,0xB0,0x4E,0x75,0x4A,0xB0,0x09,0xB0,0x00,0x00,0x19,0x78,
+0x67,0x00,0x00,0x86,0x22,0x30,0x09,0xB0,0x00,0x00,0x19,0x58,0x24,0x01,0xE7,0x89,
+0xD2,0xB0,0x09,0xB0,0x00,0x00,0x18,0x72,0x36,0x30,0x19,0x90,0x08,0x03,0x00,0x0F,
+0x66,0x00,0x00,0x66,0x8C,0xB0,0x09,0xB0,0x00,0x00,0x18,0xA2,0x53,0xB0,0x09,0xB0,
+0x00,0x00,0x19,0x78,0x22,0x30,0x09,0xB0,0x00,0x00,0x19,0x58,0x52,0x81,0x0C,0x81,
+0x00,0x00,0x00,0x0A,0x66,0x00,0x00,0x04,0x42,0x81,0x21,0x81,0x09,0xB0,0x00,0x00,
+0x19,0x58,0x4C,0x3C,0x20,0x00,0x00,0x00,0x00,0x0C,0xD4,0xB0,0x09,0xB0,0x00,0x00,
+0x10,0x04,0x06,0x82,0x00,0x00,0x00,0x1C,0x08,0x03,0x00,0x01,0x66,0x00,0x00,0x0E,
+0x21,0xBC,0x00,0x00,0x00,0x20,0x29,0x90,0x60,0x00,0xFF,0x7E,0x21,0xBC,0x00,0x00,
+0x00,0x30,0x29,0x90,0x60,0x00,0xFF,0x72,0x4E,0x75,0x2F,0x00,0x40,0xE7,0x20,0x39,
+0xFF,0xF9,0x01,0x28,0x08,0x00,0x00,0x04,0x66,0x00,0x00,0x2C,0x4E,0x72,0x22,0x00,
+0x46,0xFC,0x27,0x00,0x60,0x00,0xFF,0xE8,0x2F,0x00,0x40,0xE7,0x20,0x39,0xFF,0xF9,
+0x01,0x28,0x08,0x00,0x00,0x0C,0x66,0x00,0x00,0x0E,0x4E,0x72,0x22,0x00,0x46,0xFC,
+0x27,0x00,0x60,0x00,0xFF,0xE8,0x46,0xDF,0x20,0x1F,0x4E,0x75,0x2F,0x00,0x20,0x39,
+0xFF,0xF9,0x00,0xE0,0x23,0xC0,0xFF,0xF9,0x00,0xE0,0x81,0xB9,0x00,0x00,0x19,0x54,
+0x23,0xFC,0x00,0x00,0x09,0x09,0xFF,0xF9,0x01,0x28,0x20,0x1F,0x4E,0x73,0x00,0xB9,
+0x00,0x00,0x00,0x00,0xFF,0xFC,0x16,0x10,0x00,0xB9,0x00,0x00,0x10,0x00,0x00,0x00,
+0x19,0x54,0x23,0xFC,0x40,0x00,0x00,0x00,0xFF,0xFC,0x15,0x4C,0x4E,0x73,0x00,0xB9,
+0x00,0x00,0x00,0x00,0xFF,0xFC,0x16,0x30,0x00,0xB9,0x00,0x00,0x20,0x00,0x00,0x00,
+0x19,0x54,0x23,0xFC,0x20,0x00,0x00,0x00,0xFF,0xFC,0x15,0x4C,0x4E,0x73,0x00,0xB9,
+0x00,0x00,0x00,0x00,0xFF,0xFC,0x16,0x50,0x00,0xB9,0x00,0x00,0x40,0x00,0x00,0x00,
+0x19,0x54,0x23,0xFC,0x10,0x00,0x00,0x00,0xFF,0xFC,0x15,0x4C,0x4E,0x73,0x00,0xB9,
+0x00,0x00,0x00,0x00,0xFF,0xFC,0x16,0x70,0x00,0xB9,0x00,0x00,0x80,0x00,0x00,0x00,
+0x19,0x54,0x23,0xFC,0x08,0x00,0x00,0x00,0xFF,0xFC,0x15,0x4C,0x4E,0x73,0x4E,0x73,
+0x2F,0x00,0x2F,0x01,0x2F,0x02,0x2F,0x08,0x2F,0x09,0x42,0x80,0x20,0x7C,0xFF,0xFB,
+0x00,0x00,0x32,0x10,0x02,0x81,0x00,0x00,0x00,0xE7,0x0C,0x41,0x00,0x42,0x66,0x00,
+0x00,0x0A,0x32,0x3C,0x0E,0x08,0x60,0x00,0x00,0x3E,0x0C,0x41,0x00,0x63,0x66,0x00,
+0x00,0x0A,0x32,0x3C,0x04,0x08,0x60,0x00,0x00,0x2E,0x0C,0x41,0x00,0x84,0x66,0x00,
+0x00,0x0A,0x32,0x3C,0x02,0x08,0x60,0x00,0x00,0x1E,0x0C,0x41,0x00,0xA5,0x66,0x00,
+0x00,0x0A,0x32,0x3C,0x0D,0x08,0x60,0x00,0x00,0x0E,0x32,0x3C,0x00,0x08,0x34,0x3C,
+0x80,0xE7,0x60,0x00,0x00,0x14,0x34,0x30,0x09,0xB0,0x00,0x00,0x19,0xAA,0x02,0x42,
+0x30,0x00,0x82,0x42,0x34,0x3C,0x80,0xFF,0xB2,0x70,0x09,0xB0,0x00,0x00,0x19,0xAC,
+0x67,0x00,0x00,0x0C,0x31,0x81,0x09,0xB0,0x00,0x00,0x19,0xAC,0x30,0x81,0x32,0x39,
+0xFF,0xFC,0x15,0x66,0xC2,0x70,0x09,0xB0,0x00,0x00,0x19,0x02,0x67,0x00,0x00,0x0C,
+0x32,0x10,0x02,0x41,0xFF,0xF7,0x60,0x00,0x00,0x08,0x32,0x10,0x00,0x41,0x00,0x08,
+0xC2,0x42,0x22,0x70,0x09,0xB0,0x00,0x00,0x10,0x04,0xB2,0xA9,0x00,0x04,0x67,0x00,
+0x00,0x12,0x23,0x41,0x00,0x04,0x23,0xF0,0x09,0xB0,0x00,0x00,0x18,0xB2,0xFF,0xF9,
+0x00,0xE4,0x54,0x88,0x58,0x80,0x0C,0x80,0x00,0x00,0x00,0x10,0x66,0x00,0xFF,0x34,
+0x22,0x5F,0x20,0x5F,0x24,0x1F,0x22,0x1F,0x20,0x1F,0x4E,0x75,0x61,0x00,0xFF,0x12,
+0x4E,0x73,0xFF,0xFC,0x16,0x00,0xFF,0xFC,0x16,0x20,0xFF,0xFC,0x16,0x40,0xFF,0xFC,
+0x16,0x60,0xFF,0xFC,0x0C,0x00,0xFF,0xFC,0x0D,0x00,0xFF,0xFC,0x0E,0x00,0xFF,0xFC,
+0x0F,0x00,0xFF,0xFC,0x00,0x00,0xFF,0xFC,0x01,0x40,0xFF,0xFC,0x02,0x80,0xFF,0xFC,
+0x03,0xC0,0xFF,0xFC,0x00,0x50,0xFF,0xFC,0x01,0x90,0xFF,0xFC,0x02,0xD0,0xFF,0xFC,
+0x04,0x10,0x00,0x00,0x40,0x00,0x00,0x01,0x2F,0x60,0x00,0x02,0x1E,0xC0,0x00,0x03,
+0x0E,0x20,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x02,0x00,0x00,0x00,0x04,0x00,0x00,
+0x00,0x08,0x00,0x00,0x00,0x20,0x00,0x00,0x00,0x40,0x00,0x00,0x00,0x80,0x00,0x00,
+0x01,0x00,0x00,0x00,0x00,0x10,0x00,0x00,0x00,0x11,0x00,0x00,0x00,0x12,0x00,0x00,
+0x00,0x13,0x00,0x00,0x00,0x2C,0x00,0x00,0x3E,0x00,0x00,0x2C,0x00,0x00,0x3E,0x00,
+0x00,0x00,0x00,0x00,0x00,0x2D,0x00,0x00,0x3F,0x00,0x00,0x2D,0x00,0x00,0x3F,0x00,
+0x00,0x00,0x00,0x00,0x00,0xFF,0x00,0x00,0xFF,0x00,0x00,0xFF,0x00,0x00,0xFF,0x00,
+0x00,0x00,0x00,0x20,0x00,0x00,0x00,0x80,0x00,0x00,0x02,0x00,0x00,0x00,0x08,0x00,
+0x77,0x61,0x6E,0x58,0x4C,0x20,0x66,0x69,0x72,0x6D,0x77,0x61,0x72,0x65,0x0A,0x43,
+0x6F,0x70,0x79,0x72,0x69,0x67,0x68,0x74,0x20,0x28,0x43,0x29,0x20,0x32,0x30,0x30,
+0x33,0x20,0x4B,0x72,0x7A,0x79,0x73,0x7A,0x74,0x6F,0x66,0x20,0x48,0x61,0x6C,0x61,
+0x73,0x61,0x20,0x3C,0x6B,0x68,0x63,0x40,0x70,0x6D,0x2E,0x77,0x61,0x77,0x2E,0x70,
+0x6C,0x3E,0x0A,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00
+};
diff --git a/drivers/net/wan/x25_asy.c b/drivers/net/wan/x25_asy.c
new file mode 100644
index 0000000..8c5cfcb
--- /dev/null
+++ b/drivers/net/wan/x25_asy.c
@@ -0,0 +1,844 @@
+/*
+ *	Things to sort out:
+ *
+ *	o	tbusy handling
+ *	o	allow users to set the parameters
+ *	o	sync/async switching ?
+ *
+ *	Note: This does _not_ implement CCITT X.25 asynchronous framing
+ *	recommendations. Its primarily for testing purposes. If you wanted
+ *	to do CCITT then in theory all you need is to nick the HDLC async
+ *	checksum routines from ppp.c
+ *      Changes:
+ *
+ *	2000-10-29	Henner Eisen	lapb_data_indication() return status.
+ */
+
+#include <linux/module.h>
+
+#include <asm/system.h>
+#include <asm/uaccess.h>
+#include <linux/bitops.h>
+#include <linux/string.h>
+#include <linux/mm.h>
+#include <linux/interrupt.h>
+#include <linux/in.h>
+#include <linux/tty.h>
+#include <linux/errno.h>
+#include <linux/netdevice.h>
+#include <linux/etherdevice.h>
+#include <linux/skbuff.h>
+#include <linux/if_arp.h>
+#include <linux/x25.h>
+#include <linux/lapb.h>
+#include <linux/init.h>
+#include "x25_asy.h"
+
+#include <net/x25device.h>
+
+static struct net_device **x25_asy_devs;
+static int x25_asy_maxdev = SL_NRUNIT;
+
+module_param(x25_asy_maxdev, int, 0);
+MODULE_LICENSE("GPL");
+
+static int x25_asy_esc(unsigned char *p, unsigned char *d, int len);
+static void x25_asy_unesc(struct x25_asy *sl, unsigned char c);
+static void x25_asy_setup(struct net_device *dev);
+
+/* Find a free X.25 channel, and link in this `tty' line. */
+static struct x25_asy *x25_asy_alloc(void)
+{
+	struct net_device *dev = NULL;
+	struct x25_asy *sl;
+	int i;
+
+	if (x25_asy_devs == NULL)
+		return NULL;	/* Master array missing ! */
+
+	for (i = 0; i < x25_asy_maxdev; i++) {
+		dev = x25_asy_devs[i];
+
+		/* Not allocated ? */
+		if (dev == NULL)
+			break;
+
+		sl = dev->priv;
+		/* Not in use ? */
+		if (!test_and_set_bit(SLF_INUSE, &sl->flags))
+			return sl;
+	}
+
+
+	/* Sorry, too many, all slots in use */
+	if (i >= x25_asy_maxdev)
+		return NULL;
+
+	/* If no channels are available, allocate one */
+	if (!dev) {
+		char name[IFNAMSIZ];
+		sprintf(name, "x25asy%d", i);
+
+		dev = alloc_netdev(sizeof(struct x25_asy),
+				   name, x25_asy_setup);
+		if (!dev)
+			return NULL;
+
+		/* Initialize channel control data */
+		sl = dev->priv;
+		dev->base_addr    = i;
+
+		/* register device so that it can be ifconfig'ed       */
+		if (register_netdev(dev) == 0) {
+			/* (Re-)Set the INUSE bit.   Very Important! */
+			set_bit(SLF_INUSE, &sl->flags);
+			x25_asy_devs[i] = dev;
+			return sl;
+		} else {
+			printk("x25_asy_alloc() - register_netdev() failure.\n");
+			free_netdev(dev);
+		}
+	}
+	return NULL;
+}
+
+
+/* Free an X.25 channel. */
+static void x25_asy_free(struct x25_asy *sl)
+{
+	/* Free all X.25 frame buffers. */
+	if (sl->rbuff)  {
+		kfree(sl->rbuff);
+	}
+	sl->rbuff = NULL;
+	if (sl->xbuff)  {
+		kfree(sl->xbuff);
+	}
+	sl->xbuff = NULL;
+
+	if (!test_and_clear_bit(SLF_INUSE, &sl->flags)) {
+		printk("%s: x25_asy_free for already free unit.\n", sl->dev->name);
+	}
+}
+
+static int x25_asy_change_mtu(struct net_device *dev, int newmtu)
+{
+	struct x25_asy *sl = dev->priv;
+	unsigned char *xbuff, *rbuff;
+	int len = 2* newmtu;
+
+	xbuff = (unsigned char *) kmalloc (len + 4, GFP_ATOMIC);
+	rbuff = (unsigned char *) kmalloc (len + 4, GFP_ATOMIC);
+
+	if (xbuff == NULL || rbuff == NULL)  
+	{
+		printk("%s: unable to grow X.25 buffers, MTU change cancelled.\n",
+		       dev->name);
+		if (xbuff != NULL)  
+			kfree(xbuff);
+		if (rbuff != NULL)  
+			kfree(rbuff);
+		return -ENOMEM;
+	}
+
+	spin_lock_bh(&sl->lock);
+	xbuff    = xchg(&sl->xbuff, xbuff);
+	if (sl->xleft)  {
+		if (sl->xleft <= len)  {
+			memcpy(sl->xbuff, sl->xhead, sl->xleft);
+		} else  {
+			sl->xleft = 0;
+			sl->stats.tx_dropped++;
+		}
+	}
+	sl->xhead = sl->xbuff;
+
+	rbuff	 = xchg(&sl->rbuff, rbuff);
+	if (sl->rcount)  {
+		if (sl->rcount <= len) {
+			memcpy(sl->rbuff, rbuff, sl->rcount);
+		} else  {
+			sl->rcount = 0;
+			sl->stats.rx_over_errors++;
+			set_bit(SLF_ERROR, &sl->flags);
+		}
+	}
+
+	dev->mtu    = newmtu;
+	sl->buffsize = len;
+
+	spin_unlock_bh(&sl->lock);
+
+	if (xbuff != NULL) 
+		kfree(xbuff);
+	if (rbuff != NULL)
+		kfree(rbuff);
+	return 0;
+}
+
+
+/* Set the "sending" flag.  This must be atomic, hence the ASM. */
+
+static inline void x25_asy_lock(struct x25_asy *sl)
+{
+	netif_stop_queue(sl->dev);
+}
+
+
+/* Clear the "sending" flag.  This must be atomic, hence the ASM. */
+
+static inline void x25_asy_unlock(struct x25_asy *sl)
+{
+	netif_wake_queue(sl->dev);
+}
+
+/* Send one completely decapsulated IP datagram to the IP layer. */
+
+static void x25_asy_bump(struct x25_asy *sl)
+{
+	struct sk_buff *skb;
+	int count;
+	int err;
+
+	count = sl->rcount;
+	sl->stats.rx_bytes+=count;
+	
+	skb = dev_alloc_skb(count+1);
+	if (skb == NULL)  
+	{
+		printk("%s: memory squeeze, dropping packet.\n", sl->dev->name);
+		sl->stats.rx_dropped++;
+		return;
+	}
+	skb_push(skb,1);	/* LAPB internal control */
+	memcpy(skb_put(skb,count), sl->rbuff, count);
+	skb->protocol = x25_type_trans(skb, sl->dev);
+	if((err=lapb_data_received(skb->dev, skb))!=LAPB_OK)
+	{
+		kfree_skb(skb);
+		printk(KERN_DEBUG "x25_asy: data received err - %d\n",err);
+	}
+	else
+	{
+		netif_rx(skb);
+		sl->dev->last_rx = jiffies;
+		sl->stats.rx_packets++;
+	}
+}
+
+/* Encapsulate one IP datagram and stuff into a TTY queue. */
+static void x25_asy_encaps(struct x25_asy *sl, unsigned char *icp, int len)
+{
+	unsigned char *p;
+	int actual, count, mtu = sl->dev->mtu;
+
+	if (len > mtu) 
+	{		/* Sigh, shouldn't occur BUT ... */
+		len = mtu;
+		printk ("%s: truncating oversized transmit packet!\n", sl->dev->name);
+		sl->stats.tx_dropped++;
+		x25_asy_unlock(sl);
+		return;
+	}
+
+	p = icp;
+	count = x25_asy_esc(p, (unsigned char *) sl->xbuff, len);
+
+	/* Order of next two lines is *very* important.
+	 * When we are sending a little amount of data,
+	 * the transfer may be completed inside driver.write()
+	 * routine, because it's running with interrupts enabled.
+	 * In this case we *never* got WRITE_WAKEUP event,
+	 * if we did not request it before write operation.
+	 *       14 Oct 1994  Dmitry Gorodchanin.
+	 */
+	sl->tty->flags |= (1 << TTY_DO_WRITE_WAKEUP);
+	actual = sl->tty->driver->write(sl->tty, sl->xbuff, count);
+	sl->xleft = count - actual;
+	sl->xhead = sl->xbuff + actual;
+	/* VSV */
+	clear_bit(SLF_OUTWAIT, &sl->flags);	/* reset outfill flag */
+}
+
+/*
+ * Called by the driver when there's room for more data.  If we have
+ * more packets to send, we send them here.
+ */
+static void x25_asy_write_wakeup(struct tty_struct *tty)
+{
+	int actual;
+	struct x25_asy *sl = (struct x25_asy *) tty->disc_data;
+
+	/* First make sure we're connected. */
+	if (!sl || sl->magic != X25_ASY_MAGIC || !netif_running(sl->dev))
+		return;
+
+	if (sl->xleft <= 0)  
+	{
+		/* Now serial buffer is almost free & we can start
+		 * transmission of another packet */
+		sl->stats.tx_packets++;
+		tty->flags &= ~(1 << TTY_DO_WRITE_WAKEUP);
+		x25_asy_unlock(sl);
+		return;
+	}
+
+	actual = tty->driver->write(tty, sl->xhead, sl->xleft);
+	sl->xleft -= actual;
+	sl->xhead += actual;
+}
+
+static void x25_asy_timeout(struct net_device *dev)
+{
+	struct x25_asy *sl = (struct x25_asy*)(dev->priv);
+
+	spin_lock(&sl->lock);
+	if (netif_queue_stopped(dev)) {
+		/* May be we must check transmitter timeout here ?
+		 *      14 Oct 1994 Dmitry Gorodchanin.
+		 */
+		printk(KERN_WARNING "%s: transmit timed out, %s?\n", dev->name,
+		       (sl->tty->driver->chars_in_buffer(sl->tty) || sl->xleft) ?
+		       "bad line quality" : "driver error");
+		sl->xleft = 0;
+		sl->tty->flags &= ~(1 << TTY_DO_WRITE_WAKEUP);
+		x25_asy_unlock(sl);
+	}
+	spin_unlock(&sl->lock);
+}
+
+/* Encapsulate an IP datagram and kick it into a TTY queue. */
+
+static int x25_asy_xmit(struct sk_buff *skb, struct net_device *dev)
+{
+	struct x25_asy *sl = (struct x25_asy*)(dev->priv);
+	int err;
+
+	if (!netif_running(sl->dev)) {
+		printk("%s: xmit call when iface is down\n", dev->name);
+		kfree_skb(skb);
+		return 0;
+	}
+	
+	switch(skb->data[0])
+	{
+		case 0x00:break;
+		case 0x01: /* Connection request .. do nothing */
+			if((err=lapb_connect_request(dev))!=LAPB_OK)
+				printk(KERN_ERR "x25_asy: lapb_connect_request error - %d\n", err);
+			kfree_skb(skb);
+			return 0;
+		case 0x02: /* Disconnect request .. do nothing - hang up ?? */
+			if((err=lapb_disconnect_request(dev))!=LAPB_OK)
+				printk(KERN_ERR "x25_asy: lapb_disconnect_request error - %d\n", err);
+		default:
+			kfree_skb(skb);
+			return  0;
+	}
+	skb_pull(skb,1);	/* Remove control byte */
+	/*
+	 * If we are busy already- too bad.  We ought to be able
+	 * to queue things at this point, to allow for a little
+	 * frame buffer.  Oh well...
+	 * -----------------------------------------------------
+	 * I hate queues in X.25 driver. May be it's efficient,
+	 * but for me latency is more important. ;)
+	 * So, no queues !
+	 *        14 Oct 1994  Dmitry Gorodchanin.
+	 */
+	
+	if((err=lapb_data_request(dev,skb))!=LAPB_OK)
+	{
+		printk(KERN_ERR "lapbeth: lapb_data_request error - %d\n", err);
+		kfree_skb(skb);
+		return 0;
+	}
+	return 0;
+}
+
+
+/*
+ *	LAPB interface boilerplate
+ */
+
+/*
+ *	Called when I frame data arrives. We did the work above - throw it
+ *	at the net layer.
+ */
+  
+static int x25_asy_data_indication(struct net_device *dev, struct sk_buff *skb)
+{
+	skb->dev->last_rx = jiffies;
+	return netif_rx(skb);
+}
+
+/*
+ *	Data has emerged from the LAPB protocol machine. We don't handle
+ *	busy cases too well. Its tricky to see how to do this nicely -
+ *	perhaps lapb should allow us to bounce this ?
+ */
+ 
+static void x25_asy_data_transmit(struct net_device *dev, struct sk_buff *skb)
+{
+	struct x25_asy *sl=dev->priv;
+	
+	spin_lock(&sl->lock);
+	if (netif_queue_stopped(sl->dev) || sl->tty == NULL)
+	{
+		spin_unlock(&sl->lock);
+		printk(KERN_ERR "x25_asy: tbusy drop\n");
+		kfree_skb(skb);
+		return;
+	}
+	/* We were not busy, so we are now... :-) */
+	if (skb != NULL) 
+	{
+		x25_asy_lock(sl);
+		sl->stats.tx_bytes+=skb->len;
+		x25_asy_encaps(sl, skb->data, skb->len);
+		dev_kfree_skb(skb);
+	}
+	spin_unlock(&sl->lock);
+}
+
+/*
+ *	LAPB connection establish/down information.
+ */
+ 
+static void x25_asy_connected(struct net_device *dev, int reason)
+{
+	struct x25_asy *sl = dev->priv;
+	struct sk_buff *skb;
+	unsigned char *ptr;
+
+	if ((skb = dev_alloc_skb(1)) == NULL) {
+		printk(KERN_ERR "lapbeth: out of memory\n");
+		return;
+	}
+
+	ptr  = skb_put(skb, 1);
+	*ptr = 0x01;
+
+	skb->protocol = x25_type_trans(skb, sl->dev);
+	netif_rx(skb);
+	sl->dev->last_rx = jiffies;
+}
+
+static void x25_asy_disconnected(struct net_device *dev, int reason)
+{
+	struct x25_asy *sl = dev->priv;
+	struct sk_buff *skb;
+	unsigned char *ptr;
+
+	if ((skb = dev_alloc_skb(1)) == NULL) {
+		printk(KERN_ERR "x25_asy: out of memory\n");
+		return;
+	}
+
+	ptr  = skb_put(skb, 1);
+	*ptr = 0x02;
+
+	skb->protocol = x25_type_trans(skb, sl->dev);
+	netif_rx(skb);
+	sl->dev->last_rx = jiffies;
+}
+
+static struct lapb_register_struct x25_asy_callbacks = {
+	.connect_confirmation = x25_asy_connected,
+	.connect_indication = x25_asy_connected,
+	.disconnect_confirmation = x25_asy_disconnected,
+	.disconnect_indication = x25_asy_disconnected,
+	.data_indication = x25_asy_data_indication,
+	.data_transmit = x25_asy_data_transmit,
+
+};
+
+
+/* Open the low-level part of the X.25 channel. Easy! */
+static int x25_asy_open(struct net_device *dev)
+{
+	struct x25_asy *sl = (struct x25_asy*)(dev->priv);
+	unsigned long len;
+	int err;
+
+	if (sl->tty == NULL)
+		return -ENODEV;
+
+	/*
+	 * Allocate the X.25 frame buffers:
+	 *
+	 * rbuff	Receive buffer.
+	 * xbuff	Transmit buffer.
+	 */
+
+	len = dev->mtu * 2;
+
+	sl->rbuff = (unsigned char *) kmalloc(len + 4, GFP_KERNEL);
+	if (sl->rbuff == NULL)   {
+		goto norbuff;
+	}
+	sl->xbuff = (unsigned char *) kmalloc(len + 4, GFP_KERNEL);
+	if (sl->xbuff == NULL)   {
+		goto noxbuff;
+	}
+
+	sl->buffsize = len;
+	sl->rcount   = 0;
+	sl->xleft    = 0;
+	sl->flags   &= (1 << SLF_INUSE);      /* Clear ESCAPE & ERROR flags */
+
+	netif_start_queue(dev);
+			
+	/*
+	 *	Now attach LAPB
+	 */
+	if((err=lapb_register(dev, &x25_asy_callbacks))==LAPB_OK)
+		return 0;
+
+	/* Cleanup */
+	kfree(sl->xbuff);
+noxbuff:
+	kfree(sl->rbuff);
+norbuff:
+	return -ENOMEM;
+}
+
+
+/* Close the low-level part of the X.25 channel. Easy! */
+static int x25_asy_close(struct net_device *dev)
+{
+	struct x25_asy *sl = (struct x25_asy*)(dev->priv);
+	int err;
+
+	spin_lock(&sl->lock);
+	if (sl->tty) 
+		sl->tty->flags &= ~(1 << TTY_DO_WRITE_WAKEUP);
+
+	netif_stop_queue(dev);
+	sl->rcount = 0;
+	sl->xleft  = 0;
+	if((err=lapb_unregister(dev))!=LAPB_OK)
+		printk(KERN_ERR "x25_asy_close: lapb_unregister error -%d\n",err);
+	spin_unlock(&sl->lock);
+	return 0;
+}
+
+static int x25_asy_receive_room(struct tty_struct *tty)
+{
+	return 65536;  /* We can handle an infinite amount of data. :-) */
+}
+
+/*
+ * Handle the 'receiver data ready' interrupt.
+ * This function is called by the 'tty_io' module in the kernel when
+ * a block of X.25 data has been received, which can now be decapsulated
+ * and sent on to some IP layer for further processing.
+ */
+ 
+static void x25_asy_receive_buf(struct tty_struct *tty, const unsigned char *cp, char *fp, int count)
+{
+	struct x25_asy *sl = (struct x25_asy *) tty->disc_data;
+
+	if (!sl || sl->magic != X25_ASY_MAGIC || !netif_running(sl->dev))
+		return;
+
+
+	/* Read the characters out of the buffer */
+	while (count--) {
+		if (fp && *fp++) {
+			if (!test_and_set_bit(SLF_ERROR, &sl->flags))  {
+				sl->stats.rx_errors++;
+			}
+			cp++;
+			continue;
+		}
+		x25_asy_unesc(sl, *cp++);
+	}
+}
+
+/*
+ * Open the high-level part of the X.25 channel.
+ * This function is called by the TTY module when the
+ * X.25 line discipline is called for.  Because we are
+ * sure the tty line exists, we only have to link it to
+ * a free X.25 channel...
+ */
+
+static int x25_asy_open_tty(struct tty_struct *tty)
+{
+	struct x25_asy *sl = (struct x25_asy *) tty->disc_data;
+	int err;
+
+	/* First make sure we're not already connected. */
+	if (sl && sl->magic == X25_ASY_MAGIC) {
+		return -EEXIST;
+	}
+
+	/* OK.  Find a free X.25 channel to use. */
+	if ((sl = x25_asy_alloc()) == NULL) {
+		return -ENFILE;
+	}
+
+	sl->tty = tty;
+	tty->disc_data = sl;
+	if (tty->driver->flush_buffer)  {
+		tty->driver->flush_buffer(tty);
+	}
+	if (tty->ldisc.flush_buffer)  {
+		tty->ldisc.flush_buffer(tty);
+	}
+
+	/* Restore default settings */
+	sl->dev->type = ARPHRD_X25;
+	
+	/* Perform the low-level X.25 async init */
+	if ((err = x25_asy_open(sl->dev)))
+		return err;
+
+	/* Done.  We have linked the TTY line to a channel. */
+	return sl->dev->base_addr;
+}
+
+
+/*
+ * Close down an X.25 channel.
+ * This means flushing out any pending queues, and then restoring the
+ * TTY line discipline to what it was before it got hooked to X.25
+ * (which usually is TTY again).
+ */
+static void x25_asy_close_tty(struct tty_struct *tty)
+{
+	struct x25_asy *sl = (struct x25_asy *) tty->disc_data;
+
+	/* First make sure we're connected. */
+	if (!sl || sl->magic != X25_ASY_MAGIC)
+		return;
+
+	if (sl->dev->flags & IFF_UP)
+	{
+		(void) dev_close(sl->dev);
+	}
+
+	tty->disc_data = NULL;
+	sl->tty = NULL;
+	x25_asy_free(sl);
+}
+
+
+static struct net_device_stats *x25_asy_get_stats(struct net_device *dev)
+{
+	struct x25_asy *sl = (struct x25_asy*)(dev->priv);
+
+	return &sl->stats;
+}
+
+
+ /************************************************************************
+  *			STANDARD X.25 ENCAPSULATION		  	 *
+  ************************************************************************/
+
+int x25_asy_esc(unsigned char *s, unsigned char *d, int len)
+{
+	unsigned char *ptr = d;
+	unsigned char c;
+
+	/*
+	 * Send an initial END character to flush out any
+	 * data that may have accumulated in the receiver
+	 * due to line noise.
+	 */
+
+	*ptr++ = X25_END;	/* Send 10111110 bit seq */
+
+	/*
+	 * For each byte in the packet, send the appropriate
+	 * character sequence, according to the X.25 protocol.
+	 */
+
+	while (len-- > 0) 
+	{
+		switch(c = *s++) 
+		{
+			case X25_END:
+				*ptr++ = X25_ESC;
+				*ptr++ = X25_ESCAPE(X25_END);
+				break;
+			case X25_ESC:
+				*ptr++ = X25_ESC;
+				*ptr++ = X25_ESCAPE(X25_ESC);
+				break;
+			 default:
+				*ptr++ = c;
+				break;
+		}
+	}
+	*ptr++ = X25_END;
+	return (ptr - d);
+}
+
+static void x25_asy_unesc(struct x25_asy *sl, unsigned char s)
+{
+
+	switch(s) 
+	{
+		case X25_END:
+			if (!test_and_clear_bit(SLF_ERROR, &sl->flags) && (sl->rcount > 2))  
+			{
+				x25_asy_bump(sl);
+			}
+			clear_bit(SLF_ESCAPE, &sl->flags);
+			sl->rcount = 0;
+			return;
+
+		case X25_ESC:
+			set_bit(SLF_ESCAPE, &sl->flags);
+			return;
+			
+		case X25_ESCAPE(X25_ESC):
+		case X25_ESCAPE(X25_END):
+			if (test_and_clear_bit(SLF_ESCAPE, &sl->flags))
+				s = X25_UNESCAPE(s);
+			break;
+	}
+	if (!test_bit(SLF_ERROR, &sl->flags))  
+	{
+		if (sl->rcount < sl->buffsize)  
+		{
+			sl->rbuff[sl->rcount++] = s;
+			return;
+		}
+		sl->stats.rx_over_errors++;
+		set_bit(SLF_ERROR, &sl->flags);
+	}
+}
+
+
+/* Perform I/O control on an active X.25 channel. */
+static int x25_asy_ioctl(struct tty_struct *tty, struct file *file,
+			 unsigned int cmd,  unsigned long arg)
+{
+	struct x25_asy *sl = (struct x25_asy *) tty->disc_data;
+
+	/* First make sure we're connected. */
+	if (!sl || sl->magic != X25_ASY_MAGIC)
+		return -EINVAL;
+
+	switch(cmd) {
+	case SIOCGIFNAME:
+		if (copy_to_user((void __user *)arg, sl->dev->name,
+					strlen(sl->dev->name) + 1))
+			return -EFAULT;
+		return 0;
+	case SIOCSIFHWADDR:
+		return -EINVAL;
+	/* Allow stty to read, but not set, the serial port */
+	case TCGETS:
+	case TCGETA:
+		return n_tty_ioctl(tty, file, cmd, arg);
+	default:
+		return -ENOIOCTLCMD;
+	}
+}
+
+static int x25_asy_open_dev(struct net_device *dev)
+{
+	struct x25_asy *sl = (struct x25_asy*)(dev->priv);
+	if(sl->tty==NULL)
+		return -ENODEV;
+	return 0;
+}
+
+/* Initialise the X.25 driver.  Called by the device init code */
+static void x25_asy_setup(struct net_device *dev)
+{
+	struct x25_asy *sl = dev->priv;
+
+	sl->magic  = X25_ASY_MAGIC;
+	sl->dev	   = dev;
+	spin_lock_init(&sl->lock);
+	set_bit(SLF_INUSE, &sl->flags);
+
+	/*
+	 *	Finish setting up the DEVICE info. 
+	 */
+	 
+	dev->mtu		= SL_MTU;
+	dev->hard_start_xmit	= x25_asy_xmit;
+	dev->tx_timeout		= x25_asy_timeout;
+	dev->watchdog_timeo	= HZ*20;
+	dev->open		= x25_asy_open_dev;
+	dev->stop		= x25_asy_close;
+	dev->get_stats	        = x25_asy_get_stats;
+	dev->change_mtu		= x25_asy_change_mtu;
+	dev->hard_header_len	= 0;
+	dev->addr_len		= 0;
+	dev->type		= ARPHRD_X25;
+	dev->tx_queue_len	= 10;
+
+	/* New-style flags. */
+	dev->flags		= IFF_NOARP;
+}
+
+static struct tty_ldisc x25_ldisc = {
+	.owner		= THIS_MODULE,
+	.magic		= TTY_LDISC_MAGIC,
+	.name		= "X.25",
+	.open		= x25_asy_open_tty,
+	.close		= x25_asy_close_tty,
+	.ioctl		= x25_asy_ioctl,
+	.receive_buf	= x25_asy_receive_buf,
+	.receive_room	= x25_asy_receive_room,
+	.write_wakeup	= x25_asy_write_wakeup,
+};
+
+static int __init init_x25_asy(void)
+{
+	if (x25_asy_maxdev < 4)
+		x25_asy_maxdev = 4; /* Sanity */
+
+	printk(KERN_INFO "X.25 async: version 0.00 ALPHA "
+			"(dynamic channels, max=%d).\n", x25_asy_maxdev );
+
+	x25_asy_devs = kmalloc(sizeof(struct net_device *)*x25_asy_maxdev, 
+			       GFP_KERNEL);
+	if (!x25_asy_devs) {
+		printk(KERN_WARNING "X25 async: Can't allocate x25_asy_ctrls[] "
+				"array! Uaargh! (-> No X.25 available)\n");
+		return -ENOMEM;
+	}
+	memset(x25_asy_devs, 0, sizeof(struct net_device *)*x25_asy_maxdev); 
+
+	return tty_register_ldisc(N_X25, &x25_ldisc);
+}
+
+
+static void __exit exit_x25_asy(void)
+{
+	struct net_device *dev;
+	int i;
+
+	for (i = 0; i < x25_asy_maxdev; i++) {
+		dev = x25_asy_devs[i];
+		if (dev) {
+			struct x25_asy *sl = dev->priv;
+
+			spin_lock_bh(&sl->lock);
+			if (sl->tty) 
+				tty_hangup(sl->tty);
+
+			spin_unlock_bh(&sl->lock);
+			/*
+			 * VSV = if dev->start==0, then device
+			 * unregistered while close proc.
+			 */
+			unregister_netdev(dev);
+			free_netdev(dev);
+		}
+	}
+
+	kfree(x25_asy_devs);
+	tty_register_ldisc(N_X25, NULL);
+}
+
+module_init(init_x25_asy);
+module_exit(exit_x25_asy);
diff --git a/drivers/net/wan/x25_asy.h b/drivers/net/wan/x25_asy.h
new file mode 100644
index 0000000..4177020
--- /dev/null
+++ b/drivers/net/wan/x25_asy.h
@@ -0,0 +1,50 @@
+#ifndef _LINUX_X25_ASY_H
+#define _LINUX_X25_ASY_H
+
+/* X.25 asy configuration. */
+#define SL_NRUNIT	256		/* MAX number of X.25 channels;
+					   This can be overridden with
+					   insmod -ox25_asy_maxdev=nnn	*/
+#define SL_MTU		256	
+
+/* X25 async protocol characters. */
+#define X25_END         0x7E		/* indicates end of frame	*/
+#define X25_ESC         0x7D		/* indicates byte stuffing	*/
+#define X25_ESCAPE(x)	((x)^0x20)
+#define X25_UNESCAPE(x)	((x)^0x20)
+
+
+struct x25_asy {
+  int			magic;
+
+  /* Various fields. */
+  spinlock_t		lock;
+  struct tty_struct	*tty;		/* ptr to TTY structure		*/
+  struct net_device	*dev;		/* easy for intr handling	*/
+
+  /* These are pointers to the malloc()ed frame buffers. */
+  unsigned char		*rbuff;		/* receiver buffer		*/
+  int                   rcount;         /* received chars counter       */
+  unsigned char		*xbuff;		/* transmitter buffer		*/
+  unsigned char         *xhead;         /* pointer to next byte to XMIT */
+  int                   xleft;          /* bytes left in XMIT queue     */
+
+  /* X.25 interface statistics. */
+  struct net_device_stats stats;
+
+  int                   buffsize;       /* Max buffers sizes            */
+
+  unsigned long		flags;		/* Flag values/ mode etc	*/
+#define SLF_INUSE	0		/* Channel in use               */
+#define SLF_ESCAPE	1               /* ESC received                 */
+#define SLF_ERROR	2               /* Parity, etc. error           */
+#define SLF_OUTWAIT	4		/* Waiting for output		*/
+};
+
+
+
+#define X25_ASY_MAGIC 0x5303
+
+extern int x25_asy_init(struct net_device *dev);
+
+#endif	/* _LINUX_X25_ASY.H */
diff --git a/drivers/net/wan/z85230.c b/drivers/net/wan/z85230.c
new file mode 100644
index 0000000..caa48f1
--- /dev/null
+++ b/drivers/net/wan/z85230.c
@@ -0,0 +1,1851 @@
+/*
+ *	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.
+ *
+ *	(c) Copyright 1998 Alan Cox <alan@lxorguk.ukuu.org.uk>
+ *	(c) Copyright 2000, 2001 Red Hat Inc
+ *
+ *	Development of this driver was funded by Equiinet Ltd
+ *			http://www.equiinet.com
+ *
+ *	ChangeLog:
+ *
+ *	Asynchronous mode dropped for 2.2. For 2.5 we will attempt the
+ *	unification of all the Z85x30 asynchronous drivers for real.
+ *
+ *	DMA now uses get_free_page as kmalloc buffers may span a 64K 
+ *	boundary.
+ *
+ *	Modified for SMP safety and SMP locking by Alan Cox <alan@redhat.com>
+ *
+ *	Performance
+ *
+ *	Z85230:
+ *	Non DMA you want a 486DX50 or better to do 64Kbits. 9600 baud
+ *	X.25 is not unrealistic on all machines. DMA mode can in theory
+ *	handle T1/E1 quite nicely. In practice the limit seems to be about
+ *	512Kbit->1Mbit depending on motherboard.
+ *
+ *	Z85C30:
+ *	64K will take DMA, 9600 baud X.25 should be ok.
+ *
+ *	Z8530:
+ *	Synchronous mode without DMA is unlikely to pass about 2400 baud.
+ */
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/mm.h>
+#include <linux/net.h>
+#include <linux/skbuff.h>
+#include <linux/netdevice.h>
+#include <linux/if_arp.h>
+#include <linux/delay.h>
+#include <linux/ioport.h>
+#include <linux/init.h>
+#include <asm/dma.h>
+#include <asm/io.h>
+#define RT_LOCK
+#define RT_UNLOCK
+#include <linux/spinlock.h>
+
+#include <net/syncppp.h>
+#include "z85230.h"
+
+
+/**
+ *	z8530_read_port - Architecture specific interface function
+ *	@p: port to read
+ *
+ *	Provided port access methods. The Comtrol SV11 requires no delays
+ *	between accesses and uses PC I/O. Some drivers may need a 5uS delay
+ *	
+ *	In the longer term this should become an architecture specific
+ *	section so that this can become a generic driver interface for all
+ *	platforms. For now we only handle PC I/O ports with or without the
+ *	dread 5uS sanity delay.
+ *
+ *	The caller must hold sufficient locks to avoid violating the horrible
+ *	5uS delay rule.
+ */
+
+static inline int z8530_read_port(unsigned long p)
+{
+	u8 r=inb(Z8530_PORT_OF(p));
+	if(p&Z8530_PORT_SLEEP)	/* gcc should figure this out efficiently ! */
+		udelay(5);
+	return r;
+}
+
+/**
+ *	z8530_write_port - Architecture specific interface function
+ *	@p: port to write
+ *	@d: value to write
+ *
+ *	Write a value to a port with delays if need be. Note that the
+ *	caller must hold locks to avoid read/writes from other contexts
+ *	violating the 5uS rule
+ *
+ *	In the longer term this should become an architecture specific
+ *	section so that this can become a generic driver interface for all
+ *	platforms. For now we only handle PC I/O ports with or without the
+ *	dread 5uS sanity delay.
+ */
+
+
+static inline void z8530_write_port(unsigned long p, u8 d)
+{
+	outb(d,Z8530_PORT_OF(p));
+	if(p&Z8530_PORT_SLEEP)
+		udelay(5);
+}
+
+
+
+static void z8530_rx_done(struct z8530_channel *c);
+static void z8530_tx_done(struct z8530_channel *c);
+
+
+/**
+ *	read_zsreg - Read a register from a Z85230 
+ *	@c: Z8530 channel to read from (2 per chip)
+ *	@reg: Register to read
+ *	FIXME: Use a spinlock.
+ *	
+ *	Most of the Z8530 registers are indexed off the control registers.
+ *	A read is done by writing to the control register and reading the
+ *	register back.  The caller must hold the lock
+ */
+ 
+static inline u8 read_zsreg(struct z8530_channel *c, u8 reg)
+{
+	if(reg)
+		z8530_write_port(c->ctrlio, reg);
+	return z8530_read_port(c->ctrlio);
+}
+
+/**
+ *	read_zsdata - Read the data port of a Z8530 channel
+ *	@c: The Z8530 channel to read the data port from
+ *
+ *	The data port provides fast access to some things. We still
+ *	have all the 5uS delays to worry about.
+ */
+
+static inline u8 read_zsdata(struct z8530_channel *c)
+{
+	u8 r;
+	r=z8530_read_port(c->dataio);
+	return r;
+}
+
+/**
+ *	write_zsreg - Write to a Z8530 channel register
+ *	@c: The Z8530 channel
+ *	@reg: Register number
+ *	@val: Value to write
+ *
+ *	Write a value to an indexed register. The caller must hold the lock
+ *	to honour the irritating delay rules. We know about register 0
+ *	being fast to access.
+ *
+ *      Assumes c->lock is held.
+ */
+static inline void write_zsreg(struct z8530_channel *c, u8 reg, u8 val)
+{
+	if(reg)
+		z8530_write_port(c->ctrlio, reg);
+	z8530_write_port(c->ctrlio, val);
+
+}
+
+/**
+ *	write_zsctrl - Write to a Z8530 control register
+ *	@c: The Z8530 channel
+ *	@val: Value to write
+ *
+ *	Write directly to the control register on the Z8530
+ */
+
+static inline void write_zsctrl(struct z8530_channel *c, u8 val)
+{
+	z8530_write_port(c->ctrlio, val);
+}
+
+/**
+ *	write_zsdata - Write to a Z8530 control register
+ *	@c: The Z8530 channel
+ *	@val: Value to write
+ *
+ *	Write directly to the data register on the Z8530
+ */
+
+
+static inline void write_zsdata(struct z8530_channel *c, u8 val)
+{
+	z8530_write_port(c->dataio, val);
+}
+
+/*
+ *	Register loading parameters for a dead port
+ */
+ 
+u8 z8530_dead_port[]=
+{
+	255
+};
+
+EXPORT_SYMBOL(z8530_dead_port);
+
+/*
+ *	Register loading parameters for currently supported circuit types
+ */
+
+
+/*
+ *	Data clocked by telco end. This is the correct data for the UK
+ *	"kilostream" service, and most other similar services.
+ */
+ 
+u8 z8530_hdlc_kilostream[]=
+{
+	4,	SYNC_ENAB|SDLC|X1CLK,
+	2,	0,	/* No vector */
+	1,	0,
+	3,	ENT_HM|RxCRC_ENAB|Rx8,
+	5,	TxCRC_ENAB|RTS|TxENAB|Tx8|DTR,
+	9,	0,		/* Disable interrupts */
+	6,	0xFF,
+	7,	FLAG,
+	10,	ABUNDER|NRZ|CRCPS,/*MARKIDLE ??*/
+	11,	TCTRxCP,
+	14,	DISDPLL,
+	15,	DCDIE|SYNCIE|CTSIE|TxUIE|BRKIE,
+	1,	EXT_INT_ENAB|TxINT_ENAB|INT_ALL_Rx,
+	9,	NV|MIE|NORESET,
+	255
+};
+
+EXPORT_SYMBOL(z8530_hdlc_kilostream);
+
+/*
+ *	As above but for enhanced chips.
+ */
+ 
+u8 z8530_hdlc_kilostream_85230[]=
+{
+	4,	SYNC_ENAB|SDLC|X1CLK,
+	2,	0,	/* No vector */
+	1,	0,
+	3,	ENT_HM|RxCRC_ENAB|Rx8,
+	5,	TxCRC_ENAB|RTS|TxENAB|Tx8|DTR,
+	9,	0,		/* Disable interrupts */
+	6,	0xFF,
+	7,	FLAG,
+	10,	ABUNDER|NRZ|CRCPS,	/* MARKIDLE?? */
+	11,	TCTRxCP,
+	14,	DISDPLL,
+	15,	DCDIE|SYNCIE|CTSIE|TxUIE|BRKIE,
+	1,	EXT_INT_ENAB|TxINT_ENAB|INT_ALL_Rx,
+	9,	NV|MIE|NORESET,
+	23,	3,		/* Extended mode AUTO TX and EOM*/
+	
+	255
+};
+
+EXPORT_SYMBOL(z8530_hdlc_kilostream_85230);
+
+/**
+ *	z8530_flush_fifo - Flush on chip RX FIFO
+ *	@c: Channel to flush
+ *
+ *	Flush the receive FIFO. There is no specific option for this, we 
+ *	blindly read bytes and discard them. Reading when there is no data
+ *	is harmless. The 8530 has a 4 byte FIFO, the 85230 has 8 bytes.
+ *	
+ *	All locking is handled for the caller. On return data may still be
+ *	present if it arrived during the flush.
+ */
+ 
+static void z8530_flush_fifo(struct z8530_channel *c)
+{
+	read_zsreg(c, R1);
+	read_zsreg(c, R1);
+	read_zsreg(c, R1);
+	read_zsreg(c, R1);
+	if(c->dev->type==Z85230)
+	{
+		read_zsreg(c, R1);
+		read_zsreg(c, R1);
+		read_zsreg(c, R1);
+		read_zsreg(c, R1);
+	}
+}	
+
+/**
+ *	z8530_rtsdtr - Control the outgoing DTS/RTS line
+ *	@c: The Z8530 channel to control;
+ *	@set: 1 to set, 0 to clear
+ *
+ *	Sets or clears DTR/RTS on the requested line. All locking is handled
+ *	by the caller. For now we assume all boards use the actual RTS/DTR
+ *	on the chip. Apparently one or two don't. We'll scream about them
+ *	later.
+ */
+
+static void z8530_rtsdtr(struct z8530_channel *c, int set)
+{
+	if (set)
+		c->regs[5] |= (RTS | DTR);
+	else
+		c->regs[5] &= ~(RTS | DTR);
+	write_zsreg(c, R5, c->regs[5]);
+}
+
+/**
+ *	z8530_rx - Handle a PIO receive event
+ *	@c: Z8530 channel to process
+ *
+ *	Receive handler for receiving in PIO mode. This is much like the 
+ *	async one but not quite the same or as complex
+ *
+ *	Note: Its intended that this handler can easily be separated from
+ *	the main code to run realtime. That'll be needed for some machines
+ *	(eg to ever clock 64kbits on a sparc ;)).
+ *
+ *	The RT_LOCK macros don't do anything now. Keep the code covered
+ *	by them as short as possible in all circumstances - clocks cost
+ *	baud. The interrupt handler is assumed to be atomic w.r.t. to
+ *	other code - this is true in the RT case too.
+ *
+ *	We only cover the sync cases for this. If you want 2Mbit async
+ *	do it yourself but consider medical assistance first. This non DMA 
+ *	synchronous mode is portable code. The DMA mode assumes PCI like 
+ *	ISA DMA
+ *
+ *	Called with the device lock held
+ */
+ 
+static void z8530_rx(struct z8530_channel *c)
+{
+	u8 ch,stat;
+	spin_lock(c->lock);
+ 
+	while(1)
+	{
+		/* FIFO empty ? */
+		if(!(read_zsreg(c, R0)&1))
+			break;
+		ch=read_zsdata(c);
+		stat=read_zsreg(c, R1);
+	
+		/*
+		 *	Overrun ?
+		 */
+		if(c->count < c->max)
+		{
+			*c->dptr++=ch;
+			c->count++;
+		}
+
+		if(stat&END_FR)
+		{
+		
+			/*
+			 *	Error ?
+			 */
+			if(stat&(Rx_OVR|CRC_ERR))
+			{
+				/* Rewind the buffer and return */
+				if(c->skb)
+					c->dptr=c->skb->data;
+				c->count=0;
+				if(stat&Rx_OVR)
+				{
+					printk(KERN_WARNING "%s: overrun\n", c->dev->name);
+					c->rx_overrun++;
+				}
+				if(stat&CRC_ERR)
+				{
+					c->rx_crc_err++;
+					/* printk("crc error\n"); */
+				}
+				/* Shove the frame upstream */
+			}
+			else
+			{
+				/*
+				 *	Drop the lock for RX processing, or
+		 		 *	there are deadlocks
+		 		 */
+				z8530_rx_done(c);
+				write_zsctrl(c, RES_Rx_CRC);
+			}
+		}
+	}
+	/*
+	 *	Clear irq
+	 */
+	write_zsctrl(c, ERR_RES);
+	write_zsctrl(c, RES_H_IUS);
+	spin_unlock(c->lock);
+}
+
+
+/**
+ *	z8530_tx - Handle a PIO transmit event
+ *	@c: Z8530 channel to process
+ *
+ *	Z8530 transmit interrupt handler for the PIO mode. The basic
+ *	idea is to attempt to keep the FIFO fed. We fill as many bytes
+ *	in as possible, its quite possible that we won't keep up with the
+ *	data rate otherwise.
+ */
+ 
+static void z8530_tx(struct z8530_channel *c)
+{
+	spin_lock(c->lock);
+	while(c->txcount) {
+		/* FIFO full ? */
+		if(!(read_zsreg(c, R0)&4))
+			break;
+		c->txcount--;
+		/*
+		 *	Shovel out the byte
+		 */
+		write_zsreg(c, R8, *c->tx_ptr++);
+		write_zsctrl(c, RES_H_IUS);
+		/* We are about to underflow */
+		if(c->txcount==0)
+		{
+			write_zsctrl(c, RES_EOM_L);
+			write_zsreg(c, R10, c->regs[10]&~ABUNDER);
+		}
+	}
+
+	
+	/*
+	 *	End of frame TX - fire another one
+	 */
+	 
+	write_zsctrl(c, RES_Tx_P);
+
+	z8530_tx_done(c);	 
+	write_zsctrl(c, RES_H_IUS);
+	spin_unlock(c->lock);
+}
+
+/**
+ *	z8530_status - Handle a PIO status exception
+ *	@chan: Z8530 channel to process
+ *
+ *	A status event occurred in PIO synchronous mode. There are several
+ *	reasons the chip will bother us here. A transmit underrun means we
+ *	failed to feed the chip fast enough and just broke a packet. A DCD
+ *	change is a line up or down. We communicate that back to the protocol
+ *	layer for synchronous PPP to renegotiate.
+ */
+
+static void z8530_status(struct z8530_channel *chan)
+{
+	u8 status, altered;
+
+	spin_lock(chan->lock);
+	status=read_zsreg(chan, R0);
+	altered=chan->status^status;
+	
+	chan->status=status;
+	
+	if(status&TxEOM)
+	{
+/*		printk("%s: Tx underrun.\n", chan->dev->name); */
+		chan->stats.tx_fifo_errors++;
+		write_zsctrl(chan, ERR_RES);
+		z8530_tx_done(chan);
+	}
+		
+	if(altered&chan->dcdcheck)
+	{
+		if(status&chan->dcdcheck)
+		{
+			printk(KERN_INFO "%s: DCD raised\n", chan->dev->name);
+			write_zsreg(chan, R3, chan->regs[3]|RxENABLE);
+			if(chan->netdevice &&
+			    ((chan->netdevice->type == ARPHRD_HDLC) ||
+			    (chan->netdevice->type == ARPHRD_PPP)))
+				sppp_reopen(chan->netdevice);
+		}
+		else
+		{
+			printk(KERN_INFO "%s: DCD lost\n", chan->dev->name);
+			write_zsreg(chan, R3, chan->regs[3]&~RxENABLE);
+			z8530_flush_fifo(chan);
+		}
+		
+	}	
+	write_zsctrl(chan, RES_EXT_INT);
+	write_zsctrl(chan, RES_H_IUS);
+	spin_unlock(chan->lock);
+}
+
+struct z8530_irqhandler z8530_sync=
+{
+	z8530_rx,
+	z8530_tx,
+	z8530_status
+};
+
+EXPORT_SYMBOL(z8530_sync);
+
+/**
+ *	z8530_dma_rx - Handle a DMA RX event
+ *	@chan: Channel to handle
+ *
+ *	Non bus mastering DMA interfaces for the Z8x30 devices. This
+ *	is really pretty PC specific. The DMA mode means that most receive
+ *	events are handled by the DMA hardware. We get a kick here only if
+ *	a frame ended.
+ */
+ 
+static void z8530_dma_rx(struct z8530_channel *chan)
+{
+	spin_lock(chan->lock);
+	if(chan->rxdma_on)
+	{
+		/* Special condition check only */
+		u8 status;
+	
+		read_zsreg(chan, R7);
+		read_zsreg(chan, R6);
+		
+		status=read_zsreg(chan, R1);
+	
+		if(status&END_FR)
+		{
+			z8530_rx_done(chan);	/* Fire up the next one */
+		}		
+		write_zsctrl(chan, ERR_RES);
+		write_zsctrl(chan, RES_H_IUS);
+	}
+	else
+	{
+		/* DMA is off right now, drain the slow way */
+		z8530_rx(chan);
+	}	
+	spin_unlock(chan->lock);
+}
+
+/**
+ *	z8530_dma_tx - Handle a DMA TX event
+ *	@chan:	The Z8530 channel to handle
+ *
+ *	We have received an interrupt while doing DMA transmissions. It
+ *	shouldn't happen. Scream loudly if it does.
+ */
+ 
+static void z8530_dma_tx(struct z8530_channel *chan)
+{
+	spin_lock(chan->lock);
+	if(!chan->dma_tx)
+	{
+		printk(KERN_WARNING "Hey who turned the DMA off?\n");
+		z8530_tx(chan);
+		return;
+	}
+	/* This shouldnt occur in DMA mode */
+	printk(KERN_ERR "DMA tx - bogus event!\n");
+	z8530_tx(chan);
+	spin_unlock(chan->lock);
+}
+
+/**
+ *	z8530_dma_status - Handle a DMA status exception
+ *	@chan: Z8530 channel to process
+ *	
+ *	A status event occurred on the Z8530. We receive these for two reasons
+ *	when in DMA mode. Firstly if we finished a packet transfer we get one
+ *	and kick the next packet out. Secondly we may see a DCD change and
+ *	have to poke the protocol layer.
+ *
+ */
+ 
+static void z8530_dma_status(struct z8530_channel *chan)
+{
+	u8 status, altered;
+
+	status=read_zsreg(chan, R0);
+	altered=chan->status^status;
+	
+	chan->status=status;
+
+
+	if(chan->dma_tx)
+	{
+		if(status&TxEOM)
+		{
+			unsigned long flags;
+	
+			flags=claim_dma_lock();
+			disable_dma(chan->txdma);
+			clear_dma_ff(chan->txdma);	
+			chan->txdma_on=0;
+			release_dma_lock(flags);
+			z8530_tx_done(chan);
+		}
+	}
+
+	spin_lock(chan->lock);
+	if(altered&chan->dcdcheck)
+	{
+		if(status&chan->dcdcheck)
+		{
+			printk(KERN_INFO "%s: DCD raised\n", chan->dev->name);
+			write_zsreg(chan, R3, chan->regs[3]|RxENABLE);
+			if(chan->netdevice &&
+			    ((chan->netdevice->type == ARPHRD_HDLC) ||
+			    (chan->netdevice->type == ARPHRD_PPP)))
+				sppp_reopen(chan->netdevice);
+		}
+		else
+		{
+			printk(KERN_INFO "%s:DCD lost\n", chan->dev->name);
+			write_zsreg(chan, R3, chan->regs[3]&~RxENABLE);
+			z8530_flush_fifo(chan);
+		}
+	}	
+
+	write_zsctrl(chan, RES_EXT_INT);
+	write_zsctrl(chan, RES_H_IUS);
+	spin_unlock(chan->lock);
+}
+
+struct z8530_irqhandler z8530_dma_sync=
+{
+	z8530_dma_rx,
+	z8530_dma_tx,
+	z8530_dma_status
+};
+
+EXPORT_SYMBOL(z8530_dma_sync);
+
+struct z8530_irqhandler z8530_txdma_sync=
+{
+	z8530_rx,
+	z8530_dma_tx,
+	z8530_dma_status
+};
+
+EXPORT_SYMBOL(z8530_txdma_sync);
+
+/**
+ *	z8530_rx_clear - Handle RX events from a stopped chip
+ *	@c: Z8530 channel to shut up
+ *
+ *	Receive interrupt vectors for a Z8530 that is in 'parked' mode.
+ *	For machines with PCI Z85x30 cards, or level triggered interrupts
+ *	(eg the MacII) we must clear the interrupt cause or die.
+ */
+
+
+static void z8530_rx_clear(struct z8530_channel *c)
+{
+	/*
+	 *	Data and status bytes
+	 */
+	u8 stat;
+
+	read_zsdata(c);
+	stat=read_zsreg(c, R1);
+	
+	if(stat&END_FR)
+		write_zsctrl(c, RES_Rx_CRC);
+	/*
+	 *	Clear irq
+	 */
+	write_zsctrl(c, ERR_RES);
+	write_zsctrl(c, RES_H_IUS);
+}
+
+/**
+ *	z8530_tx_clear - Handle TX events from a stopped chip
+ *	@c: Z8530 channel to shut up
+ *
+ *	Transmit interrupt vectors for a Z8530 that is in 'parked' mode.
+ *	For machines with PCI Z85x30 cards, or level triggered interrupts
+ *	(eg the MacII) we must clear the interrupt cause or die.
+ */
+
+static void z8530_tx_clear(struct z8530_channel *c)
+{
+	write_zsctrl(c, RES_Tx_P);
+	write_zsctrl(c, RES_H_IUS);
+}
+
+/**
+ *	z8530_status_clear - Handle status events from a stopped chip
+ *	@chan: Z8530 channel to shut up
+ *
+ *	Status interrupt vectors for a Z8530 that is in 'parked' mode.
+ *	For machines with PCI Z85x30 cards, or level triggered interrupts
+ *	(eg the MacII) we must clear the interrupt cause or die.
+ */
+
+static void z8530_status_clear(struct z8530_channel *chan)
+{
+	u8 status=read_zsreg(chan, R0);
+	if(status&TxEOM)
+		write_zsctrl(chan, ERR_RES);
+	write_zsctrl(chan, RES_EXT_INT);
+	write_zsctrl(chan, RES_H_IUS);
+}
+
+struct z8530_irqhandler z8530_nop=
+{
+	z8530_rx_clear,
+	z8530_tx_clear,
+	z8530_status_clear
+};
+
+
+EXPORT_SYMBOL(z8530_nop);
+
+/**
+ *	z8530_interrupt - Handle an interrupt from a Z8530
+ *	@irq: 	Interrupt number
+ *	@dev_id: The Z8530 device that is interrupting.
+ *	@regs: unused
+ *
+ *	A Z85[2]30 device has stuck its hand in the air for attention.
+ *	We scan both the channels on the chip for events and then call
+ *	the channel specific call backs for each channel that has events.
+ *	We have to use callback functions because the two channels can be
+ *	in different modes.
+ *
+ *	Locking is done for the handlers. Note that locking is done
+ *	at the chip level (the 5uS delay issue is per chip not per
+ *	channel). c->lock for both channels points to dev->lock
+ */
+
+irqreturn_t z8530_interrupt(int irq, void *dev_id, struct pt_regs *regs)
+{
+	struct z8530_dev *dev=dev_id;
+	u8 intr;
+	static volatile int locker=0;
+	int work=0;
+	struct z8530_irqhandler *irqs;
+	
+	if(locker)
+	{
+		printk(KERN_ERR "IRQ re-enter\n");
+		return IRQ_NONE;
+	}
+	locker=1;
+
+	spin_lock(&dev->lock);
+
+	while(++work<5000)
+	{
+
+		intr = read_zsreg(&dev->chanA, R3);
+		if(!(intr & (CHARxIP|CHATxIP|CHAEXT|CHBRxIP|CHBTxIP|CHBEXT)))
+			break;
+	
+		/* This holds the IRQ status. On the 8530 you must read it from chan 
+		   A even though it applies to the whole chip */
+		
+		/* Now walk the chip and see what it is wanting - it may be
+		   an IRQ for someone else remember */
+		   
+		irqs=dev->chanA.irqs;
+
+		if(intr & (CHARxIP|CHATxIP|CHAEXT))
+		{
+			if(intr&CHARxIP)
+				irqs->rx(&dev->chanA);
+			if(intr&CHATxIP)
+				irqs->tx(&dev->chanA);
+			if(intr&CHAEXT)
+				irqs->status(&dev->chanA);
+		}
+
+		irqs=dev->chanB.irqs;
+
+		if(intr & (CHBRxIP|CHBTxIP|CHBEXT))
+		{
+			if(intr&CHBRxIP)
+				irqs->rx(&dev->chanB);
+			if(intr&CHBTxIP)
+				irqs->tx(&dev->chanB);
+			if(intr&CHBEXT)
+				irqs->status(&dev->chanB);
+		}
+	}
+	spin_unlock(&dev->lock);
+	if(work==5000)
+		printk(KERN_ERR "%s: interrupt jammed - abort(0x%X)!\n", dev->name, intr);
+	/* Ok all done */
+	locker=0;
+	return IRQ_HANDLED;
+}
+
+EXPORT_SYMBOL(z8530_interrupt);
+
+static char reg_init[16]=
+{
+	0,0,0,0,
+	0,0,0,0,
+	0,0,0,0,
+	0x55,0,0,0
+};
+
+
+/**
+ *	z8530_sync_open - Open a Z8530 channel for PIO
+ *	@dev:	The network interface we are using
+ *	@c:	The Z8530 channel to open in synchronous PIO mode
+ *
+ *	Switch a Z8530 into synchronous mode without DMA assist. We
+ *	raise the RTS/DTR and commence network operation.
+ */
+ 
+int z8530_sync_open(struct net_device *dev, struct z8530_channel *c)
+{
+	unsigned long flags;
+
+	spin_lock_irqsave(c->lock, flags);
+
+	c->sync = 1;
+	c->mtu = dev->mtu+64;
+	c->count = 0;
+	c->skb = NULL;
+	c->skb2 = NULL;
+	c->irqs = &z8530_sync;
+
+	/* This loads the double buffer up */
+	z8530_rx_done(c);	/* Load the frame ring */
+	z8530_rx_done(c);	/* Load the backup frame */
+	z8530_rtsdtr(c,1);
+	c->dma_tx = 0;
+	c->regs[R1]|=TxINT_ENAB;
+	write_zsreg(c, R1, c->regs[R1]);
+	write_zsreg(c, R3, c->regs[R3]|RxENABLE);
+
+	spin_unlock_irqrestore(c->lock, flags);
+	return 0;
+}
+
+
+EXPORT_SYMBOL(z8530_sync_open);
+
+/**
+ *	z8530_sync_close - Close a PIO Z8530 channel
+ *	@dev: Network device to close
+ *	@c: Z8530 channel to disassociate and move to idle
+ *
+ *	Close down a Z8530 interface and switch its interrupt handlers
+ *	to discard future events.
+ */
+ 
+int z8530_sync_close(struct net_device *dev, struct z8530_channel *c)
+{
+	u8 chk;
+	unsigned long flags;
+	
+	spin_lock_irqsave(c->lock, flags);
+	c->irqs = &z8530_nop;
+	c->max = 0;
+	c->sync = 0;
+	
+	chk=read_zsreg(c,R0);
+	write_zsreg(c, R3, c->regs[R3]);
+	z8530_rtsdtr(c,0);
+
+	spin_unlock_irqrestore(c->lock, flags);
+	return 0;
+}
+
+EXPORT_SYMBOL(z8530_sync_close);
+
+/**
+ *	z8530_sync_dma_open - Open a Z8530 for DMA I/O
+ *	@dev: The network device to attach
+ *	@c: The Z8530 channel to configure in sync DMA mode.
+ *
+ *	Set up a Z85x30 device for synchronous DMA in both directions. Two
+ *	ISA DMA channels must be available for this to work. We assume ISA
+ *	DMA driven I/O and PC limits on access.
+ */
+ 
+int z8530_sync_dma_open(struct net_device *dev, struct z8530_channel *c)
+{
+	unsigned long cflags, dflags;
+	
+	c->sync = 1;
+	c->mtu = dev->mtu+64;
+	c->count = 0;
+	c->skb = NULL;
+	c->skb2 = NULL;
+	/*
+	 *	Load the DMA interfaces up
+	 */
+	c->rxdma_on = 0;
+	c->txdma_on = 0;
+	
+	/*
+	 *	Allocate the DMA flip buffers. Limit by page size.
+	 *	Everyone runs 1500 mtu or less on wan links so this
+	 *	should be fine.
+	 */
+	 
+	if(c->mtu  > PAGE_SIZE/2)
+		return -EMSGSIZE;
+	 
+	c->rx_buf[0]=(void *)get_zeroed_page(GFP_KERNEL|GFP_DMA);
+	if(c->rx_buf[0]==NULL)
+		return -ENOBUFS;
+	c->rx_buf[1]=c->rx_buf[0]+PAGE_SIZE/2;
+	
+	c->tx_dma_buf[0]=(void *)get_zeroed_page(GFP_KERNEL|GFP_DMA);
+	if(c->tx_dma_buf[0]==NULL)
+	{
+		free_page((unsigned long)c->rx_buf[0]);
+		c->rx_buf[0]=NULL;
+		return -ENOBUFS;
+	}
+	c->tx_dma_buf[1]=c->tx_dma_buf[0]+PAGE_SIZE/2;
+
+	c->tx_dma_used=0;
+	c->dma_tx = 1;
+	c->dma_num=0;
+	c->dma_ready=1;
+	
+	/*
+	 *	Enable DMA control mode
+	 */
+
+	spin_lock_irqsave(c->lock, cflags);
+	 
+	/*
+	 *	TX DMA via DIR/REQ
+	 */
+	 
+	c->regs[R14]|= DTRREQ;
+	write_zsreg(c, R14, c->regs[R14]);     
+
+	c->regs[R1]&= ~TxINT_ENAB;
+	write_zsreg(c, R1, c->regs[R1]);
+	
+	/*
+	 *	RX DMA via W/Req
+	 */	 
+
+	c->regs[R1]|= WT_FN_RDYFN;
+	c->regs[R1]|= WT_RDY_RT;
+	c->regs[R1]|= INT_ERR_Rx;
+	c->regs[R1]&= ~TxINT_ENAB;
+	write_zsreg(c, R1, c->regs[R1]);
+	c->regs[R1]|= WT_RDY_ENAB;
+	write_zsreg(c, R1, c->regs[R1]);            
+	
+	/*
+	 *	DMA interrupts
+	 */
+	 
+	/*
+	 *	Set up the DMA configuration
+	 */	
+	 
+	dflags=claim_dma_lock();
+	 
+	disable_dma(c->rxdma);
+	clear_dma_ff(c->rxdma);
+	set_dma_mode(c->rxdma, DMA_MODE_READ|0x10);
+	set_dma_addr(c->rxdma, virt_to_bus(c->rx_buf[0]));
+	set_dma_count(c->rxdma, c->mtu);
+	enable_dma(c->rxdma);
+
+	disable_dma(c->txdma);
+	clear_dma_ff(c->txdma);
+	set_dma_mode(c->txdma, DMA_MODE_WRITE);
+	disable_dma(c->txdma);
+	
+	release_dma_lock(dflags);
+	
+	/*
+	 *	Select the DMA interrupt handlers
+	 */
+
+	c->rxdma_on = 1;
+	c->txdma_on = 1;
+	c->tx_dma_used = 1;
+	 
+	c->irqs = &z8530_dma_sync;
+	z8530_rtsdtr(c,1);
+	write_zsreg(c, R3, c->regs[R3]|RxENABLE);
+
+	spin_unlock_irqrestore(c->lock, cflags);
+	
+	return 0;
+}
+
+EXPORT_SYMBOL(z8530_sync_dma_open);
+
+/**
+ *	z8530_sync_dma_close - Close down DMA I/O
+ *	@dev: Network device to detach
+ *	@c: Z8530 channel to move into discard mode
+ *
+ *	Shut down a DMA mode synchronous interface. Halt the DMA, and
+ *	free the buffers.
+ */
+ 
+int z8530_sync_dma_close(struct net_device *dev, struct z8530_channel *c)
+{
+	u8 chk;
+	unsigned long flags;
+	
+	c->irqs = &z8530_nop;
+	c->max = 0;
+	c->sync = 0;
+	
+	/*
+	 *	Disable the PC DMA channels
+	 */
+	
+	flags=claim_dma_lock(); 
+	disable_dma(c->rxdma);
+	clear_dma_ff(c->rxdma);
+	
+	c->rxdma_on = 0;
+	
+	disable_dma(c->txdma);
+	clear_dma_ff(c->txdma);
+	release_dma_lock(flags);
+	
+	c->txdma_on = 0;
+	c->tx_dma_used = 0;
+
+	spin_lock_irqsave(c->lock, flags);
+
+	/*
+	 *	Disable DMA control mode
+	 */
+	 
+	c->regs[R1]&= ~WT_RDY_ENAB;
+	write_zsreg(c, R1, c->regs[R1]);            
+	c->regs[R1]&= ~(WT_RDY_RT|WT_FN_RDYFN|INT_ERR_Rx);
+	c->regs[R1]|= INT_ALL_Rx;
+	write_zsreg(c, R1, c->regs[R1]);
+	c->regs[R14]&= ~DTRREQ;
+	write_zsreg(c, R14, c->regs[R14]);   
+	
+	if(c->rx_buf[0])
+	{
+		free_page((unsigned long)c->rx_buf[0]);
+		c->rx_buf[0]=NULL;
+	}
+	if(c->tx_dma_buf[0])
+	{
+		free_page((unsigned  long)c->tx_dma_buf[0]);
+		c->tx_dma_buf[0]=NULL;
+	}
+	chk=read_zsreg(c,R0);
+	write_zsreg(c, R3, c->regs[R3]);
+	z8530_rtsdtr(c,0);
+
+	spin_unlock_irqrestore(c->lock, flags);
+
+	return 0;
+}
+
+EXPORT_SYMBOL(z8530_sync_dma_close);
+
+/**
+ *	z8530_sync_txdma_open - Open a Z8530 for TX driven DMA
+ *	@dev: The network device to attach
+ *	@c: The Z8530 channel to configure in sync DMA mode.
+ *
+ *	Set up a Z85x30 device for synchronous DMA tranmission. One
+ *	ISA DMA channel must be available for this to work. The receive
+ *	side is run in PIO mode, but then it has the bigger FIFO.
+ */
+
+int z8530_sync_txdma_open(struct net_device *dev, struct z8530_channel *c)
+{
+	unsigned long cflags, dflags;
+
+	printk("Opening sync interface for TX-DMA\n");
+	c->sync = 1;
+	c->mtu = dev->mtu+64;
+	c->count = 0;
+	c->skb = NULL;
+	c->skb2 = NULL;
+	
+	/*
+	 *	Allocate the DMA flip buffers. Limit by page size.
+	 *	Everyone runs 1500 mtu or less on wan links so this
+	 *	should be fine.
+	 */
+	 
+	if(c->mtu  > PAGE_SIZE/2)
+		return -EMSGSIZE;
+	 
+	c->tx_dma_buf[0]=(void *)get_zeroed_page(GFP_KERNEL|GFP_DMA);
+	if(c->tx_dma_buf[0]==NULL)
+		return -ENOBUFS;
+
+	c->tx_dma_buf[1] = c->tx_dma_buf[0] + PAGE_SIZE/2;
+
+
+	spin_lock_irqsave(c->lock, cflags);
+
+	/*
+	 *	Load the PIO receive ring
+	 */
+
+	z8530_rx_done(c);
+	z8530_rx_done(c);
+
+ 	/*
+	 *	Load the DMA interfaces up
+	 */
+
+	c->rxdma_on = 0;
+	c->txdma_on = 0;
+	
+	c->tx_dma_used=0;
+	c->dma_num=0;
+	c->dma_ready=1;
+	c->dma_tx = 1;
+
+ 	/*
+	 *	Enable DMA control mode
+	 */
+
+ 	/*
+	 *	TX DMA via DIR/REQ
+ 	 */
+	c->regs[R14]|= DTRREQ;
+	write_zsreg(c, R14, c->regs[R14]);     
+	
+	c->regs[R1]&= ~TxINT_ENAB;
+	write_zsreg(c, R1, c->regs[R1]);
+	
+	/*
+	 *	Set up the DMA configuration
+	 */	
+	 
+	dflags = claim_dma_lock();
+
+	disable_dma(c->txdma);
+	clear_dma_ff(c->txdma);
+	set_dma_mode(c->txdma, DMA_MODE_WRITE);
+	disable_dma(c->txdma);
+
+	release_dma_lock(dflags);
+	
+	/*
+	 *	Select the DMA interrupt handlers
+	 */
+
+	c->rxdma_on = 0;
+	c->txdma_on = 1;
+	c->tx_dma_used = 1;
+	 
+	c->irqs = &z8530_txdma_sync;
+	z8530_rtsdtr(c,1);
+	write_zsreg(c, R3, c->regs[R3]|RxENABLE);
+	spin_unlock_irqrestore(c->lock, cflags);
+	
+	return 0;
+}
+
+EXPORT_SYMBOL(z8530_sync_txdma_open);
+
+/**
+ *	z8530_sync_txdma_close - Close down a TX driven DMA channel
+ *	@dev: Network device to detach
+ *	@c: Z8530 channel to move into discard mode
+ *
+ *	Shut down a DMA/PIO split mode synchronous interface. Halt the DMA, 
+ *	and  free the buffers.
+ */
+
+int z8530_sync_txdma_close(struct net_device *dev, struct z8530_channel *c)
+{
+	unsigned long dflags, cflags;
+	u8 chk;
+
+	
+	spin_lock_irqsave(c->lock, cflags);
+	
+	c->irqs = &z8530_nop;
+	c->max = 0;
+	c->sync = 0;
+	
+	/*
+	 *	Disable the PC DMA channels
+	 */
+	 
+	dflags = claim_dma_lock();
+
+	disable_dma(c->txdma);
+	clear_dma_ff(c->txdma);
+	c->txdma_on = 0;
+	c->tx_dma_used = 0;
+
+	release_dma_lock(dflags);
+
+	/*
+	 *	Disable DMA control mode
+	 */
+	 
+	c->regs[R1]&= ~WT_RDY_ENAB;
+	write_zsreg(c, R1, c->regs[R1]);            
+	c->regs[R1]&= ~(WT_RDY_RT|WT_FN_RDYFN|INT_ERR_Rx);
+	c->regs[R1]|= INT_ALL_Rx;
+	write_zsreg(c, R1, c->regs[R1]);
+	c->regs[R14]&= ~DTRREQ;
+	write_zsreg(c, R14, c->regs[R14]);   
+	
+	if(c->tx_dma_buf[0])
+	{
+		free_page((unsigned long)c->tx_dma_buf[0]);
+		c->tx_dma_buf[0]=NULL;
+	}
+	chk=read_zsreg(c,R0);
+	write_zsreg(c, R3, c->regs[R3]);
+	z8530_rtsdtr(c,0);
+
+	spin_unlock_irqrestore(c->lock, cflags);
+	return 0;
+}
+
+
+EXPORT_SYMBOL(z8530_sync_txdma_close);
+
+
+/*
+ *	Name strings for Z8530 chips. SGI claim to have a 130, Zilog deny
+ *	it exists...
+ */
+ 
+static char *z8530_type_name[]={
+	"Z8530",
+	"Z85C30",
+	"Z85230"
+};
+
+/**
+ *	z8530_describe - Uniformly describe a Z8530 port
+ *	@dev: Z8530 device to describe
+ *	@mapping: string holding mapping type (eg "I/O" or "Mem")
+ *	@io: the port value in question
+ *
+ *	Describe a Z8530 in a standard format. We must pass the I/O as
+ *	the port offset isnt predictable. The main reason for this function
+ *	is to try and get a common format of report.
+ */
+
+void z8530_describe(struct z8530_dev *dev, char *mapping, unsigned long io)
+{
+	printk(KERN_INFO "%s: %s found at %s 0x%lX, IRQ %d.\n",
+		dev->name, 
+		z8530_type_name[dev->type],
+		mapping,
+		Z8530_PORT_OF(io),
+		dev->irq);
+}
+
+EXPORT_SYMBOL(z8530_describe);
+
+/*
+ *	Locked operation part of the z8530 init code
+ */
+ 
+static inline int do_z8530_init(struct z8530_dev *dev)
+{
+	/* NOP the interrupt handlers first - we might get a
+	   floating IRQ transition when we reset the chip */
+	dev->chanA.irqs=&z8530_nop;
+	dev->chanB.irqs=&z8530_nop;
+	dev->chanA.dcdcheck=DCD;
+	dev->chanB.dcdcheck=DCD;
+
+	/* Reset the chip */
+	write_zsreg(&dev->chanA, R9, 0xC0);
+	udelay(200);
+	/* Now check its valid */
+	write_zsreg(&dev->chanA, R12, 0xAA);
+	if(read_zsreg(&dev->chanA, R12)!=0xAA)
+		return -ENODEV;
+	write_zsreg(&dev->chanA, R12, 0x55);
+	if(read_zsreg(&dev->chanA, R12)!=0x55)
+		return -ENODEV;
+		
+	dev->type=Z8530;
+	
+	/*
+	 *	See the application note.
+	 */
+	 
+	write_zsreg(&dev->chanA, R15, 0x01);
+	
+	/*
+	 *	If we can set the low bit of R15 then
+	 *	the chip is enhanced.
+	 */
+	 
+	if(read_zsreg(&dev->chanA, R15)==0x01)
+	{
+		/* This C30 versus 230 detect is from Klaus Kudielka's dmascc */
+		/* Put a char in the fifo */
+		write_zsreg(&dev->chanA, R8, 0);
+		if(read_zsreg(&dev->chanA, R0)&Tx_BUF_EMP)
+			dev->type = Z85230;	/* Has a FIFO */
+		else
+			dev->type = Z85C30;	/* Z85C30, 1 byte FIFO */
+	}
+		
+	/*
+	 *	The code assumes R7' and friends are
+	 *	off. Use write_zsext() for these and keep
+	 *	this bit clear.
+	 */
+	 
+	write_zsreg(&dev->chanA, R15, 0);
+		
+	/*
+	 *	At this point it looks like the chip is behaving
+	 */
+	 
+	memcpy(dev->chanA.regs, reg_init, 16);
+	memcpy(dev->chanB.regs, reg_init ,16);
+	
+	return 0;
+}
+
+/**
+ *	z8530_init - Initialise a Z8530 device
+ *	@dev: Z8530 device to initialise.
+ *
+ *	Configure up a Z8530/Z85C30 or Z85230 chip. We check the device
+ *	is present, identify the type and then program it to hopefully
+ *	keep quite and behave. This matters a lot, a Z8530 in the wrong
+ *	state will sometimes get into stupid modes generating 10Khz
+ *	interrupt streams and the like.
+ *
+ *	We set the interrupt handler up to discard any events, in case
+ *	we get them during reset or setp.
+ *
+ *	Return 0 for success, or a negative value indicating the problem
+ *	in errno form.
+ */
+
+int z8530_init(struct z8530_dev *dev)
+{
+	unsigned long flags;
+	int ret;
+
+	/* Set up the chip level lock */
+	spin_lock_init(&dev->lock);
+	dev->chanA.lock = &dev->lock;
+	dev->chanB.lock = &dev->lock;
+
+	spin_lock_irqsave(&dev->lock, flags);
+	ret = do_z8530_init(dev);
+	spin_unlock_irqrestore(&dev->lock, flags);
+
+	return ret;
+}
+
+
+EXPORT_SYMBOL(z8530_init);
+
+/**
+ *	z8530_shutdown - Shutdown a Z8530 device
+ *	@dev: The Z8530 chip to shutdown
+ *
+ *	We set the interrupt handlers to silence any interrupts. We then 
+ *	reset the chip and wait 100uS to be sure the reset completed. Just
+ *	in case the caller then tries to do stuff.
+ *
+ *	This is called without the lock held
+ */
+ 
+int z8530_shutdown(struct z8530_dev *dev)
+{
+	unsigned long flags;
+	/* Reset the chip */
+
+	spin_lock_irqsave(&dev->lock, flags);
+	dev->chanA.irqs=&z8530_nop;
+	dev->chanB.irqs=&z8530_nop;
+	write_zsreg(&dev->chanA, R9, 0xC0);
+	/* We must lock the udelay, the chip is offlimits here */
+	udelay(100);
+	spin_unlock_irqrestore(&dev->lock, flags);
+	return 0;
+}
+
+EXPORT_SYMBOL(z8530_shutdown);
+
+/**
+ *	z8530_channel_load - Load channel data
+ *	@c: Z8530 channel to configure
+ *	@rtable: table of register, value pairs
+ *	FIXME: ioctl to allow user uploaded tables
+ *
+ *	Load a Z8530 channel up from the system data. We use +16 to 
+ *	indicate the "prime" registers. The value 255 terminates the
+ *	table.
+ */
+
+int z8530_channel_load(struct z8530_channel *c, u8 *rtable)
+{
+	unsigned long flags;
+
+	spin_lock_irqsave(c->lock, flags);
+
+	while(*rtable!=255)
+	{
+		int reg=*rtable++;
+		if(reg>0x0F)
+			write_zsreg(c, R15, c->regs[15]|1);
+		write_zsreg(c, reg&0x0F, *rtable);
+		if(reg>0x0F)
+			write_zsreg(c, R15, c->regs[15]&~1);
+		c->regs[reg]=*rtable++;
+	}
+	c->rx_function=z8530_null_rx;
+	c->skb=NULL;
+	c->tx_skb=NULL;
+	c->tx_next_skb=NULL;
+	c->mtu=1500;
+	c->max=0;
+	c->count=0;
+	c->status=read_zsreg(c, R0);
+	c->sync=1;
+	write_zsreg(c, R3, c->regs[R3]|RxENABLE);
+
+	spin_unlock_irqrestore(c->lock, flags);
+	return 0;
+}
+
+EXPORT_SYMBOL(z8530_channel_load);
+
+
+/**
+ *	z8530_tx_begin - Begin packet transmission
+ *	@c: The Z8530 channel to kick
+ *
+ *	This is the speed sensitive side of transmission. If we are called
+ *	and no buffer is being transmitted we commence the next buffer. If
+ *	nothing is queued we idle the sync. 
+ *
+ *	Note: We are handling this code path in the interrupt path, keep it
+ *	fast or bad things will happen.
+ *
+ *	Called with the lock held.
+ */
+
+static void z8530_tx_begin(struct z8530_channel *c)
+{
+	unsigned long flags;
+	if(c->tx_skb)
+		return;
+		
+	c->tx_skb=c->tx_next_skb;
+	c->tx_next_skb=NULL;
+	c->tx_ptr=c->tx_next_ptr;
+	
+	if(c->tx_skb==NULL)
+	{
+		/* Idle on */
+		if(c->dma_tx)
+		{
+			flags=claim_dma_lock();
+			disable_dma(c->txdma);
+			/*
+			 *	Check if we crapped out.
+			 */
+			if(get_dma_residue(c->txdma))
+			{
+				c->stats.tx_dropped++;
+				c->stats.tx_fifo_errors++;
+			}
+			release_dma_lock(flags);
+		}
+		c->txcount=0;
+	}
+	else
+	{
+		c->txcount=c->tx_skb->len;
+		
+		
+		if(c->dma_tx)
+		{
+			/*
+			 *	FIXME. DMA is broken for the original 8530,
+			 *	on the older parts we need to set a flag and
+			 *	wait for a further TX interrupt to fire this
+			 *	stage off	
+			 */
+			 
+			flags=claim_dma_lock();
+			disable_dma(c->txdma);
+
+			/*
+			 *	These two are needed by the 8530/85C30
+			 *	and must be issued when idling.
+			 */
+			 
+			if(c->dev->type!=Z85230)
+			{
+				write_zsctrl(c, RES_Tx_CRC);
+				write_zsctrl(c, RES_EOM_L);
+			}	
+			write_zsreg(c, R10, c->regs[10]&~ABUNDER);
+			clear_dma_ff(c->txdma);
+			set_dma_addr(c->txdma, virt_to_bus(c->tx_ptr));
+			set_dma_count(c->txdma, c->txcount);
+			enable_dma(c->txdma);
+			release_dma_lock(flags);
+			write_zsctrl(c, RES_EOM_L);
+			write_zsreg(c, R5, c->regs[R5]|TxENAB);
+		}
+		else
+		{
+
+			/* ABUNDER off */
+			write_zsreg(c, R10, c->regs[10]);
+			write_zsctrl(c, RES_Tx_CRC);
+	
+			while(c->txcount && (read_zsreg(c,R0)&Tx_BUF_EMP))
+			{		
+				write_zsreg(c, R8, *c->tx_ptr++);
+				c->txcount--;
+			}
+
+		}
+	}
+	/*
+	 *	Since we emptied tx_skb we can ask for more
+	 */
+	netif_wake_queue(c->netdevice);
+}
+
+/**
+ *	z8530_tx_done - TX complete callback
+ *	@c: The channel that completed a transmit.
+ *
+ *	This is called when we complete a packet send. We wake the queue,
+ *	start the next packet going and then free the buffer of the existing
+ *	packet. This code is fairly timing sensitive.
+ *
+ *	Called with the register lock held.
+ */ 
+ 
+static void z8530_tx_done(struct z8530_channel *c)
+{
+	struct sk_buff *skb;
+
+	/* Actually this can happen.*/
+	if(c->tx_skb==NULL)
+		return;
+
+	skb=c->tx_skb;
+	c->tx_skb=NULL;
+	z8530_tx_begin(c);
+	c->stats.tx_packets++;
+	c->stats.tx_bytes+=skb->len;
+	dev_kfree_skb_irq(skb);
+}
+
+/**
+ *	z8530_null_rx - Discard a packet
+ *	@c: The channel the packet arrived on
+ *	@skb: The buffer
+ *
+ *	We point the receive handler at this function when idle. Instead
+ *	of syncppp processing the frames we get to throw them away.
+ */
+ 
+void z8530_null_rx(struct z8530_channel *c, struct sk_buff *skb)
+{
+	dev_kfree_skb_any(skb);
+}
+
+EXPORT_SYMBOL(z8530_null_rx);
+
+/**
+ *	z8530_rx_done - Receive completion callback
+ *	@c: The channel that completed a receive
+ *
+ *	A new packet is complete. Our goal here is to get back into receive
+ *	mode as fast as possible. On the Z85230 we could change to using
+ *	ESCC mode, but on the older chips we have no choice. We flip to the
+ *	new buffer immediately in DMA mode so that the DMA of the next
+ *	frame can occur while we are copying the previous buffer to an sk_buff
+ *
+ *	Called with the lock held
+ */
+ 
+static void z8530_rx_done(struct z8530_channel *c)
+{
+	struct sk_buff *skb;
+	int ct;
+	
+	/*
+	 *	Is our receive engine in DMA mode
+	 */
+	 
+	if(c->rxdma_on)
+	{
+		/*
+		 *	Save the ready state and the buffer currently
+		 *	being used as the DMA target
+		 */
+		 
+		int ready=c->dma_ready;
+		unsigned char *rxb=c->rx_buf[c->dma_num];
+		unsigned long flags;
+		
+		/*
+		 *	Complete this DMA. Neccessary to find the length
+		 */		
+		 
+		flags=claim_dma_lock();
+		
+		disable_dma(c->rxdma);
+		clear_dma_ff(c->rxdma);
+		c->rxdma_on=0;
+		ct=c->mtu-get_dma_residue(c->rxdma);
+		if(ct<0)
+			ct=2;	/* Shit happens.. */
+		c->dma_ready=0;
+		
+		/*
+		 *	Normal case: the other slot is free, start the next DMA
+		 *	into it immediately.
+		 */
+		 
+		if(ready)
+		{
+			c->dma_num^=1;
+			set_dma_mode(c->rxdma, DMA_MODE_READ|0x10);
+			set_dma_addr(c->rxdma, virt_to_bus(c->rx_buf[c->dma_num]));
+			set_dma_count(c->rxdma, c->mtu);
+			c->rxdma_on = 1;
+			enable_dma(c->rxdma);
+			/* Stop any frames that we missed the head of 
+			   from passing */
+			write_zsreg(c, R0, RES_Rx_CRC);
+		}
+		else
+			/* Can't occur as we dont reenable the DMA irq until
+			   after the flip is done */
+			printk(KERN_WARNING "%s: DMA flip overrun!\n", c->netdevice->name);
+			
+		release_dma_lock(flags);
+		
+		/*
+		 *	Shove the old buffer into an sk_buff. We can't DMA
+		 *	directly into one on a PC - it might be above the 16Mb
+		 *	boundary. Optimisation - we could check to see if we
+		 *	can avoid the copy. Optimisation 2 - make the memcpy
+		 *	a copychecksum.
+		 */
+		 
+		skb=dev_alloc_skb(ct);
+		if(skb==NULL)
+		{
+			c->stats.rx_dropped++;
+			printk(KERN_WARNING "%s: Memory squeeze.\n", c->netdevice->name);
+		}
+		else
+		{
+			skb_put(skb, ct);
+			memcpy(skb->data, rxb, ct);
+			c->stats.rx_packets++;
+			c->stats.rx_bytes+=ct;
+		}
+		c->dma_ready=1;
+	}
+	else
+	{
+		RT_LOCK;	
+		skb=c->skb;
+		
+		/*
+		 *	The game we play for non DMA is similar. We want to
+		 *	get the controller set up for the next packet as fast
+		 *	as possible. We potentially only have one byte + the
+		 *	fifo length for this. Thus we want to flip to the new
+		 *	buffer and then mess around copying and allocating
+		 *	things. For the current case it doesn't matter but
+		 *	if you build a system where the sync irq isnt blocked
+		 *	by the kernel IRQ disable then you need only block the
+		 *	sync IRQ for the RT_LOCK area.
+		 *	
+		 */
+		ct=c->count;
+		
+		c->skb = c->skb2;
+		c->count = 0;
+		c->max = c->mtu;
+		if(c->skb)
+		{
+			c->dptr = c->skb->data;
+			c->max = c->mtu;
+		}
+		else
+		{
+			c->count= 0;
+			c->max = 0;
+		}
+		RT_UNLOCK;
+
+		c->skb2 = dev_alloc_skb(c->mtu);
+		if(c->skb2==NULL)
+			printk(KERN_WARNING "%s: memory squeeze.\n",
+				c->netdevice->name);
+		else
+		{
+			skb_put(c->skb2,c->mtu);
+		}
+		c->stats.rx_packets++;
+		c->stats.rx_bytes+=ct;
+		
+	}
+	/*
+	 *	If we received a frame we must now process it.
+	 */
+	if(skb)
+	{
+		skb_trim(skb, ct);
+		c->rx_function(c,skb);
+	}
+	else
+	{
+		c->stats.rx_dropped++;
+		printk(KERN_ERR "%s: Lost a frame\n", c->netdevice->name);
+	}
+}
+
+/**
+ *	spans_boundary - Check a packet can be ISA DMA'd
+ *	@skb: The buffer to check
+ *
+ *	Returns true if the buffer cross a DMA boundary on a PC. The poor
+ *	thing can only DMA within a 64K block not across the edges of it.
+ */
+ 
+static inline int spans_boundary(struct sk_buff *skb)
+{
+	unsigned long a=(unsigned long)skb->data;
+	a^=(a+skb->len);
+	if(a&0x00010000)	/* If the 64K bit is different.. */
+		return 1;
+	return 0;
+}
+
+/**
+ *	z8530_queue_xmit - Queue a packet
+ *	@c: The channel to use
+ *	@skb: The packet to kick down the channel
+ *
+ *	Queue a packet for transmission. Because we have rather
+ *	hard to hit interrupt latencies for the Z85230 per packet 
+ *	even in DMA mode we do the flip to DMA buffer if needed here
+ *	not in the IRQ.
+ *
+ *	Called from the network code. The lock is not held at this 
+ *	point.
+ */
+
+int z8530_queue_xmit(struct z8530_channel *c, struct sk_buff *skb)
+{
+	unsigned long flags;
+	
+	netif_stop_queue(c->netdevice);
+	if(c->tx_next_skb)
+	{
+		return 1;
+	}
+	
+	/* PC SPECIFIC - DMA limits */
+	
+	/*
+	 *	If we will DMA the transmit and its gone over the ISA bus
+	 *	limit, then copy to the flip buffer
+	 */
+	 
+	if(c->dma_tx && ((unsigned long)(virt_to_bus(skb->data+skb->len))>=16*1024*1024 || spans_boundary(skb)))
+	{
+		/* 
+		 *	Send the flip buffer, and flip the flippy bit.
+		 *	We don't care which is used when just so long as
+		 *	we never use the same buffer twice in a row. Since
+		 *	only one buffer can be going out at a time the other
+		 *	has to be safe.
+		 */
+		c->tx_next_ptr=c->tx_dma_buf[c->tx_dma_used];
+		c->tx_dma_used^=1;	/* Flip temp buffer */
+		memcpy(c->tx_next_ptr, skb->data, skb->len);
+	}
+	else
+		c->tx_next_ptr=skb->data;	
+	RT_LOCK;
+	c->tx_next_skb=skb;
+	RT_UNLOCK;
+	
+	spin_lock_irqsave(c->lock, flags);
+	z8530_tx_begin(c);
+	spin_unlock_irqrestore(c->lock, flags);
+	
+	return 0;
+}
+
+EXPORT_SYMBOL(z8530_queue_xmit);
+
+/**
+ *	z8530_get_stats - Get network statistics
+ *	@c: The channel to use
+ *
+ *	Get the statistics block. We keep the statistics in software as
+ *	the chip doesn't do it for us.
+ *
+ *	Locking is ignored here - we could lock for a copy but its
+ *	not likely to be that big an issue
+ */
+ 
+struct net_device_stats *z8530_get_stats(struct z8530_channel *c)
+{
+	return &c->stats;
+}
+
+EXPORT_SYMBOL(z8530_get_stats);
+
+/*
+ *	Module support
+ */
+static char banner[] __initdata = KERN_INFO "Generic Z85C30/Z85230 interface driver v0.02\n";
+
+static int __init z85230_init_driver(void)
+{
+	printk(banner);
+	return 0;
+}
+module_init(z85230_init_driver);
+
+static void __exit z85230_cleanup_driver(void)
+{
+}
+module_exit(z85230_cleanup_driver);
+
+MODULE_AUTHOR("Red Hat Inc.");
+MODULE_DESCRIPTION("Z85x30 synchronous driver core");
+MODULE_LICENSE("GPL");
diff --git a/drivers/net/wan/z85230.h b/drivers/net/wan/z85230.h
new file mode 100644
index 0000000..77e5320
--- /dev/null
+++ b/drivers/net/wan/z85230.h
@@ -0,0 +1,449 @@
+/*
+ *	Description of Z8530 Z85C30 and Z85230 communications chips
+ *
+ * Copyright (C) 1995 David S. Miller (davem@caip.rutgers.edu)
+ * Copyright (C) 1998 Alan Cox <alan@redhat.com>
+ */
+
+#ifndef _Z8530_H
+#define _Z8530_H
+
+#include <linux/tty.h>
+#include <linux/interrupt.h>
+
+/* Conversion routines to/from brg time constants from/to bits
+ * per second.
+ */
+#define BRG_TO_BPS(brg, freq) ((freq) / 2 / ((brg) + 2))
+#define BPS_TO_BRG(bps, freq) ((((freq) + (bps)) / (2 * (bps))) - 2)
+
+/* The Zilog register set */
+
+#define	FLAG	0x7e
+
+/* Write Register 0 */
+#define	R0	0		/* Register selects */
+#define	R1	1
+#define	R2	2
+#define	R3	3
+#define	R4	4
+#define	R5	5
+#define	R6	6
+#define	R7	7
+#define	R8	8
+#define	R9	9
+#define	R10	10
+#define	R11	11
+#define	R12	12
+#define	R13	13
+#define	R14	14
+#define	R15	15
+
+#define RPRIME	16		/* Indicate a prime register access on 230 */
+
+#define	NULLCODE	0	/* Null Code */
+#define	POINT_HIGH	0x8	/* Select upper half of registers */
+#define	RES_EXT_INT	0x10	/* Reset Ext. Status Interrupts */
+#define	SEND_ABORT	0x18	/* HDLC Abort */
+#define	RES_RxINT_FC	0x20	/* Reset RxINT on First Character */
+#define	RES_Tx_P	0x28	/* Reset TxINT Pending */
+#define	ERR_RES		0x30	/* Error Reset */
+#define	RES_H_IUS	0x38	/* Reset highest IUS */
+
+#define	RES_Rx_CRC	0x40	/* Reset Rx CRC Checker */
+#define	RES_Tx_CRC	0x80	/* Reset Tx CRC Checker */
+#define	RES_EOM_L	0xC0	/* Reset EOM latch */
+
+/* Write Register 1 */
+
+#define	EXT_INT_ENAB	0x1	/* Ext Int Enable */
+#define	TxINT_ENAB	0x2	/* Tx Int Enable */
+#define	PAR_SPEC	0x4	/* Parity is special condition */
+
+#define	RxINT_DISAB	0	/* Rx Int Disable */
+#define	RxINT_FCERR	0x8	/* Rx Int on First Character Only or Error */
+#define	INT_ALL_Rx	0x10	/* Int on all Rx Characters or error */
+#define	INT_ERR_Rx	0x18	/* Int on error only */
+
+#define	WT_RDY_RT	0x20	/* Wait/Ready on R/T */
+#define	WT_FN_RDYFN	0x40	/* Wait/FN/Ready FN */
+#define	WT_RDY_ENAB	0x80	/* Wait/Ready Enable */
+
+/* Write Register #2 (Interrupt Vector) */
+
+/* Write Register 3 */
+
+#define	RxENABLE	0x1	/* Rx Enable */
+#define	SYNC_L_INH	0x2	/* Sync Character Load Inhibit */
+#define	ADD_SM		0x4	/* Address Search Mode (SDLC) */
+#define	RxCRC_ENAB	0x8	/* Rx CRC Enable */
+#define	ENT_HM		0x10	/* Enter Hunt Mode */
+#define	AUTO_ENAB	0x20	/* Auto Enables */
+#define	Rx5		0x0	/* Rx 5 Bits/Character */
+#define	Rx7		0x40	/* Rx 7 Bits/Character */
+#define	Rx6		0x80	/* Rx 6 Bits/Character */
+#define	Rx8		0xc0	/* Rx 8 Bits/Character */
+
+/* Write Register 4 */
+
+#define	PAR_ENA		0x1	/* Parity Enable */
+#define	PAR_EVEN	0x2	/* Parity Even/Odd* */
+
+#define	SYNC_ENAB	0	/* Sync Modes Enable */
+#define	SB1		0x4	/* 1 stop bit/char */
+#define	SB15		0x8	/* 1.5 stop bits/char */
+#define	SB2		0xc	/* 2 stop bits/char */
+
+#define	MONSYNC		0	/* 8 Bit Sync character */
+#define	BISYNC		0x10	/* 16 bit sync character */
+#define	SDLC		0x20	/* SDLC Mode (01111110 Sync Flag) */
+#define	EXTSYNC		0x30	/* External Sync Mode */
+
+#define	X1CLK		0x0	/* x1 clock mode */
+#define	X16CLK		0x40	/* x16 clock mode */
+#define	X32CLK		0x80	/* x32 clock mode */
+#define	X64CLK		0xC0	/* x64 clock mode */
+
+/* Write Register 5 */
+
+#define	TxCRC_ENAB	0x1	/* Tx CRC Enable */
+#define	RTS		0x2	/* RTS */
+#define	SDLC_CRC	0x4	/* SDLC/CRC-16 */
+#define	TxENAB		0x8	/* Tx Enable */
+#define	SND_BRK		0x10	/* Send Break */
+#define	Tx5		0x0	/* Tx 5 bits (or less)/character */
+#define	Tx7		0x20	/* Tx 7 bits/character */
+#define	Tx6		0x40	/* Tx 6 bits/character */
+#define	Tx8		0x60	/* Tx 8 bits/character */
+#define	DTR		0x80	/* DTR */
+
+/* Write Register 6 (Sync bits 0-7/SDLC Address Field) */
+
+/* Write Register 7 (Sync bits 8-15/SDLC 01111110) */
+
+/* Write Register 8 (transmit buffer) */
+
+/* Write Register 9 (Master interrupt control) */
+#define	VIS	1	/* Vector Includes Status */
+#define	NV	2	/* No Vector */
+#define	DLC	4	/* Disable Lower Chain */
+#define	MIE	8	/* Master Interrupt Enable */
+#define	STATHI	0x10	/* Status high */
+#define	NORESET	0	/* No reset on write to R9 */
+#define	CHRB	0x40	/* Reset channel B */
+#define	CHRA	0x80	/* Reset channel A */
+#define	FHWRES	0xc0	/* Force hardware reset */
+
+/* Write Register 10 (misc control bits) */
+#define	BIT6	1	/* 6 bit/8bit sync */
+#define	LOOPMODE 2	/* SDLC Loop mode */
+#define	ABUNDER	4	/* Abort/flag on SDLC xmit underrun */
+#define	MARKIDLE 8	/* Mark/flag on idle */
+#define	GAOP	0x10	/* Go active on poll */
+#define	NRZ	0	/* NRZ mode */
+#define	NRZI	0x20	/* NRZI mode */
+#define	FM1	0x40	/* FM1 (transition = 1) */
+#define	FM0	0x60	/* FM0 (transition = 0) */
+#define	CRCPS	0x80	/* CRC Preset I/O */
+
+/* Write Register 11 (Clock Mode control) */
+#define	TRxCXT	0	/* TRxC = Xtal output */
+#define	TRxCTC	1	/* TRxC = Transmit clock */
+#define	TRxCBR	2	/* TRxC = BR Generator Output */
+#define	TRxCDP	3	/* TRxC = DPLL output */
+#define	TRxCOI	4	/* TRxC O/I */
+#define	TCRTxCP	0	/* Transmit clock = RTxC pin */
+#define	TCTRxCP	8	/* Transmit clock = TRxC pin */
+#define	TCBR	0x10	/* Transmit clock = BR Generator output */
+#define	TCDPLL	0x18	/* Transmit clock = DPLL output */
+#define	RCRTxCP	0	/* Receive clock = RTxC pin */
+#define	RCTRxCP	0x20	/* Receive clock = TRxC pin */
+#define	RCBR	0x40	/* Receive clock = BR Generator output */
+#define	RCDPLL	0x60	/* Receive clock = DPLL output */
+#define	RTxCX	0x80	/* RTxC Xtal/No Xtal */
+
+/* Write Register 12 (lower byte of baud rate generator time constant) */
+
+/* Write Register 13 (upper byte of baud rate generator time constant) */
+
+/* Write Register 14 (Misc control bits) */
+#define	BRENABL	1	/* Baud rate generator enable */
+#define	BRSRC	2	/* Baud rate generator source */
+#define	DTRREQ	4	/* DTR/Request function */
+#define	AUTOECHO 8	/* Auto Echo */
+#define	LOOPBAK	0x10	/* Local loopback */
+#define	SEARCH	0x20	/* Enter search mode */
+#define	RMC	0x40	/* Reset missing clock */
+#define	DISDPLL	0x60	/* Disable DPLL */
+#define	SSBR	0x80	/* Set DPLL source = BR generator */
+#define	SSRTxC	0xa0	/* Set DPLL source = RTxC */
+#define	SFMM	0xc0	/* Set FM mode */
+#define	SNRZI	0xe0	/* Set NRZI mode */
+
+/* Write Register 15 (external/status interrupt control) */
+#define PRIME	1	/* R5' etc register access (Z85C30/230 only) */
+#define	ZCIE	2	/* Zero count IE */
+#define FIFOE	4	/* Z85230 only */
+#define	DCDIE	8	/* DCD IE */
+#define	SYNCIE	0x10	/* Sync/hunt IE */
+#define	CTSIE	0x20	/* CTS IE */
+#define	TxUIE	0x40	/* Tx Underrun/EOM IE */
+#define	BRKIE	0x80	/* Break/Abort IE */
+
+
+/* Read Register 0 */
+#define	Rx_CH_AV	0x1	/* Rx Character Available */
+#define	ZCOUNT		0x2	/* Zero count */
+#define	Tx_BUF_EMP	0x4	/* Tx Buffer empty */
+#define	DCD		0x8	/* DCD */
+#define	SYNC_HUNT	0x10	/* Sync/hunt */
+#define	CTS		0x20	/* CTS */
+#define	TxEOM		0x40	/* Tx underrun */
+#define	BRK_ABRT	0x80	/* Break/Abort */
+
+/* Read Register 1 */
+#define	ALL_SNT		0x1	/* All sent */
+/* Residue Data for 8 Rx bits/char programmed */
+#define	RES3		0x8	/* 0/3 */
+#define	RES4		0x4	/* 0/4 */
+#define	RES5		0xc	/* 0/5 */
+#define	RES6		0x2	/* 0/6 */
+#define	RES7		0xa	/* 0/7 */
+#define	RES8		0x6	/* 0/8 */
+#define	RES18		0xe	/* 1/8 */
+#define	RES28		0x0	/* 2/8 */
+/* Special Rx Condition Interrupts */
+#define	PAR_ERR		0x10	/* Parity error */
+#define	Rx_OVR		0x20	/* Rx Overrun Error */
+#define	CRC_ERR		0x40	/* CRC/Framing Error */
+#define	END_FR		0x80	/* End of Frame (SDLC) */
+
+/* Read Register 2 (channel b only) - Interrupt vector */
+
+/* Read Register 3 (interrupt pending register) ch a only */
+#define	CHBEXT	0x1		/* Channel B Ext/Stat IP */
+#define	CHBTxIP	0x2		/* Channel B Tx IP */
+#define	CHBRxIP	0x4		/* Channel B Rx IP */
+#define	CHAEXT	0x8		/* Channel A Ext/Stat IP */
+#define	CHATxIP	0x10		/* Channel A Tx IP */
+#define	CHARxIP	0x20		/* Channel A Rx IP */
+
+/* Read Register 8 (receive data register) */
+
+/* Read Register 10  (misc status bits) */
+#define	ONLOOP	2		/* On loop */
+#define	LOOPSEND 0x10		/* Loop sending */
+#define	CLK2MIS	0x40		/* Two clocks missing */
+#define	CLK1MIS	0x80		/* One clock missing */
+
+/* Read Register 12 (lower byte of baud rate generator constant) */
+
+/* Read Register 13 (upper byte of baud rate generator constant) */
+
+/* Read Register 15 (value of WR 15) */
+
+
+/*
+ *	Interrupt handling functions for this SCC
+ */
+
+struct z8530_channel;
+ 
+struct z8530_irqhandler
+{
+	void (*rx)(struct z8530_channel *);
+	void (*tx)(struct z8530_channel *);
+	void (*status)(struct z8530_channel *);
+};
+
+/*
+ *	A channel of the Z8530
+ */
+
+struct z8530_channel
+{
+	struct		z8530_irqhandler *irqs;		/* IRQ handlers */
+	/*
+	 *	Synchronous
+	 */
+	u16		count;		/* Buyes received */
+	u16		max;		/* Most we can receive this frame */
+	u16		mtu;		/* MTU of the device */
+	u8		*dptr;		/* Pointer into rx buffer */
+	struct sk_buff	*skb;		/* Buffer dptr points into */
+	struct sk_buff	*skb2;		/* Pending buffer */
+	u8		status;		/* Current DCD */
+	u8		dcdcheck;	/* which bit to check for line */
+	u8		sync;		/* Set if in sync mode */
+
+	u8		regs[32];	/* Register map for the chip */
+	u8		pendregs[32];	/* Pending register values */
+	
+	struct sk_buff 	*tx_skb;	/* Buffer being transmitted */
+	struct sk_buff  *tx_next_skb;	/* Next transmit buffer */
+	u8		*tx_ptr;	/* Byte pointer into the buffer */
+	u8		*tx_next_ptr;	/* Next pointer to use */
+	u8		*tx_dma_buf[2];	/* TX flip buffers for DMA */
+	u8		tx_dma_used;	/* Flip buffer usage toggler */
+	u16		txcount;	/* Count of bytes to transmit */
+	
+	void		(*rx_function)(struct z8530_channel *, struct sk_buff *);
+	
+	/*
+	 *	Sync DMA
+	 */
+	
+	u8		rxdma;		/* DMA channels */
+	u8		txdma;		
+	u8		rxdma_on;	/* DMA active if flag set */
+	u8		txdma_on;
+	u8		dma_num;	/* Buffer we are DMAing into */
+	u8		dma_ready;	/* Is the other buffer free */
+	u8		dma_tx;		/* TX is to use DMA */
+	u8		*rx_buf[2];	/* The flip buffers */
+	
+	/*
+	 *	System
+	 */
+	 
+	struct z8530_dev *dev;		/* Z85230 chip instance we are from */
+	unsigned long	ctrlio;		/* I/O ports */
+	unsigned long	dataio;
+
+	/*
+	 *	For PC we encode this way.
+	 */	
+#define Z8530_PORT_SLEEP	0x80000000
+#define Z8530_PORT_OF(x)	((x)&0xFFFF)
+
+	u32		rx_overrun;		/* Overruns - not done yet */
+	u32		rx_crc_err;
+
+	/*
+	 *	Bound device pointers
+	 */
+
+	void		*private;	/* For our owner */
+	struct net_device	*netdevice;	/* Network layer device */
+	struct net_device_stats stats;	/* Network layer statistics */
+
+	/*
+	 *	Async features
+	 */
+
+	struct tty_struct 	*tty;		/* Attached terminal */
+	int			line;		/* Minor number */
+	wait_queue_head_t	open_wait;	/* Tasks waiting to open */
+	wait_queue_head_t	close_wait;	/* and for close to end */
+	unsigned long		event;		/* Pending events */
+	int			fdcount;    	/* # of fd on device */
+	int			blocked_open;	/* # of blocked opens */
+	int			x_char;		/* XON/XOF char */
+	unsigned char 		*xmit_buf;	/* Transmit pointer */
+	int			xmit_head;	/* Transmit ring */
+	int			xmit_tail;
+	int			xmit_cnt;
+	int			flags;	
+	int			timeout;
+	int			xmit_fifo_size;	/* Transmit FIFO info */
+
+	int			close_delay;	/* Do we wait for drain on close ? */
+	unsigned short		closing_wait;
+
+	/* We need to know the current clock divisor
+	 * to read the bps rate the chip has currently
+	 * loaded.
+	 */
+
+	unsigned char		clk_divisor;  /* May be 1, 16, 32, or 64 */
+	int			zs_baud;
+
+	int			magic;
+	int			baud_base;		/* Baud parameters */
+	int			custom_divisor;
+
+
+	unsigned char		tx_active; /* character is being xmitted */
+	unsigned char		tx_stopped; /* output is suspended */
+
+	spinlock_t		*lock;	  /* Devicr lock */
+};	
+
+/*
+ *	Each Z853x0 device.
+ */	
+ 
+struct z8530_dev
+{
+	char *name;	/* Device instance name */
+	struct z8530_channel chanA;	/* SCC channel A */
+	struct z8530_channel chanB;	/* SCC channel B */
+	int type;
+#define Z8530	0	/* NMOS dinosaur */	
+#define Z85C30	1	/* CMOS - better */
+#define Z85230	2	/* CMOS with real FIFO */
+	int irq;	/* Interrupt for the device */
+	int active;	/* Soft interrupt enable - the Mac doesn't 
+			   always have a hard disable on its 8530s... */
+	spinlock_t lock;
+};
+
+
+/*
+ *	Functions
+ */
+ 
+extern u8 z8530_dead_port[];
+extern u8 z8530_hdlc_kilostream_85230[];
+extern u8 z8530_hdlc_kilostream[];
+extern irqreturn_t z8530_interrupt(int, void *, struct pt_regs *);
+extern void z8530_describe(struct z8530_dev *, char *mapping, unsigned long io);
+extern int z8530_init(struct z8530_dev *);
+extern int z8530_shutdown(struct z8530_dev *);
+extern int z8530_sync_open(struct net_device *, struct z8530_channel *);
+extern int z8530_sync_close(struct net_device *, struct z8530_channel *);
+extern int z8530_sync_dma_open(struct net_device *, struct z8530_channel *);
+extern int z8530_sync_dma_close(struct net_device *, struct z8530_channel *);
+extern int z8530_sync_txdma_open(struct net_device *, struct z8530_channel *);
+extern int z8530_sync_txdma_close(struct net_device *, struct z8530_channel *);
+extern int z8530_channel_load(struct z8530_channel *, u8 *);
+extern int z8530_queue_xmit(struct z8530_channel *c, struct sk_buff *skb);
+extern struct net_device_stats *z8530_get_stats(struct z8530_channel *c);
+extern void z8530_null_rx(struct z8530_channel *c, struct sk_buff *skb);
+
+
+/*
+ *	Standard interrupt vector sets
+ */
+ 
+extern struct z8530_irqhandler z8530_sync, z8530_async, z8530_nop;
+
+/*
+ *	Asynchronous Interfacing
+ */
+
+#define SERIAL_MAGIC 0x5301
+
+/*
+ * The size of the serial xmit buffer is 1 page, or 4096 bytes
+ */
+
+#define SERIAL_XMIT_SIZE 4096
+#define WAKEUP_CHARS	256
+
+/*
+ * Events are used to schedule things to happen at timer-interrupt
+ * time, instead of at rs interrupt time.
+ */
+#define RS_EVENT_WRITE_WAKEUP	0
+
+/* Internal flags used only by kernel/chr_drv/serial.c */
+#define ZILOG_INITIALIZED	0x80000000 /* Serial port was initialized */
+#define ZILOG_CALLOUT_ACTIVE	0x40000000 /* Call out device is active */
+#define ZILOG_NORMAL_ACTIVE	0x20000000 /* Normal device is active */
+#define ZILOG_BOOT_AUTOCONF	0x10000000 /* Autoconfigure port on bootup */
+#define ZILOG_CLOSING		0x08000000 /* Serial port is closing */
+#define ZILOG_CTS_FLOW		0x04000000 /* Do CTS flow control */
+#define ZILOG_CHECK_CD		0x02000000 /* i.e., CLOCAL */
+
+#endif /* !(_Z8530_H) */