aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/staging/i4l
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/staging/i4l')
-rw-r--r--drivers/staging/i4l/Documentation/README.act2000104
-rw-r--r--drivers/staging/i4l/Documentation/README.icn148
-rw-r--r--drivers/staging/i4l/Documentation/README.pcbit40
-rw-r--r--drivers/staging/i4l/Documentation/README.sc281
-rw-r--r--drivers/staging/i4l/Kconfig13
-rw-r--r--drivers/staging/i4l/Makefile5
-rw-r--r--drivers/staging/i4l/TODO3
-rw-r--r--drivers/staging/i4l/act2000/Kconfig9
-rw-r--r--drivers/staging/i4l/act2000/Makefile9
-rw-r--r--drivers/staging/i4l/act2000/act2000.h202
-rw-r--r--drivers/staging/i4l/act2000/act2000_isa.c443
-rw-r--r--drivers/staging/i4l/act2000/act2000_isa.h136
-rw-r--r--drivers/staging/i4l/act2000/capi.c1180
-rw-r--r--drivers/staging/i4l/act2000/capi.h365
-rw-r--r--drivers/staging/i4l/act2000/module.c813
-rw-r--r--drivers/staging/i4l/icn/Kconfig12
-rw-r--r--drivers/staging/i4l/icn/Makefile5
-rw-r--r--drivers/staging/i4l/icn/icn.c1693
-rw-r--r--drivers/staging/i4l/icn/icn.h253
-rw-r--r--drivers/staging/i4l/pcbit/Kconfig10
-rw-r--r--drivers/staging/i4l/pcbit/Makefile9
-rw-r--r--drivers/staging/i4l/pcbit/callbacks.c345
-rw-r--r--drivers/staging/i4l/pcbit/callbacks.h44
-rw-r--r--drivers/staging/i4l/pcbit/capi.c649
-rw-r--r--drivers/staging/i4l/pcbit/capi.h81
-rw-r--r--drivers/staging/i4l/pcbit/drv.c1077
-rw-r--r--drivers/staging/i4l/pcbit/edss1.c313
-rw-r--r--drivers/staging/i4l/pcbit/edss1.h99
-rw-r--r--drivers/staging/i4l/pcbit/layer2.c712
-rw-r--r--drivers/staging/i4l/pcbit/layer2.h281
-rw-r--r--drivers/staging/i4l/pcbit/module.c125
-rw-r--r--drivers/staging/i4l/pcbit/pcbit.h177
32 files changed, 9636 insertions, 0 deletions
diff --git a/drivers/staging/i4l/Documentation/README.act2000 b/drivers/staging/i4l/Documentation/README.act2000
new file mode 100644
index 000000000000..ce7115e7f4ce
--- /dev/null
+++ b/drivers/staging/i4l/Documentation/README.act2000
@@ -0,0 +1,104 @@
+$Id: README.act2000,v 1.3 2000/08/06 09:22:51 armin Exp $
+
+This document describes the ACT2000 driver for the
+IBM Active 2000 ISDN card.
+
+There are 3 Types of this card available. A ISA-, MCA-, and PCMCIA-Bus
+Version. Currently, only the ISA-Bus version of the card is supported.
+However MCA and PCMCIA will follow soon.
+
+The ISA-Bus Version uses 8 IO-ports. The base port address has to be set
+manually using the DIP switches.
+
+Setting up the DIP switches for the IBM Active 2000 ISDN card:
+
+ Note: S5 and S6 always set off!
+
+ S1 S2 S3 S4 Base-port
+ on on on on 0x0200 (Factory default)
+ off on on on 0x0240
+ on off on on 0x0280
+ off off on on 0x02c0
+ on on off on 0x0300
+ off on off on 0x0340
+ on off off on 0x0380
+ on on on off 0xcfe0
+ off on on off 0xcfa0
+ on off on off 0xcf60
+ off off on off 0xcf20
+ on on off off 0xcee0
+ off on off off 0xcea0
+ on off off off 0xce60
+ off off off off Card disabled
+
+IRQ is configured by software. Possible values are:
+
+ 3, 5, 7, 10, 11, 12, 15 and none (polled mode)
+
+
+The ACT2000 driver may either be built into the kernel or as a module.
+Initialization depends on how the driver is built:
+
+Driver built into the kernel:
+
+ The ACT2000 driver can be configured using the commandline-feature while
+ loading the kernel with LILO or LOADLIN. It accepts the following syntax:
+
+ act2000=b,p,i[,idstring]
+
+ where
+
+ b = Bus-Type (1=ISA, 2=MCA, 3=PCMCIA)
+ p = portbase (-1 means autoprobe)
+ i = Interrupt (-1 means use next free IRQ, 0 means polled mode)
+
+ The idstring is an arbitrary string used for referencing the card
+ by the actctrl tool later.
+
+ Defaults used, when no parameters given at all:
+
+ 1,-1,-1,""
+
+ which means: Autoprobe for an ISA card, use next free IRQ, let the
+ ISDN linklevel fill the IdString (usually "line0" for the first card).
+
+ If you like to use more than one card, you can use the program
+ "actctrl" from the utility-package to configure additional cards.
+
+ Using the "actctrl"-utility, portbase and irq can also be changed
+ during runtime. The D-channel protocol is configured by the "dproto"
+ option of the "actctrl"-utility after loading the firmware into the
+ card's memory using the "actctrl"-utility.
+
+Driver built as module:
+
+ The module act2000.o can be configured during modprobe (insmod) by
+ appending its parameters to the modprobe resp. insmod commandline.
+ The following syntax is accepted:
+
+ act_bus=b act_port=p act_irq=i act_id=idstring
+
+ where b, p, i and idstring have the same meanings as the parameters
+ described for the builtin version above.
+
+ Using the "actctrl"-utility, the same features apply to the modularized
+ version as to the kernel-builtin one. (i.e. loading of firmware and
+ configuring the D-channel protocol)
+
+Loading the firmware into the card:
+
+ The firmware is supplied together with the isdn4k-utils package. It
+ can be found in the subdirectory act2000/firmware/
+
+ Assuming you have installed the utility-package correctly, the firmware
+ will be downloaded into the card using the following command:
+
+ actctrl -d idstring load /etc/isdn/bip11.btl
+
+ where idstring is the Name of the card, given during insmod-time or
+ (for kernel-builtin driver) on the kernel commandline. If only one
+ ISDN card is used, the -d isdstrin may be omitted.
+
+ For further documentation (adding more IBM Active 2000 cards), refer to
+ the manpage actctrl.8 which is included in the isdn4k-utils package.
+
diff --git a/drivers/staging/i4l/Documentation/README.icn b/drivers/staging/i4l/Documentation/README.icn
new file mode 100644
index 000000000000..13f833d4e910
--- /dev/null
+++ b/drivers/staging/i4l/Documentation/README.icn
@@ -0,0 +1,148 @@
+$Id: README.icn,v 1.7 2000/08/06 09:22:51 armin Exp $
+
+You can get the ICN-ISDN-card from:
+
+Thinking Objects Software GmbH
+Versbacher Röthe 159
+97078 Würzburg
+Tel: +49 931 2877950
+Fax: +49 931 2877951
+
+email info@think.de
+WWW http:/www.think.de
+
+
+The card communicates with the PC by two interfaces:
+ 1. A range of 4 successive port-addresses, whose base address can be
+ configured with the switches.
+ 2. A memory window with 16KB-256KB size, which can be setup in 16k steps
+ over the whole range of 16MB. Isdn4linux only uses a 16k window.
+ The base address of the window can be configured when loading
+ the lowlevel-module (see README). If using more than one card,
+ all cards are mapped to the same window and activated as needed.
+
+Setting up the IO-address dipswitches for the ICN-ISDN-card:
+
+ Two types of cards exist, one with dip-switches and one with
+ hook-switches.
+
+ 1. Setting for the card with hook-switches:
+
+ (0 = switch closed, 1 = switch open)
+
+ S3 S2 S1 Base-address
+ 0 0 0 0x300
+ 0 0 1 0x310
+ 0 1 0 0x320 (Default for isdn4linux)
+ 0 1 1 0x330
+ 1 0 0 0x340
+ 1 0 1 0x350
+ 1 1 0 0x360
+ 1 1 1 NOT ALLOWED!
+
+ 2. Setting for the card with dip-switches:
+
+ (0 = switch closed, 1 = switch open)
+
+ S1 S2 S3 S4 Base-Address
+ 0 0 0 0 0x300
+ 0 0 0 1 0x310
+ 0 0 1 0 0x320 (Default for isdn4linux)
+ 0 0 1 1 0x330
+ 0 1 0 0 0x340
+ 0 1 0 1 0x350
+ 0 1 1 0 0x360
+ 0 1 1 1 NOT ALLOWED!
+ 1 0 0 0 0x308
+ 1 0 0 1 0x318
+ 1 0 1 0 0x328
+ 1 0 1 1 0x338
+ 1 1 0 0 0x348
+ 1 1 0 1 0x358
+ 1 1 1 0 0x368
+ 1 1 1 1 NOT ALLOWED!
+
+The ICN driver may be built into the kernel or as a module. Initialization
+depends on how the driver is built:
+
+Driver built into the kernel:
+
+ The ICN driver can be configured using the commandline-feature while
+ loading the kernel with LILO or LOADLIN. It accepts the following syntax:
+
+ icn=p,m[,idstring1[,idstring2]]
+
+ where
+
+ p = portbase (default: 0x320)
+ m = shared memory (default: 0xd0000)
+
+ When using the ICN double card (4B), you MUST define TWO idstrings.
+ idstring must start with a character! There is no way for the driver
+ to distinguish between a 2B and 4B type card. Therefore, by supplying
+ TWO idstrings, you tell the driver that you have a 4B installed.
+
+ If you like to use more than one card, you can use the program
+ "icnctrl" from the utility-package to configure additional cards.
+ You need to configure shared memory only once, since the icn-driver
+ maps all cards into the same address-space.
+
+ Using the "icnctrl"-utility, portbase and shared memory can also be
+ changed during runtime.
+
+ The D-channel protocol is configured by loading different firmware
+ into the card's memory using the "icnctrl"-utility.
+
+
+Driver built as module:
+
+ The module icn.o can be configured during "insmod'ing" it by
+ appending its parameters to the insmod-commandline. The following
+ syntax is accepted:
+
+ portbase=p membase=m icn_id=idstring [icn_id2=idstring2]
+
+ where p, m, idstring1 and idstring2 have the same meanings as the
+ parameters described for the kernel-version above.
+
+ When using the ICN double card (4B), you MUST define TWO idstrings.
+ idstring must start with a character! There is no way for the driver
+ to distinguish between a 2B and 4B type card. Therefore, by supplying
+ TWO idstrings, you tell the driver that you have a 4B installed.
+
+ Using the "icnctrl"-utility, the same features apply to the modularized
+ version like to the kernel-builtin one.
+
+ The D-channel protocol is configured by loading different firmware
+ into the card's memory using the "icnctrl"-utility.
+
+Loading the firmware into the card:
+
+ The firmware is supplied together with the isdn4k-utils package. It
+ can be found in the subdirectory icnctrl/firmware/
+
+ There are 3 files:
+
+ loadpg.bin - Image of the bootstrap loader.
+ pc_1t_ca.bin - Image of firmware for german 1TR6 protocol.
+ pc_eu_ca.bin - Image if firmware for EDSS1 (Euro-ISDN) protocol.
+
+ Assuming you have installed the utility-package correctly, the firmware
+ will be downloaded into the 2B-card using the following command:
+
+ icnctrl -d Idstring load /etc/isdn/loadpg.bin /etc/isdn/pc_XX_ca.bin
+
+ where XX is either "1t" or "eu", depending on the D-Channel protocol
+ used on your S0-bus and Idstring is the Name of the card, given during
+ insmod-time or (for kernel-builtin driver) on the kernel commandline.
+
+ To load a 4B-card, the same command is used, except a second firmware
+ file is appended to the commandline of icnctrl.
+
+ -> After downloading firmware, the two LEDs at the back cover of the card
+ (ICN-4B: 4 LEDs) must be blinking intermittently now. If a connection
+ is up, the corresponding led is lit continuously.
+
+ For further documentation (adding more ICN-cards), refer to the manpage
+ icnctrl.8 which is included in the isdn4k-utils package.
+
diff --git a/drivers/staging/i4l/Documentation/README.pcbit b/drivers/staging/i4l/Documentation/README.pcbit
new file mode 100644
index 000000000000..5125002282e5
--- /dev/null
+++ b/drivers/staging/i4l/Documentation/README.pcbit
@@ -0,0 +1,40 @@
+------------------------------------------------------------------------------
+ README file for the PCBIT-D Device Driver.
+------------------------------------------------------------------------------
+
+The PCBIT is a Euro ISDN adapter manufactured in Portugal by Octal and
+developed in cooperation with Portugal Telecom and Inesc.
+The driver interfaces with the standard kernel isdn facilities
+originally developed by Fritz Elfert in the isdn4linux project.
+
+The common versions of the pcbit board require a firmware that is
+distributed (and copyrighted) by the manufacturer. To load this
+firmware you need "pcbitctl" available on the standard isdn4k-utils
+package or in the pcbit package available in:
+
+ftp://ftp.di.fc.ul.pt/pub/systems/Linux/isdn
+
+Known Limitations:
+
+- The board reset procedure is at the moment incorrect and will only
+allow you to load the firmware after a hard reset.
+
+- Only HDLC in B-channels is supported at the moment. There is no
+current support for X.25 in B or D channels nor LAPD in B
+channels. The main reason is that these two other protocol modes have,
+to my knowledge, very little use. If you want to see them implemented
+*do* send me a mail.
+
+- The driver often triggers errors in the board that I and the
+manufacturer believe to be caused by bugs in the firmware. The current
+version includes several procedures for error recovery that should
+allow normal operation. Plans for the future include cooperation with
+the manufacturer in order to solve this problem.
+
+Information/hints/help can be obtained in the linux isdn
+mailing list (isdn4linux@listserv.isdn4linux.de) or directly from me.
+
+regards,
+ Pedro.
+
+<roque@di.fc.ul.pt>
diff --git a/drivers/staging/i4l/Documentation/README.sc b/drivers/staging/i4l/Documentation/README.sc
new file mode 100644
index 000000000000..1153cd926059
--- /dev/null
+++ b/drivers/staging/i4l/Documentation/README.sc
@@ -0,0 +1,281 @@
+Welcome to Beta Release 2 of the combination ISDN driver for SpellCaster's
+ISA ISDN adapters. Please note this release 2 includes support for the
+DataCommute/BRI and TeleCommute/BRI adapters only and any other use is
+guaranteed to fail. If you have a DataCommute/PRI installed in the test
+computer, we recommend removing it as it will be detected but will not
+be usable. To see what we have done to Beta Release 2, see section 3.
+
+Speaking of guarantees, THIS IS BETA SOFTWARE and as such contains
+bugs and defects either known or unknown. Use this software at your own
+risk. There is NO SUPPORT for this software. Some help may be available
+through the web site or the mailing list but such support is totally at
+our own option and without warranty. If you choose to assume all and
+total risk by using this driver, we encourage you to join the beta
+mailing list.
+
+To join the Linux beta mailing list, send a message to:
+majordomo@spellcast.com with the words "subscribe linux-beta" as the only
+contents of the message. Do not include a signature. If you choose to
+remove yourself from this list at a later date, send another message to
+the same address with the words "unsubscribe linux-beta" as its only
+contents.
+
+TABLE OF CONTENTS
+-----------------
+ 1. Introduction
+ 1.1 What is ISDN4Linux?
+ 1.2 What is different between this driver and previous drivers?
+ 1.3 How do I setup my system with the correct software to use
+ this driver release?
+
+ 2. Basic Operations
+ 2.1 Unpacking and installing the driver
+ 2.2 Read the man pages!!!
+ 2.3 Installing the driver
+ 2.4 Removing the driver
+ 2.5 What to do if it doesn't load
+ 2.6 How to setup ISDN4Linux with the driver
+
+ 3. Beta Change Summaries and Miscellaneous Notes
+
+1. Introduction
+---------------
+
+The revision 2 Linux driver for SpellCaster ISA ISDN adapters is built
+upon ISDN4Linux available separately or as included in Linux 2.0 and later.
+The driver will support a maximum of 4 adapters in any one system of any
+type including DataCommute/BRI, DataCommute/PRI and TeleCommute/BRI for a
+maximum of 92 channels for host. The driver is supplied as a module in
+source form and needs to be complied before it can be used. It has been
+tested on Linux 2.0.20.
+
+1.1 What Is ISDN4Linux
+
+ISDN4Linux is a driver and set of tools used to access and use ISDN devices
+on a Linux platform in a common and standard way. It supports HDLC and PPP
+protocols and offers channel bundling and MLPPP support. To use ISDN4Linux
+you need to configure your kernel for ISDN support and get the ISDN4Linux
+tool kit from our web site.
+
+ISDN4Linux creates a channel pool from all of the available ISDN channels
+and therefore can function across adapters. When an ISDN4Linux compliant
+driver (such as ours) is loaded, all of the channels go into a pool and
+are used on a first-come first-served basis. In addition, individual
+channels can be specifically bound to particular interfaces.
+
+1.2 What is different between this driver and previous drivers?
+
+The revision 2 driver besides adopting the ISDN4Linux architecture has many
+subtle and not so subtle functional differences from previous releases. These
+include:
+ - More efficient shared memory management combined with a simpler
+ configuration. All adapters now use only 16Kbytes of shared RAM
+ versus between 16K and 64K. New methods for using the shared RAM
+ allow us to utilize all of the available RAM on the adapter through
+ only one 16K page.
+ - Better detection of available upper memory. The probing routines
+ have been improved to better detect available shared RAM pages and
+ used pages are now locked.
+ - Decreased loading time and a wider range of I/O ports probed.
+ We have significantly reduced the amount of time it takes to load
+ the driver and at the same time doubled the number of I/O ports
+ probed increasing the likelihood of finding an adapter.
+ - We now support all ISA adapter models with a single driver instead
+ of separate drivers for each model. The revision 2 driver supports
+ the DataCommute/BRI, DataCommute/PRI and TeleCommute/BRI in any
+ combination up to a maximum of four adapters per system.
+ - On board PPP protocol support has been removed in favour of the
+ sync-PPP support used in ISDN4Linux. This means more control of
+ the protocol parameters, faster negotiation time and a more
+ familiar interface.
+
+1.3 How do I setup my system with the correct software to use
+ this driver release?
+
+Before you can compile, install and use the SpellCaster ISA ISDN driver, you
+must ensure that the following software is installed, configured and running:
+
+ - Linux kernel 2.0.20 or later with the required init and ps
+ versions. Please see your distribution vendor for the correct
+ utility packages. The latest kernel is available from
+ ftp://sunsite.unc.edu/pub/Linux/kernel/v2.0/
+
+ - The latest modules package (modules-2.0.0.tar.gz) from
+ ftp://sunsite.unc.edu/pub/Linux/kernel/modules-2.0.0.tar.gz
+
+ - The ISDN4Linux tools available from
+ ftp://ftp.franken.de/pub/isdn4linux/v2.0/isdn4k-utils-2.0.tar.gz
+ This package may fail to compile for you so you can alternatively
+ get a pre-compiled version from
+ ftp://ftp.spellcast.com/pub/drivers/isdn4linux/isdn4k-bin-2.0.tar.gz
+
+
+2. Basic Operations
+-------------------
+
+2.1 Unpacking and installing the driver
+
+ 1. As root, create a directory in a convenient place. We suggest
+ /usr/src/spellcaster.
+
+ 2. Unpack the archive with :
+ tar xzf sc-n.nn.tar.gz -C /usr/src/spellcaster
+
+ 3. Change directory to /usr/src/spellcaster
+
+ 4. Read the README and RELNOTES files.
+
+ 5. Run 'make' and if all goes well, run 'make install'.
+
+2.2 Read the man pages!!!
+
+Make sure you read the scctrl(8) and sc(4) manual pages before continuing
+any further. Type 'man 8 scctrl' and 'man 4 sc'.
+
+2.3 Installing the driver
+
+To install the driver, type '/sbin/insmod sc' as root. sc(4) details options
+you can specify but you shouldn't need to use any unless this doesn't work.
+
+Make sure the driver loaded and detected all of the adapters by typing
+'dmesg'.
+
+The driver can be configured so that it is loaded upon startup. To do this,
+edit the file "/etc/modules/'uname -f'/'uname -v'" and insert the driver name
+"sc" into this file.
+
+2.4 Removing the driver
+
+To remove the driver, delete any interfaces that may exist (see isdnctrl(8)
+for more on this) and then type '/sbin/rmmod sc'.
+
+2.5 What to do if it doesn't load
+
+If, when you try to install the driver, you get a message mentioning
+'register_isdn' then you do not have the ISDN4Linux system installed. Please
+make sure that ISDN support is configured in the kernel.
+
+If you get a message that says 'initialization of sc failed', then the
+driver failed to detect an adapter or failed to find resources needed such
+as a free IRQ line or shared memory segment. If you are sure there are free
+resources available, use the insmod options detailed in sc(4) to override
+the probing function.
+
+Upon testing, the following problem was noted, the driver would load without
+problems, but the board would not respond beyond that point. When a check was
+done with 'cat /proc/interrupts' the interrupt count for sc was 0. In the event
+of this problem, change the BIOS settings so that the interrupts in question are
+reserved for ISA use only.
+
+
+2.6 How to setup ISDN4Linux with the driver
+
+There are three main configurations which you can use with the driver:
+
+A) Basic HDLC connection
+B) PPP connection
+C) MLPPP connection
+
+It should be mentioned here that you may also use a tty connection if you
+desire. The Documentation directory of the isdn4linux subsystem offers good
+documentation on this feature.
+
+A) 10 steps to the establishment of a basic HDLC connection
+-----------------------------------------------------------
+
+- please open the isdn-hdlc file in the examples directory and follow along...
+
+ This file is a script used to configure a BRI ISDN TA to establish a
+ basic HDLC connection between its two channels. Two network
+ interfaces are created and two routes added between the channels.
+
+ i) using the isdnctrl utility, add an interface with "addif" and
+ name it "isdn0"
+ ii) add the outgoing and inbound telephone numbers
+ iii) set the Layer 2 protocol to hdlc
+ iv) set the eaz of the interface to be the phone number of that
+ specific channel
+ v) to turn the callback features off, set the callback to "off" and
+ the callback delay (cbdelay) to 0.
+ vi) the hangup timeout can be set to a specified number of seconds
+ vii) the hangup upon incoming call can be set on or off
+ viii) use the ifconfig command to bring up the network interface with
+ a specific IP address and point to point address
+ ix) add a route to the IP address through the isdn0 interface
+ x) a ping should result in the establishment of the connection
+
+
+B) Establishment of a PPP connection
+------------------------------------
+
+- please open the isdn-ppp file in the examples directory and follow along...
+
+ This file is a script used to configure a BRI ISDN TA to establish a
+ PPP connection between the two channels. The file is almost
+ identical to the HDLC connection example except that the packet
+ encapsulation type has to be set.
+
+ use the same procedure as in the HDLC connection from steps i) to
+ iii) then, after the Layer 2 protocol is set, set the encapsulation
+ "encap" to syncppp. With this done, the rest of the steps, iv) to x)
+ can be followed from above.
+
+ Then, the ipppd (ippp daemon) must be setup:
+
+ xi) use the ipppd function found in /sbin/ipppd to set the following:
+ xii) take out (minus) VJ compression and bsd compression
+ xiii) set the mru size to 2000
+ xiv) link the two /dev interfaces to the daemon
+
+NOTE: A "*" in the inbound telephone number specifies that a call can be
+accepted on any number.
+
+C) Establishment of a MLPPP connection
+--------------------------------------
+
+- please open the isdn-mppp file in the examples directory and follow along...
+
+ This file is a script used to configure a BRI ISDN TA to accept a
+ Multi Link PPP connection.
+
+ i) using the isdnctrl utility, add an interface with "addif" and
+ name it "ippp0"
+ ii) add the inbound telephone number
+ iii) set the Layer 2 protocol to hdlc and the Layer 3 protocol to
+ trans (transparent)
+ iv) set the packet encapsulation to syncppp
+ v) set the eaz of the interface to be the phone number of that
+ specific channel
+ vi) to turn the callback features off, set the callback to "off" and
+ the callback delay (cbdelay) to 0.
+ vi) the hangup timeout can be set to a specified number of seconds
+ vii) the hangup upon incoming call can be set on or off
+ viii) add a slave interface and name it "ippp32" for example
+ ix) set the similar parameters for the ippp32 interface
+ x) use the ifconfig command to bring-up the ippp0 interface with a
+ specific IP address and point to point address
+ xi) add a route to the IP address through the ippp0 interface
+ xii) use the ipppd function found in /sbin/ipppd to set the following:
+ xiii) take out (minus) bsd compression
+ xiv) set the mru size to 2000
+ xv) add (+) the multi-link function "+mp"
+ xvi) link the two /dev interfaces to the daemon
+
+NOTE: To use the MLPPP connection to dial OUT to a MLPPP connection, change
+the inbound telephone numbers to the outgoing telephone numbers of the MLPPP
+host.
+
+
+3. Beta Change Summaries and Miscellaneous Notes
+------------------------------------------------
+When using the "scctrl" utility to upload firmware revisions on the board,
+please note that the byte count displayed at the end of the operation may be
+different from the total number of bytes in the "dcbfwn.nn.sr" file. Please
+disregard the displayed byte count.
+
+It was noted that in Beta Release 1, the module would fail to load and result
+in a segmentation fault when 'insmod'ed. This problem was created when one of
+the isdn4linux parameters, (isdn_ctrl, data field) was filled in. In some
+cases, this data field was NULL, and was left unchecked, so when it was
+referenced... segv. The bug has been fixed around line 63-68 of event.c.
+
diff --git a/drivers/staging/i4l/Kconfig b/drivers/staging/i4l/Kconfig
new file mode 100644
index 000000000000..920216e88de7
--- /dev/null
+++ b/drivers/staging/i4l/Kconfig
@@ -0,0 +1,13 @@
+#
+# Old ISDN4Linux config
+#
+menu "Old ISDN4Linux (deprecated)"
+ depends on ISDN_I4L
+
+source "drivers/staging/i4l/icn/Kconfig"
+
+source "drivers/staging/i4l/pcbit/Kconfig"
+
+source "drivers/staging/i4l/act2000/Kconfig"
+
+endmenu
diff --git a/drivers/staging/i4l/Makefile b/drivers/staging/i4l/Makefile
new file mode 100644
index 000000000000..158b87093db5
--- /dev/null
+++ b/drivers/staging/i4l/Makefile
@@ -0,0 +1,5 @@
+# Makefile for the old ISDN I4L subsystem and device drivers.
+
+obj-$(CONFIG_ISDN_DRV_ICN) += icn/
+obj-$(CONFIG_ISDN_DRV_PCBIT) += pcbit/
+obj-$(CONFIG_ISDN_DRV_ACT2000) += act2000/
diff --git a/drivers/staging/i4l/TODO b/drivers/staging/i4l/TODO
new file mode 100644
index 000000000000..6fe2c08bec7a
--- /dev/null
+++ b/drivers/staging/i4l/TODO
@@ -0,0 +1,3 @@
+* The icn, pcbit and act2000 drivers are dead, remove them in 2017
+ after another longterm kernel has been released, just in the
+ unlikely case someone still has this hardware.
diff --git a/drivers/staging/i4l/act2000/Kconfig b/drivers/staging/i4l/act2000/Kconfig
new file mode 100644
index 000000000000..fa2673fc69c2
--- /dev/null
+++ b/drivers/staging/i4l/act2000/Kconfig
@@ -0,0 +1,9 @@
+config ISDN_DRV_ACT2000
+ tristate "IBM Active 2000 support"
+ depends on ISA
+ help
+ Say Y here if you have an IBM Active 2000 ISDN card. In order to use
+ this card, additional firmware is necessary, which has to be loaded
+ into the card using a utility which is part of the latest
+ isdn4k-utils package. Please read the file
+ <file:Documentation/isdn/README.act2000> for more information.
diff --git a/drivers/staging/i4l/act2000/Makefile b/drivers/staging/i4l/act2000/Makefile
new file mode 100644
index 000000000000..05e582fb5c00
--- /dev/null
+++ b/drivers/staging/i4l/act2000/Makefile
@@ -0,0 +1,9 @@
+# Makefile for the act2000 ISDN device driver
+
+# Each configuration option enables a list of files.
+
+obj-$(CONFIG_ISDN_DRV_ACT2000) += act2000.o
+
+# Multipart objects.
+
+act2000-y := module.o capi.o act2000_isa.o
diff --git a/drivers/staging/i4l/act2000/act2000.h b/drivers/staging/i4l/act2000/act2000.h
new file mode 100644
index 000000000000..321d437f579e
--- /dev/null
+++ b/drivers/staging/i4l/act2000/act2000.h
@@ -0,0 +1,202 @@
+/* $Id: act2000.h,v 1.8.6.3 2001/09/23 22:24:32 kai Exp $
+ *
+ * ISDN lowlevel-module for the IBM ISDN-S0 Active 2000.
+ *
+ * Author Fritz Elfert
+ * Copyright by Fritz Elfert <fritz@isdn4linux.de>
+ *
+ * This software may be used and distributed according to the terms
+ * of the GNU General Public License, incorporated herein by reference.
+ *
+ * Thanks to Friedemann Baitinger and IBM Germany
+ *
+ */
+
+#ifndef act2000_h
+#define act2000_h
+
+#include <linux/compiler.h>
+
+#define ACT2000_IOCTL_SETPORT 1
+#define ACT2000_IOCTL_GETPORT 2
+#define ACT2000_IOCTL_SETIRQ 3
+#define ACT2000_IOCTL_GETIRQ 4
+#define ACT2000_IOCTL_SETBUS 5
+#define ACT2000_IOCTL_GETBUS 6
+#define ACT2000_IOCTL_SETPROTO 7
+#define ACT2000_IOCTL_GETPROTO 8
+#define ACT2000_IOCTL_SETMSN 9
+#define ACT2000_IOCTL_GETMSN 10
+#define ACT2000_IOCTL_LOADBOOT 11
+#define ACT2000_IOCTL_ADDCARD 12
+
+#define ACT2000_IOCTL_TEST 98
+#define ACT2000_IOCTL_DEBUGVAR 99
+
+#define ACT2000_BUS_ISA 1
+#define ACT2000_BUS_MCA 2
+#define ACT2000_BUS_PCMCIA 3
+
+/* Struct for adding new cards */
+typedef struct act2000_cdef {
+ int bus;
+ int port;
+ int irq;
+ char id[10];
+} act2000_cdef;
+
+/* Struct for downloading firmware */
+typedef struct act2000_ddef {
+ int length; /* Length of code */
+ char __user *buffer; /* Ptr. to code */
+} act2000_ddef;
+
+typedef struct act2000_fwid {
+ char isdn[4];
+ char revlen[2];
+ char revision[504];
+} act2000_fwid;
+
+#if defined(__KERNEL__) || defined(__DEBUGVAR__)
+
+#ifdef __KERNEL__
+/* Kernel includes */
+
+#include <linux/sched.h>
+#include <linux/string.h>
+#include <linux/workqueue.h>
+#include <linux/interrupt.h>
+#include <linux/skbuff.h>
+#include <linux/errno.h>
+#include <linux/fs.h>
+#include <linux/major.h>
+#include <asm/io.h>
+#include <linux/kernel.h>
+#include <linux/signal.h>
+#include <linux/slab.h>
+#include <linux/mm.h>
+#include <linux/mman.h>
+#include <linux/ioport.h>
+#include <linux/timer.h>
+#include <linux/wait.h>
+#include <linux/delay.h>
+#include <linux/ctype.h>
+#include <linux/isdnif.h>
+
+#endif /* __KERNEL__ */
+
+#define ACT2000_PORTLEN 8
+
+#define ACT2000_FLAGS_RUNNING 1 /* Cards driver activated */
+#define ACT2000_FLAGS_PVALID 2 /* Cards port is valid */
+#define ACT2000_FLAGS_IVALID 4 /* Cards irq is valid */
+#define ACT2000_FLAGS_LOADED 8 /* Firmware loaded */
+
+#define ACT2000_BCH 2 /* # of channels per card */
+
+/* D-Channel states */
+#define ACT2000_STATE_NULL 0
+#define ACT2000_STATE_ICALL 1
+#define ACT2000_STATE_OCALL 2
+#define ACT2000_STATE_IWAIT 3
+#define ACT2000_STATE_OWAIT 4
+#define ACT2000_STATE_IBWAIT 5
+#define ACT2000_STATE_OBWAIT 6
+#define ACT2000_STATE_BWAIT 7
+#define ACT2000_STATE_BHWAIT 8
+#define ACT2000_STATE_BHWAIT2 9
+#define ACT2000_STATE_DHWAIT 10
+#define ACT2000_STATE_DHWAIT2 11
+#define ACT2000_STATE_BSETUP 12
+#define ACT2000_STATE_ACTIVE 13
+
+#define ACT2000_MAX_QUEUED 8000 /* 2 * maxbuff */
+
+#define ACT2000_LOCK_TX 0
+#define ACT2000_LOCK_RX 1
+
+typedef struct act2000_chan {
+ unsigned short callref; /* Call Reference */
+ unsigned short fsm_state; /* Current D-Channel state */
+ unsigned short eazmask; /* EAZ-Mask for this Channel */
+ short queued; /* User-Data Bytes in TX queue */
+ unsigned short plci;
+ unsigned short ncci;
+ unsigned char l2prot; /* Layer 2 protocol */
+ unsigned char l3prot; /* Layer 3 protocol */
+} act2000_chan;
+
+typedef struct msn_entry {
+ char eaz;
+ char msn[16];
+ struct msn_entry *next;
+} msn_entry;
+
+typedef struct irq_data_isa {
+ __u8 *rcvptr;
+ __u16 rcvidx;
+ __u16 rcvlen;
+ struct sk_buff *rcvskb;
+ __u8 rcvignore;
+ __u8 rcvhdr[8];
+} irq_data_isa;
+
+typedef union act2000_irq_data {
+ irq_data_isa isa;
+} act2000_irq_data;
+
+/*
+ * Per card driver data
+ */
+typedef struct act2000_card {
+ unsigned short port; /* Base-port-address */
+ unsigned short irq; /* Interrupt */
+ u_char ptype; /* Protocol type (1TR6 or Euro) */
+ u_char bus; /* Cardtype (ISA, MCA, PCMCIA) */
+ struct act2000_card *next; /* Pointer to next device struct */
+ spinlock_t lock; /* protect critical operations */
+ int myid; /* Driver-Nr. assigned by linklevel */
+ unsigned long flags; /* Statusflags */
+ unsigned long ilock; /* Semaphores for IRQ-Routines */
+ struct sk_buff_head rcvq; /* Receive-Message queue */
+ struct sk_buff_head sndq; /* Send-Message queue */
+ struct sk_buff_head ackq; /* Data-Ack-Message queue */
+ u_char *ack_msg; /* Ptr to User Data in User skb */
+ __u16 need_b3ack; /* Flag: Need ACK for current skb */
+ struct sk_buff *sbuf; /* skb which is currently sent */
+ struct timer_list ptimer; /* Poll timer */
+ struct work_struct snd_tq; /* Task struct for xmit bh */
+ struct work_struct rcv_tq; /* Task struct for rcv bh */
+ struct work_struct poll_tq; /* Task struct for polled rcv bh */
+ msn_entry *msn_list;
+ unsigned short msgnum; /* Message number for sending */
+ spinlock_t mnlock; /* lock for msgnum */
+ act2000_chan bch[ACT2000_BCH]; /* B-Channel status/control */
+ char status_buf[256]; /* Buffer for status messages */
+ char *status_buf_read;
+ char *status_buf_write;
+ char *status_buf_end;
+ act2000_irq_data idat; /* Data used for IRQ handler */
+ isdn_if interface; /* Interface to upper layer */
+ char regname[35]; /* Name used for request_region */
+} act2000_card;
+
+static inline void act2000_schedule_tx(act2000_card *card)
+{
+ schedule_work(&card->snd_tq);
+}
+
+static inline void act2000_schedule_rx(act2000_card *card)
+{
+ schedule_work(&card->rcv_tq);
+}
+
+static inline void act2000_schedule_poll(act2000_card *card)
+{
+ schedule_work(&card->poll_tq);
+}
+
+extern char *act2000_find_eaz(act2000_card *, char);
+
+#endif /* defined(__KERNEL__) || defined(__DEBUGVAR__) */
+#endif /* act2000_h */
diff --git a/drivers/staging/i4l/act2000/act2000_isa.c b/drivers/staging/i4l/act2000/act2000_isa.c
new file mode 100644
index 000000000000..b5fad29a9ba6
--- /dev/null
+++ b/drivers/staging/i4l/act2000/act2000_isa.c
@@ -0,0 +1,443 @@
+/* $Id: act2000_isa.c,v 1.11.6.3 2001/09/23 22:24:32 kai Exp $
+ *
+ * ISDN lowlevel-module for the IBM ISDN-S0 Active 2000 (ISA-Version).
+ *
+ * Author Fritz Elfert
+ * Copyright by Fritz Elfert <fritz@isdn4linux.de>
+ *
+ * This software may be used and distributed according to the terms
+ * of the GNU General Public License, incorporated herein by reference.
+ *
+ * Thanks to Friedemann Baitinger and IBM Germany
+ *
+ */
+
+#include "act2000.h"
+#include "act2000_isa.h"
+#include "capi.h"
+
+/*
+ * Reset Controller, then try to read the Card's signature.
+ + Return:
+ * 1 = Signature found.
+ * 0 = Signature not found.
+ */
+static int
+act2000_isa_reset(unsigned short portbase)
+{
+ unsigned char reg;
+ int i;
+ int found;
+ int serial = 0;
+
+ found = 0;
+ if ((reg = inb(portbase + ISA_COR)) != 0xff) {
+ outb(reg | ISA_COR_RESET, portbase + ISA_COR);
+ mdelay(10);
+ outb(reg, portbase + ISA_COR);
+ mdelay(10);
+
+ for (i = 0; i < 16; i++) {
+ if (inb(portbase + ISA_ISR) & ISA_ISR_SERIAL)
+ serial |= 0x10000;
+ serial >>= 1;
+ }
+ if (serial == ISA_SER_ID)
+ found++;
+ }
+ return found;
+}
+
+int
+act2000_isa_detect(unsigned short portbase)
+{
+ int ret = 0;
+
+ if (request_region(portbase, ACT2000_PORTLEN, "act2000isa")) {
+ ret = act2000_isa_reset(portbase);
+ release_region(portbase, ISA_REGION);
+ }
+ return ret;
+}
+
+static irqreturn_t
+act2000_isa_interrupt(int dummy, void *dev_id)
+{
+ act2000_card *card = dev_id;
+ u_char istatus;
+
+ istatus = (inb(ISA_PORT_ISR) & 0x07);
+ if (istatus & ISA_ISR_OUT) {
+ /* RX fifo has data */
+ istatus &= ISA_ISR_OUT_MASK;
+ outb(0, ISA_PORT_SIS);
+ act2000_isa_receive(card);
+ outb(ISA_SIS_INT, ISA_PORT_SIS);
+ }
+ if (istatus & ISA_ISR_ERR) {
+ /* Error Interrupt */
+ istatus &= ISA_ISR_ERR_MASK;
+ printk(KERN_WARNING "act2000: errIRQ\n");
+ }
+ if (istatus)
+ printk(KERN_DEBUG "act2000: ?IRQ %d %02x\n", card->irq, istatus);
+ return IRQ_HANDLED;
+}
+
+static void
+act2000_isa_select_irq(act2000_card *card)
+{
+ unsigned char reg;
+
+ reg = (inb(ISA_PORT_COR) & ~ISA_COR_IRQOFF) | ISA_COR_PERR;
+ switch (card->irq) {
+ case 3:
+ reg = ISA_COR_IRQ03;
+ break;
+ case 5:
+ reg = ISA_COR_IRQ05;
+ break;
+ case 7:
+ reg = ISA_COR_IRQ07;
+ break;
+ case 10:
+ reg = ISA_COR_IRQ10;
+ break;
+ case 11:
+ reg = ISA_COR_IRQ11;
+ break;
+ case 12:
+ reg = ISA_COR_IRQ12;
+ break;
+ case 15:
+ reg = ISA_COR_IRQ15;
+ break;
+ }
+ outb(reg, ISA_PORT_COR);
+}
+
+static void
+act2000_isa_enable_irq(act2000_card *card)
+{
+ act2000_isa_select_irq(card);
+ /* Enable READ irq */
+ outb(ISA_SIS_INT, ISA_PORT_SIS);
+}
+
+/*
+ * Install interrupt handler, enable irq on card.
+ * If irq is -1, choose next free irq, else irq is given explicitly.
+ */
+int
+act2000_isa_config_irq(act2000_card *card, short irq)
+{
+ int old_irq;
+
+ if (card->flags & ACT2000_FLAGS_IVALID) {
+ free_irq(card->irq, card);
+ }
+ card->flags &= ~ACT2000_FLAGS_IVALID;
+ outb(ISA_COR_IRQOFF, ISA_PORT_COR);
+ if (!irq)
+ return 0;
+
+ old_irq = card->irq;
+ card->irq = irq;
+ if (request_irq(irq, &act2000_isa_interrupt, 0, card->regname, card)) {
+ card->irq = old_irq;
+ card->flags |= ACT2000_FLAGS_IVALID;
+ printk(KERN_WARNING
+ "act2000: Could not request irq %d\n", irq);
+ return -EBUSY;
+ } else {
+ act2000_isa_select_irq(card);
+ /* Disable READ and WRITE irq */
+ outb(0, ISA_PORT_SIS);
+ outb(0, ISA_PORT_SOS);
+ }
+ return 0;
+}
+
+int
+act2000_isa_config_port(act2000_card *card, unsigned short portbase)
+{
+ if (card->flags & ACT2000_FLAGS_PVALID) {
+ release_region(card->port, ISA_REGION);
+ card->flags &= ~ACT2000_FLAGS_PVALID;
+ }
+ if (request_region(portbase, ACT2000_PORTLEN, card->regname) == NULL)
+ return -EBUSY;
+ else {
+ card->port = portbase;
+ card->flags |= ACT2000_FLAGS_PVALID;
+ return 0;
+ }
+}
+
+/*
+ * Release ressources, used by an adaptor.
+ */
+void
+act2000_isa_release(act2000_card *card)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&card->lock, flags);
+ if (card->flags & ACT2000_FLAGS_IVALID)
+ free_irq(card->irq, card);
+
+ card->flags &= ~ACT2000_FLAGS_IVALID;
+ if (card->flags & ACT2000_FLAGS_PVALID)
+ release_region(card->port, ISA_REGION);
+ card->flags &= ~ACT2000_FLAGS_PVALID;
+ spin_unlock_irqrestore(&card->lock, flags);
+}
+
+static int
+act2000_isa_writeb(act2000_card *card, u_char data)
+{
+ u_char timeout = 40;
+
+ while (timeout) {
+ if (inb(ISA_PORT_SOS) & ISA_SOS_READY) {
+ outb(data, ISA_PORT_SDO);
+ return 0;
+ } else {
+ timeout--;
+ udelay(10);
+ }
+ }
+ return 1;
+}
+
+static int
+act2000_isa_readb(act2000_card *card, u_char *data)
+{
+ u_char timeout = 40;
+
+ while (timeout) {
+ if (inb(ISA_PORT_SIS) & ISA_SIS_READY) {
+ *data = inb(ISA_PORT_SDI);
+ return 0;
+ } else {
+ timeout--;
+ udelay(10);
+ }
+ }
+ return 1;
+}
+
+void
+act2000_isa_receive(act2000_card *card)
+{
+ u_char c;
+
+ if (test_and_set_bit(ACT2000_LOCK_RX, (void *) &card->ilock) != 0)
+ return;
+ while (!act2000_isa_readb(card, &c)) {
+ if (card->idat.isa.rcvidx < 8) {
+ card->idat.isa.rcvhdr[card->idat.isa.rcvidx++] = c;
+ if (card->idat.isa.rcvidx == 8) {
+ int valid = actcapi_chkhdr(card, (actcapi_msghdr *)&card->idat.isa.rcvhdr);
+
+ if (valid) {
+ card->idat.isa.rcvlen = ((actcapi_msghdr *)&card->idat.isa.rcvhdr)->len;
+ card->idat.isa.rcvskb = dev_alloc_skb(card->idat.isa.rcvlen);
+ if (card->idat.isa.rcvskb == NULL) {
+ card->idat.isa.rcvignore = 1;
+ printk(KERN_WARNING
+ "act2000_isa_receive: no memory\n");
+ test_and_clear_bit(ACT2000_LOCK_RX, (void *) &card->ilock);
+ return;
+ }
+ memcpy(skb_put(card->idat.isa.rcvskb, 8), card->idat.isa.rcvhdr, 8);
+ card->idat.isa.rcvptr = skb_put(card->idat.isa.rcvskb, card->idat.isa.rcvlen - 8);
+ } else {
+ card->idat.isa.rcvidx = 0;
+ printk(KERN_WARNING
+ "act2000_isa_receive: Invalid CAPI msg\n");
+ {
+ int i; __u8 *p; __u8 *t; __u8 tmp[30];
+ for (i = 0, p = (__u8 *)&card->idat.isa.rcvhdr, t = tmp; i < 8; i++)
+ t += sprintf(t, "%02x ", *(p++));
+ printk(KERN_WARNING "act2000_isa_receive: %s\n", tmp);
+ }
+ }
+ }
+ } else {
+ if (!card->idat.isa.rcvignore)
+ *card->idat.isa.rcvptr++ = c;
+ if (++card->idat.isa.rcvidx >= card->idat.isa.rcvlen) {
+ if (!card->idat.isa.rcvignore) {
+ skb_queue_tail(&card->rcvq, card->idat.isa.rcvskb);
+ act2000_schedule_rx(card);
+ }
+ card->idat.isa.rcvidx = 0;
+ card->idat.isa.rcvlen = 8;
+ card->idat.isa.rcvignore = 0;
+ card->idat.isa.rcvskb = NULL;
+ card->idat.isa.rcvptr = card->idat.isa.rcvhdr;
+ }
+ }
+ }
+ if (!(card->flags & ACT2000_FLAGS_IVALID)) {
+ /* In polling mode, schedule myself */
+ if ((card->idat.isa.rcvidx) &&
+ (card->idat.isa.rcvignore ||
+ (card->idat.isa.rcvidx < card->idat.isa.rcvlen)))
+ act2000_schedule_poll(card);
+ }
+ test_and_clear_bit(ACT2000_LOCK_RX, (void *) &card->ilock);
+}
+
+void
+act2000_isa_send(act2000_card *card)
+{
+ unsigned long flags;
+ struct sk_buff *skb;
+ actcapi_msg *msg;
+ int l;
+
+ if (test_and_set_bit(ACT2000_LOCK_TX, (void *) &card->ilock) != 0)
+ return;
+ while (1) {
+ spin_lock_irqsave(&card->lock, flags);
+ if (!(card->sbuf)) {
+ if ((card->sbuf = skb_dequeue(&card->sndq))) {
+ card->ack_msg = card->sbuf->data;
+ msg = (actcapi_msg *)card->sbuf->data;
+ if ((msg->hdr.cmd.cmd == 0x86) &&
+ (msg->hdr.cmd.subcmd == 0)) {
+ /* Save flags in message */
+ card->need_b3ack = msg->msg.data_b3_req.flags;
+ msg->msg.data_b3_req.flags = 0;
+ }
+ }
+ }
+ spin_unlock_irqrestore(&card->lock, flags);
+ if (!(card->sbuf)) {
+ /* No more data to send */
+ test_and_clear_bit(ACT2000_LOCK_TX, (void *) &card->ilock);
+ return;
+ }
+ skb = card->sbuf;
+ l = 0;
+ while (skb->len) {
+ if (act2000_isa_writeb(card, *(skb->data))) {
+ /* Fifo is full, but more data to send */
+ test_and_clear_bit(ACT2000_LOCK_TX, (void *) &card->ilock);
+ /* Schedule myself */
+ act2000_schedule_tx(card);
+ return;
+ }
+ skb_pull(skb, 1);
+ l++;
+ }
+ msg = (actcapi_msg *)card->ack_msg;
+ if ((msg->hdr.cmd.cmd == 0x86) &&
+ (msg->hdr.cmd.subcmd == 0)) {
+ /*
+ * If it's user data, reset data-ptr
+ * and put skb into ackq.
+ */
+ skb->data = card->ack_msg;
+ /* Restore flags in message */
+ msg->msg.data_b3_req.flags = card->need_b3ack;
+ skb_queue_tail(&card->ackq, skb);
+ } else
+ dev_kfree_skb(skb);
+ card->sbuf = NULL;
+ }
+}
+
+/*
+ * Get firmware ID, check for 'ISDN' signature.
+ */
+static int
+act2000_isa_getid(act2000_card *card)
+{
+
+ act2000_fwid fid;
+ u_char *p = (u_char *)&fid;
+ int count = 0;
+
+ while (1) {
+ if (count > 510)
+ return -EPROTO;
+ if (act2000_isa_readb(card, p++))
+ break;
+ count++;
+ }
+ if (count <= 20) {
+ printk(KERN_WARNING "act2000: No Firmware-ID!\n");
+ return -ETIME;
+ }
+ *p = '\0';
+ fid.revlen[0] = '\0';
+ if (strcmp(fid.isdn, "ISDN")) {
+ printk(KERN_WARNING "act2000: Wrong Firmware-ID!\n");
+ return -EPROTO;
+ }
+ if ((p = strchr(fid.revision, '\n')))
+ *p = '\0';
+ printk(KERN_INFO "act2000: Firmware-ID: %s\n", fid.revision);
+ if (card->flags & ACT2000_FLAGS_IVALID) {
+ printk(KERN_DEBUG "Enabling Interrupts ...\n");
+ act2000_isa_enable_irq(card);
+ }
+ return 0;
+}
+
+/*
+ * Download microcode into card, check Firmware signature.
+ */
+int
+act2000_isa_download(act2000_card *card, act2000_ddef __user *cb)
+{
+ unsigned int length;
+ int l;
+ int c;
+ long timeout;
+ u_char *b;
+ u_char __user *p;
+ u_char *buf;
+ act2000_ddef cblock;
+
+ if (!act2000_isa_reset(card->port))
+ return -ENXIO;
+ msleep_interruptible(500);
+ if (copy_from_user(&cblock, cb, sizeof(cblock)))
+ return -EFAULT;
+ length = cblock.length;
+ p = cblock.buffer;
+ if (!access_ok(VERIFY_READ, p, length))
+ return -EFAULT;
+ buf = kmalloc(1024, GFP_KERNEL);
+ if (!buf)
+ return -ENOMEM;
+ timeout = 0;
+ while (length) {
+ l = (length > 1024) ? 1024 : length;
+ c = 0;
+ b = buf;
+ if (copy_from_user(buf, p, l)) {
+ kfree(buf);
+ return -EFAULT;
+ }
+ while (c < l) {
+ if (act2000_isa_writeb(card, *b++)) {
+ printk(KERN_WARNING
+ "act2000: loader timed out"
+ " len=%d c=%d\n", length, c);
+ kfree(buf);
+ return -ETIME;
+ }
+ c++;
+ }
+ length -= l;
+ p += l;
+ }
+ kfree(buf);
+ msleep_interruptible(500);
+ return (act2000_isa_getid(card));
+}
diff --git a/drivers/staging/i4l/act2000/act2000_isa.h b/drivers/staging/i4l/act2000/act2000_isa.h
new file mode 100644
index 000000000000..1a728984ede1
--- /dev/null
+++ b/drivers/staging/i4l/act2000/act2000_isa.h
@@ -0,0 +1,136 @@
+/* $Id: act2000_isa.h,v 1.4.6.1 2001/09/23 22:24:32 kai Exp $
+ *
+ * ISDN lowlevel-module for the IBM ISDN-S0 Active 2000 (ISA-Version).
+ *
+ * Author Fritz Elfert
+ * Copyright by Fritz Elfert <fritz@isdn4linux.de>
+ *
+ * This software may be used and distributed according to the terms
+ * of the GNU General Public License, incorporated herein by reference.
+ *
+ * Thanks to Friedemann Baitinger and IBM Germany
+ *
+ */
+
+#ifndef act2000_isa_h
+#define act2000_isa_h
+
+#define ISA_POLL_LOOP 40 /* Try to read-write before give up */
+
+typedef enum {
+ INT_NO_CHANGE = 0, /* Do not change the Mask */
+ INT_ON = 1, /* Set to Enable */
+ INT_OFF = 2, /* Set to Disable */
+} ISA_INT_T;
+
+/**************************************************************************/
+/* Configuration Register COR (RW) */
+/**************************************************************************/
+/* 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 */
+/* Soft Res| IRQM | IRQ Select | N/A | WAIT |Proc err */
+/**************************************************************************/
+#define ISA_COR 0 /* Offset for ISA config register */
+#define ISA_COR_PERR 0x01 /* Processor Error Enabled */
+#define ISA_COR_WS 0x02 /* Insert Wait State if 1 */
+#define ISA_COR_IRQOFF 0x38 /* No Interrupt */
+#define ISA_COR_IRQ07 0x30 /* IRQ 7 Enable */
+#define ISA_COR_IRQ05 0x28 /* IRQ 5 Enable */
+#define ISA_COR_IRQ03 0x20 /* IRQ 3 Enable */
+#define ISA_COR_IRQ10 0x18 /* IRQ 10 Enable */
+#define ISA_COR_IRQ11 0x10 /* IRQ 11 Enable */
+#define ISA_COR_IRQ12 0x08 /* IRQ 12 Enable */
+#define ISA_COR_IRQ15 0x00 /* IRQ 15 Enable */
+#define ISA_COR_IRQPULSE 0x40 /* 0 = Level 1 = Pulse Interrupt */
+#define ISA_COR_RESET 0x80 /* Soft Reset for Transputer */
+
+/**************************************************************************/
+/* Interrupt Source Register ISR (RO) */
+/**************************************************************************/
+/* 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 */
+/* N/A | N/A | N/A |Err sig |Ser ID |IN Intr |Out Intr| Error */
+/**************************************************************************/
+#define ISA_ISR 1 /* Offset for Interrupt Register */
+#define ISA_ISR_ERR 0x01 /* Error Interrupt */
+#define ISA_ISR_OUT 0x02 /* Output Interrupt */
+#define ISA_ISR_INP 0x04 /* Input Interrupt */
+#define ISA_ISR_SERIAL 0x08 /* Read out Serial ID after Reset */
+#define ISA_ISR_ERRSIG 0x10 /* Error Signal Input */
+#define ISA_ISR_ERR_MASK 0xfe /* Mask Error Interrupt */
+#define ISA_ISR_OUT_MASK 0xfd /* Mask Output Interrupt */
+#define ISA_ISR_INP_MASK 0xfb /* Mask Input Interrupt */
+
+/* Signature delivered after Reset at ISA_ISR_SERIAL (LSB first) */
+#define ISA_SER_ID 0x0201 /* ID for ISA Card */
+
+/**************************************************************************/
+/* EEPROM Register EPR (RW) */
+/**************************************************************************/
+/* 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 */
+/* N/A | N/A | N/A |ROM Hold| ROM CS |ROM CLK | ROM IN |ROM Out */
+/**************************************************************************/
+#define ISA_EPR 2 /* Offset for this Register */
+#define ISA_EPR_OUT 0x01 /* Rome Register Out (RO) */
+#define ISA_EPR_IN 0x02 /* Rom Register In (WR) */
+#define ISA_EPR_CLK 0x04 /* Rom Clock (WR) */
+#define ISA_EPR_CS 0x08 /* Rom Cip Select (WR) */
+#define ISA_EPR_HOLD 0x10 /* Rom Hold Signal (WR) */
+
+/**************************************************************************/
+/* EEPROM enable Register EER (unused) */
+/**************************************************************************/
+#define ISA_EER 3 /* Offset for this Register */
+
+/**************************************************************************/
+/* SLC Data Input SDI (RO) */
+/**************************************************************************/
+#define ISA_SDI 4 /* Offset for this Register */
+
+/**************************************************************************/
+/* SLC Data Output SDO (WO) */
+/**************************************************************************/
+#define ISA_SDO 5 /* Offset for this Register */
+
+/**************************************************************************/
+/* IMS C011 Mode 2 Input Status Register for INMOS CPU SIS (RW) */
+/**************************************************************************/
+/* 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 */
+/* N/A | N/A | N/A | N/A | N/A | N/A |Int Ena |Data Pre */
+/**************************************************************************/
+#define ISA_SIS 6 /* Offset for this Register */
+#define ISA_SIS_READY 0x01 /* If 1 : data is available */
+#define ISA_SIS_INT 0x02 /* Enable Interrupt for READ */
+
+/**************************************************************************/
+/* IMS C011 Mode 2 Output Status Register from INMOS CPU SOS (RW) */
+/**************************************************************************/
+/* 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 */
+/* N/A | N/A | N/A | N/A | N/A | N/A |Int Ena |Out Rdy */
+/**************************************************************************/
+#define ISA_SOS 7 /* Offset for this Register */
+#define ISA_SOS_READY 0x01 /* If 1 : we can write Data */
+#define ISA_SOS_INT 0x02 /* Enable Interrupt for WRITE */
+
+#define ISA_REGION 8 /* Number of Registers */
+
+
+/* Macros for accessing ports */
+#define ISA_PORT_COR (card->port + ISA_COR)
+#define ISA_PORT_ISR (card->port + ISA_ISR)
+#define ISA_PORT_EPR (card->port + ISA_EPR)
+#define ISA_PORT_EER (card->port + ISA_EER)
+#define ISA_PORT_SDI (card->port + ISA_SDI)
+#define ISA_PORT_SDO (card->port + ISA_SDO)
+#define ISA_PORT_SIS (card->port + ISA_SIS)
+#define ISA_PORT_SOS (card->port + ISA_SOS)
+
+/* Prototypes */
+
+extern int act2000_isa_detect(unsigned short portbase);
+extern int act2000_isa_config_irq(act2000_card *card, short irq);
+extern int act2000_isa_config_port(act2000_card *card, unsigned short portbase);
+extern int act2000_isa_download(act2000_card *card, act2000_ddef __user *cb);
+extern void act2000_isa_release(act2000_card *card);
+extern void act2000_isa_receive(act2000_card *card);
+extern void act2000_isa_send(act2000_card *card);
+
+#endif /* act2000_isa_h */
diff --git a/drivers/staging/i4l/act2000/capi.c b/drivers/staging/i4l/act2000/capi.c
new file mode 100644
index 000000000000..3f66ca20b5e5
--- /dev/null
+++ b/drivers/staging/i4l/act2000/capi.c
@@ -0,0 +1,1180 @@
+/* $Id: capi.c,v 1.9.6.2 2001/09/23 22:24:32 kai Exp $
+ *
+ * ISDN lowlevel-module for the IBM ISDN-S0 Active 2000.
+ * CAPI encoder/decoder
+ *
+ * Author Fritz Elfert
+ * Copyright by Fritz Elfert <fritz@isdn4linux.de>
+ *
+ * This software may be used and distributed according to the terms
+ * of the GNU General Public License, incorporated herein by reference.
+ *
+ * Thanks to Friedemann Baitinger and IBM Germany
+ *
+ */
+
+#include "act2000.h"
+#include "capi.h"
+
+static actcapi_msgdsc valid_msg[] = {
+ {{ 0x86, 0x02}, "DATA_B3_IND"}, /* DATA_B3_IND/CONF must be first because of speed!!! */
+ {{ 0x86, 0x01}, "DATA_B3_CONF"},
+ {{ 0x02, 0x01}, "CONNECT_CONF"},
+ {{ 0x02, 0x02}, "CONNECT_IND"},
+ {{ 0x09, 0x01}, "CONNECT_INFO_CONF"},
+ {{ 0x03, 0x02}, "CONNECT_ACTIVE_IND"},
+ {{ 0x04, 0x01}, "DISCONNECT_CONF"},
+ {{ 0x04, 0x02}, "DISCONNECT_IND"},
+ {{ 0x05, 0x01}, "LISTEN_CONF"},
+ {{ 0x06, 0x01}, "GET_PARAMS_CONF"},
+ {{ 0x07, 0x01}, "INFO_CONF"},
+ {{ 0x07, 0x02}, "INFO_IND"},
+ {{ 0x08, 0x01}, "DATA_CONF"},
+ {{ 0x08, 0x02}, "DATA_IND"},
+ {{ 0x40, 0x01}, "SELECT_B2_PROTOCOL_CONF"},
+ {{ 0x80, 0x01}, "SELECT_B3_PROTOCOL_CONF"},
+ {{ 0x81, 0x01}, "LISTEN_B3_CONF"},
+ {{ 0x82, 0x01}, "CONNECT_B3_CONF"},
+ {{ 0x82, 0x02}, "CONNECT_B3_IND"},
+ {{ 0x83, 0x02}, "CONNECT_B3_ACTIVE_IND"},
+ {{ 0x84, 0x01}, "DISCONNECT_B3_CONF"},
+ {{ 0x84, 0x02}, "DISCONNECT_B3_IND"},
+ {{ 0x85, 0x01}, "GET_B3_PARAMS_CONF"},
+ {{ 0x01, 0x01}, "RESET_B3_CONF"},
+ {{ 0x01, 0x02}, "RESET_B3_IND"},
+ /* {{ 0x87, 0x02, "HANDSET_IND"}, not implemented */
+ {{ 0xff, 0x01}, "MANUFACTURER_CONF"},
+ {{ 0xff, 0x02}, "MANUFACTURER_IND"},
+#ifdef DEBUG_MSG
+ /* Requests */
+ {{ 0x01, 0x00}, "RESET_B3_REQ"},
+ {{ 0x02, 0x00}, "CONNECT_REQ"},
+ {{ 0x04, 0x00}, "DISCONNECT_REQ"},
+ {{ 0x05, 0x00}, "LISTEN_REQ"},
+ {{ 0x06, 0x00}, "GET_PARAMS_REQ"},
+ {{ 0x07, 0x00}, "INFO_REQ"},
+ {{ 0x08, 0x00}, "DATA_REQ"},
+ {{ 0x09, 0x00}, "CONNECT_INFO_REQ"},
+ {{ 0x40, 0x00}, "SELECT_B2_PROTOCOL_REQ"},
+ {{ 0x80, 0x00}, "SELECT_B3_PROTOCOL_REQ"},
+ {{ 0x81, 0x00}, "LISTEN_B3_REQ"},
+ {{ 0x82, 0x00}, "CONNECT_B3_REQ"},
+ {{ 0x84, 0x00}, "DISCONNECT_B3_REQ"},
+ {{ 0x85, 0x00}, "GET_B3_PARAMS_REQ"},
+ {{ 0x86, 0x00}, "DATA_B3_REQ"},
+ {{ 0xff, 0x00}, "MANUFACTURER_REQ"},
+ /* Responses */
+ {{ 0x01, 0x03}, "RESET_B3_RESP"},
+ {{ 0x02, 0x03}, "CONNECT_RESP"},
+ {{ 0x03, 0x03}, "CONNECT_ACTIVE_RESP"},
+ {{ 0x04, 0x03}, "DISCONNECT_RESP"},
+ {{ 0x07, 0x03}, "INFO_RESP"},
+ {{ 0x08, 0x03}, "DATA_RESP"},
+ {{ 0x82, 0x03}, "CONNECT_B3_RESP"},
+ {{ 0x83, 0x03}, "CONNECT_B3_ACTIVE_RESP"},
+ {{ 0x84, 0x03}, "DISCONNECT_B3_RESP"},
+ {{ 0x86, 0x03}, "DATA_B3_RESP"},
+ {{ 0xff, 0x03}, "MANUFACTURER_RESP"},
+#endif
+ {{ 0x00, 0x00}, NULL},
+};
+#define num_valid_imsg 27 /* MANUFACTURER_IND */
+
+/*
+ * Check for a valid incoming CAPI message.
+ * Return:
+ * 0 = Invalid message
+ * 1 = Valid message, no B-Channel-data
+ * 2 = Valid message, B-Channel-data
+ */
+int
+actcapi_chkhdr(act2000_card *card, actcapi_msghdr *hdr)
+{
+ int i;
+
+ if (hdr->applicationID != 1)
+ return 0;
+ if (hdr->len < 9)
+ return 0;
+ for (i = 0; i < num_valid_imsg; i++)
+ if ((hdr->cmd.cmd == valid_msg[i].cmd.cmd) &&
+ (hdr->cmd.subcmd == valid_msg[i].cmd.subcmd)) {
+ return (i ? 1 : 2);
+ }
+ return 0;
+}
+
+#define ACTCAPI_MKHDR(l, c, s) { \
+ skb = alloc_skb(l + 8, GFP_ATOMIC); \
+ if (skb) { \
+ m = (actcapi_msg *)skb_put(skb, l + 8); \
+ m->hdr.len = l + 8; \
+ m->hdr.applicationID = 1; \
+ m->hdr.cmd.cmd = c; \
+ m->hdr.cmd.subcmd = s; \
+ m->hdr.msgnum = actcapi_nextsmsg(card); \
+ } else m = NULL; \
+ }
+
+#define ACTCAPI_CHKSKB if (!skb) { \
+ printk(KERN_WARNING "actcapi: alloc_skb failed\n"); \
+ return; \
+ }
+
+#define ACTCAPI_QUEUE_TX { \
+ actcapi_debug_msg(skb, 1); \
+ skb_queue_tail(&card->sndq, skb); \
+ act2000_schedule_tx(card); \
+ }
+
+int
+actcapi_listen_req(act2000_card *card)
+{
+ __u16 eazmask = 0;
+ int i;
+ actcapi_msg *m;
+ struct sk_buff *skb;
+
+ for (i = 0; i < ACT2000_BCH; i++)
+ eazmask |= card->bch[i].eazmask;
+ ACTCAPI_MKHDR(9, 0x05, 0x00);
+ if (!skb) {
+ printk(KERN_WARNING "actcapi: alloc_skb failed\n");
+ return -ENOMEM;
+ }
+ m->msg.listen_req.controller = 0;
+ m->msg.listen_req.infomask = 0x3f; /* All information */
+ m->msg.listen_req.eazmask = eazmask;
+ m->msg.listen_req.simask = (eazmask) ? 0x86 : 0; /* All SI's */
+ ACTCAPI_QUEUE_TX;
+ return 0;
+}
+
+int
+actcapi_connect_req(act2000_card *card, act2000_chan *chan, char *phone,
+ char eaz, int si1, int si2)
+{
+ actcapi_msg *m;
+ struct sk_buff *skb;
+
+ ACTCAPI_MKHDR((11 + strlen(phone)), 0x02, 0x00);
+ if (!skb) {
+ printk(KERN_WARNING "actcapi: alloc_skb failed\n");
+ chan->fsm_state = ACT2000_STATE_NULL;
+ return -ENOMEM;
+ }
+ m->msg.connect_req.controller = 0;
+ m->msg.connect_req.bchan = 0x83;
+ m->msg.connect_req.infomask = 0x3f;
+ m->msg.connect_req.si1 = si1;
+ m->msg.connect_req.si2 = si2;
+ m->msg.connect_req.eaz = eaz ? eaz : '0';
+ m->msg.connect_req.addr.len = strlen(phone) + 1;
+ m->msg.connect_req.addr.tnp = 0x81;
+ memcpy(m->msg.connect_req.addr.num, phone, strlen(phone));
+ chan->callref = m->hdr.msgnum;
+ ACTCAPI_QUEUE_TX;
+ return 0;
+}
+
+static void
+actcapi_connect_b3_req(act2000_card *card, act2000_chan *chan)
+{
+ actcapi_msg *m;
+ struct sk_buff *skb;
+
+ ACTCAPI_MKHDR(17, 0x82, 0x00);
+ ACTCAPI_CHKSKB;
+ m->msg.connect_b3_req.plci = chan->plci;
+ memset(&m->msg.connect_b3_req.ncpi, 0,
+ sizeof(m->msg.connect_b3_req.ncpi));
+ m->msg.connect_b3_req.ncpi.len = 13;
+ m->msg.connect_b3_req.ncpi.modulo = 8;
+ ACTCAPI_QUEUE_TX;
+}
+
+/*
+ * Set net type (1TR6) or (EDSS1)
+ */
+int
+actcapi_manufacturer_req_net(act2000_card *card)
+{
+ actcapi_msg *m;
+ struct sk_buff *skb;
+
+ ACTCAPI_MKHDR(5, 0xff, 0x00);
+ if (!skb) {
+ printk(KERN_WARNING "actcapi: alloc_skb failed\n");
+ return -ENOMEM;
+ }
+ m->msg.manufacturer_req_net.manuf_msg = 0x11;
+ m->msg.manufacturer_req_net.controller = 1;
+ m->msg.manufacturer_req_net.nettype = (card->ptype == ISDN_PTYPE_EURO) ? 1 : 0;
+ ACTCAPI_QUEUE_TX;
+ printk(KERN_INFO "act2000 %s: D-channel protocol now %s\n",
+ card->interface.id, (card->ptype == ISDN_PTYPE_EURO) ? "euro" : "1tr6");
+ card->interface.features &=
+ ~(ISDN_FEATURE_P_UNKNOWN | ISDN_FEATURE_P_EURO | ISDN_FEATURE_P_1TR6);
+ card->interface.features |=
+ ((card->ptype == ISDN_PTYPE_EURO) ? ISDN_FEATURE_P_EURO : ISDN_FEATURE_P_1TR6);
+ return 0;
+}
+
+/*
+ * Switch V.42 on or off
+ */
+#if 0
+int
+actcapi_manufacturer_req_v42(act2000_card *card, ulong arg)
+{
+ actcapi_msg *m;
+ struct sk_buff *skb;
+
+ ACTCAPI_MKHDR(8, 0xff, 0x00);
+ if (!skb) {
+
+ printk(KERN_WARNING "actcapi: alloc_skb failed\n");
+ return -ENOMEM;
+ }
+ m->msg.manufacturer_req_v42.manuf_msg = 0x10;
+ m->msg.manufacturer_req_v42.controller = 0;
+ m->msg.manufacturer_req_v42.v42control = (arg ? 1 : 0);
+ ACTCAPI_QUEUE_TX;
+ return 0;
+}
+#endif /* 0 */
+
+/*
+ * Set error-handler
+ */
+int
+actcapi_manufacturer_req_errh(act2000_card *card)
+{
+ actcapi_msg *m;
+ struct sk_buff *skb;
+
+ ACTCAPI_MKHDR(4, 0xff, 0x00);
+ if (!skb) {
+
+ printk(KERN_WARNING "actcapi: alloc_skb failed\n");
+ return -ENOMEM;
+ }
+ m->msg.manufacturer_req_err.manuf_msg = 0x03;
+ m->msg.manufacturer_req_err.controller = 0;
+ ACTCAPI_QUEUE_TX;
+ return 0;
+}
+
+/*
+ * Set MSN-Mapping.
+ */
+int
+actcapi_manufacturer_req_msn(act2000_card *card)
+{
+ msn_entry *p = card->msn_list;
+ actcapi_msg *m;
+ struct sk_buff *skb;
+ int len;
+
+ while (p) {
+ int i;
+
+ len = strlen(p->msn);
+ for (i = 0; i < 2; i++) {
+ ACTCAPI_MKHDR(6 + len, 0xff, 0x00);
+ if (!skb) {
+ printk(KERN_WARNING "actcapi: alloc_skb failed\n");
+ return -ENOMEM;
+ }
+ m->msg.manufacturer_req_msn.manuf_msg = 0x13 + i;
+ m->msg.manufacturer_req_msn.controller = 0;
+ m->msg.manufacturer_req_msn.msnmap.eaz = p->eaz;
+ m->msg.manufacturer_req_msn.msnmap.len = len;
+ memcpy(m->msg.manufacturer_req_msn.msnmap.msn, p->msn, len);
+ ACTCAPI_QUEUE_TX;
+ }
+ p = p->next;
+ }
+ return 0;
+}
+
+void
+actcapi_select_b2_protocol_req(act2000_card *card, act2000_chan *chan)
+{
+ actcapi_msg *m;
+ struct sk_buff *skb;
+
+ ACTCAPI_MKHDR(10, 0x40, 0x00);
+ ACTCAPI_CHKSKB;
+ m->msg.select_b2_protocol_req.plci = chan->plci;
+ memset(&m->msg.select_b2_protocol_req.dlpd, 0,
+ sizeof(m->msg.select_b2_protocol_req.dlpd));
+ m->msg.select_b2_protocol_req.dlpd.len = 6;
+ switch (chan->l2prot) {
+ case ISDN_PROTO_L2_TRANS:
+ m->msg.select_b2_protocol_req.protocol = 0x03;
+ m->msg.select_b2_protocol_req.dlpd.dlen = 4000;
+ break;
+ case ISDN_PROTO_L2_HDLC:
+ m->msg.select_b2_protocol_req.protocol = 0x02;
+ m->msg.select_b2_protocol_req.dlpd.dlen = 4000;
+ break;
+ case ISDN_PROTO_L2_X75I:
+ case ISDN_PROTO_L2_X75UI:
+ case ISDN_PROTO_L2_X75BUI:
+ m->msg.select_b2_protocol_req.protocol = 0x01;
+ m->msg.select_b2_protocol_req.dlpd.dlen = 4000;
+ m->msg.select_b2_protocol_req.dlpd.laa = 3;
+ m->msg.select_b2_protocol_req.dlpd.lab = 1;
+ m->msg.select_b2_protocol_req.dlpd.win = 7;
+ m->msg.select_b2_protocol_req.dlpd.modulo = 8;
+ break;
+ }
+ ACTCAPI_QUEUE_TX;
+}
+
+static void
+actcapi_select_b3_protocol_req(act2000_card *card, act2000_chan *chan)
+{
+ actcapi_msg *m;
+ struct sk_buff *skb;
+
+ ACTCAPI_MKHDR(17, 0x80, 0x00);
+ ACTCAPI_CHKSKB;
+ m->msg.select_b3_protocol_req.plci = chan->plci;
+ memset(&m->msg.select_b3_protocol_req.ncpd, 0,
+ sizeof(m->msg.select_b3_protocol_req.ncpd));
+ switch (chan->l3prot) {
+ case ISDN_PROTO_L3_TRANS:
+ m->msg.select_b3_protocol_req.protocol = 0x04;
+ m->msg.select_b3_protocol_req.ncpd.len = 13;
+ m->msg.select_b3_protocol_req.ncpd.modulo = 8;
+ break;
+ }
+ ACTCAPI_QUEUE_TX;
+}
+
+static void
+actcapi_listen_b3_req(act2000_card *card, act2000_chan *chan)
+{
+ actcapi_msg *m;
+ struct sk_buff *skb;
+
+ ACTCAPI_MKHDR(2, 0x81, 0x00);
+ ACTCAPI_CHKSKB;
+ m->msg.listen_b3_req.plci = chan->plci;
+ ACTCAPI_QUEUE_TX;
+}
+
+static void
+actcapi_disconnect_req(act2000_card *card, act2000_chan *chan)
+{
+ actcapi_msg *m;
+ struct sk_buff *skb;
+
+ ACTCAPI_MKHDR(3, 0x04, 0x00);
+ ACTCAPI_CHKSKB;
+ m->msg.disconnect_req.plci = chan->plci;
+ m->msg.disconnect_req.cause = 0;
+ ACTCAPI_QUEUE_TX;
+}
+
+void
+actcapi_disconnect_b3_req(act2000_card *card, act2000_chan *chan)
+{
+ actcapi_msg *m;
+ struct sk_buff *skb;
+
+ ACTCAPI_MKHDR(17, 0x84, 0x00);
+ ACTCAPI_CHKSKB;
+ m->msg.disconnect_b3_req.ncci = chan->ncci;
+ memset(&m->msg.disconnect_b3_req.ncpi, 0,
+ sizeof(m->msg.disconnect_b3_req.ncpi));
+ m->msg.disconnect_b3_req.ncpi.len = 13;
+ m->msg.disconnect_b3_req.ncpi.modulo = 8;
+ chan->fsm_state = ACT2000_STATE_BHWAIT;
+ ACTCAPI_QUEUE_TX;
+}
+
+void
+actcapi_connect_resp(act2000_card *card, act2000_chan *chan, __u8 cause)
+{
+ actcapi_msg *m;
+ struct sk_buff *skb;
+
+ ACTCAPI_MKHDR(3, 0x02, 0x03);
+ ACTCAPI_CHKSKB;
+ m->msg.connect_resp.plci = chan->plci;
+ m->msg.connect_resp.rejectcause = cause;
+ if (cause) {
+ chan->fsm_state = ACT2000_STATE_NULL;
+ chan->plci = 0x8000;
+ } else
+ chan->fsm_state = ACT2000_STATE_IWAIT;
+ ACTCAPI_QUEUE_TX;
+}
+
+static void
+actcapi_connect_active_resp(act2000_card *card, act2000_chan *chan)
+{
+ actcapi_msg *m;
+ struct sk_buff *skb;
+
+ ACTCAPI_MKHDR(2, 0x03, 0x03);
+ ACTCAPI_CHKSKB;
+ m->msg.connect_resp.plci = chan->plci;
+ if (chan->fsm_state == ACT2000_STATE_IWAIT)
+ chan->fsm_state = ACT2000_STATE_IBWAIT;
+ ACTCAPI_QUEUE_TX;
+}
+
+static void
+actcapi_connect_b3_resp(act2000_card *card, act2000_chan *chan, __u8 rejectcause)
+{
+ actcapi_msg *m;
+ struct sk_buff *skb;
+
+ ACTCAPI_MKHDR((rejectcause ? 3 : 17), 0x82, 0x03);
+ ACTCAPI_CHKSKB;
+ m->msg.connect_b3_resp.ncci = chan->ncci;
+ m->msg.connect_b3_resp.rejectcause = rejectcause;
+ if (!rejectcause) {
+ memset(&m->msg.connect_b3_resp.ncpi, 0,
+ sizeof(m->msg.connect_b3_resp.ncpi));
+ m->msg.connect_b3_resp.ncpi.len = 13;
+ m->msg.connect_b3_resp.ncpi.modulo = 8;
+ chan->fsm_state = ACT2000_STATE_BWAIT;
+ }
+ ACTCAPI_QUEUE_TX;
+}
+
+static void
+actcapi_connect_b3_active_resp(act2000_card *card, act2000_chan *chan)
+{
+ actcapi_msg *m;
+ struct sk_buff *skb;
+
+ ACTCAPI_MKHDR(2, 0x83, 0x03);
+ ACTCAPI_CHKSKB;
+ m->msg.connect_b3_active_resp.ncci = chan->ncci;
+ chan->fsm_state = ACT2000_STATE_ACTIVE;
+ ACTCAPI_QUEUE_TX;
+}
+
+static void
+actcapi_info_resp(act2000_card *card, act2000_chan *chan)
+{
+ actcapi_msg *m;
+ struct sk_buff *skb;
+
+ ACTCAPI_MKHDR(2, 0x07, 0x03);
+ ACTCAPI_CHKSKB;
+ m->msg.info_resp.plci = chan->plci;
+ ACTCAPI_QUEUE_TX;
+}
+
+static void
+actcapi_disconnect_b3_resp(act2000_card *card, act2000_chan *chan)
+{
+ actcapi_msg *m;
+ struct sk_buff *skb;
+
+ ACTCAPI_MKHDR(2, 0x84, 0x03);
+ ACTCAPI_CHKSKB;
+ m->msg.disconnect_b3_resp.ncci = chan->ncci;
+ chan->ncci = 0x8000;
+ chan->queued = 0;
+ ACTCAPI_QUEUE_TX;
+}
+
+static void
+actcapi_disconnect_resp(act2000_card *card, act2000_chan *chan)
+{
+ actcapi_msg *m;
+ struct sk_buff *skb;
+
+ ACTCAPI_MKHDR(2, 0x04, 0x03);
+ ACTCAPI_CHKSKB;
+ m->msg.disconnect_resp.plci = chan->plci;
+ chan->plci = 0x8000;
+ ACTCAPI_QUEUE_TX;
+}
+
+static int
+new_plci(act2000_card *card, __u16 plci)
+{
+ int i;
+ for (i = 0; i < ACT2000_BCH; i++)
+ if (card->bch[i].plci == 0x8000) {
+ card->bch[i].plci = plci;
+ return i;
+ }
+ return -1;
+}
+
+static int
+find_plci(act2000_card *card, __u16 plci)
+{
+ int i;
+ for (i = 0; i < ACT2000_BCH; i++)
+ if (card->bch[i].plci == plci)
+ return i;
+ return -1;
+}
+
+static int
+find_ncci(act2000_card *card, __u16 ncci)
+{
+ int i;
+ for (i = 0; i < ACT2000_BCH; i++)
+ if (card->bch[i].ncci == ncci)
+ return i;
+ return -1;
+}
+
+static int
+find_dialing(act2000_card *card, __u16 callref)
+{
+ int i;
+ for (i = 0; i < ACT2000_BCH; i++)
+ if ((card->bch[i].callref == callref) &&
+ (card->bch[i].fsm_state == ACT2000_STATE_OCALL))
+ return i;
+ return -1;
+}
+
+static int
+actcapi_data_b3_ind(act2000_card *card, struct sk_buff *skb) {
+ __u16 plci;
+ __u16 ncci;
+ __u16 controller;
+ __u8 blocknr;
+ int chan;
+ actcapi_msg *msg = (actcapi_msg *)skb->data;
+
+ EVAL_NCCI(msg->msg.data_b3_ind.fakencci, plci, controller, ncci);
+ chan = find_ncci(card, ncci);
+ if (chan < 0)
+ return 0;
+ if (card->bch[chan].fsm_state != ACT2000_STATE_ACTIVE)
+ return 0;
+ if (card->bch[chan].plci != plci)
+ return 0;
+ blocknr = msg->msg.data_b3_ind.blocknr;
+ skb_pull(skb, 19);
+ card->interface.rcvcallb_skb(card->myid, chan, skb);
+ if (!(skb = alloc_skb(11, GFP_ATOMIC))) {
+ printk(KERN_WARNING "actcapi: alloc_skb failed\n");
+ return 1;
+ }
+ msg = (actcapi_msg *)skb_put(skb, 11);
+ msg->hdr.len = 11;
+ msg->hdr.applicationID = 1;
+ msg->hdr.cmd.cmd = 0x86;
+ msg->hdr.cmd.subcmd = 0x03;
+ msg->hdr.msgnum = actcapi_nextsmsg(card);
+ msg->msg.data_b3_resp.ncci = ncci;
+ msg->msg.data_b3_resp.blocknr = blocknr;
+ ACTCAPI_QUEUE_TX;
+ return 1;
+}
+
+/*
+ * Walk over ackq, unlink DATA_B3_REQ from it, if
+ * ncci and blocknr are matching.
+ * Decrement queued-bytes counter.
+ */
+static int
+handle_ack(act2000_card *card, act2000_chan *chan, __u8 blocknr) {
+ unsigned long flags;
+ struct sk_buff *skb;
+ struct sk_buff *tmp;
+ struct actcapi_msg *m;
+ int ret = 0;
+
+ spin_lock_irqsave(&card->lock, flags);
+ skb = skb_peek(&card->ackq);
+ spin_unlock_irqrestore(&card->lock, flags);
+ if (!skb) {
+ printk(KERN_WARNING "act2000: handle_ack nothing found!\n");
+ return 0;
+ }
+ tmp = skb;
+ while (1) {
+ m = (actcapi_msg *)tmp->data;
+ if ((((m->msg.data_b3_req.fakencci >> 8) & 0xff) == chan->ncci) &&
+ (m->msg.data_b3_req.blocknr == blocknr)) {
+ /* found corresponding DATA_B3_REQ */
+ skb_unlink(tmp, &card->ackq);
+ chan->queued -= m->msg.data_b3_req.datalen;
+ if (m->msg.data_b3_req.flags)
+ ret = m->msg.data_b3_req.datalen;
+ dev_kfree_skb(tmp);
+ if (chan->queued < 0)
+ chan->queued = 0;
+ return ret;
+ }
+ spin_lock_irqsave(&card->lock, flags);
+ tmp = skb_peek((struct sk_buff_head *)tmp);
+ spin_unlock_irqrestore(&card->lock, flags);
+ if ((tmp == skb) || (tmp == NULL)) {
+ /* reached end of queue */
+ printk(KERN_WARNING "act2000: handle_ack nothing found!\n");
+ return 0;
+ }
+ }
+}
+
+void
+actcapi_dispatch(struct work_struct *work)
+{
+ struct act2000_card *card =
+ container_of(work, struct act2000_card, rcv_tq);
+ struct sk_buff *skb;
+ actcapi_msg *msg;
+ __u16 ccmd;
+ int chan;
+ int len;
+ act2000_chan *ctmp;
+ isdn_ctrl cmd;
+ char tmp[170];
+
+ while ((skb = skb_dequeue(&card->rcvq))) {
+ actcapi_debug_msg(skb, 0);
+ msg = (actcapi_msg *)skb->data;
+ ccmd = ((msg->hdr.cmd.cmd << 8) | msg->hdr.cmd.subcmd);
+ switch (ccmd) {
+ case 0x8602:
+ /* DATA_B3_IND */
+ if (actcapi_data_b3_ind(card, skb))
+ return;
+ break;
+ case 0x8601:
+ /* DATA_B3_CONF */
+ chan = find_ncci(card, msg->msg.data_b3_conf.ncci);
+ if ((chan >= 0) && (card->bch[chan].fsm_state == ACT2000_STATE_ACTIVE)) {
+ if (msg->msg.data_b3_conf.info != 0)
+ printk(KERN_WARNING "act2000: DATA_B3_CONF: %04x\n",
+ msg->msg.data_b3_conf.info);
+ len = handle_ack(card, &card->bch[chan],
+ msg->msg.data_b3_conf.blocknr);
+ if (len) {
+ cmd.driver = card->myid;
+ cmd.command = ISDN_STAT_BSENT;
+ cmd.arg = chan;
+ cmd.parm.length = len;
+ card->interface.statcallb(&cmd);
+ }
+ }
+ break;
+ case 0x0201:
+ /* CONNECT_CONF */
+ chan = find_dialing(card, msg->hdr.msgnum);
+ if (chan >= 0) {
+ if (msg->msg.connect_conf.info) {
+ card->bch[chan].fsm_state = ACT2000_STATE_NULL;
+ cmd.driver = card->myid;
+ cmd.command = ISDN_STAT_DHUP;
+ cmd.arg = chan;
+ card->interface.statcallb(&cmd);
+ } else {
+ card->bch[chan].fsm_state = ACT2000_STATE_OWAIT;
+ card->bch[chan].plci = msg->msg.connect_conf.plci;
+ }
+ }
+ break;
+ case 0x0202:
+ /* CONNECT_IND */
+ chan = new_plci(card, msg->msg.connect_ind.plci);
+ if (chan < 0) {
+ ctmp = (act2000_chan *)tmp;
+ ctmp->plci = msg->msg.connect_ind.plci;
+ actcapi_connect_resp(card, ctmp, 0x11); /* All Card-Cannels busy */
+ } else {
+ card->bch[chan].fsm_state = ACT2000_STATE_ICALL;
+ cmd.driver = card->myid;
+ cmd.command = ISDN_STAT_ICALL;
+ cmd.arg = chan;
+ cmd.parm.setup.si1 = msg->msg.connect_ind.si1;
+ cmd.parm.setup.si2 = msg->msg.connect_ind.si2;
+ if (card->ptype == ISDN_PTYPE_EURO)
+ strcpy(cmd.parm.setup.eazmsn,
+ act2000_find_eaz(card, msg->msg.connect_ind.eaz));
+ else {
+ cmd.parm.setup.eazmsn[0] = msg->msg.connect_ind.eaz;
+ cmd.parm.setup.eazmsn[1] = 0;
+ }
+ memset(cmd.parm.setup.phone, 0, sizeof(cmd.parm.setup.phone));
+ memcpy(cmd.parm.setup.phone, msg->msg.connect_ind.addr.num,
+ msg->msg.connect_ind.addr.len - 1);
+ cmd.parm.setup.plan = msg->msg.connect_ind.addr.tnp;
+ cmd.parm.setup.screen = 0;
+ if (card->interface.statcallb(&cmd) == 2)
+ actcapi_connect_resp(card, &card->bch[chan], 0x15); /* Reject Call */
+ }
+ break;
+ case 0x0302:
+ /* CONNECT_ACTIVE_IND */
+ chan = find_plci(card, msg->msg.connect_active_ind.plci);
+ if (chan >= 0)
+ switch (card->bch[chan].fsm_state) {
+ case ACT2000_STATE_IWAIT:
+ actcapi_connect_active_resp(card, &card->bch[chan]);
+ break;
+ case ACT2000_STATE_OWAIT:
+ actcapi_connect_active_resp(card, &card->bch[chan]);
+ actcapi_select_b2_protocol_req(card, &card->bch[chan]);
+ break;
+ }
+ break;
+ case 0x8202:
+ /* CONNECT_B3_IND */
+ chan = find_plci(card, msg->msg.connect_b3_ind.plci);
+ if ((chan >= 0) && (card->bch[chan].fsm_state == ACT2000_STATE_IBWAIT)) {
+ card->bch[chan].ncci = msg->msg.connect_b3_ind.ncci;
+ actcapi_connect_b3_resp(card, &card->bch[chan], 0);
+ } else {
+ ctmp = (act2000_chan *)tmp;
+ ctmp->ncci = msg->msg.connect_b3_ind.ncci;
+ actcapi_connect_b3_resp(card, ctmp, 0x11); /* All Card-Cannels busy */
+ }
+ break;
+ case 0x8302:
+ /* CONNECT_B3_ACTIVE_IND */
+ chan = find_ncci(card, msg->msg.connect_b3_active_ind.ncci);
+ if ((chan >= 0) && (card->bch[chan].fsm_state == ACT2000_STATE_BWAIT)) {
+ actcapi_connect_b3_active_resp(card, &card->bch[chan]);
+ cmd.driver = card->myid;
+ cmd.command = ISDN_STAT_BCONN;
+ cmd.arg = chan;
+ card->interface.statcallb(&cmd);
+ }
+ break;
+ case 0x8402:
+ /* DISCONNECT_B3_IND */
+ chan = find_ncci(card, msg->msg.disconnect_b3_ind.ncci);
+ if (chan >= 0) {
+ ctmp = &card->bch[chan];
+ actcapi_disconnect_b3_resp(card, ctmp);
+ switch (ctmp->fsm_state) {
+ case ACT2000_STATE_ACTIVE:
+ ctmp->fsm_state = ACT2000_STATE_DHWAIT2;
+ cmd.driver = card->myid;
+ cmd.command = ISDN_STAT_BHUP;
+ cmd.arg = chan;
+ card->interface.statcallb(&cmd);
+ break;
+ case ACT2000_STATE_BHWAIT2:
+ actcapi_disconnect_req(card, ctmp);
+ ctmp->fsm_state = ACT2000_STATE_DHWAIT;
+ cmd.driver = card->myid;
+ cmd.command = ISDN_STAT_BHUP;
+ cmd.arg = chan;
+ card->interface.statcallb(&cmd);
+ break;
+ }
+ }
+ break;
+ case 0x0402:
+ /* DISCONNECT_IND */
+ chan = find_plci(card, msg->msg.disconnect_ind.plci);
+ if (chan >= 0) {
+ ctmp = &card->bch[chan];
+ actcapi_disconnect_resp(card, ctmp);
+ ctmp->fsm_state = ACT2000_STATE_NULL;
+ cmd.driver = card->myid;
+ cmd.command = ISDN_STAT_DHUP;
+ cmd.arg = chan;
+ card->interface.statcallb(&cmd);
+ } else {
+ ctmp = (act2000_chan *)tmp;
+ ctmp->plci = msg->msg.disconnect_ind.plci;
+ actcapi_disconnect_resp(card, ctmp);
+ }
+ break;
+ case 0x4001:
+ /* SELECT_B2_PROTOCOL_CONF */
+ chan = find_plci(card, msg->msg.select_b2_protocol_conf.plci);
+ if (chan >= 0)
+ switch (card->bch[chan].fsm_state) {
+ case ACT2000_STATE_ICALL:
+ case ACT2000_STATE_OWAIT:
+ ctmp = &card->bch[chan];
+ if (msg->msg.select_b2_protocol_conf.info == 0)
+ actcapi_select_b3_protocol_req(card, ctmp);
+ else {
+ ctmp->fsm_state = ACT2000_STATE_NULL;
+ cmd.driver = card->myid;
+ cmd.command = ISDN_STAT_DHUP;
+ cmd.arg = chan;
+ card->interface.statcallb(&cmd);
+ }
+ break;
+ }
+ break;
+ case 0x8001:
+ /* SELECT_B3_PROTOCOL_CONF */
+ chan = find_plci(card, msg->msg.select_b3_protocol_conf.plci);
+ if (chan >= 0)
+ switch (card->bch[chan].fsm_state) {
+ case ACT2000_STATE_ICALL:
+ case ACT2000_STATE_OWAIT:
+ ctmp = &card->bch[chan];
+ if (msg->msg.select_b3_protocol_conf.info == 0)
+ actcapi_listen_b3_req(card, ctmp);
+ else {
+ ctmp->fsm_state = ACT2000_STATE_NULL;
+ cmd.driver = card->myid;
+ cmd.command = ISDN_STAT_DHUP;
+ cmd.arg = chan;
+ card->interface.statcallb(&cmd);
+ }
+ }
+ break;
+ case 0x8101:
+ /* LISTEN_B3_CONF */
+ chan = find_plci(card, msg->msg.listen_b3_conf.plci);
+ if (chan >= 0)
+ switch (card->bch[chan].fsm_state) {
+ case ACT2000_STATE_ICALL:
+ ctmp = &card->bch[chan];
+ if (msg->msg.listen_b3_conf.info == 0)
+ actcapi_connect_resp(card, ctmp, 0);
+ else {
+ ctmp->fsm_state = ACT2000_STATE_NULL;
+ cmd.driver = card->myid;
+ cmd.command = ISDN_STAT_DHUP;
+ cmd.arg = chan;
+ card->interface.statcallb(&cmd);
+ }
+ break;
+ case ACT2000_STATE_OWAIT:
+ ctmp = &card->bch[chan];
+ if (msg->msg.listen_b3_conf.info == 0) {
+ actcapi_connect_b3_req(card, ctmp);
+ ctmp->fsm_state = ACT2000_STATE_OBWAIT;
+ cmd.driver = card->myid;
+ cmd.command = ISDN_STAT_DCONN;
+ cmd.arg = chan;
+ card->interface.statcallb(&cmd);
+ } else {
+ ctmp->fsm_state = ACT2000_STATE_NULL;
+ cmd.driver = card->myid;
+ cmd.command = ISDN_STAT_DHUP;
+ cmd.arg = chan;
+ card->interface.statcallb(&cmd);
+ }
+ break;
+ }
+ break;
+ case 0x8201:
+ /* CONNECT_B3_CONF */
+ chan = find_plci(card, msg->msg.connect_b3_conf.plci);
+ if ((chan >= 0) && (card->bch[chan].fsm_state == ACT2000_STATE_OBWAIT)) {
+ ctmp = &card->bch[chan];
+ if (msg->msg.connect_b3_conf.info) {
+ ctmp->fsm_state = ACT2000_STATE_NULL;
+ cmd.driver = card->myid;
+ cmd.command = ISDN_STAT_DHUP;
+ cmd.arg = chan;
+ card->interface.statcallb(&cmd);
+ } else {
+ ctmp->ncci = msg->msg.connect_b3_conf.ncci;
+ ctmp->fsm_state = ACT2000_STATE_BWAIT;
+ }
+ }
+ break;
+ case 0x8401:
+ /* DISCONNECT_B3_CONF */
+ chan = find_ncci(card, msg->msg.disconnect_b3_conf.ncci);
+ if ((chan >= 0) && (card->bch[chan].fsm_state == ACT2000_STATE_BHWAIT))
+ card->bch[chan].fsm_state = ACT2000_STATE_BHWAIT2;
+ break;
+ case 0x0702:
+ /* INFO_IND */
+ chan = find_plci(card, msg->msg.info_ind.plci);
+ if (chan >= 0)
+ /* TODO: Eval Charging info / cause */
+ actcapi_info_resp(card, &card->bch[chan]);
+ break;
+ case 0x0401:
+ /* LISTEN_CONF */
+ case 0x0501:
+ /* LISTEN_CONF */
+ case 0xff01:
+ /* MANUFACTURER_CONF */
+ break;
+ case 0xff02:
+ /* MANUFACTURER_IND */
+ if (msg->msg.manuf_msg == 3) {
+ memset(tmp, 0, sizeof(tmp));
+ strncpy(tmp,
+ &msg->msg.manufacturer_ind_err.errstring,
+ msg->hdr.len - 16);
+ if (msg->msg.manufacturer_ind_err.errcode)
+ printk(KERN_WARNING "act2000: %s\n", tmp);
+ else {
+ printk(KERN_DEBUG "act2000: %s\n", tmp);
+ if ((!strncmp(tmp, "INFO: Trace buffer con", 22)) ||
+ (!strncmp(tmp, "INFO: Compile Date/Tim", 22))) {
+ card->flags |= ACT2000_FLAGS_RUNNING;
+ cmd.command = ISDN_STAT_RUN;
+ cmd.driver = card->myid;
+ cmd.arg = 0;
+ actcapi_manufacturer_req_net(card);
+ actcapi_manufacturer_req_msn(card);
+ actcapi_listen_req(card);
+ card->interface.statcallb(&cmd);
+ }
+ }
+ }
+ break;
+ default:
+ printk(KERN_WARNING "act2000: UNHANDLED Message %04x\n", ccmd);
+ break;
+ }
+ dev_kfree_skb(skb);
+ }
+}
+
+#ifdef DEBUG_MSG
+static void
+actcapi_debug_caddr(actcapi_addr *addr)
+{
+ char tmp[30];
+
+ printk(KERN_DEBUG " Alen = %d\n", addr->len);
+ if (addr->len > 0)
+ printk(KERN_DEBUG " Atnp = 0x%02x\n", addr->tnp);
+ if (addr->len > 1) {
+ memset(tmp, 0, 30);
+ memcpy(tmp, addr->num, addr->len - 1);
+ printk(KERN_DEBUG " Anum = '%s'\n", tmp);
+ }
+}
+
+static void
+actcapi_debug_ncpi(actcapi_ncpi *ncpi)
+{
+ printk(KERN_DEBUG " ncpi.len = %d\n", ncpi->len);
+ if (ncpi->len >= 2)
+ printk(KERN_DEBUG " ncpi.lic = 0x%04x\n", ncpi->lic);
+ if (ncpi->len >= 4)
+ printk(KERN_DEBUG " ncpi.hic = 0x%04x\n", ncpi->hic);
+ if (ncpi->len >= 6)
+ printk(KERN_DEBUG " ncpi.ltc = 0x%04x\n", ncpi->ltc);
+ if (ncpi->len >= 8)
+ printk(KERN_DEBUG " ncpi.htc = 0x%04x\n", ncpi->htc);
+ if (ncpi->len >= 10)
+ printk(KERN_DEBUG " ncpi.loc = 0x%04x\n", ncpi->loc);
+ if (ncpi->len >= 12)
+ printk(KERN_DEBUG " ncpi.hoc = 0x%04x\n", ncpi->hoc);
+ if (ncpi->len >= 13)
+ printk(KERN_DEBUG " ncpi.mod = %d\n", ncpi->modulo);
+}
+
+static void
+actcapi_debug_dlpd(actcapi_dlpd *dlpd)
+{
+ printk(KERN_DEBUG " dlpd.len = %d\n", dlpd->len);
+ if (dlpd->len >= 2)
+ printk(KERN_DEBUG " dlpd.dlen = 0x%04x\n", dlpd->dlen);
+ if (dlpd->len >= 3)
+ printk(KERN_DEBUG " dlpd.laa = 0x%02x\n", dlpd->laa);
+ if (dlpd->len >= 4)
+ printk(KERN_DEBUG " dlpd.lab = 0x%02x\n", dlpd->lab);
+ if (dlpd->len >= 5)
+ printk(KERN_DEBUG " dlpd.modulo = %d\n", dlpd->modulo);
+ if (dlpd->len >= 6)
+ printk(KERN_DEBUG " dlpd.win = %d\n", dlpd->win);
+}
+
+#ifdef DEBUG_DUMP_SKB
+static void dump_skb(struct sk_buff *skb) {
+ char tmp[80];
+ char *p = skb->data;
+ char *t = tmp;
+ int i;
+
+ for (i = 0; i < skb->len; i++) {
+ t += sprintf(t, "%02x ", *p++ & 0xff);
+ if ((i & 0x0f) == 8) {
+ printk(KERN_DEBUG "dump: %s\n", tmp);
+ t = tmp;
+ }
+ }
+ if (i & 0x07)
+ printk(KERN_DEBUG "dump: %s\n", tmp);
+}
+#endif
+
+void
+actcapi_debug_msg(struct sk_buff *skb, int direction)
+{
+ actcapi_msg *msg = (actcapi_msg *)skb->data;
+ char *descr;
+ int i;
+ char tmp[170];
+
+#ifndef DEBUG_DATA_MSG
+ if (msg->hdr.cmd.cmd == 0x86)
+ return;
+#endif
+ descr = "INVALID";
+#ifdef DEBUG_DUMP_SKB
+ dump_skb(skb);
+#endif
+ for (i = 0; i < ARRAY_SIZE(valid_msg); i++)
+ if ((msg->hdr.cmd.cmd == valid_msg[i].cmd.cmd) &&
+ (msg->hdr.cmd.subcmd == valid_msg[i].cmd.subcmd)) {
+ descr = valid_msg[i].description;
+ break;
+ }
+ printk(KERN_DEBUG "%s %s msg\n", direction ? "Outgoing" : "Incoming", descr);
+ printk(KERN_DEBUG " ApplID = %d\n", msg->hdr.applicationID);
+ printk(KERN_DEBUG " Len = %d\n", msg->hdr.len);
+ printk(KERN_DEBUG " MsgNum = 0x%04x\n", msg->hdr.msgnum);
+ printk(KERN_DEBUG " Cmd = 0x%02x\n", msg->hdr.cmd.cmd);
+ printk(KERN_DEBUG " SubCmd = 0x%02x\n", msg->hdr.cmd.subcmd);
+ switch (i) {
+ case 0:
+ /* DATA B3 IND */
+ printk(KERN_DEBUG " BLOCK = 0x%02x\n",
+ msg->msg.data_b3_ind.blocknr);
+ break;
+ case 2:
+ /* CONNECT CONF */
+ printk(KERN_DEBUG " PLCI = 0x%04x\n",
+ msg->msg.connect_conf.plci);
+ printk(KERN_DEBUG " Info = 0x%04x\n",
+ msg->msg.connect_conf.info);
+ break;
+ case 3:
+ /* CONNECT IND */
+ printk(KERN_DEBUG " PLCI = 0x%04x\n",
+ msg->msg.connect_ind.plci);
+ printk(KERN_DEBUG " Contr = %d\n",
+ msg->msg.connect_ind.controller);
+ printk(KERN_DEBUG " SI1 = %d\n",
+ msg->msg.connect_ind.si1);
+ printk(KERN_DEBUG " SI2 = %d\n",
+ msg->msg.connect_ind.si2);
+ printk(KERN_DEBUG " EAZ = '%c'\n",
+ msg->msg.connect_ind.eaz);
+ actcapi_debug_caddr(&msg->msg.connect_ind.addr);
+ break;
+ case 5:
+ /* CONNECT ACTIVE IND */
+ printk(KERN_DEBUG " PLCI = 0x%04x\n",
+ msg->msg.connect_active_ind.plci);
+ actcapi_debug_caddr(&msg->msg.connect_active_ind.addr);
+ break;
+ case 8:
+ /* LISTEN CONF */
+ printk(KERN_DEBUG " Contr = %d\n",
+ msg->msg.listen_conf.controller);
+ printk(KERN_DEBUG " Info = 0x%04x\n",
+ msg->msg.listen_conf.info);
+ break;
+ case 11:
+ /* INFO IND */
+ printk(KERN_DEBUG " PLCI = 0x%04x\n",
+ msg->msg.info_ind.plci);
+ printk(KERN_DEBUG " Imsk = 0x%04x\n",
+ msg->msg.info_ind.nr.mask);
+ if (msg->hdr.len > 12) {
+ int l = msg->hdr.len - 12;
+ int j;
+ char *p = tmp;
+ for (j = 0; j < l; j++)
+ p += sprintf(p, "%02x ", msg->msg.info_ind.el.display[j]);
+ printk(KERN_DEBUG " D = '%s'\n", tmp);
+ }
+ break;
+ case 14:
+ /* SELECT B2 PROTOCOL CONF */
+ printk(KERN_DEBUG " PLCI = 0x%04x\n",
+ msg->msg.select_b2_protocol_conf.plci);
+ printk(KERN_DEBUG " Info = 0x%04x\n",
+ msg->msg.select_b2_protocol_conf.info);
+ break;
+ case 15:
+ /* SELECT B3 PROTOCOL CONF */
+ printk(KERN_DEBUG " PLCI = 0x%04x\n",
+ msg->msg.select_b3_protocol_conf.plci);
+ printk(KERN_DEBUG " Info = 0x%04x\n",
+ msg->msg.select_b3_protocol_conf.info);
+ break;
+ case 16:
+ /* LISTEN B3 CONF */
+ printk(KERN_DEBUG " PLCI = 0x%04x\n",
+ msg->msg.listen_b3_conf.plci);
+ printk(KERN_DEBUG " Info = 0x%04x\n",
+ msg->msg.listen_b3_conf.info);
+ break;
+ case 18:
+ /* CONNECT B3 IND */
+ printk(KERN_DEBUG " NCCI = 0x%04x\n",
+ msg->msg.connect_b3_ind.ncci);
+ printk(KERN_DEBUG " PLCI = 0x%04x\n",
+ msg->msg.connect_b3_ind.plci);
+ actcapi_debug_ncpi(&msg->msg.connect_b3_ind.ncpi);
+ break;
+ case 19:
+ /* CONNECT B3 ACTIVE IND */
+ printk(KERN_DEBUG " NCCI = 0x%04x\n",
+ msg->msg.connect_b3_active_ind.ncci);
+ actcapi_debug_ncpi(&msg->msg.connect_b3_active_ind.ncpi);
+ break;
+ case 26:
+ /* MANUFACTURER IND */
+ printk(KERN_DEBUG " Mmsg = 0x%02x\n",
+ msg->msg.manufacturer_ind_err.manuf_msg);
+ switch (msg->msg.manufacturer_ind_err.manuf_msg) {
+ case 3:
+ printk(KERN_DEBUG " Contr = %d\n",
+ msg->msg.manufacturer_ind_err.controller);
+ printk(KERN_DEBUG " Code = 0x%08x\n",
+ msg->msg.manufacturer_ind_err.errcode);
+ memset(tmp, 0, sizeof(tmp));
+ strncpy(tmp, &msg->msg.manufacturer_ind_err.errstring,
+ msg->hdr.len - 16);
+ printk(KERN_DEBUG " Emsg = '%s'\n", tmp);
+ break;
+ }
+ break;
+ case 30:
+ /* LISTEN REQ */
+ printk(KERN_DEBUG " Imsk = 0x%08x\n",
+ msg->msg.listen_req.infomask);
+ printk(KERN_DEBUG " Emsk = 0x%04x\n",
+ msg->msg.listen_req.eazmask);
+ printk(KERN_DEBUG " Smsk = 0x%04x\n",
+ msg->msg.listen_req.simask);
+ break;
+ case 35:
+ /* SELECT_B2_PROTOCOL_REQ */
+ printk(KERN_DEBUG " PLCI = 0x%04x\n",
+ msg->msg.select_b2_protocol_req.plci);
+ printk(KERN_DEBUG " prot = 0x%02x\n",
+ msg->msg.select_b2_protocol_req.protocol);
+ if (msg->hdr.len >= 11)
+ printk(KERN_DEBUG "No dlpd\n");
+ else
+ actcapi_debug_dlpd(&msg->msg.select_b2_protocol_req.dlpd);
+ break;
+ case 44:
+ /* CONNECT RESP */
+ printk(KERN_DEBUG " PLCI = 0x%04x\n",
+ msg->msg.connect_resp.plci);
+ printk(KERN_DEBUG " CAUSE = 0x%02x\n",
+ msg->msg.connect_resp.rejectcause);
+ break;
+ case 45:
+ /* CONNECT ACTIVE RESP */
+ printk(KERN_DEBUG " PLCI = 0x%04x\n",
+ msg->msg.connect_active_resp.plci);
+ break;
+ }
+}
+#endif
diff --git a/drivers/staging/i4l/act2000/capi.h b/drivers/staging/i4l/act2000/capi.h
new file mode 100644
index 000000000000..01ccdecd43f7
--- /dev/null
+++ b/drivers/staging/i4l/act2000/capi.h
@@ -0,0 +1,365 @@
+/* $Id: capi.h,v 1.6.6.2 2001/09/23 22:24:32 kai Exp $
+ *
+ * ISDN lowlevel-module for the IBM ISDN-S0 Active 2000.
+ *
+ * Author Fritz Elfert
+ * Copyright by Fritz Elfert <fritz@isdn4linux.de>
+ *
+ * This software may be used and distributed according to the terms
+ * of the GNU General Public License, incorporated herein by reference.
+ *
+ * Thanks to Friedemann Baitinger and IBM Germany
+ *
+ */
+
+#ifndef CAPI_H
+#define CAPI_H
+
+/* Command-part of a CAPI message */
+typedef struct actcapi_msgcmd {
+ __u8 cmd;
+ __u8 subcmd;
+} actcapi_msgcmd;
+
+/* CAPI message header */
+typedef struct actcapi_msghdr {
+ __u16 len;
+ __u16 applicationID;
+ actcapi_msgcmd cmd;
+ __u16 msgnum;
+} actcapi_msghdr;
+
+/* CAPI message description (for debugging) */
+typedef struct actcapi_msgdsc {
+ actcapi_msgcmd cmd;
+ char *description;
+} actcapi_msgdsc;
+
+/* CAPI Address */
+typedef struct actcapi_addr {
+ __u8 len; /* Length of element */
+ __u8 tnp; /* Type/Numbering Plan */
+ __u8 num[20]; /* Caller ID */
+} actcapi_addr;
+
+/* CAPI INFO element mask */
+typedef union actcapi_infonr { /* info number */
+ __u16 mask; /* info-mask field */
+ struct bmask { /* bit definitions */
+ unsigned codes:3; /* code set */
+ unsigned rsvd:5; /* reserved */
+ unsigned svind:1; /* single, variable length ind. */
+ unsigned wtype:7; /* W-element type */
+ } bmask;
+} actcapi_infonr;
+
+/* CAPI INFO element */
+typedef union actcapi_infoel { /* info element */
+ __u8 len; /* length of info element */
+ __u8 display[40]; /* display contents */
+ __u8 uuinfo[40]; /* User-user info field */
+ struct cause { /* Cause information */
+ unsigned ext2:1; /* extension */
+ unsigned cod:2; /* coding standard */
+ unsigned spare:1; /* spare */
+ unsigned loc:4; /* location */
+ unsigned ext1:1; /* extension */
+ unsigned cval:7; /* Cause value */
+ } cause;
+ struct charge { /* Charging information */
+ __u8 toc; /* type of charging info */
+ __u8 unit[10]; /* charging units */
+ } charge;
+ __u8 date[20]; /* date fields */
+ __u8 stat; /* state of remote party */
+} actcapi_infoel;
+
+/* Message for EAZ<->MSN Mapping */
+typedef struct actcapi_msn {
+ __u8 eaz;
+ __u8 len; /* Length of MSN */
+ __u8 msn[15];
+} __attribute__((packed)) actcapi_msn;
+
+typedef struct actcapi_dlpd {
+ __u8 len; /* Length of structure */
+ __u16 dlen; /* Data Length */
+ __u8 laa; /* Link Address A */
+ __u8 lab; /* Link Address B */
+ __u8 modulo; /* Modulo Mode */
+ __u8 win; /* Window size */
+ __u8 xid[100]; /* XID Information */
+} __attribute__((packed)) actcapi_dlpd;
+
+typedef struct actcapi_ncpd {
+ __u8 len; /* Length of structure */
+ __u16 lic;
+ __u16 hic;
+ __u16 ltc;
+ __u16 htc;
+ __u16 loc;
+ __u16 hoc;
+ __u8 modulo;
+} __attribute__((packed)) actcapi_ncpd;
+#define actcapi_ncpi actcapi_ncpd
+
+/*
+ * Layout of NCCI field in a B3 DATA CAPI message is different from
+ * standard at act2000:
+ *
+ * Bit 0-4 = PLCI
+ * Bit 5-7 = Controller
+ * Bit 8-15 = NCCI
+ */
+#define MAKE_NCCI(plci, contr, ncci) \
+ ((plci & 0x1f) | ((contr & 0x7) << 5) | ((ncci & 0xff) << 8))
+
+#define EVAL_NCCI(fakencci, plci, contr, ncci) { \
+ plci = fakencci & 0x1f; \
+ contr = (fakencci >> 5) & 0x7; \
+ ncci = (fakencci >> 8) & 0xff; \
+ }
+
+/*
+ * Layout of PLCI field in a B3 DATA CAPI message is different from
+ * standard at act2000:
+ *
+ * Bit 0-4 = PLCI
+ * Bit 5-7 = Controller
+ * Bit 8-15 = reserved (must be 0)
+ */
+#define MAKE_PLCI(plci, contr) \
+ ((plci & 0x1f) | ((contr & 0x7) << 5))
+
+#define EVAL_PLCI(fakeplci, plci, contr) { \
+ plci = fakeplci & 0x1f; \
+ contr = (fakeplci >> 5) & 0x7; \
+ }
+
+typedef struct actcapi_msg {
+ actcapi_msghdr hdr;
+ union {
+ __u16 manuf_msg;
+ struct manufacturer_req_net {
+ __u16 manuf_msg;
+ __u16 controller;
+ __u8 nettype;
+ } manufacturer_req_net;
+ struct manufacturer_req_v42 {
+ __u16 manuf_msg;
+ __u16 controller;
+ __u32 v42control;
+ } manufacturer_req_v42;
+ struct manufacturer_conf_v42 {
+ __u16 manuf_msg;
+ __u16 controller;
+ } manufacturer_conf_v42;
+ struct manufacturer_req_err {
+ __u16 manuf_msg;
+ __u16 controller;
+ } manufacturer_req_err;
+ struct manufacturer_ind_err {
+ __u16 manuf_msg;
+ __u16 controller;
+ __u32 errcode;
+ __u8 errstring; /* actually up to 160 */
+ } manufacturer_ind_err;
+ struct manufacturer_req_msn {
+ __u16 manuf_msg;
+ __u16 controller;
+ actcapi_msn msnmap;
+ } __attribute ((packed)) manufacturer_req_msn;
+ /* TODO: TraceInit-req/conf/ind/resp and
+ * TraceDump-req/conf/ind/resp
+ */
+ struct connect_req {
+ __u8 controller;
+ __u8 bchan;
+ __u32 infomask;
+ __u8 si1;
+ __u8 si2;
+ __u8 eaz;
+ actcapi_addr addr;
+ } __attribute__ ((packed)) connect_req;
+ struct connect_conf {
+ __u16 plci;
+ __u16 info;
+ } connect_conf;
+ struct connect_ind {
+ __u16 plci;
+ __u8 controller;
+ __u8 si1;
+ __u8 si2;
+ __u8 eaz;
+ actcapi_addr addr;
+ } __attribute__ ((packed)) connect_ind;
+ struct connect_resp {
+ __u16 plci;
+ __u8 rejectcause;
+ } connect_resp;
+ struct connect_active_ind {
+ __u16 plci;
+ actcapi_addr addr;
+ } __attribute__ ((packed)) connect_active_ind;
+ struct connect_active_resp {
+ __u16 plci;
+ } connect_active_resp;
+ struct connect_b3_req {
+ __u16 plci;
+ actcapi_ncpi ncpi;
+ } __attribute__ ((packed)) connect_b3_req;
+ struct connect_b3_conf {
+ __u16 plci;
+ __u16 ncci;
+ __u16 info;
+ } connect_b3_conf;
+ struct connect_b3_ind {
+ __u16 ncci;
+ __u16 plci;
+ actcapi_ncpi ncpi;
+ } __attribute__ ((packed)) connect_b3_ind;
+ struct connect_b3_resp {
+ __u16 ncci;
+ __u8 rejectcause;
+ actcapi_ncpi ncpi;
+ } __attribute__ ((packed)) connect_b3_resp;
+ struct disconnect_req {
+ __u16 plci;
+ __u8 cause;
+ } disconnect_req;
+ struct disconnect_conf {
+ __u16 plci;
+ __u16 info;
+ } disconnect_conf;
+ struct disconnect_ind {
+ __u16 plci;
+ __u16 info;
+ } disconnect_ind;
+ struct disconnect_resp {
+ __u16 plci;
+ } disconnect_resp;
+ struct connect_b3_active_ind {
+ __u16 ncci;
+ actcapi_ncpi ncpi;
+ } __attribute__ ((packed)) connect_b3_active_ind;
+ struct connect_b3_active_resp {
+ __u16 ncci;
+ } connect_b3_active_resp;
+ struct disconnect_b3_req {
+ __u16 ncci;
+ actcapi_ncpi ncpi;
+ } __attribute__ ((packed)) disconnect_b3_req;
+ struct disconnect_b3_conf {
+ __u16 ncci;
+ __u16 info;
+ } disconnect_b3_conf;
+ struct disconnect_b3_ind {
+ __u16 ncci;
+ __u16 info;
+ actcapi_ncpi ncpi;
+ } __attribute__ ((packed)) disconnect_b3_ind;
+ struct disconnect_b3_resp {
+ __u16 ncci;
+ } disconnect_b3_resp;
+ struct info_ind {
+ __u16 plci;
+ actcapi_infonr nr;
+ actcapi_infoel el;
+ } __attribute__ ((packed)) info_ind;
+ struct info_resp {
+ __u16 plci;
+ } info_resp;
+ struct listen_b3_req {
+ __u16 plci;
+ } listen_b3_req;
+ struct listen_b3_conf {
+ __u16 plci;
+ __u16 info;
+ } listen_b3_conf;
+ struct select_b2_protocol_req {
+ __u16 plci;
+ __u8 protocol;
+ actcapi_dlpd dlpd;
+ } __attribute__ ((packed)) select_b2_protocol_req;
+ struct select_b2_protocol_conf {
+ __u16 plci;
+ __u16 info;
+ } select_b2_protocol_conf;
+ struct select_b3_protocol_req {
+ __u16 plci;
+ __u8 protocol;
+ actcapi_ncpd ncpd;
+ } __attribute__ ((packed)) select_b3_protocol_req;
+ struct select_b3_protocol_conf {
+ __u16 plci;
+ __u16 info;
+ } select_b3_protocol_conf;
+ struct listen_req {
+ __u8 controller;
+ __u32 infomask;
+ __u16 eazmask;
+ __u16 simask;
+ } __attribute__ ((packed)) listen_req;
+ struct listen_conf {
+ __u8 controller;
+ __u16 info;
+ } __attribute__ ((packed)) listen_conf;
+ struct data_b3_req {
+ __u16 fakencci;
+ __u16 datalen;
+ __u32 unused;
+ __u8 blocknr;
+ __u16 flags;
+ } __attribute ((packed)) data_b3_req;
+ struct data_b3_ind {
+ __u16 fakencci;
+ __u16 datalen;
+ __u32 unused;
+ __u8 blocknr;
+ __u16 flags;
+ } __attribute__ ((packed)) data_b3_ind;
+ struct data_b3_resp {
+ __u16 ncci;
+ __u8 blocknr;
+ } __attribute__ ((packed)) data_b3_resp;
+ struct data_b3_conf {
+ __u16 ncci;
+ __u8 blocknr;
+ __u16 info;
+ } __attribute__ ((packed)) data_b3_conf;
+ } msg;
+} __attribute__ ((packed)) actcapi_msg;
+
+static inline unsigned short
+actcapi_nextsmsg(act2000_card *card)
+{
+ unsigned long flags;
+ unsigned short n;
+
+ spin_lock_irqsave(&card->mnlock, flags);
+ n = card->msgnum;
+ card->msgnum++;
+ card->msgnum &= 0x7fff;
+ spin_unlock_irqrestore(&card->mnlock, flags);
+ return n;
+}
+#define DEBUG_MSG
+#undef DEBUG_DATA_MSG
+#undef DEBUG_DUMP_SKB
+
+extern int actcapi_chkhdr(act2000_card *, actcapi_msghdr *);
+extern int actcapi_listen_req(act2000_card *);
+extern int actcapi_manufacturer_req_net(act2000_card *);
+extern int actcapi_manufacturer_req_errh(act2000_card *);
+extern int actcapi_manufacturer_req_msn(act2000_card *);
+extern int actcapi_connect_req(act2000_card *, act2000_chan *, char *, char, int, int);
+extern void actcapi_select_b2_protocol_req(act2000_card *, act2000_chan *);
+extern void actcapi_disconnect_b3_req(act2000_card *, act2000_chan *);
+extern void actcapi_connect_resp(act2000_card *, act2000_chan *, __u8);
+extern void actcapi_dispatch(struct work_struct *);
+#ifdef DEBUG_MSG
+extern void actcapi_debug_msg(struct sk_buff *skb, int);
+#else
+#define actcapi_debug_msg(skb, len)
+#endif
+#endif
diff --git a/drivers/staging/i4l/act2000/module.c b/drivers/staging/i4l/act2000/module.c
new file mode 100644
index 000000000000..68073d0da0e3
--- /dev/null
+++ b/drivers/staging/i4l/act2000/module.c
@@ -0,0 +1,813 @@
+/* $Id: module.c,v 1.14.6.4 2001/09/23 22:24:32 kai Exp $
+ *
+ * ISDN lowlevel-module for the IBM ISDN-S0 Active 2000.
+ *
+ * Author Fritz Elfert
+ * Copyright by Fritz Elfert <fritz@isdn4linux.de>
+ *
+ * This software may be used and distributed according to the terms
+ * of the GNU General Public License, incorporated herein by reference.
+ *
+ * Thanks to Friedemann Baitinger and IBM Germany
+ *
+ */
+
+#include "act2000.h"
+#include "act2000_isa.h"
+#include "capi.h"
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/init.h>
+
+static unsigned short act2000_isa_ports[] =
+{
+ 0x0200, 0x0240, 0x0280, 0x02c0, 0x0300, 0x0340, 0x0380,
+ 0xcfe0, 0xcfa0, 0xcf60, 0xcf20, 0xcee0, 0xcea0, 0xce60,
+};
+
+static act2000_card *cards = (act2000_card *) NULL;
+
+/* Parameters to be set by insmod */
+static int act_bus = 0;
+static int act_port = -1; /* -1 = Autoprobe */
+static int act_irq = -1;
+static char *act_id = "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0";
+
+MODULE_DESCRIPTION("ISDN4Linux: Driver for IBM Active 2000 ISDN card");
+MODULE_AUTHOR("Fritz Elfert");
+MODULE_LICENSE("GPL");
+MODULE_PARM_DESC(act_bus, "BusType of first card, 1=ISA, 2=MCA, 3=PCMCIA, currently only ISA");
+MODULE_PARM_DESC(act_port, "Base port address of first card");
+MODULE_PARM_DESC(act_irq, "IRQ of first card");
+MODULE_PARM_DESC(act_id, "ID-String of first card");
+module_param(act_bus, int, 0);
+module_param(act_port, int, 0);
+module_param(act_irq, int, 0);
+module_param(act_id, charp, 0);
+
+static int act2000_addcard(int, int, int, char *);
+
+static act2000_chan *
+find_channel(act2000_card *card, int channel)
+{
+ if ((channel >= 0) && (channel < ACT2000_BCH))
+ return &(card->bch[channel]);
+ printk(KERN_WARNING "act2000: Invalid channel %d\n", channel);
+ return NULL;
+}
+
+/*
+ * Free MSN list
+ */
+static void
+act2000_clear_msn(act2000_card *card)
+{
+ struct msn_entry *p = card->msn_list;
+ struct msn_entry *q;
+ unsigned long flags;
+
+ spin_lock_irqsave(&card->lock, flags);
+ card->msn_list = NULL;
+ spin_unlock_irqrestore(&card->lock, flags);
+ while (p) {
+ q = p->next;
+ kfree(p);
+ p = q;
+ }
+}
+
+/*
+ * Find an MSN entry in the list.
+ * If ia5 != 0, return IA5-encoded EAZ, else
+ * return a bitmask with corresponding bit set.
+ */
+static __u16
+act2000_find_msn(act2000_card *card, char *msn, int ia5)
+{
+ struct msn_entry *p = card->msn_list;
+ __u8 eaz = '0';
+
+ while (p) {
+ if (!strcmp(p->msn, msn)) {
+ eaz = p->eaz;
+ break;
+ }
+ p = p->next;
+ }
+ if (!ia5)
+ return (1 << (eaz - '0'));
+ else
+ return eaz;
+}
+
+/*
+ * Find an EAZ entry in the list.
+ * return a string with corresponding msn.
+ */
+char *
+act2000_find_eaz(act2000_card *card, char eaz)
+{
+ struct msn_entry *p = card->msn_list;
+
+ while (p) {
+ if (p->eaz == eaz)
+ return (p->msn);
+ p = p->next;
+ }
+ return ("\0");
+}
+
+/*
+ * Add or delete an MSN to the MSN list
+ *
+ * First character of msneaz is EAZ, rest is MSN.
+ * If length of eazmsn is 1, delete that entry.
+ */
+static int
+act2000_set_msn(act2000_card *card, char *eazmsn)
+{
+ struct msn_entry *p = card->msn_list;
+ struct msn_entry *q = NULL;
+ unsigned long flags;
+ int i;
+
+ if (!strlen(eazmsn))
+ return 0;
+ if (strlen(eazmsn) > 16)
+ return -EINVAL;
+ for (i = 0; i < strlen(eazmsn); i++)
+ if (!isdigit(eazmsn[i]))
+ return -EINVAL;
+ if (strlen(eazmsn) == 1) {
+ /* Delete a single MSN */
+ while (p) {
+ if (p->eaz == eazmsn[0]) {
+ spin_lock_irqsave(&card->lock, flags);
+ if (q)
+ q->next = p->next;
+ else
+ card->msn_list = p->next;
+ spin_unlock_irqrestore(&card->lock, flags);
+ kfree(p);
+ printk(KERN_DEBUG
+ "Mapping for EAZ %c deleted\n",
+ eazmsn[0]);
+ return 0;
+ }
+ q = p;
+ p = p->next;
+ }
+ return 0;
+ }
+ /* Add a single MSN */
+ while (p) {
+ /* Found in list, replace MSN */
+ if (p->eaz == eazmsn[0]) {
+ spin_lock_irqsave(&card->lock, flags);
+ strcpy(p->msn, &eazmsn[1]);
+ spin_unlock_irqrestore(&card->lock, flags);
+ printk(KERN_DEBUG
+ "Mapping for EAZ %c changed to %s\n",
+ eazmsn[0],
+ &eazmsn[1]);
+ return 0;
+ }
+ p = p->next;
+ }
+ /* Not found in list, add new entry */
+ p = kmalloc(sizeof(msn_entry), GFP_KERNEL);
+ if (!p)
+ return -ENOMEM;
+ p->eaz = eazmsn[0];
+ strcpy(p->msn, &eazmsn[1]);
+ p->next = card->msn_list;
+ spin_lock_irqsave(&card->lock, flags);
+ card->msn_list = p;
+ spin_unlock_irqrestore(&card->lock, flags);
+ printk(KERN_DEBUG
+ "Mapping %c -> %s added\n",
+ eazmsn[0],
+ &eazmsn[1]);
+ return 0;
+}
+
+static void
+act2000_transmit(struct work_struct *work)
+{
+ struct act2000_card *card =
+ container_of(work, struct act2000_card, snd_tq);
+
+ switch (card->bus) {
+ case ACT2000_BUS_ISA:
+ act2000_isa_send(card);
+ break;
+ case ACT2000_BUS_PCMCIA:
+ case ACT2000_BUS_MCA:
+ default:
+ printk(KERN_WARNING
+ "act2000_transmit: Illegal bustype %d\n", card->bus);
+ }
+}
+
+static void
+act2000_receive(struct work_struct *work)
+{
+ struct act2000_card *card =
+ container_of(work, struct act2000_card, poll_tq);
+
+ switch (card->bus) {
+ case ACT2000_BUS_ISA:
+ act2000_isa_receive(card);
+ break;
+ case ACT2000_BUS_PCMCIA:
+ case ACT2000_BUS_MCA:
+ default:
+ printk(KERN_WARNING
+ "act2000_receive: Illegal bustype %d\n", card->bus);
+ }
+}
+
+static void
+act2000_poll(unsigned long data)
+{
+ act2000_card *card = (act2000_card *)data;
+ unsigned long flags;
+
+ act2000_receive(&card->poll_tq);
+ spin_lock_irqsave(&card->lock, flags);
+ mod_timer(&card->ptimer, jiffies + 3);
+ spin_unlock_irqrestore(&card->lock, flags);
+}
+
+static int
+act2000_command(act2000_card *card, isdn_ctrl *c)
+{
+ ulong a;
+ act2000_chan *chan;
+ act2000_cdef cdef;
+ isdn_ctrl cmd;
+ char tmp[17];
+ int ret;
+ unsigned long flags;
+ void __user *arg;
+
+ switch (c->command) {
+ case ISDN_CMD_IOCTL:
+ memcpy(&a, c->parm.num, sizeof(ulong));
+ arg = (void __user *)a;
+ switch (c->arg) {
+ case ACT2000_IOCTL_LOADBOOT:
+ switch (card->bus) {
+ case ACT2000_BUS_ISA:
+ ret = act2000_isa_download(card,
+ arg);
+ if (!ret) {
+ card->flags |= ACT2000_FLAGS_LOADED;
+ if (!(card->flags & ACT2000_FLAGS_IVALID)) {
+ card->ptimer.expires = jiffies + 3;
+ card->ptimer.function = act2000_poll;
+ card->ptimer.data = (unsigned long)card;
+ add_timer(&card->ptimer);
+ }
+ actcapi_manufacturer_req_errh(card);
+ }
+ break;
+ default:
+ printk(KERN_WARNING
+ "act2000: Illegal BUS type %d\n",
+ card->bus);
+ ret = -EIO;
+ }
+ return ret;
+ case ACT2000_IOCTL_SETPROTO:
+ card->ptype = a ? ISDN_PTYPE_EURO : ISDN_PTYPE_1TR6;
+ if (!(card->flags & ACT2000_FLAGS_RUNNING))
+ return 0;
+ actcapi_manufacturer_req_net(card);
+ return 0;
+ case ACT2000_IOCTL_SETMSN:
+ if (copy_from_user(tmp, arg,
+ sizeof(tmp)))
+ return -EFAULT;
+ if ((ret = act2000_set_msn(card, tmp)))
+ return ret;
+ if (card->flags & ACT2000_FLAGS_RUNNING)
+ return (actcapi_manufacturer_req_msn(card));
+ return 0;
+ case ACT2000_IOCTL_ADDCARD:
+ if (copy_from_user(&cdef, arg,
+ sizeof(cdef)))
+ return -EFAULT;
+ if (act2000_addcard(cdef.bus, cdef.port, cdef.irq, cdef.id))
+ return -EIO;
+ return 0;
+ case ACT2000_IOCTL_TEST:
+ if (!(card->flags & ACT2000_FLAGS_RUNNING))
+ return -ENODEV;
+ return 0;
+ default:
+ return -EINVAL;
+ }
+ break;
+ case ISDN_CMD_DIAL:
+ if (!(card->flags & ACT2000_FLAGS_RUNNING))
+ return -ENODEV;
+ if (!(chan = find_channel(card, c->arg & 0x0f)))
+ break;
+ spin_lock_irqsave(&card->lock, flags);
+ if (chan->fsm_state != ACT2000_STATE_NULL) {
+ spin_unlock_irqrestore(&card->lock, flags);
+ printk(KERN_WARNING "Dial on channel with state %d\n",
+ chan->fsm_state);
+ return -EBUSY;
+ }
+ if (card->ptype == ISDN_PTYPE_EURO)
+ tmp[0] = act2000_find_msn(card, c->parm.setup.eazmsn, 1);
+ else
+ tmp[0] = c->parm.setup.eazmsn[0];
+ chan->fsm_state = ACT2000_STATE_OCALL;
+ chan->callref = 0xffff;
+ spin_unlock_irqrestore(&card->lock, flags);
+ ret = actcapi_connect_req(card, chan, c->parm.setup.phone,
+ tmp[0], c->parm.setup.si1,
+ c->parm.setup.si2);
+ if (ret) {
+ cmd.driver = card->myid;
+ cmd.command = ISDN_STAT_DHUP;
+ cmd.arg &= 0x0f;
+ card->interface.statcallb(&cmd);
+ }
+ return ret;
+ case ISDN_CMD_ACCEPTD:
+ if (!(card->flags & ACT2000_FLAGS_RUNNING))
+ return -ENODEV;
+ if (!(chan = find_channel(card, c->arg & 0x0f)))
+ break;
+ if (chan->fsm_state == ACT2000_STATE_ICALL)
+ actcapi_select_b2_protocol_req(card, chan);
+ return 0;
+ case ISDN_CMD_ACCEPTB:
+ if (!(card->flags & ACT2000_FLAGS_RUNNING))
+ return -ENODEV;
+ return 0;
+ case ISDN_CMD_HANGUP:
+ if (!(card->flags & ACT2000_FLAGS_RUNNING))
+ return -ENODEV;
+ if (!(chan = find_channel(card, c->arg & 0x0f)))
+ break;
+ switch (chan->fsm_state) {
+ case ACT2000_STATE_ICALL:
+ case ACT2000_STATE_BSETUP:
+ actcapi_connect_resp(card, chan, 0x15);
+ break;
+ case ACT2000_STATE_ACTIVE:
+ actcapi_disconnect_b3_req(card, chan);
+ break;
+ }
+ return 0;
+ case ISDN_CMD_SETEAZ:
+ if (!(card->flags & ACT2000_FLAGS_RUNNING))
+ return -ENODEV;
+ if (!(chan = find_channel(card, c->arg & 0x0f)))
+ break;
+ if (strlen(c->parm.num)) {
+ if (card->ptype == ISDN_PTYPE_EURO) {
+ chan->eazmask = act2000_find_msn(card, c->parm.num, 0);
+ }
+ if (card->ptype == ISDN_PTYPE_1TR6) {
+ int i;
+ chan->eazmask = 0;
+ for (i = 0; i < strlen(c->parm.num); i++)
+ if (isdigit(c->parm.num[i]))
+ chan->eazmask |= (1 << (c->parm.num[i] - '0'));
+ }
+ } else
+ chan->eazmask = 0x3ff;
+ actcapi_listen_req(card);
+ return 0;
+ case ISDN_CMD_CLREAZ:
+ if (!(card->flags & ACT2000_FLAGS_RUNNING))
+ return -ENODEV;
+ if (!(chan = find_channel(card, c->arg & 0x0f)))
+ break;
+ chan->eazmask = 0;
+ actcapi_listen_req(card);
+ return 0;
+ case ISDN_CMD_SETL2:
+ if (!(card->flags & ACT2000_FLAGS_RUNNING))
+ return -ENODEV;
+ if (!(chan = find_channel(card, c->arg & 0x0f)))
+ break;
+ chan->l2prot = (c->arg >> 8);
+ return 0;
+ case ISDN_CMD_SETL3:
+ if (!(card->flags & ACT2000_FLAGS_RUNNING))
+ return -ENODEV;
+ if ((c->arg >> 8) != ISDN_PROTO_L3_TRANS) {
+ printk(KERN_WARNING "L3 protocol unknown\n");
+ return -1;
+ }
+ if (!(chan = find_channel(card, c->arg & 0x0f)))
+ break;
+ chan->l3prot = (c->arg >> 8);
+ return 0;
+ }
+
+ return -EINVAL;
+}
+
+static int
+act2000_sendbuf(act2000_card *card, int channel, int ack, struct sk_buff *skb)
+{
+ struct sk_buff *xmit_skb;
+ int len;
+ act2000_chan *chan;
+ actcapi_msg *msg;
+
+ if (!(chan = find_channel(card, channel)))
+ return -1;
+ if (chan->fsm_state != ACT2000_STATE_ACTIVE)
+ return -1;
+ len = skb->len;
+ if ((chan->queued + len) >= ACT2000_MAX_QUEUED)
+ return 0;
+ if (!len)
+ return 0;
+ if (skb_headroom(skb) < 19) {
+ printk(KERN_WARNING "act2000_sendbuf: Headroom only %d\n",
+ skb_headroom(skb));
+ xmit_skb = alloc_skb(len + 19, GFP_ATOMIC);
+ if (!xmit_skb) {
+ printk(KERN_WARNING "act2000_sendbuf: Out of memory\n");
+ return 0;
+ }
+ skb_reserve(xmit_skb, 19);
+ skb_copy_from_linear_data(skb, skb_put(xmit_skb, len), len);
+ } else {
+ xmit_skb = skb_clone(skb, GFP_ATOMIC);
+ if (!xmit_skb) {
+ printk(KERN_WARNING "act2000_sendbuf: Out of memory\n");
+ return 0;
+ }
+ }
+ dev_kfree_skb(skb);
+ msg = (actcapi_msg *)skb_push(xmit_skb, 19);
+ msg->hdr.len = 19 + len;
+ msg->hdr.applicationID = 1;
+ msg->hdr.cmd.cmd = 0x86;
+ msg->hdr.cmd.subcmd = 0x00;
+ msg->hdr.msgnum = actcapi_nextsmsg(card);
+ msg->msg.data_b3_req.datalen = len;
+ msg->msg.data_b3_req.blocknr = (msg->hdr.msgnum & 0xff);
+ msg->msg.data_b3_req.fakencci = MAKE_NCCI(chan->plci, 0, chan->ncci);
+ msg->msg.data_b3_req.flags = ack; /* Will be set to 0 on actual sending */
+ actcapi_debug_msg(xmit_skb, 1);
+ chan->queued += len;
+ skb_queue_tail(&card->sndq, xmit_skb);
+ act2000_schedule_tx(card);
+ return len;
+}
+
+
+/* Read the Status-replies from the Interface */
+static int
+act2000_readstatus(u_char __user *buf, int len, act2000_card *card)
+{
+ int count;
+ u_char __user *p;
+
+ for (p = buf, count = 0; count < len; p++, count++) {
+ if (card->status_buf_read == card->status_buf_write)
+ return count;
+ put_user(*card->status_buf_read++, p);
+ if (card->status_buf_read > card->status_buf_end)
+ card->status_buf_read = card->status_buf;
+ }
+ return count;
+}
+
+/*
+ * Find card with given driverId
+ */
+static inline act2000_card *
+act2000_findcard(int driverid)
+{
+ act2000_card *p = cards;
+
+ while (p) {
+ if (p->myid == driverid)
+ return p;
+ p = p->next;
+ }
+ return (act2000_card *) 0;
+}
+
+/*
+ * Wrapper functions for interface to linklevel
+ */
+static int
+if_command(isdn_ctrl *c)
+{
+ act2000_card *card = act2000_findcard(c->driver);
+
+ if (card)
+ return (act2000_command(card, c));
+ printk(KERN_ERR
+ "act2000: if_command %d called with invalid driverId %d!\n",
+ c->command, c->driver);
+ return -ENODEV;
+}
+
+static int
+if_writecmd(const u_char __user *buf, int len, int id, int channel)
+{
+ act2000_card *card = act2000_findcard(id);
+
+ if (card) {
+ if (!(card->flags & ACT2000_FLAGS_RUNNING))
+ return -ENODEV;
+ return (len);
+ }
+ printk(KERN_ERR
+ "act2000: if_writecmd called with invalid driverId!\n");
+ return -ENODEV;
+}
+
+static int
+if_readstatus(u_char __user *buf, int len, int id, int channel)
+{
+ act2000_card *card = act2000_findcard(id);
+
+ if (card) {
+ if (!(card->flags & ACT2000_FLAGS_RUNNING))
+ return -ENODEV;
+ return (act2000_readstatus(buf, len, card));
+ }
+ printk(KERN_ERR
+ "act2000: if_readstatus called with invalid driverId!\n");
+ return -ENODEV;
+}
+
+static int
+if_sendbuf(int id, int channel, int ack, struct sk_buff *skb)
+{
+ act2000_card *card = act2000_findcard(id);
+
+ if (card) {
+ if (!(card->flags & ACT2000_FLAGS_RUNNING))
+ return -ENODEV;
+ return (act2000_sendbuf(card, channel, ack, skb));
+ }
+ printk(KERN_ERR
+ "act2000: if_sendbuf called with invalid driverId!\n");
+ return -ENODEV;
+}
+
+
+/*
+ * Allocate a new card-struct, initialize it
+ * link it into cards-list.
+ */
+static void
+act2000_alloccard(int bus, int port, int irq, char *id)
+{
+ int i;
+ act2000_card *card;
+ if (!(card = kzalloc(sizeof(act2000_card), GFP_KERNEL))) {
+ printk(KERN_WARNING
+ "act2000: (%s) Could not allocate card-struct.\n", id);
+ return;
+ }
+ spin_lock_init(&card->lock);
+ spin_lock_init(&card->mnlock);
+ skb_queue_head_init(&card->sndq);
+ skb_queue_head_init(&card->rcvq);
+ skb_queue_head_init(&card->ackq);
+ INIT_WORK(&card->snd_tq, act2000_transmit);
+ INIT_WORK(&card->rcv_tq, actcapi_dispatch);
+ INIT_WORK(&card->poll_tq, act2000_receive);
+ init_timer(&card->ptimer);
+ card->interface.owner = THIS_MODULE;
+ card->interface.channels = ACT2000_BCH;
+ card->interface.maxbufsize = 4000;
+ card->interface.command = if_command;
+ card->interface.writebuf_skb = if_sendbuf;
+ card->interface.writecmd = if_writecmd;
+ card->interface.readstat = if_readstatus;
+ card->interface.features =
+ ISDN_FEATURE_L2_X75I |
+ ISDN_FEATURE_L2_HDLC |
+ ISDN_FEATURE_L3_TRANS |
+ ISDN_FEATURE_P_UNKNOWN;
+ card->interface.hl_hdrlen = 20;
+ card->ptype = ISDN_PTYPE_EURO;
+ strlcpy(card->interface.id, id, sizeof(card->interface.id));
+ for (i = 0; i < ACT2000_BCH; i++) {
+ card->bch[i].plci = 0x8000;
+ card->bch[i].ncci = 0x8000;
+ card->bch[i].l2prot = ISDN_PROTO_L2_X75I;
+ card->bch[i].l3prot = ISDN_PROTO_L3_TRANS;
+ }
+ card->myid = -1;
+ card->bus = bus;
+ card->port = port;
+ card->irq = irq;
+ card->next = cards;
+ cards = card;
+}
+
+/*
+ * register card at linklevel
+ */
+static int
+act2000_registercard(act2000_card *card)
+{
+ switch (card->bus) {
+ case ACT2000_BUS_ISA:
+ break;
+ case ACT2000_BUS_MCA:
+ case ACT2000_BUS_PCMCIA:
+ default:
+ printk(KERN_WARNING
+ "act2000: Illegal BUS type %d\n",
+ card->bus);
+ return -1;
+ }
+ if (!register_isdn(&card->interface)) {
+ printk(KERN_WARNING
+ "act2000: Unable to register %s\n",
+ card->interface.id);
+ return -1;
+ }
+ card->myid = card->interface.channels;
+ sprintf(card->regname, "act2000-isdn (%s)", card->interface.id);
+ return 0;
+}
+
+static void
+unregister_card(act2000_card *card)
+{
+ isdn_ctrl cmd;
+
+ cmd.command = ISDN_STAT_UNLOAD;
+ cmd.driver = card->myid;
+ card->interface.statcallb(&cmd);
+ switch (card->bus) {
+ case ACT2000_BUS_ISA:
+ act2000_isa_release(card);
+ break;
+ case ACT2000_BUS_MCA:
+ case ACT2000_BUS_PCMCIA:
+ default:
+ printk(KERN_WARNING
+ "act2000: Invalid BUS type %d\n",
+ card->bus);
+ break;
+ }
+}
+
+static int
+act2000_addcard(int bus, int port, int irq, char *id)
+{
+ act2000_card *p;
+ act2000_card *q = NULL;
+ int initialized;
+ int added = 0;
+ int failed = 0;
+ int i;
+
+ if (!bus)
+ bus = ACT2000_BUS_ISA;
+ if (port != -1) {
+ /* Port defined, do fixed setup */
+ act2000_alloccard(bus, port, irq, id);
+ } else {
+ /* No port defined, perform autoprobing.
+ * This may result in more than one card detected.
+ */
+ switch (bus) {
+ case ACT2000_BUS_ISA:
+ for (i = 0; i < ARRAY_SIZE(act2000_isa_ports); i++)
+ if (act2000_isa_detect(act2000_isa_ports[i])) {
+ printk(KERN_INFO "act2000: Detected "
+ "ISA card at port 0x%x\n",
+ act2000_isa_ports[i]);
+ act2000_alloccard(bus,
+ act2000_isa_ports[i], irq, id);
+ }
+ break;
+ case ACT2000_BUS_MCA:
+ case ACT2000_BUS_PCMCIA:
+ default:
+ printk(KERN_WARNING
+ "act2000: addcard: Invalid BUS type %d\n", bus);
+ }
+ }
+ if (!cards)
+ return 1;
+ p = cards;
+ while (p) {
+ initialized = 0;
+ if (!p->interface.statcallb) {
+ /* Not yet registered.
+ * Try to register and activate it.
+ */
+ added++;
+ switch (p->bus) {
+ case ACT2000_BUS_ISA:
+ if (act2000_isa_detect(p->port)) {
+ if (act2000_registercard(p))
+ break;
+ if (act2000_isa_config_port(p, p->port)) {
+ printk(KERN_WARNING
+ "act2000: Could not request port 0x%04x\n",
+ p->port);
+ unregister_card(p);
+ p->interface.statcallb = NULL;
+ break;
+ }
+ if (act2000_isa_config_irq(p, p->irq)) {
+ printk(KERN_INFO
+ "act2000: No IRQ available, fallback to polling\n");
+ /* Fall back to polled operation */
+ p->irq = 0;
+ }
+ printk(KERN_INFO
+ "act2000: ISA"
+ "-type card at port "
+ "0x%04x ",
+ p->port);
+ if (p->irq)
+ printk("irq %d\n", p->irq);
+ else
+ printk("polled\n");
+ initialized = 1;
+ }
+ break;
+ case ACT2000_BUS_MCA:
+ case ACT2000_BUS_PCMCIA:
+ default:
+ printk(KERN_WARNING
+ "act2000: addcard: Invalid BUS type %d\n",
+ p->bus);
+ }
+ } else
+ /* Card already initialized */
+ initialized = 1;
+ if (initialized) {
+ /* Init OK, next card ... */
+ q = p;
+ p = p->next;
+ } else {
+ /* Init failed, remove card from list, free memory */
+ printk(KERN_WARNING
+ "act2000: Initialization of %s failed\n",
+ p->interface.id);
+ if (q) {
+ q->next = p->next;
+ kfree(p);
+ p = q->next;
+ } else {
+ cards = p->next;
+ kfree(p);
+ p = cards;
+ }
+ failed++;
+ }
+ }
+ return (added - failed);
+}
+
+#define DRIVERNAME "IBM Active 2000 ISDN driver"
+
+static int __init act2000_init(void)
+{
+ printk(KERN_INFO "%s\n", DRIVERNAME);
+ if (!cards)
+ act2000_addcard(act_bus, act_port, act_irq, act_id);
+ if (!cards)
+ printk(KERN_INFO "act2000: No cards defined yet\n");
+ return 0;
+}
+
+static void __exit act2000_exit(void)
+{
+ act2000_card *card = cards;
+ act2000_card *last;
+ while (card) {
+ unregister_card(card);
+ del_timer_sync(&card->ptimer);
+ card = card->next;
+ }
+ card = cards;
+ while (card) {
+ last = card;
+ card = card->next;
+ act2000_clear_msn(last);
+ kfree(last);
+ }
+ printk(KERN_INFO "%s unloaded\n", DRIVERNAME);
+}
+
+module_init(act2000_init);
+module_exit(act2000_exit);
diff --git a/drivers/staging/i4l/icn/Kconfig b/drivers/staging/i4l/icn/Kconfig
new file mode 100644
index 000000000000..4534f21a1852
--- /dev/null
+++ b/drivers/staging/i4l/icn/Kconfig
@@ -0,0 +1,12 @@
+config ISDN_DRV_ICN
+ tristate "ICN 2B and 4B support"
+ depends on ISA
+ help
+ This enables support for two kinds of ISDN-cards made by a German
+ company called ICN. 2B is the standard version for a single ISDN
+ line with two B-channels, 4B supports two ISDN lines. For running
+ this card, additional firmware is necessary, which has to be
+ downloaded into the card using a utility which is distributed
+ separately. See <file:Documentation/isdn/README> and
+ <file:Documentation/isdn/README.icn> for more
+ information.
diff --git a/drivers/staging/i4l/icn/Makefile b/drivers/staging/i4l/icn/Makefile
new file mode 100644
index 000000000000..d9b476fcf384
--- /dev/null
+++ b/drivers/staging/i4l/icn/Makefile
@@ -0,0 +1,5 @@
+# Makefile for the icn ISDN device driver
+
+# Each configuration option enables a list of files.
+
+obj-$(CONFIG_ISDN_DRV_ICN) += icn.o
diff --git a/drivers/staging/i4l/icn/icn.c b/drivers/staging/i4l/icn/icn.c
new file mode 100644
index 000000000000..46d957c34be1
--- /dev/null
+++ b/drivers/staging/i4l/icn/icn.c
@@ -0,0 +1,1693 @@
+/* $Id: icn.c,v 1.65.6.8 2001/09/23 22:24:55 kai Exp $
+ *
+ * ISDN low-level module for the ICN active ISDN-Card.
+ *
+ * Copyright 1994,95,96 by Fritz Elfert (fritz@isdn4linux.de)
+ *
+ * This software may be used and distributed according to the terms
+ * of the GNU General Public License, incorporated herein by reference.
+ *
+ */
+
+#include "icn.h"
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/sched.h>
+
+static int portbase = ICN_BASEADDR;
+static unsigned long membase = ICN_MEMADDR;
+static char *icn_id = "\0";
+static char *icn_id2 = "\0";
+
+MODULE_DESCRIPTION("ISDN4Linux: Driver for ICN active ISDN card");
+MODULE_AUTHOR("Fritz Elfert");
+MODULE_LICENSE("GPL");
+module_param(portbase, int, 0);
+MODULE_PARM_DESC(portbase, "Port address of first card");
+module_param(membase, ulong, 0);
+MODULE_PARM_DESC(membase, "Shared memory address of all cards");
+module_param(icn_id, charp, 0);
+MODULE_PARM_DESC(icn_id, "ID-String of first card");
+module_param(icn_id2, charp, 0);
+MODULE_PARM_DESC(icn_id2, "ID-String of first card, second S0 (4B only)");
+
+/*
+ * Verbose bootcode- and protocol-downloading.
+ */
+#undef BOOT_DEBUG
+
+/*
+ * Verbose Shmem-Mapping.
+ */
+#undef MAP_DEBUG
+
+static char
+*revision = "$Revision: 1.65.6.8 $";
+
+static int icn_addcard(int, char *, char *);
+
+/*
+ * Free send-queue completely.
+ * Parameter:
+ * card = pointer to card struct
+ * channel = channel number
+ */
+static void
+icn_free_queue(icn_card *card, int channel)
+{
+ struct sk_buff_head *queue = &card->spqueue[channel];
+ struct sk_buff *skb;
+
+ skb_queue_purge(queue);
+ card->xlen[channel] = 0;
+ card->sndcount[channel] = 0;
+ if ((skb = card->xskb[channel])) {
+ card->xskb[channel] = NULL;
+ dev_kfree_skb(skb);
+ }
+}
+
+/* Put a value into a shift-register, highest bit first.
+ * Parameters:
+ * port = port for output (bit 0 is significant)
+ * val = value to be output
+ * firstbit = Bit-Number of highest bit
+ * bitcount = Number of bits to output
+ */
+static inline void
+icn_shiftout(unsigned short port,
+ unsigned long val,
+ int firstbit,
+ int bitcount)
+{
+
+ register u_char s;
+ register u_char c;
+
+ for (s = firstbit, c = bitcount; c > 0; s--, c--)
+ OUTB_P((u_char) ((val >> s) & 1) ? 0xff : 0, port);
+}
+
+/*
+ * disable a cards shared memory
+ */
+static inline void
+icn_disable_ram(icn_card *card)
+{
+ OUTB_P(0, ICN_MAPRAM);
+}
+
+/*
+ * enable a cards shared memory
+ */
+static inline void
+icn_enable_ram(icn_card *card)
+{
+ OUTB_P(0xff, ICN_MAPRAM);
+}
+
+/*
+ * Map a cards channel0 (Bank0/Bank8) or channel1 (Bank4/Bank12)
+ *
+ * must called with holding the devlock
+ */
+static inline void
+icn_map_channel(icn_card *card, int channel)
+{
+#ifdef MAP_DEBUG
+ printk(KERN_DEBUG "icn_map_channel %d %d\n", dev.channel, channel);
+#endif
+ if ((channel == dev.channel) && (card == dev.mcard))
+ return;
+ if (dev.mcard)
+ icn_disable_ram(dev.mcard);
+ icn_shiftout(ICN_BANK, chan2bank[channel], 3, 4); /* Select Bank */
+ icn_enable_ram(card);
+ dev.mcard = card;
+ dev.channel = channel;
+#ifdef MAP_DEBUG
+ printk(KERN_DEBUG "icn_map_channel done\n");
+#endif
+}
+
+/*
+ * Lock a cards channel.
+ * Return 0 if requested card/channel is unmapped (failure).
+ * Return 1 on success.
+ *
+ * must called with holding the devlock
+ */
+static inline int
+icn_lock_channel(icn_card *card, int channel)
+{
+ register int retval;
+
+#ifdef MAP_DEBUG
+ printk(KERN_DEBUG "icn_lock_channel %d\n", channel);
+#endif
+ if ((dev.channel == channel) && (card == dev.mcard)) {
+ dev.chanlock++;
+ retval = 1;
+#ifdef MAP_DEBUG
+ printk(KERN_DEBUG "icn_lock_channel %d OK\n", channel);
+#endif
+ } else {
+ retval = 0;
+#ifdef MAP_DEBUG
+ printk(KERN_DEBUG "icn_lock_channel %d FAILED, dc=%d\n", channel, dev.channel);
+#endif
+ }
+ return retval;
+}
+
+/*
+ * Release current card/channel lock
+ *
+ * must called with holding the devlock
+ */
+static inline void
+__icn_release_channel(void)
+{
+#ifdef MAP_DEBUG
+ printk(KERN_DEBUG "icn_release_channel l=%d\n", dev.chanlock);
+#endif
+ if (dev.chanlock > 0)
+ dev.chanlock--;
+}
+
+/*
+ * Release current card/channel lock
+ */
+static inline void
+icn_release_channel(void)
+{
+ ulong flags;
+
+ spin_lock_irqsave(&dev.devlock, flags);
+ __icn_release_channel();
+ spin_unlock_irqrestore(&dev.devlock, flags);
+}
+
+/*
+ * Try to map and lock a cards channel.
+ * Return 1 on success, 0 on failure.
+ */
+static inline int
+icn_trymaplock_channel(icn_card *card, int channel)
+{
+ ulong flags;
+
+#ifdef MAP_DEBUG
+ printk(KERN_DEBUG "trymaplock c=%d dc=%d l=%d\n", channel, dev.channel,
+ dev.chanlock);
+#endif
+ spin_lock_irqsave(&dev.devlock, flags);
+ if ((!dev.chanlock) ||
+ ((dev.channel == channel) && (dev.mcard == card))) {
+ dev.chanlock++;
+ icn_map_channel(card, channel);
+ spin_unlock_irqrestore(&dev.devlock, flags);
+#ifdef MAP_DEBUG
+ printk(KERN_DEBUG "trymaplock %d OK\n", channel);
+#endif
+ return 1;
+ }
+ spin_unlock_irqrestore(&dev.devlock, flags);
+#ifdef MAP_DEBUG
+ printk(KERN_DEBUG "trymaplock %d FAILED\n", channel);
+#endif
+ return 0;
+}
+
+/*
+ * Release current card/channel lock,
+ * then map same or other channel without locking.
+ */
+static inline void
+icn_maprelease_channel(icn_card *card, int channel)
+{
+ ulong flags;
+
+#ifdef MAP_DEBUG
+ printk(KERN_DEBUG "map_release c=%d l=%d\n", channel, dev.chanlock);
+#endif
+ spin_lock_irqsave(&dev.devlock, flags);
+ if (dev.chanlock > 0)
+ dev.chanlock--;
+ if (!dev.chanlock)
+ icn_map_channel(card, channel);
+ spin_unlock_irqrestore(&dev.devlock, flags);
+}
+
+/* Get Data from the B-Channel, assemble fragmented packets and put them
+ * into receive-queue. Wake up any B-Channel-reading processes.
+ * This routine is called via timer-callback from icn_pollbchan().
+ */
+
+static void
+icn_pollbchan_receive(int channel, icn_card *card)
+{
+ int mch = channel + ((card->secondhalf) ? 2 : 0);
+ int eflag;
+ int cnt;
+ struct sk_buff *skb;
+
+ if (icn_trymaplock_channel(card, mch)) {
+ while (rbavl) {
+ cnt = readb(&rbuf_l);
+ if ((card->rcvidx[channel] + cnt) > 4000) {
+ printk(KERN_WARNING
+ "icn: (%s) bogus packet on ch%d, dropping.\n",
+ CID,
+ channel + 1);
+ card->rcvidx[channel] = 0;
+ eflag = 0;
+ } else {
+ memcpy_fromio(&card->rcvbuf[channel][card->rcvidx[channel]],
+ &rbuf_d, cnt);
+ card->rcvidx[channel] += cnt;
+ eflag = readb(&rbuf_f);
+ }
+ rbnext;
+ icn_maprelease_channel(card, mch & 2);
+ if (!eflag) {
+ if ((cnt = card->rcvidx[channel])) {
+ if (!(skb = dev_alloc_skb(cnt))) {
+ printk(KERN_WARNING "icn: receive out of memory\n");
+ break;
+ }
+ memcpy(skb_put(skb, cnt), card->rcvbuf[channel], cnt);
+ card->rcvidx[channel] = 0;
+ card->interface.rcvcallb_skb(card->myid, channel, skb);
+ }
+ }
+ if (!icn_trymaplock_channel(card, mch))
+ break;
+ }
+ icn_maprelease_channel(card, mch & 2);
+ }
+}
+
+/* Send data-packet to B-Channel, split it up into fragments of
+ * ICN_FRAGSIZE length. If last fragment is sent out, signal
+ * success to upper layers via statcallb with ISDN_STAT_BSENT argument.
+ * This routine is called via timer-callback from icn_pollbchan() or
+ * directly from icn_sendbuf().
+ */
+
+static void
+icn_pollbchan_send(int channel, icn_card *card)
+{
+ int mch = channel + ((card->secondhalf) ? 2 : 0);
+ int cnt;
+ unsigned long flags;
+ struct sk_buff *skb;
+ isdn_ctrl cmd;
+
+ if (!(card->sndcount[channel] || card->xskb[channel] ||
+ !skb_queue_empty(&card->spqueue[channel])))
+ return;
+ if (icn_trymaplock_channel(card, mch)) {
+ while (sbfree &&
+ (card->sndcount[channel] ||
+ !skb_queue_empty(&card->spqueue[channel]) ||
+ card->xskb[channel])) {
+ spin_lock_irqsave(&card->lock, flags);
+ if (card->xmit_lock[channel]) {
+ spin_unlock_irqrestore(&card->lock, flags);
+ break;
+ }
+ card->xmit_lock[channel]++;
+ spin_unlock_irqrestore(&card->lock, flags);
+ skb = card->xskb[channel];
+ if (!skb) {
+ skb = skb_dequeue(&card->spqueue[channel]);
+ if (skb) {
+ /* Pop ACK-flag off skb.
+ * Store length to xlen.
+ */
+ if (*(skb_pull(skb, 1)))
+ card->xlen[channel] = skb->len;
+ else
+ card->xlen[channel] = 0;
+ }
+ }
+ if (!skb)
+ break;
+ if (skb->len > ICN_FRAGSIZE) {
+ writeb(0xff, &sbuf_f);
+ cnt = ICN_FRAGSIZE;
+ } else {
+ writeb(0x0, &sbuf_f);
+ cnt = skb->len;
+ }
+ writeb(cnt, &sbuf_l);
+ memcpy_toio(&sbuf_d, skb->data, cnt);
+ skb_pull(skb, cnt);
+ sbnext; /* switch to next buffer */
+ icn_maprelease_channel(card, mch & 2);
+ spin_lock_irqsave(&card->lock, flags);
+ card->sndcount[channel] -= cnt;
+ if (!skb->len) {
+ if (card->xskb[channel])
+ card->xskb[channel] = NULL;
+ card->xmit_lock[channel] = 0;
+ spin_unlock_irqrestore(&card->lock, flags);
+ dev_kfree_skb(skb);
+ if (card->xlen[channel]) {
+ cmd.command = ISDN_STAT_BSENT;
+ cmd.driver = card->myid;
+ cmd.arg = channel;
+ cmd.parm.length = card->xlen[channel];
+ card->interface.statcallb(&cmd);
+ }
+ } else {
+ card->xskb[channel] = skb;
+ card->xmit_lock[channel] = 0;
+ spin_unlock_irqrestore(&card->lock, flags);
+ }
+ if (!icn_trymaplock_channel(card, mch))
+ break;
+ }
+ icn_maprelease_channel(card, mch & 2);
+ }
+}
+
+/* Send/Receive Data to/from the B-Channel.
+ * This routine is called via timer-callback.
+ * It schedules itself while any B-Channel is open.
+ */
+
+static void
+icn_pollbchan(unsigned long data)
+{
+ icn_card *card = (icn_card *) data;
+ unsigned long flags;
+
+ if (card->flags & ICN_FLAGS_B1ACTIVE) {
+ icn_pollbchan_receive(0, card);
+ icn_pollbchan_send(0, card);
+ }
+ if (card->flags & ICN_FLAGS_B2ACTIVE) {
+ icn_pollbchan_receive(1, card);
+ icn_pollbchan_send(1, card);
+ }
+ if (card->flags & (ICN_FLAGS_B1ACTIVE | ICN_FLAGS_B2ACTIVE)) {
+ /* schedule b-channel polling again */
+ spin_lock_irqsave(&card->lock, flags);
+ mod_timer(&card->rb_timer, jiffies + ICN_TIMER_BCREAD);
+ card->flags |= ICN_FLAGS_RBTIMER;
+ spin_unlock_irqrestore(&card->lock, flags);
+ } else
+ card->flags &= ~ICN_FLAGS_RBTIMER;
+}
+
+typedef struct icn_stat {
+ char *statstr;
+ int command;
+ int action;
+} icn_stat;
+/* *INDENT-OFF* */
+static icn_stat icn_stat_table[] =
+{
+ {"BCON_", ISDN_STAT_BCONN, 1}, /* B-Channel connected */
+ {"BDIS_", ISDN_STAT_BHUP, 2}, /* B-Channel disconnected */
+ /*
+ ** add d-channel connect and disconnect support to link-level
+ */
+ {"DCON_", ISDN_STAT_DCONN, 10}, /* D-Channel connected */
+ {"DDIS_", ISDN_STAT_DHUP, 11}, /* D-Channel disconnected */
+ {"DCAL_I", ISDN_STAT_ICALL, 3}, /* Incoming call dialup-line */
+ {"DSCA_I", ISDN_STAT_ICALL, 3}, /* Incoming call 1TR6-SPV */
+ {"FCALL", ISDN_STAT_ICALL, 4}, /* Leased line connection up */
+ {"CIF", ISDN_STAT_CINF, 5}, /* Charge-info, 1TR6-type */
+ {"AOC", ISDN_STAT_CINF, 6}, /* Charge-info, DSS1-type */
+ {"CAU", ISDN_STAT_CAUSE, 7}, /* Cause code */
+ {"TEI OK", ISDN_STAT_RUN, 0}, /* Card connected to wallplug */
+ {"E_L1: ACT FAIL", ISDN_STAT_BHUP, 8}, /* Layer-1 activation failed */
+ {"E_L2: DATA LIN", ISDN_STAT_BHUP, 8}, /* Layer-2 data link lost */
+ {"E_L1: ACTIVATION FAILED",
+ ISDN_STAT_BHUP, 8}, /* Layer-1 activation failed */
+ {NULL, 0, -1}
+};
+/* *INDENT-ON* */
+
+
+/*
+ * Check Statusqueue-Pointer from isdn-cards.
+ * If there are new status-replies from the interface, check
+ * them against B-Channel-connects/disconnects and set flags accordingly.
+ * Wake-Up any processes, who are reading the status-device.
+ * If there are B-Channels open, initiate a timer-callback to
+ * icn_pollbchan().
+ * This routine is called periodically via timer.
+ */
+
+static void
+icn_parse_status(u_char *status, int channel, icn_card *card)
+{
+ icn_stat *s = icn_stat_table;
+ int action = -1;
+ unsigned long flags;
+ isdn_ctrl cmd;
+
+ while (s->statstr) {
+ if (!strncmp(status, s->statstr, strlen(s->statstr))) {
+ cmd.command = s->command;
+ action = s->action;
+ break;
+ }
+ s++;
+ }
+ if (action == -1)
+ return;
+ cmd.driver = card->myid;
+ cmd.arg = channel;
+ switch (action) {
+ case 11:
+ spin_lock_irqsave(&card->lock, flags);
+ icn_free_queue(card, channel);
+ card->rcvidx[channel] = 0;
+
+ if (card->flags &
+ ((channel) ? ICN_FLAGS_B2ACTIVE : ICN_FLAGS_B1ACTIVE)) {
+
+ isdn_ctrl ncmd;
+
+ card->flags &= ~((channel) ?
+ ICN_FLAGS_B2ACTIVE : ICN_FLAGS_B1ACTIVE);
+
+ memset(&ncmd, 0, sizeof(ncmd));
+
+ ncmd.driver = card->myid;
+ ncmd.arg = channel;
+ ncmd.command = ISDN_STAT_BHUP;
+ spin_unlock_irqrestore(&card->lock, flags);
+ card->interface.statcallb(&cmd);
+ } else
+ spin_unlock_irqrestore(&card->lock, flags);
+ break;
+ case 1:
+ spin_lock_irqsave(&card->lock, flags);
+ icn_free_queue(card, channel);
+ card->flags |= (channel) ?
+ ICN_FLAGS_B2ACTIVE : ICN_FLAGS_B1ACTIVE;
+ spin_unlock_irqrestore(&card->lock, flags);
+ break;
+ case 2:
+ spin_lock_irqsave(&card->lock, flags);
+ card->flags &= ~((channel) ?
+ ICN_FLAGS_B2ACTIVE : ICN_FLAGS_B1ACTIVE);
+ icn_free_queue(card, channel);
+ card->rcvidx[channel] = 0;
+ spin_unlock_irqrestore(&card->lock, flags);
+ break;
+ case 3:
+ {
+ char *t = status + 6;
+ char *s = strchr(t, ',');
+
+ *s++ = '\0';
+ strlcpy(cmd.parm.setup.phone, t,
+ sizeof(cmd.parm.setup.phone));
+ s = strchr(t = s, ',');
+ *s++ = '\0';
+ if (!strlen(t))
+ cmd.parm.setup.si1 = 0;
+ else
+ cmd.parm.setup.si1 =
+ simple_strtoul(t, NULL, 10);
+ s = strchr(t = s, ',');
+ *s++ = '\0';
+ if (!strlen(t))
+ cmd.parm.setup.si2 = 0;
+ else
+ cmd.parm.setup.si2 =
+ simple_strtoul(t, NULL, 10);
+ strlcpy(cmd.parm.setup.eazmsn, s,
+ sizeof(cmd.parm.setup.eazmsn));
+ }
+ cmd.parm.setup.plan = 0;
+ cmd.parm.setup.screen = 0;
+ break;
+ case 4:
+ sprintf(cmd.parm.setup.phone, "LEASED%d", card->myid);
+ sprintf(cmd.parm.setup.eazmsn, "%d", channel + 1);
+ cmd.parm.setup.si1 = 7;
+ cmd.parm.setup.si2 = 0;
+ cmd.parm.setup.plan = 0;
+ cmd.parm.setup.screen = 0;
+ break;
+ case 5:
+ strlcpy(cmd.parm.num, status + 3, sizeof(cmd.parm.num));
+ break;
+ case 6:
+ snprintf(cmd.parm.num, sizeof(cmd.parm.num), "%d",
+ (int) simple_strtoul(status + 7, NULL, 16));
+ break;
+ case 7:
+ status += 3;
+ if (strlen(status) == 4)
+ snprintf(cmd.parm.num, sizeof(cmd.parm.num), "%s%c%c",
+ status + 2, *status, *(status + 1));
+ else
+ strlcpy(cmd.parm.num, status + 1, sizeof(cmd.parm.num));
+ break;
+ case 8:
+ spin_lock_irqsave(&card->lock, flags);
+ card->flags &= ~ICN_FLAGS_B1ACTIVE;
+ icn_free_queue(card, 0);
+ card->rcvidx[0] = 0;
+ spin_unlock_irqrestore(&card->lock, flags);
+ cmd.arg = 0;
+ cmd.driver = card->myid;
+ card->interface.statcallb(&cmd);
+ cmd.command = ISDN_STAT_DHUP;
+ cmd.arg = 0;
+ cmd.driver = card->myid;
+ card->interface.statcallb(&cmd);
+ cmd.command = ISDN_STAT_BHUP;
+ spin_lock_irqsave(&card->lock, flags);
+ card->flags &= ~ICN_FLAGS_B2ACTIVE;
+ icn_free_queue(card, 1);
+ card->rcvidx[1] = 0;
+ spin_unlock_irqrestore(&card->lock, flags);
+ cmd.arg = 1;
+ cmd.driver = card->myid;
+ card->interface.statcallb(&cmd);
+ cmd.command = ISDN_STAT_DHUP;
+ cmd.arg = 1;
+ cmd.driver = card->myid;
+ break;
+ }
+ card->interface.statcallb(&cmd);
+ return;
+}
+
+static void
+icn_putmsg(icn_card *card, unsigned char c)
+{
+ ulong flags;
+
+ spin_lock_irqsave(&card->lock, flags);
+ *card->msg_buf_write++ = (c == 0xff) ? '\n' : c;
+ if (card->msg_buf_write == card->msg_buf_read) {
+ if (++card->msg_buf_read > card->msg_buf_end)
+ card->msg_buf_read = card->msg_buf;
+ }
+ if (card->msg_buf_write > card->msg_buf_end)
+ card->msg_buf_write = card->msg_buf;
+ spin_unlock_irqrestore(&card->lock, flags);
+}
+
+static void
+icn_polldchan(unsigned long data)
+{
+ icn_card *card = (icn_card *) data;
+ int mch = card->secondhalf ? 2 : 0;
+ int avail = 0;
+ int left;
+ u_char c;
+ int ch;
+ unsigned long flags;
+ int i;
+ u_char *p;
+ isdn_ctrl cmd;
+
+ if (icn_trymaplock_channel(card, mch)) {
+ avail = msg_avail;
+ for (left = avail, i = readb(&msg_o); left > 0; i++, left--) {
+ c = readb(&dev.shmem->comm_buffers.iopc_buf[i & 0xff]);
+ icn_putmsg(card, c);
+ if (c == 0xff) {
+ card->imsg[card->iptr] = 0;
+ card->iptr = 0;
+ if (card->imsg[0] == '0' && card->imsg[1] >= '0' &&
+ card->imsg[1] <= '2' && card->imsg[2] == ';') {
+ ch = (card->imsg[1] - '0') - 1;
+ p = &card->imsg[3];
+ icn_parse_status(p, ch, card);
+ } else {
+ p = card->imsg;
+ if (!strncmp(p, "DRV1.", 5)) {
+ u_char vstr[10];
+ u_char *q = vstr;
+
+ printk(KERN_INFO "icn: (%s) %s\n", CID, p);
+ if (!strncmp(p + 7, "TC", 2)) {
+ card->ptype = ISDN_PTYPE_1TR6;
+ card->interface.features |= ISDN_FEATURE_P_1TR6;
+ printk(KERN_INFO
+ "icn: (%s) 1TR6-Protocol loaded and running\n", CID);
+ }
+ if (!strncmp(p + 7, "EC", 2)) {
+ card->ptype = ISDN_PTYPE_EURO;
+ card->interface.features |= ISDN_FEATURE_P_EURO;
+ printk(KERN_INFO
+ "icn: (%s) Euro-Protocol loaded and running\n", CID);
+ }
+ p = strstr(card->imsg, "BRV") + 3;
+ while (*p) {
+ if (*p >= '0' && *p <= '9')
+ *q++ = *p;
+ p++;
+ }
+ *q = '\0';
+ strcat(vstr, "000");
+ vstr[3] = '\0';
+ card->fw_rev = (int) simple_strtoul(vstr, NULL, 10);
+ continue;
+
+ }
+ }
+ } else {
+ card->imsg[card->iptr] = c;
+ if (card->iptr < 59)
+ card->iptr++;
+ }
+ }
+ writeb((readb(&msg_o) + avail) & 0xff, &msg_o);
+ icn_release_channel();
+ }
+ if (avail) {
+ cmd.command = ISDN_STAT_STAVAIL;
+ cmd.driver = card->myid;
+ cmd.arg = avail;
+ card->interface.statcallb(&cmd);
+ }
+ spin_lock_irqsave(&card->lock, flags);
+ if (card->flags & (ICN_FLAGS_B1ACTIVE | ICN_FLAGS_B2ACTIVE))
+ if (!(card->flags & ICN_FLAGS_RBTIMER)) {
+ /* schedule b-channel polling */
+ card->flags |= ICN_FLAGS_RBTIMER;
+ del_timer(&card->rb_timer);
+ card->rb_timer.function = icn_pollbchan;
+ card->rb_timer.data = (unsigned long) card;
+ card->rb_timer.expires = jiffies + ICN_TIMER_BCREAD;
+ add_timer(&card->rb_timer);
+ }
+ /* schedule again */
+ mod_timer(&card->st_timer, jiffies + ICN_TIMER_DCREAD);
+ spin_unlock_irqrestore(&card->lock, flags);
+}
+
+/* Append a packet to the transmit buffer-queue.
+ * Parameters:
+ * channel = Number of B-channel
+ * skb = pointer to sk_buff
+ * card = pointer to card-struct
+ * Return:
+ * Number of bytes transferred, -E??? on error
+ */
+
+static int
+icn_sendbuf(int channel, int ack, struct sk_buff *skb, icn_card *card)
+{
+ int len = skb->len;
+ unsigned long flags;
+ struct sk_buff *nskb;
+
+ if (len > 4000) {
+ printk(KERN_WARNING
+ "icn: Send packet too large\n");
+ return -EINVAL;
+ }
+ if (len) {
+ if (!(card->flags & (channel) ? ICN_FLAGS_B2ACTIVE : ICN_FLAGS_B1ACTIVE))
+ return 0;
+ if (card->sndcount[channel] > ICN_MAX_SQUEUE)
+ return 0;
+ /* TODO test headroom or use skb->nb to flag ACK */
+ nskb = skb_clone(skb, GFP_ATOMIC);
+ if (nskb) {
+ /* Push ACK flag as one
+ * byte in front of data.
+ */
+ *(skb_push(nskb, 1)) = ack ? 1 : 0;
+ skb_queue_tail(&card->spqueue[channel], nskb);
+ dev_kfree_skb(skb);
+ } else
+ len = 0;
+ spin_lock_irqsave(&card->lock, flags);
+ card->sndcount[channel] += len;
+ spin_unlock_irqrestore(&card->lock, flags);
+ }
+ return len;
+}
+
+/*
+ * Check card's status after starting the bootstrap loader.
+ * On entry, the card's shared memory has already to be mapped.
+ * Return:
+ * 0 on success (Boot loader ready)
+ * -EIO on failure (timeout)
+ */
+static int
+icn_check_loader(int cardnumber)
+{
+ int timer = 0;
+
+ while (1) {
+#ifdef BOOT_DEBUG
+ printk(KERN_DEBUG "Loader %d ?\n", cardnumber);
+#endif
+ if (readb(&dev.shmem->data_control.scns) ||
+ readb(&dev.shmem->data_control.scnr)) {
+ if (timer++ > 5) {
+ printk(KERN_WARNING
+ "icn: Boot-Loader %d timed out.\n",
+ cardnumber);
+ icn_release_channel();
+ return -EIO;
+ }
+#ifdef BOOT_DEBUG
+ printk(KERN_DEBUG "Loader %d TO?\n", cardnumber);
+#endif
+ msleep_interruptible(ICN_BOOT_TIMEOUT1);
+ } else {
+#ifdef BOOT_DEBUG
+ printk(KERN_DEBUG "Loader %d OK\n", cardnumber);
+#endif
+ icn_release_channel();
+ return 0;
+ }
+ }
+}
+
+/* Load the boot-code into the interface-card's memory and start it.
+ * Always called from user-process.
+ *
+ * Parameters:
+ * buffer = pointer to packet
+ * Return:
+ * 0 if successfully loaded
+ */
+
+#ifdef BOOT_DEBUG
+#define SLEEP(sec) { \
+ int slsec = sec; \
+ printk(KERN_DEBUG "SLEEP(%d)\n", slsec); \
+ while (slsec) { \
+ msleep_interruptible(1000); \
+ slsec--; \
+ } \
+ }
+#else
+#define SLEEP(sec)
+#endif
+
+static int
+icn_loadboot(u_char __user *buffer, icn_card *card)
+{
+ int ret;
+ u_char *codebuf;
+ unsigned long flags;
+
+#ifdef BOOT_DEBUG
+ printk(KERN_DEBUG "icn_loadboot called, buffaddr=%08lx\n", (ulong) buffer);
+#endif
+ if (!(codebuf = kmalloc(ICN_CODE_STAGE1, GFP_KERNEL))) {
+ printk(KERN_WARNING "icn: Could not allocate code buffer\n");
+ ret = -ENOMEM;
+ goto out;
+ }
+ if (copy_from_user(codebuf, buffer, ICN_CODE_STAGE1)) {
+ ret = -EFAULT;
+ goto out_kfree;
+ }
+ if (!card->rvalid) {
+ if (!request_region(card->port, ICN_PORTLEN, card->regname)) {
+ printk(KERN_WARNING
+ "icn: (%s) ports 0x%03x-0x%03x in use.\n",
+ CID,
+ card->port,
+ card->port + ICN_PORTLEN);
+ ret = -EBUSY;
+ goto out_kfree;
+ }
+ card->rvalid = 1;
+ if (card->doubleS0)
+ card->other->rvalid = 1;
+ }
+ if (!dev.mvalid) {
+ if (!request_mem_region(dev.memaddr, 0x4000, "icn-isdn (all cards)")) {
+ printk(KERN_WARNING
+ "icn: memory at 0x%08lx in use.\n", dev.memaddr);
+ ret = -EBUSY;
+ goto out_kfree;
+ }
+ dev.shmem = ioremap(dev.memaddr, 0x4000);
+ dev.mvalid = 1;
+ }
+ OUTB_P(0, ICN_RUN); /* Reset Controller */
+ OUTB_P(0, ICN_MAPRAM); /* Disable RAM */
+ icn_shiftout(ICN_CFG, 0x0f, 3, 4); /* Windowsize= 16k */
+ icn_shiftout(ICN_CFG, dev.memaddr, 23, 10); /* Set RAM-Addr. */
+#ifdef BOOT_DEBUG
+ printk(KERN_DEBUG "shmem=%08lx\n", dev.memaddr);
+#endif
+ SLEEP(1);
+#ifdef BOOT_DEBUG
+ printk(KERN_DEBUG "Map Bank 0\n");
+#endif
+ spin_lock_irqsave(&dev.devlock, flags);
+ icn_map_channel(card, 0); /* Select Bank 0 */
+ icn_lock_channel(card, 0); /* Lock Bank 0 */
+ spin_unlock_irqrestore(&dev.devlock, flags);
+ SLEEP(1);
+ memcpy_toio(dev.shmem, codebuf, ICN_CODE_STAGE1); /* Copy code */
+#ifdef BOOT_DEBUG
+ printk(KERN_DEBUG "Bootloader transferred\n");
+#endif
+ if (card->doubleS0) {
+ SLEEP(1);
+#ifdef BOOT_DEBUG
+ printk(KERN_DEBUG "Map Bank 8\n");
+#endif
+ spin_lock_irqsave(&dev.devlock, flags);
+ __icn_release_channel();
+ icn_map_channel(card, 2); /* Select Bank 8 */
+ icn_lock_channel(card, 2); /* Lock Bank 8 */
+ spin_unlock_irqrestore(&dev.devlock, flags);
+ SLEEP(1);
+ memcpy_toio(dev.shmem, codebuf, ICN_CODE_STAGE1); /* Copy code */
+#ifdef BOOT_DEBUG
+ printk(KERN_DEBUG "Bootloader transferred\n");
+#endif
+ }
+ SLEEP(1);
+ OUTB_P(0xff, ICN_RUN); /* Start Boot-Code */
+ if ((ret = icn_check_loader(card->doubleS0 ? 2 : 1))) {
+ goto out_kfree;
+ }
+ if (!card->doubleS0) {
+ ret = 0;
+ goto out_kfree;
+ }
+ /* reached only, if we have a Double-S0-Card */
+#ifdef BOOT_DEBUG
+ printk(KERN_DEBUG "Map Bank 0\n");
+#endif
+ spin_lock_irqsave(&dev.devlock, flags);
+ icn_map_channel(card, 0); /* Select Bank 0 */
+ icn_lock_channel(card, 0); /* Lock Bank 0 */
+ spin_unlock_irqrestore(&dev.devlock, flags);
+ SLEEP(1);
+ ret = (icn_check_loader(1));
+
+out_kfree:
+ kfree(codebuf);
+out:
+ return ret;
+}
+
+static int
+icn_loadproto(u_char __user *buffer, icn_card *card)
+{
+ register u_char __user *p = buffer;
+ u_char codebuf[256];
+ uint left = ICN_CODE_STAGE2;
+ uint cnt;
+ int timer;
+ unsigned long flags;
+
+#ifdef BOOT_DEBUG
+ printk(KERN_DEBUG "icn_loadproto called\n");
+#endif
+ if (!access_ok(VERIFY_READ, buffer, ICN_CODE_STAGE2))
+ return -EFAULT;
+ timer = 0;
+ spin_lock_irqsave(&dev.devlock, flags);
+ if (card->secondhalf) {
+ icn_map_channel(card, 2);
+ icn_lock_channel(card, 2);
+ } else {
+ icn_map_channel(card, 0);
+ icn_lock_channel(card, 0);
+ }
+ spin_unlock_irqrestore(&dev.devlock, flags);
+ while (left) {
+ if (sbfree) { /* If there is a free buffer... */
+ cnt = left;
+ if (cnt > 256)
+ cnt = 256;
+ if (copy_from_user(codebuf, p, cnt)) {
+ icn_maprelease_channel(card, 0);
+ return -EFAULT;
+ }
+ memcpy_toio(&sbuf_l, codebuf, cnt); /* copy data */
+ sbnext; /* switch to next buffer */
+ p += cnt;
+ left -= cnt;
+ timer = 0;
+ } else {
+#ifdef BOOT_DEBUG
+ printk(KERN_DEBUG "boot 2 !sbfree\n");
+#endif
+ if (timer++ > 5) {
+ icn_maprelease_channel(card, 0);
+ return -EIO;
+ }
+ schedule_timeout_interruptible(10);
+ }
+ }
+ writeb(0x20, &sbuf_n);
+ timer = 0;
+ while (1) {
+ if (readb(&cmd_o) || readb(&cmd_i)) {
+#ifdef BOOT_DEBUG
+ printk(KERN_DEBUG "Proto?\n");
+#endif
+ if (timer++ > 5) {
+ printk(KERN_WARNING
+ "icn: (%s) Protocol timed out.\n",
+ CID);
+#ifdef BOOT_DEBUG
+ printk(KERN_DEBUG "Proto TO!\n");
+#endif
+ icn_maprelease_channel(card, 0);
+ return -EIO;
+ }
+#ifdef BOOT_DEBUG
+ printk(KERN_DEBUG "Proto TO?\n");
+#endif
+ msleep_interruptible(ICN_BOOT_TIMEOUT1);
+ } else {
+ if ((card->secondhalf) || (!card->doubleS0)) {
+#ifdef BOOT_DEBUG
+ printk(KERN_DEBUG "Proto loaded, install poll-timer %d\n",
+ card->secondhalf);
+#endif
+ spin_lock_irqsave(&card->lock, flags);
+ init_timer(&card->st_timer);
+ card->st_timer.expires = jiffies + ICN_TIMER_DCREAD;
+ card->st_timer.function = icn_polldchan;
+ card->st_timer.data = (unsigned long) card;
+ add_timer(&card->st_timer);
+ card->flags |= ICN_FLAGS_RUNNING;
+ if (card->doubleS0) {
+ init_timer(&card->other->st_timer);
+ card->other->st_timer.expires = jiffies + ICN_TIMER_DCREAD;
+ card->other->st_timer.function = icn_polldchan;
+ card->other->st_timer.data = (unsigned long) card->other;
+ add_timer(&card->other->st_timer);
+ card->other->flags |= ICN_FLAGS_RUNNING;
+ }
+ spin_unlock_irqrestore(&card->lock, flags);
+ }
+ icn_maprelease_channel(card, 0);
+ return 0;
+ }
+ }
+}
+
+/* Read the Status-replies from the Interface */
+static int
+icn_readstatus(u_char __user *buf, int len, icn_card *card)
+{
+ int count;
+ u_char __user *p;
+
+ for (p = buf, count = 0; count < len; p++, count++) {
+ if (card->msg_buf_read == card->msg_buf_write)
+ return count;
+ if (put_user(*card->msg_buf_read++, p))
+ return -EFAULT;
+ if (card->msg_buf_read > card->msg_buf_end)
+ card->msg_buf_read = card->msg_buf;
+ }
+ return count;
+}
+
+/* Put command-strings into the command-queue of the Interface */
+static int
+icn_writecmd(const u_char *buf, int len, int user, icn_card *card)
+{
+ int mch = card->secondhalf ? 2 : 0;
+ int pp;
+ int i;
+ int count;
+ int xcount;
+ int ocount;
+ int loop;
+ unsigned long flags;
+ int lastmap_channel;
+ struct icn_card *lastmap_card;
+ u_char *p;
+ isdn_ctrl cmd;
+ u_char msg[0x100];
+
+ ocount = 1;
+ xcount = loop = 0;
+ while (len) {
+ count = cmd_free;
+ if (count > len)
+ count = len;
+ if (user) {
+ if (copy_from_user(msg, buf, count))
+ return -EFAULT;
+ } else
+ memcpy(msg, buf, count);
+
+ spin_lock_irqsave(&dev.devlock, flags);
+ lastmap_card = dev.mcard;
+ lastmap_channel = dev.channel;
+ icn_map_channel(card, mch);
+
+ icn_putmsg(card, '>');
+ for (p = msg, pp = readb(&cmd_i), i = count; i > 0; i--, p++, pp
+ ++) {
+ writeb((*p == '\n') ? 0xff : *p,
+ &dev.shmem->comm_buffers.pcio_buf[pp & 0xff]);
+ len--;
+ xcount++;
+ icn_putmsg(card, *p);
+ if ((*p == '\n') && (i > 1)) {
+ icn_putmsg(card, '>');
+ ocount++;
+ }
+ ocount++;
+ }
+ writeb((readb(&cmd_i) + count) & 0xff, &cmd_i);
+ if (lastmap_card)
+ icn_map_channel(lastmap_card, lastmap_channel);
+ spin_unlock_irqrestore(&dev.devlock, flags);
+ if (len) {
+ mdelay(1);
+ if (loop++ > 20)
+ break;
+ } else
+ break;
+ }
+ if (len && (!user))
+ printk(KERN_WARNING "icn: writemsg incomplete!\n");
+ cmd.command = ISDN_STAT_STAVAIL;
+ cmd.driver = card->myid;
+ cmd.arg = ocount;
+ card->interface.statcallb(&cmd);
+ return xcount;
+}
+
+/*
+ * Delete card's pending timers, send STOP to linklevel
+ */
+static void
+icn_stopcard(icn_card *card)
+{
+ unsigned long flags;
+ isdn_ctrl cmd;
+
+ spin_lock_irqsave(&card->lock, flags);
+ if (card->flags & ICN_FLAGS_RUNNING) {
+ card->flags &= ~ICN_FLAGS_RUNNING;
+ del_timer(&card->st_timer);
+ del_timer(&card->rb_timer);
+ spin_unlock_irqrestore(&card->lock, flags);
+ cmd.command = ISDN_STAT_STOP;
+ cmd.driver = card->myid;
+ card->interface.statcallb(&cmd);
+ if (card->doubleS0)
+ icn_stopcard(card->other);
+ } else
+ spin_unlock_irqrestore(&card->lock, flags);
+}
+
+static void
+icn_stopallcards(void)
+{
+ icn_card *p = cards;
+
+ while (p) {
+ icn_stopcard(p);
+ p = p->next;
+ }
+}
+
+/*
+ * Unmap all cards, because some of them may be mapped accidetly during
+ * autoprobing of some network drivers (SMC-driver?)
+ */
+static void
+icn_disable_cards(void)
+{
+ icn_card *card = cards;
+
+ while (card) {
+ if (!request_region(card->port, ICN_PORTLEN, "icn-isdn")) {
+ printk(KERN_WARNING
+ "icn: (%s) ports 0x%03x-0x%03x in use.\n",
+ CID,
+ card->port,
+ card->port + ICN_PORTLEN);
+ } else {
+ OUTB_P(0, ICN_RUN); /* Reset Controller */
+ OUTB_P(0, ICN_MAPRAM); /* Disable RAM */
+ release_region(card->port, ICN_PORTLEN);
+ }
+ card = card->next;
+ }
+}
+
+static int
+icn_command(isdn_ctrl *c, icn_card *card)
+{
+ ulong a;
+ ulong flags;
+ int i;
+ char cbuf[80];
+ isdn_ctrl cmd;
+ icn_cdef cdef;
+ char __user *arg;
+
+ switch (c->command) {
+ case ISDN_CMD_IOCTL:
+ memcpy(&a, c->parm.num, sizeof(ulong));
+ arg = (char __user *)a;
+ switch (c->arg) {
+ case ICN_IOCTL_SETMMIO:
+ if (dev.memaddr != (a & 0x0ffc000)) {
+ if (!request_mem_region(a & 0x0ffc000, 0x4000, "icn-isdn (all cards)")) {
+ printk(KERN_WARNING
+ "icn: memory at 0x%08lx in use.\n",
+ a & 0x0ffc000);
+ return -EINVAL;
+ }
+ release_mem_region(a & 0x0ffc000, 0x4000);
+ icn_stopallcards();
+ spin_lock_irqsave(&card->lock, flags);
+ if (dev.mvalid) {
+ iounmap(dev.shmem);
+ release_mem_region(dev.memaddr, 0x4000);
+ }
+ dev.mvalid = 0;
+ dev.memaddr = a & 0x0ffc000;
+ spin_unlock_irqrestore(&card->lock, flags);
+ printk(KERN_INFO
+ "icn: (%s) mmio set to 0x%08lx\n",
+ CID,
+ dev.memaddr);
+ }
+ break;
+ case ICN_IOCTL_GETMMIO:
+ return (long) dev.memaddr;
+ case ICN_IOCTL_SETPORT:
+ if (a == 0x300 || a == 0x310 || a == 0x320 || a == 0x330
+ || a == 0x340 || a == 0x350 || a == 0x360 ||
+ a == 0x308 || a == 0x318 || a == 0x328 || a == 0x338
+ || a == 0x348 || a == 0x358 || a == 0x368) {
+ if (card->port != (unsigned short) a) {
+ if (!request_region((unsigned short) a, ICN_PORTLEN, "icn-isdn")) {
+ printk(KERN_WARNING
+ "icn: (%s) ports 0x%03x-0x%03x in use.\n",
+ CID, (int) a, (int) a + ICN_PORTLEN);
+ return -EINVAL;
+ }
+ release_region((unsigned short) a, ICN_PORTLEN);
+ icn_stopcard(card);
+ spin_lock_irqsave(&card->lock, flags);
+ if (card->rvalid)
+ release_region(card->port, ICN_PORTLEN);
+ card->port = (unsigned short) a;
+ card->rvalid = 0;
+ if (card->doubleS0) {
+ card->other->port = (unsigned short) a;
+ card->other->rvalid = 0;
+ }
+ spin_unlock_irqrestore(&card->lock, flags);
+ printk(KERN_INFO
+ "icn: (%s) port set to 0x%03x\n",
+ CID, card->port);
+ }
+ } else
+ return -EINVAL;
+ break;
+ case ICN_IOCTL_GETPORT:
+ return (int) card->port;
+ case ICN_IOCTL_GETDOUBLE:
+ return (int) card->doubleS0;
+ case ICN_IOCTL_DEBUGVAR:
+ if (copy_to_user(arg,
+ &card,
+ sizeof(ulong)))
+ return -EFAULT;
+ a += sizeof(ulong);
+ {
+ ulong l = (ulong)&dev;
+ if (copy_to_user(arg,
+ &l,
+ sizeof(ulong)))
+ return -EFAULT;
+ }
+ return 0;
+ case ICN_IOCTL_LOADBOOT:
+ if (dev.firstload) {
+ icn_disable_cards();
+ dev.firstload = 0;
+ }
+ icn_stopcard(card);
+ return (icn_loadboot(arg, card));
+ case ICN_IOCTL_LOADPROTO:
+ icn_stopcard(card);
+ if ((i = (icn_loadproto(arg, card))))
+ return i;
+ if (card->doubleS0)
+ i = icn_loadproto(arg + ICN_CODE_STAGE2, card->other);
+ return i;
+ break;
+ case ICN_IOCTL_ADDCARD:
+ if (!dev.firstload)
+ return -EBUSY;
+ if (copy_from_user(&cdef,
+ arg,
+ sizeof(cdef)))
+ return -EFAULT;
+ return (icn_addcard(cdef.port, cdef.id1, cdef.id2));
+ break;
+ case ICN_IOCTL_LEASEDCFG:
+ if (a) {
+ if (!card->leased) {
+ card->leased = 1;
+ while (card->ptype == ISDN_PTYPE_UNKNOWN) {
+ msleep_interruptible(ICN_BOOT_TIMEOUT1);
+ }
+ msleep_interruptible(ICN_BOOT_TIMEOUT1);
+ sprintf(cbuf, "00;FV2ON\n01;EAZ%c\n02;EAZ%c\n",
+ (a & 1) ? '1' : 'C', (a & 2) ? '2' : 'C');
+ i = icn_writecmd(cbuf, strlen(cbuf), 0, card);
+ printk(KERN_INFO
+ "icn: (%s) Leased-line mode enabled\n",
+ CID);
+ cmd.command = ISDN_STAT_RUN;
+ cmd.driver = card->myid;
+ cmd.arg = 0;
+ card->interface.statcallb(&cmd);
+ }
+ } else {
+ if (card->leased) {
+ card->leased = 0;
+ sprintf(cbuf, "00;FV2OFF\n");
+ i = icn_writecmd(cbuf, strlen(cbuf), 0, card);
+ printk(KERN_INFO
+ "icn: (%s) Leased-line mode disabled\n",
+ CID);
+ cmd.command = ISDN_STAT_RUN;
+ cmd.driver = card->myid;
+ cmd.arg = 0;
+ card->interface.statcallb(&cmd);
+ }
+ }
+ return 0;
+ default:
+ return -EINVAL;
+ }
+ break;
+ case ISDN_CMD_DIAL:
+ if (!(card->flags & ICN_FLAGS_RUNNING))
+ return -ENODEV;
+ if (card->leased)
+ break;
+ if ((c->arg & 255) < ICN_BCH) {
+ char *p;
+ char dcode[4];
+
+ a = c->arg;
+ p = c->parm.setup.phone;
+ if (*p == 's' || *p == 'S') {
+ /* Dial for SPV */
+ p++;
+ strcpy(dcode, "SCA");
+ } else
+ /* Normal Dial */
+ strcpy(dcode, "CAL");
+ snprintf(cbuf, sizeof(cbuf),
+ "%02d;D%s_R%s,%02d,%02d,%s\n", (int) (a + 1),
+ dcode, p, c->parm.setup.si1,
+ c->parm.setup.si2, c->parm.setup.eazmsn);
+ i = icn_writecmd(cbuf, strlen(cbuf), 0, card);
+ }
+ break;
+ case ISDN_CMD_ACCEPTD:
+ if (!(card->flags & ICN_FLAGS_RUNNING))
+ return -ENODEV;
+ if (c->arg < ICN_BCH) {
+ a = c->arg + 1;
+ if (card->fw_rev >= 300) {
+ switch (card->l2_proto[a - 1]) {
+ case ISDN_PROTO_L2_X75I:
+ sprintf(cbuf, "%02d;BX75\n", (int) a);
+ break;
+ case ISDN_PROTO_L2_HDLC:
+ sprintf(cbuf, "%02d;BTRA\n", (int) a);
+ break;
+ }
+ i = icn_writecmd(cbuf, strlen(cbuf), 0, card);
+ }
+ sprintf(cbuf, "%02d;DCON_R\n", (int) a);
+ i = icn_writecmd(cbuf, strlen(cbuf), 0, card);
+ }
+ break;
+ case ISDN_CMD_ACCEPTB:
+ if (!(card->flags & ICN_FLAGS_RUNNING))
+ return -ENODEV;
+ if (c->arg < ICN_BCH) {
+ a = c->arg + 1;
+ if (card->fw_rev >= 300)
+ switch (card->l2_proto[a - 1]) {
+ case ISDN_PROTO_L2_X75I:
+ sprintf(cbuf, "%02d;BCON_R,BX75\n", (int) a);
+ break;
+ case ISDN_PROTO_L2_HDLC:
+ sprintf(cbuf, "%02d;BCON_R,BTRA\n", (int) a);
+ break;
+ } else
+ sprintf(cbuf, "%02d;BCON_R\n", (int) a);
+ i = icn_writecmd(cbuf, strlen(cbuf), 0, card);
+ }
+ break;
+ case ISDN_CMD_HANGUP:
+ if (!(card->flags & ICN_FLAGS_RUNNING))
+ return -ENODEV;
+ if (c->arg < ICN_BCH) {
+ a = c->arg + 1;
+ sprintf(cbuf, "%02d;BDIS_R\n%02d;DDIS_R\n", (int) a, (int) a);
+ i = icn_writecmd(cbuf, strlen(cbuf), 0, card);
+ }
+ break;
+ case ISDN_CMD_SETEAZ:
+ if (!(card->flags & ICN_FLAGS_RUNNING))
+ return -ENODEV;
+ if (card->leased)
+ break;
+ if (c->arg < ICN_BCH) {
+ a = c->arg + 1;
+ if (card->ptype == ISDN_PTYPE_EURO) {
+ sprintf(cbuf, "%02d;MS%s%s\n", (int) a,
+ c->parm.num[0] ? "N" : "ALL", c->parm.num);
+ } else
+ sprintf(cbuf, "%02d;EAZ%s\n", (int) a,
+ c->parm.num[0] ? (char *)(c->parm.num) : "0123456789");
+ i = icn_writecmd(cbuf, strlen(cbuf), 0, card);
+ }
+ break;
+ case ISDN_CMD_CLREAZ:
+ if (!(card->flags & ICN_FLAGS_RUNNING))
+ return -ENODEV;
+ if (card->leased)
+ break;
+ if (c->arg < ICN_BCH) {
+ a = c->arg + 1;
+ if (card->ptype == ISDN_PTYPE_EURO)
+ sprintf(cbuf, "%02d;MSNC\n", (int) a);
+ else
+ sprintf(cbuf, "%02d;EAZC\n", (int) a);
+ i = icn_writecmd(cbuf, strlen(cbuf), 0, card);
+ }
+ break;
+ case ISDN_CMD_SETL2:
+ if (!(card->flags & ICN_FLAGS_RUNNING))
+ return -ENODEV;
+ if ((c->arg & 255) < ICN_BCH) {
+ a = c->arg;
+ switch (a >> 8) {
+ case ISDN_PROTO_L2_X75I:
+ sprintf(cbuf, "%02d;BX75\n", (int) (a & 255) + 1);
+ break;
+ case ISDN_PROTO_L2_HDLC:
+ sprintf(cbuf, "%02d;BTRA\n", (int) (a & 255) + 1);
+ break;
+ default:
+ return -EINVAL;
+ }
+ i = icn_writecmd(cbuf, strlen(cbuf), 0, card);
+ card->l2_proto[a & 255] = (a >> 8);
+ }
+ break;
+ case ISDN_CMD_SETL3:
+ if (!(card->flags & ICN_FLAGS_RUNNING))
+ return -ENODEV;
+ return 0;
+ default:
+ return -EINVAL;
+ }
+ return 0;
+}
+
+/*
+ * Find card with given driverId
+ */
+static inline icn_card *
+icn_findcard(int driverid)
+{
+ icn_card *p = cards;
+
+ while (p) {
+ if (p->myid == driverid)
+ return p;
+ p = p->next;
+ }
+ return (icn_card *) 0;
+}
+
+/*
+ * Wrapper functions for interface to linklevel
+ */
+static int
+if_command(isdn_ctrl *c)
+{
+ icn_card *card = icn_findcard(c->driver);
+
+ if (card)
+ return (icn_command(c, card));
+ printk(KERN_ERR
+ "icn: if_command %d called with invalid driverId %d!\n",
+ c->command, c->driver);
+ return -ENODEV;
+}
+
+static int
+if_writecmd(const u_char __user *buf, int len, int id, int channel)
+{
+ icn_card *card = icn_findcard(id);
+
+ if (card) {
+ if (!(card->flags & ICN_FLAGS_RUNNING))
+ return -ENODEV;
+ return (icn_writecmd(buf, len, 1, card));
+ }
+ printk(KERN_ERR
+ "icn: if_writecmd called with invalid driverId!\n");
+ return -ENODEV;
+}
+
+static int
+if_readstatus(u_char __user *buf, int len, int id, int channel)
+{
+ icn_card *card = icn_findcard(id);
+
+ if (card) {
+ if (!(card->flags & ICN_FLAGS_RUNNING))
+ return -ENODEV;
+ return (icn_readstatus(buf, len, card));
+ }
+ printk(KERN_ERR
+ "icn: if_readstatus called with invalid driverId!\n");
+ return -ENODEV;
+}
+
+static int
+if_sendbuf(int id, int channel, int ack, struct sk_buff *skb)
+{
+ icn_card *card = icn_findcard(id);
+
+ if (card) {
+ if (!(card->flags & ICN_FLAGS_RUNNING))
+ return -ENODEV;
+ return (icn_sendbuf(channel, ack, skb, card));
+ }
+ printk(KERN_ERR
+ "icn: if_sendbuf called with invalid driverId!\n");
+ return -ENODEV;
+}
+
+/*
+ * Allocate a new card-struct, initialize it
+ * link it into cards-list and register it at linklevel.
+ */
+static icn_card *
+icn_initcard(int port, char *id)
+{
+ icn_card *card;
+ int i;
+
+ if (!(card = kzalloc(sizeof(icn_card), GFP_KERNEL))) {
+ printk(KERN_WARNING
+ "icn: (%s) Could not allocate card-struct.\n", id);
+ return (icn_card *) 0;
+ }
+ spin_lock_init(&card->lock);
+ card->port = port;
+ card->interface.owner = THIS_MODULE;
+ card->interface.hl_hdrlen = 1;
+ card->interface.channels = ICN_BCH;
+ card->interface.maxbufsize = 4000;
+ card->interface.command = if_command;
+ card->interface.writebuf_skb = if_sendbuf;
+ card->interface.writecmd = if_writecmd;
+ card->interface.readstat = if_readstatus;
+ card->interface.features = ISDN_FEATURE_L2_X75I |
+ ISDN_FEATURE_L2_HDLC |
+ ISDN_FEATURE_L3_TRANS |
+ ISDN_FEATURE_P_UNKNOWN;
+ card->ptype = ISDN_PTYPE_UNKNOWN;
+ strlcpy(card->interface.id, id, sizeof(card->interface.id));
+ card->msg_buf_write = card->msg_buf;
+ card->msg_buf_read = card->msg_buf;
+ card->msg_buf_end = &card->msg_buf[sizeof(card->msg_buf) - 1];
+ for (i = 0; i < ICN_BCH; i++) {
+ card->l2_proto[i] = ISDN_PROTO_L2_X75I;
+ skb_queue_head_init(&card->spqueue[i]);
+ }
+ card->next = cards;
+ cards = card;
+ if (!register_isdn(&card->interface)) {
+ cards = cards->next;
+ printk(KERN_WARNING
+ "icn: Unable to register %s\n", id);
+ kfree(card);
+ return (icn_card *) 0;
+ }
+ card->myid = card->interface.channels;
+ sprintf(card->regname, "icn-isdn (%s)", card->interface.id);
+ return card;
+}
+
+static int
+icn_addcard(int port, char *id1, char *id2)
+{
+ icn_card *card;
+ icn_card *card2;
+
+ if (!(card = icn_initcard(port, id1))) {
+ return -EIO;
+ }
+ if (!strlen(id2)) {
+ printk(KERN_INFO
+ "icn: (%s) ICN-2B, port 0x%x added\n",
+ card->interface.id, port);
+ return 0;
+ }
+ if (!(card2 = icn_initcard(port, id2))) {
+ printk(KERN_INFO
+ "icn: (%s) half ICN-4B, port 0x%x added\n", id2, port);
+ return 0;
+ }
+ card->doubleS0 = 1;
+ card->secondhalf = 0;
+ card->other = card2;
+ card2->doubleS0 = 1;
+ card2->secondhalf = 1;
+ card2->other = card;
+ printk(KERN_INFO
+ "icn: (%s and %s) ICN-4B, port 0x%x added\n",
+ card->interface.id, card2->interface.id, port);
+ return 0;
+}
+
+#ifndef MODULE
+static int __init
+icn_setup(char *line)
+{
+ char *p, *str;
+ int ints[3];
+ static char sid[20];
+ static char sid2[20];
+
+ str = get_options(line, 2, ints);
+ if (ints[0])
+ portbase = ints[1];
+ if (ints[0] > 1)
+ membase = (unsigned long)ints[2];
+ if (str && *str) {
+ strlcpy(sid, str, sizeof(sid));
+ icn_id = sid;
+ if ((p = strchr(sid, ','))) {
+ *p++ = 0;
+ strcpy(sid2, p);
+ icn_id2 = sid2;
+ }
+ }
+ return (1);
+}
+__setup("icn=", icn_setup);
+#endif /* MODULE */
+
+static int __init icn_init(void)
+{
+ char *p;
+ char rev[21];
+
+ memset(&dev, 0, sizeof(icn_dev));
+ dev.memaddr = (membase & 0x0ffc000);
+ dev.channel = -1;
+ dev.mcard = NULL;
+ dev.firstload = 1;
+ spin_lock_init(&dev.devlock);
+
+ if ((p = strchr(revision, ':'))) {
+ strncpy(rev, p + 1, 20);
+ rev[20] = '\0';
+ p = strchr(rev, '$');
+ if (p)
+ *p = 0;
+ } else
+ strcpy(rev, " ??? ");
+ printk(KERN_NOTICE "ICN-ISDN-driver Rev%smem=0x%08lx\n", rev,
+ dev.memaddr);
+ return (icn_addcard(portbase, icn_id, icn_id2));
+}
+
+static void __exit icn_exit(void)
+{
+ isdn_ctrl cmd;
+ icn_card *card = cards;
+ icn_card *last, *tmpcard;
+ int i;
+ unsigned long flags;
+
+ icn_stopallcards();
+ while (card) {
+ cmd.command = ISDN_STAT_UNLOAD;
+ cmd.driver = card->myid;
+ card->interface.statcallb(&cmd);
+ spin_lock_irqsave(&card->lock, flags);
+ if (card->rvalid) {
+ OUTB_P(0, ICN_RUN); /* Reset Controller */
+ OUTB_P(0, ICN_MAPRAM); /* Disable RAM */
+ if (card->secondhalf || (!card->doubleS0)) {
+ release_region(card->port, ICN_PORTLEN);
+ card->rvalid = 0;
+ }
+ for (i = 0; i < ICN_BCH; i++)
+ icn_free_queue(card, i);
+ }
+ tmpcard = card->next;
+ spin_unlock_irqrestore(&card->lock, flags);
+ card = tmpcard;
+ }
+ card = cards;
+ cards = NULL;
+ while (card) {
+ last = card;
+ card = card->next;
+ kfree(last);
+ }
+ if (dev.mvalid) {
+ iounmap(dev.shmem);
+ release_mem_region(dev.memaddr, 0x4000);
+ }
+ printk(KERN_NOTICE "ICN-ISDN-driver unloaded\n");
+}
+
+module_init(icn_init);
+module_exit(icn_exit);
diff --git a/drivers/staging/i4l/icn/icn.h b/drivers/staging/i4l/icn/icn.h
new file mode 100644
index 000000000000..f8f2e76d34bf
--- /dev/null
+++ b/drivers/staging/i4l/icn/icn.h
@@ -0,0 +1,253 @@
+/* $Id: icn.h,v 1.30.6.5 2001/09/23 22:24:55 kai Exp $
+ *
+ * ISDN lowlevel-module for the ICN active ISDN-Card.
+ *
+ * Copyright 1994 by Fritz Elfert (fritz@isdn4linux.de)
+ *
+ * This software may be used and distributed according to the terms
+ * of the GNU General Public License, incorporated herein by reference.
+ *
+ */
+
+#ifndef icn_h
+#define icn_h
+
+#define ICN_IOCTL_SETMMIO 0
+#define ICN_IOCTL_GETMMIO 1
+#define ICN_IOCTL_SETPORT 2
+#define ICN_IOCTL_GETPORT 3
+#define ICN_IOCTL_LOADBOOT 4
+#define ICN_IOCTL_LOADPROTO 5
+#define ICN_IOCTL_LEASEDCFG 6
+#define ICN_IOCTL_GETDOUBLE 7
+#define ICN_IOCTL_DEBUGVAR 8
+#define ICN_IOCTL_ADDCARD 9
+
+/* Struct for adding new cards */
+typedef struct icn_cdef {
+ int port;
+ char id1[10];
+ char id2[10];
+} icn_cdef;
+
+#if defined(__KERNEL__) || defined(__DEBUGVAR__)
+
+#ifdef __KERNEL__
+/* Kernel includes */
+
+#include <linux/errno.h>
+#include <linux/fs.h>
+#include <linux/major.h>
+#include <linux/io.h>
+#include <linux/kernel.h>
+#include <linux/signal.h>
+#include <linux/slab.h>
+#include <linux/mm.h>
+#include <linux/mman.h>
+#include <linux/ioport.h>
+#include <linux/timer.h>
+#include <linux/wait.h>
+#include <linux/delay.h>
+#include <linux/isdnif.h>
+
+#endif /* __KERNEL__ */
+
+/* some useful macros for debugging */
+#ifdef ICN_DEBUG_PORT
+#define OUTB_P(v, p) {printk(KERN_DEBUG "icn: outb_p(0x%02x,0x%03x)\n", v, p); outb_p(v, p);}
+#else
+#define OUTB_P outb
+#endif
+
+/* Defaults for Port-Address and shared-memory */
+#define ICN_BASEADDR 0x320
+#define ICN_PORTLEN (0x04)
+#define ICN_MEMADDR 0x0d0000
+
+#define ICN_FLAGS_B1ACTIVE 1 /* B-Channel-1 is open */
+#define ICN_FLAGS_B2ACTIVE 2 /* B-Channel-2 is open */
+#define ICN_FLAGS_RUNNING 4 /* Cards driver activated */
+#define ICN_FLAGS_RBTIMER 8 /* cyclic scheduling of B-Channel-poll */
+
+#define ICN_BOOT_TIMEOUT1 1000 /* Delay for Boot-download (msecs) */
+
+#define ICN_TIMER_BCREAD (HZ / 100) /* B-Channel poll-cycle */
+#define ICN_TIMER_DCREAD (HZ / 2) /* D-Channel poll-cycle */
+
+#define ICN_CODE_STAGE1 4096 /* Size of bootcode */
+#define ICN_CODE_STAGE2 65536 /* Size of protocol-code */
+
+#define ICN_MAX_SQUEUE 8000 /* Max. outstanding send-data (2* hw-buf.) */
+#define ICN_FRAGSIZE (250) /* Max. size of send-fragments */
+#define ICN_BCH 2 /* Number of supported channels per card */
+
+/* type-definitions for accessing the mmap-io-areas */
+
+#define SHM_DCTL_OFFSET (0) /* Offset to data-controlstructures in shm */
+#define SHM_CCTL_OFFSET (0x1d2) /* Offset to comm-controlstructures in shm */
+#define SHM_CBUF_OFFSET (0x200) /* Offset to comm-buffers in shm */
+#define SHM_DBUF_OFFSET (0x2000) /* Offset to data-buffers in shm */
+
+/*
+ * Layout of card's data buffers
+ */
+typedef struct {
+ unsigned char length; /* Bytecount of fragment (max 250) */
+ unsigned char endflag; /* 0=last frag., 0xff=frag. continued */
+ unsigned char data[ICN_FRAGSIZE]; /* The data */
+ /* Fill to 256 bytes */
+ char unused[0x100 - ICN_FRAGSIZE - 2];
+} frag_buf;
+
+/*
+ * Layout of card's shared memory
+ */
+typedef union {
+ struct {
+ unsigned char scns; /* Index to free SendFrag. */
+ unsigned char scnr; /* Index to active SendFrag READONLY */
+ unsigned char ecns; /* Index to free RcvFrag. READONLY */
+ unsigned char ecnr; /* Index to valid RcvFrag */
+ char unused[6];
+ unsigned short fuell1; /* Internal Buf Bytecount */
+ } data_control;
+ struct {
+ char unused[SHM_CCTL_OFFSET];
+ unsigned char iopc_i; /* Read-Ptr Status-Queue READONLY */
+ unsigned char iopc_o; /* Write-Ptr Status-Queue */
+ unsigned char pcio_i; /* Write-Ptr Command-Queue */
+ unsigned char pcio_o; /* Read-Ptr Command Queue READONLY */
+ } comm_control;
+ struct {
+ char unused[SHM_CBUF_OFFSET];
+ unsigned char pcio_buf[0x100]; /* Ring-Buffer Command-Queue */
+ unsigned char iopc_buf[0x100]; /* Ring-Buffer Status-Queue */
+ } comm_buffers;
+ struct {
+ char unused[SHM_DBUF_OFFSET];
+ frag_buf receive_buf[0x10];
+ frag_buf send_buf[0x10];
+ } data_buffers;
+} icn_shmem;
+
+/*
+ * Per card driver data
+ */
+typedef struct icn_card {
+ struct icn_card *next; /* Pointer to next device struct */
+ struct icn_card *other; /* Pointer to other card for ICN4B */
+ unsigned short port; /* Base-port-address */
+ int myid; /* Driver-Nr. assigned by linklevel */
+ int rvalid; /* IO-portregion has been requested */
+ int leased; /* Flag: This Adapter is connected */
+ /* to a leased line */
+ unsigned short flags; /* Statusflags */
+ int doubleS0; /* Flag: ICN4B */
+ int secondhalf; /* Flag: Second half of a doubleS0 */
+ int fw_rev; /* Firmware revision loaded */
+ int ptype; /* Protocol type (1TR6 or Euro) */
+ struct timer_list st_timer; /* Timer for Status-Polls */
+ struct timer_list rb_timer; /* Timer for B-Channel-Polls */
+ u_char rcvbuf[ICN_BCH][4096]; /* B-Channel-Receive-Buffers */
+ int rcvidx[ICN_BCH]; /* Index for above buffers */
+ int l2_proto[ICN_BCH]; /* Current layer-2-protocol */
+ isdn_if interface; /* Interface to upper layer */
+ int iptr; /* Index to imsg-buffer */
+ char imsg[60]; /* Internal buf for status-parsing */
+ char msg_buf[2048]; /* Buffer for status-messages */
+ char *msg_buf_write; /* Writepointer for statusbuffer */
+ char *msg_buf_read; /* Readpointer for statusbuffer */
+ char *msg_buf_end; /* Pointer to end of statusbuffer */
+ int sndcount[ICN_BCH]; /* Byte-counters for B-Ch.-send */
+ int xlen[ICN_BCH]; /* Byte-counters/Flags for sent-ACK */
+ struct sk_buff *xskb[ICN_BCH]; /* Current transmitted skb */
+ struct sk_buff_head spqueue[ICN_BCH]; /* Sendqueue */
+ char regname[35]; /* Name used for request_region */
+ u_char xmit_lock[ICN_BCH]; /* Semaphore for pollbchan_send()*/
+ spinlock_t lock; /* protect critical operations */
+} icn_card;
+
+/*
+ * Main driver data
+ */
+typedef struct icn_dev {
+ spinlock_t devlock; /* spinlock to protect this struct */
+ unsigned long memaddr; /* Address of memory mapped buffers */
+ icn_shmem __iomem *shmem; /* Pointer to memory-mapped-buffers */
+ int mvalid; /* IO-shmem has been requested */
+ int channel; /* Currently mapped channel */
+ struct icn_card *mcard; /* Currently mapped card */
+ int chanlock; /* Semaphore for channel-mapping */
+ int firstload; /* Flag: firmware never loaded */
+} icn_dev;
+
+typedef icn_dev *icn_devptr;
+
+#ifdef __KERNEL__
+
+static icn_card *cards = (icn_card *) 0;
+static u_char chan2bank[] =
+{0, 4, 8, 12}; /* for icn_map_channel() */
+
+static icn_dev dev;
+
+#endif /* __KERNEL__ */
+
+/* Utility-Macros */
+
+/* Macros for accessing ports */
+#define ICN_CFG (card->port)
+#define ICN_MAPRAM (card->port + 1)
+#define ICN_RUN (card->port + 2)
+#define ICN_BANK (card->port + 3)
+
+/* Return true, if there is a free transmit-buffer */
+#define sbfree (((readb(&dev.shmem->data_control.scns) + 1) & 0xf) != \
+ readb(&dev.shmem->data_control.scnr))
+
+/* Switch to next transmit-buffer */
+#define sbnext (writeb((readb(&dev.shmem->data_control.scns) + 1) & 0xf, \
+ &dev.shmem->data_control.scns))
+
+/* Shortcuts for transmit-buffer-access */
+#define sbuf_n dev.shmem->data_control.scns
+#define sbuf_d dev.shmem->data_buffers.send_buf[readb(&sbuf_n)].data
+#define sbuf_l dev.shmem->data_buffers.send_buf[readb(&sbuf_n)].length
+#define sbuf_f dev.shmem->data_buffers.send_buf[readb(&sbuf_n)].endflag
+
+/* Return true, if there is receive-data is available */
+#define rbavl (readb(&dev.shmem->data_control.ecnr) != \
+ readb(&dev.shmem->data_control.ecns))
+
+/* Switch to next receive-buffer */
+#define rbnext (writeb((readb(&dev.shmem->data_control.ecnr) + 1) & 0xf, \
+ &dev.shmem->data_control.ecnr))
+
+/* Shortcuts for receive-buffer-access */
+#define rbuf_n dev.shmem->data_control.ecnr
+#define rbuf_d dev.shmem->data_buffers.receive_buf[readb(&rbuf_n)].data
+#define rbuf_l dev.shmem->data_buffers.receive_buf[readb(&rbuf_n)].length
+#define rbuf_f dev.shmem->data_buffers.receive_buf[readb(&rbuf_n)].endflag
+
+/* Shortcuts for command-buffer-access */
+#define cmd_o (dev.shmem->comm_control.pcio_o)
+#define cmd_i (dev.shmem->comm_control.pcio_i)
+
+/* Return free space in command-buffer */
+#define cmd_free ((readb(&cmd_i) >= readb(&cmd_o)) ? \
+ 0x100 - readb(&cmd_i) + readb(&cmd_o) : \
+ readb(&cmd_o) - readb(&cmd_i))
+
+/* Shortcuts for message-buffer-access */
+#define msg_o (dev.shmem->comm_control.iopc_o)
+#define msg_i (dev.shmem->comm_control.iopc_i)
+
+/* Return length of Message, if avail. */
+#define msg_avail ((readb(&msg_o) > readb(&msg_i)) ? \
+ 0x100 - readb(&msg_o) + readb(&msg_i) : \
+ readb(&msg_i) - readb(&msg_o))
+
+#define CID (card->interface.id)
+
+#endif /* defined(__KERNEL__) || defined(__DEBUGVAR__) */
+#endif /* icn_h */
diff --git a/drivers/staging/i4l/pcbit/Kconfig b/drivers/staging/i4l/pcbit/Kconfig
new file mode 100644
index 000000000000..e9b2dd85d410
--- /dev/null
+++ b/drivers/staging/i4l/pcbit/Kconfig
@@ -0,0 +1,10 @@
+config ISDN_DRV_PCBIT
+ tristate "PCBIT-D support"
+ depends on ISA && (BROKEN || X86)
+ help
+ This enables support for the PCBIT ISDN-card. This card is
+ manufactured in Portugal by Octal. For running this card,
+ additional firmware is necessary, which has to be downloaded into
+ the card using a utility which is distributed separately. See
+ <file:Documentation/isdn/README> and
+ <file:Documentation/isdn/README.pcbit> for more information.
diff --git a/drivers/staging/i4l/pcbit/Makefile b/drivers/staging/i4l/pcbit/Makefile
new file mode 100644
index 000000000000..2d026c3242e8
--- /dev/null
+++ b/drivers/staging/i4l/pcbit/Makefile
@@ -0,0 +1,9 @@
+# Makefile for the pcbit ISDN device driver
+
+# Each configuration option enables a list of files.
+
+obj-$(CONFIG_ISDN_DRV_PCBIT) += pcbit.o
+
+# Multipart objects.
+
+pcbit-y := module.o edss1.o drv.o layer2.o capi.o callbacks.o
diff --git a/drivers/staging/i4l/pcbit/callbacks.c b/drivers/staging/i4l/pcbit/callbacks.c
new file mode 100644
index 000000000000..efb6d6a3639a
--- /dev/null
+++ b/drivers/staging/i4l/pcbit/callbacks.c
@@ -0,0 +1,345 @@
+/*
+ * Callbacks for the FSM
+ *
+ * Copyright (C) 1996 Universidade de Lisboa
+ *
+ * Written by Pedro Roque Marques (roque@di.fc.ul.pt)
+ *
+ * This software may be used and distributed according to the terms of
+ * the GNU General Public License, incorporated herein by reference.
+ */
+
+/*
+ * Fix: 19981230 - Carlos Morgado <chbm@techie.com>
+ * Port of Nelson Escravana's <nelson.escravana@usa.net> fix to CalledPN
+ * NULL pointer dereference in cb_in_1 (originally fixed in 2.0)
+ */
+
+#include <linux/string.h>
+#include <linux/kernel.h>
+
+#include <linux/types.h>
+#include <linux/mm.h>
+#include <linux/skbuff.h>
+
+#include <asm/io.h>
+
+#include <linux/isdnif.h>
+
+#include "pcbit.h"
+#include "layer2.h"
+#include "edss1.h"
+#include "callbacks.h"
+#include "capi.h"
+
+ushort last_ref_num = 1;
+
+/*
+ * send_conn_req
+ *
+ */
+
+void cb_out_1(struct pcbit_dev *dev, struct pcbit_chan *chan,
+ struct callb_data *cbdata)
+{
+ struct sk_buff *skb;
+ int len;
+ ushort refnum;
+
+
+#ifdef DEBUG
+ printk(KERN_DEBUG "Called Party Number: %s\n",
+ cbdata->data.setup.CalledPN);
+#endif
+ /*
+ * hdr - kmalloc in capi_conn_req
+ * - kfree when msg has been sent
+ */
+
+ if ((len = capi_conn_req(cbdata->data.setup.CalledPN, &skb,
+ chan->proto)) < 0)
+ {
+ printk("capi_conn_req failed\n");
+ return;
+ }
+
+
+ refnum = last_ref_num++ & 0x7fffU;
+
+ chan->callref = 0;
+ chan->layer2link = 0;
+ chan->snum = 0;
+ chan->s_refnum = refnum;
+
+ pcbit_l2_write(dev, MSG_CONN_REQ, refnum, skb, len);
+}
+
+/*
+ * rcv CONNECT
+ * will go into ACTIVE state
+ * send CONN_ACTIVE_RESP
+ * send Select protocol request
+ */
+
+void cb_out_2(struct pcbit_dev *dev, struct pcbit_chan *chan,
+ struct callb_data *data)
+{
+ isdn_ctrl ictl;
+ struct sk_buff *skb;
+ int len;
+ ushort refnum;
+
+ if ((len = capi_conn_active_resp(chan, &skb)) < 0)
+ {
+ printk("capi_conn_active_req failed\n");
+ return;
+ }
+
+ refnum = last_ref_num++ & 0x7fffU;
+ chan->s_refnum = refnum;
+
+ pcbit_l2_write(dev, MSG_CONN_ACTV_RESP, refnum, skb, len);
+
+
+ ictl.command = ISDN_STAT_DCONN;
+ ictl.driver = dev->id;
+ ictl.arg = chan->id;
+ dev->dev_if->statcallb(&ictl);
+
+ /* ACTIVE D-channel */
+
+ /* Select protocol */
+
+ if ((len = capi_select_proto_req(chan, &skb, 1 /*outgoing*/)) < 0) {
+ printk("capi_select_proto_req failed\n");
+ return;
+ }
+
+ refnum = last_ref_num++ & 0x7fffU;
+ chan->s_refnum = refnum;
+
+ pcbit_l2_write(dev, MSG_SELP_REQ, refnum, skb, len);
+}
+
+
+/*
+ * Incoming call received
+ * inform user
+ */
+
+void cb_in_1(struct pcbit_dev *dev, struct pcbit_chan *chan,
+ struct callb_data *cbdata)
+{
+ isdn_ctrl ictl;
+ unsigned short refnum;
+ struct sk_buff *skb;
+ int len;
+
+
+ ictl.command = ISDN_STAT_ICALL;
+ ictl.driver = dev->id;
+ ictl.arg = chan->id;
+
+ /*
+ * ictl.num >= strlen() + strlen() + 5
+ */
+
+ if (cbdata->data.setup.CallingPN == NULL) {
+ printk(KERN_DEBUG "NULL CallingPN to phone; using 0\n");
+ strcpy(ictl.parm.setup.phone, "0");
+ }
+ else {
+ strcpy(ictl.parm.setup.phone, cbdata->data.setup.CallingPN);
+ }
+ if (cbdata->data.setup.CalledPN == NULL) {
+ printk(KERN_DEBUG "NULL CalledPN to eazmsn; using 0\n");
+ strcpy(ictl.parm.setup.eazmsn, "0");
+ }
+ else {
+ strcpy(ictl.parm.setup.eazmsn, cbdata->data.setup.CalledPN);
+ }
+ ictl.parm.setup.si1 = 7;
+ ictl.parm.setup.si2 = 0;
+ ictl.parm.setup.plan = 0;
+ ictl.parm.setup.screen = 0;
+
+#ifdef DEBUG
+ printk(KERN_DEBUG "statstr: %s\n", ictl.num);
+#endif
+
+ dev->dev_if->statcallb(&ictl);
+
+
+ if ((len = capi_conn_resp(chan, &skb)) < 0) {
+ printk(KERN_DEBUG "capi_conn_resp failed\n");
+ return;
+ }
+
+ refnum = last_ref_num++ & 0x7fffU;
+ chan->s_refnum = refnum;
+
+ pcbit_l2_write(dev, MSG_CONN_RESP, refnum, skb, len);
+}
+
+/*
+ * user has replied
+ * open the channel
+ * send CONNECT message CONNECT_ACTIVE_REQ in CAPI
+ */
+
+void cb_in_2(struct pcbit_dev *dev, struct pcbit_chan *chan,
+ struct callb_data *data)
+{
+ unsigned short refnum;
+ struct sk_buff *skb;
+ int len;
+
+ if ((len = capi_conn_active_req(chan, &skb)) < 0) {
+ printk(KERN_DEBUG "capi_conn_active_req failed\n");
+ return;
+ }
+
+
+ refnum = last_ref_num++ & 0x7fffU;
+ chan->s_refnum = refnum;
+
+ printk(KERN_DEBUG "sending MSG_CONN_ACTV_REQ\n");
+ pcbit_l2_write(dev, MSG_CONN_ACTV_REQ, refnum, skb, len);
+}
+
+/*
+ * CONN_ACK arrived
+ * start b-proto selection
+ *
+ */
+
+void cb_in_3(struct pcbit_dev *dev, struct pcbit_chan *chan,
+ struct callb_data *data)
+{
+ unsigned short refnum;
+ struct sk_buff *skb;
+ int len;
+
+ if ((len = capi_select_proto_req(chan, &skb, 0 /*incoming*/)) < 0)
+ {
+ printk("capi_select_proto_req failed\n");
+ return;
+ }
+
+ refnum = last_ref_num++ & 0x7fffU;
+ chan->s_refnum = refnum;
+
+ pcbit_l2_write(dev, MSG_SELP_REQ, refnum, skb, len);
+
+}
+
+
+/*
+ * Received disconnect ind on active state
+ * send disconnect resp
+ * send msg to user
+ */
+void cb_disc_1(struct pcbit_dev *dev, struct pcbit_chan *chan,
+ struct callb_data *data)
+{
+ struct sk_buff *skb;
+ int len;
+ ushort refnum;
+ isdn_ctrl ictl;
+
+ if ((len = capi_disc_resp(chan, &skb)) < 0) {
+ printk("capi_disc_resp failed\n");
+ return;
+ }
+
+ refnum = last_ref_num++ & 0x7fffU;
+ chan->s_refnum = refnum;
+
+ pcbit_l2_write(dev, MSG_DISC_RESP, refnum, skb, len);
+
+ ictl.command = ISDN_STAT_BHUP;
+ ictl.driver = dev->id;
+ ictl.arg = chan->id;
+ dev->dev_if->statcallb(&ictl);
+}
+
+
+/*
+ * User HANGUP on active/call proceeding state
+ * send disc.req
+ */
+void cb_disc_2(struct pcbit_dev *dev, struct pcbit_chan *chan,
+ struct callb_data *data)
+{
+ struct sk_buff *skb;
+ int len;
+ ushort refnum;
+
+ if ((len = capi_disc_req(chan->callref, &skb, CAUSE_NORMAL)) < 0)
+ {
+ printk("capi_disc_req failed\n");
+ return;
+ }
+
+ refnum = last_ref_num++ & 0x7fffU;
+ chan->s_refnum = refnum;
+
+ pcbit_l2_write(dev, MSG_DISC_REQ, refnum, skb, len);
+}
+
+/*
+ * Disc confirm received send BHUP
+ * Problem: when the HL driver sends the disc req itself
+ * LL receives BHUP
+ */
+void cb_disc_3(struct pcbit_dev *dev, struct pcbit_chan *chan,
+ struct callb_data *data)
+{
+ isdn_ctrl ictl;
+
+ ictl.command = ISDN_STAT_BHUP;
+ ictl.driver = dev->id;
+ ictl.arg = chan->id;
+ dev->dev_if->statcallb(&ictl);
+}
+
+void cb_notdone(struct pcbit_dev *dev, struct pcbit_chan *chan,
+ struct callb_data *data)
+{
+}
+
+/*
+ * send activate b-chan protocol
+ */
+void cb_selp_1(struct pcbit_dev *dev, struct pcbit_chan *chan,
+ struct callb_data *data)
+{
+ struct sk_buff *skb;
+ int len;
+ ushort refnum;
+
+ if ((len = capi_activate_transp_req(chan, &skb)) < 0)
+ {
+ printk("capi_conn_activate_transp_req failed\n");
+ return;
+ }
+
+ refnum = last_ref_num++ & 0x7fffU;
+ chan->s_refnum = refnum;
+
+ pcbit_l2_write(dev, MSG_ACT_TRANSP_REQ, refnum, skb, len);
+}
+
+/*
+ * Inform User that the B-channel is available
+ */
+void cb_open(struct pcbit_dev *dev, struct pcbit_chan *chan,
+ struct callb_data *data)
+{
+ isdn_ctrl ictl;
+
+ ictl.command = ISDN_STAT_BCONN;
+ ictl.driver = dev->id;
+ ictl.arg = chan->id;
+ dev->dev_if->statcallb(&ictl);
+}
diff --git a/drivers/staging/i4l/pcbit/callbacks.h b/drivers/staging/i4l/pcbit/callbacks.h
new file mode 100644
index 000000000000..a036b4a7ffad
--- /dev/null
+++ b/drivers/staging/i4l/pcbit/callbacks.h
@@ -0,0 +1,44 @@
+/*
+ * Callbacks prototypes for FSM
+ *
+ * Copyright (C) 1996 Universidade de Lisboa
+ *
+ * Written by Pedro Roque Marques (roque@di.fc.ul.pt)
+ *
+ * This software may be used and distributed according to the terms of
+ * the GNU General Public License, incorporated herein by reference.
+ */
+
+#ifndef CALLBACKS_H
+#define CALLBACKS_H
+
+
+extern void cb_out_1(struct pcbit_dev *dev, struct pcbit_chan *chan,
+ struct callb_data *data);
+
+extern void cb_out_2(struct pcbit_dev *dev, struct pcbit_chan *chan,
+ struct callb_data *data);
+
+extern void cb_in_1(struct pcbit_dev *dev, struct pcbit_chan *chan,
+ struct callb_data *data);
+extern void cb_in_2(struct pcbit_dev *dev, struct pcbit_chan *chan,
+ struct callb_data *data);
+extern void cb_in_3(struct pcbit_dev *dev, struct pcbit_chan *chan,
+ struct callb_data *data);
+
+extern void cb_disc_1(struct pcbit_dev *dev, struct pcbit_chan *chan,
+ struct callb_data *data);
+extern void cb_disc_2(struct pcbit_dev *dev, struct pcbit_chan *chan,
+ struct callb_data *data);
+extern void cb_disc_3(struct pcbit_dev *dev, struct pcbit_chan *chan,
+ struct callb_data *data);
+
+extern void cb_notdone(struct pcbit_dev *dev, struct pcbit_chan *chan,
+ struct callb_data *data);
+
+extern void cb_selp_1(struct pcbit_dev *dev, struct pcbit_chan *chan,
+ struct callb_data *data);
+extern void cb_open(struct pcbit_dev *dev, struct pcbit_chan *chan,
+ struct callb_data *data);
+
+#endif
diff --git a/drivers/staging/i4l/pcbit/capi.c b/drivers/staging/i4l/pcbit/capi.c
new file mode 100644
index 000000000000..4e3cbf857d60
--- /dev/null
+++ b/drivers/staging/i4l/pcbit/capi.c
@@ -0,0 +1,649 @@
+/*
+ * CAPI encoder/decoder for
+ * Portugal Telecom CAPI 2.0
+ *
+ * Copyright (C) 1996 Universidade de Lisboa
+ *
+ * Written by Pedro Roque Marques (roque@di.fc.ul.pt)
+ *
+ * This software may be used and distributed according to the terms of
+ * the GNU General Public License, incorporated herein by reference.
+ *
+ * Not compatible with the AVM Gmbh. CAPI 2.0
+ *
+ */
+
+/*
+ * Documentation:
+ * - "Common ISDN API - Perfil Português - Versão 2.1",
+ * Telecom Portugal, Fev 1992.
+ * - "Common ISDN API - Especificação de protocolos para
+ * acesso aos canais B", Inesc, Jan 1994.
+ */
+
+/*
+ * TODO: better decoding of Information Elements
+ * for debug purposes mainly
+ * encode our number in CallerPN and ConnectedPN
+ */
+
+#include <linux/string.h>
+#include <linux/kernel.h>
+
+#include <linux/types.h>
+#include <linux/slab.h>
+#include <linux/mm.h>
+
+#include <linux/skbuff.h>
+
+#include <asm/io.h>
+#include <asm/string.h>
+
+#include <linux/isdnif.h>
+
+#include "pcbit.h"
+#include "edss1.h"
+#include "capi.h"
+
+
+/*
+ * Encoding of CAPI messages
+ *
+ */
+
+int capi_conn_req(const char *calledPN, struct sk_buff **skb, int proto)
+{
+ ushort len;
+
+ /*
+ * length
+ * AppInfoMask - 2
+ * BC0 - 3
+ * BC1 - 1
+ * Chan - 2
+ * Keypad - 1
+ * CPN - 1
+ * CPSA - 1
+ * CalledPN - 2 + strlen
+ * CalledPSA - 1
+ * rest... - 4
+ * ----------------
+ * Total 18 + strlen
+ */
+
+ len = 18 + strlen(calledPN);
+
+ if (proto == ISDN_PROTO_L2_TRANS)
+ len++;
+
+ if ((*skb = dev_alloc_skb(len)) == NULL) {
+
+ printk(KERN_WARNING "capi_conn_req: alloc_skb failed\n");
+ return -1;
+ }
+
+ /* InfoElmMask */
+ *((ushort *)skb_put(*skb, 2)) = AppInfoMask;
+
+ if (proto == ISDN_PROTO_L2_TRANS)
+ {
+ /* Bearer Capability - Mandatory*/
+ *(skb_put(*skb, 1)) = 3; /* BC0.Length */
+ *(skb_put(*skb, 1)) = 0x80; /* Speech */
+ *(skb_put(*skb, 1)) = 0x10; /* Circuit Mode */
+ *(skb_put(*skb, 1)) = 0x23; /* A-law */
+ }
+ else
+ {
+ /* Bearer Capability - Mandatory*/
+ *(skb_put(*skb, 1)) = 2; /* BC0.Length */
+ *(skb_put(*skb, 1)) = 0x88; /* Digital Information */
+ *(skb_put(*skb, 1)) = 0x90; /* BC0.Octect4 */
+ }
+
+ /* Bearer Capability - Optional*/
+ *(skb_put(*skb, 1)) = 0; /* BC1.Length = 0 */
+
+ *(skb_put(*skb, 1)) = 1; /* ChannelID.Length = 1 */
+ *(skb_put(*skb, 1)) = 0x83; /* Basic Interface - Any Channel */
+
+ *(skb_put(*skb, 1)) = 0; /* Keypad.Length = 0 */
+
+
+ *(skb_put(*skb, 1)) = 0; /* CallingPN.Length = 0 */
+ *(skb_put(*skb, 1)) = 0; /* CallingPSA.Length = 0 */
+
+ /* Called Party Number */
+ *(skb_put(*skb, 1)) = strlen(calledPN) + 1;
+ *(skb_put(*skb, 1)) = 0x81;
+ memcpy(skb_put(*skb, strlen(calledPN)), calledPN, strlen(calledPN));
+
+ /* '#' */
+
+ *(skb_put(*skb, 1)) = 0; /* CalledPSA.Length = 0 */
+
+ /* LLC.Length = 0; */
+ /* HLC0.Length = 0; */
+ /* HLC1.Length = 0; */
+ /* UTUS.Length = 0; */
+ memset(skb_put(*skb, 4), 0, 4);
+
+ return len;
+}
+
+int capi_conn_resp(struct pcbit_chan *chan, struct sk_buff **skb)
+{
+
+ if ((*skb = dev_alloc_skb(5)) == NULL) {
+
+ printk(KERN_WARNING "capi_conn_resp: alloc_skb failed\n");
+ return -1;
+ }
+
+ *((ushort *)skb_put(*skb, 2)) = chan->callref;
+ *(skb_put(*skb, 1)) = 0x01; /* ACCEPT_CALL */
+ *(skb_put(*skb, 1)) = 0;
+ *(skb_put(*skb, 1)) = 0;
+
+ return 5;
+}
+
+int capi_conn_active_req(struct pcbit_chan *chan, struct sk_buff **skb)
+{
+ /*
+ * 8 bytes
+ */
+
+ if ((*skb = dev_alloc_skb(8)) == NULL) {
+
+ printk(KERN_WARNING "capi_conn_active_req: alloc_skb failed\n");
+ return -1;
+ }
+
+ *((ushort *)skb_put(*skb, 2)) = chan->callref;
+
+#ifdef DEBUG
+ printk(KERN_DEBUG "Call Reference: %04x\n", chan->callref);
+#endif
+
+ *(skb_put(*skb, 1)) = 0; /* BC.Length = 0; */
+ *(skb_put(*skb, 1)) = 0; /* ConnectedPN.Length = 0 */
+ *(skb_put(*skb, 1)) = 0; /* PSA.Length */
+ *(skb_put(*skb, 1)) = 0; /* LLC.Length = 0; */
+ *(skb_put(*skb, 1)) = 0; /* HLC.Length = 0; */
+ *(skb_put(*skb, 1)) = 0; /* UTUS.Length = 0; */
+
+ return 8;
+}
+
+int capi_conn_active_resp(struct pcbit_chan *chan, struct sk_buff **skb)
+{
+ /*
+ * 2 bytes
+ */
+
+ if ((*skb = dev_alloc_skb(2)) == NULL) {
+
+ printk(KERN_WARNING "capi_conn_active_resp: alloc_skb failed\n");
+ return -1;
+ }
+
+ *((ushort *)skb_put(*skb, 2)) = chan->callref;
+
+ return 2;
+}
+
+
+int capi_select_proto_req(struct pcbit_chan *chan, struct sk_buff **skb,
+ int outgoing)
+{
+
+ /*
+ * 18 bytes
+ */
+
+ if ((*skb = dev_alloc_skb(18)) == NULL) {
+
+ printk(KERN_WARNING "capi_select_proto_req: alloc_skb failed\n");
+ return -1;
+ }
+
+ *((ushort *)skb_put(*skb, 2)) = chan->callref;
+
+ /* Layer2 protocol */
+
+ switch (chan->proto) {
+ case ISDN_PROTO_L2_X75I:
+ *(skb_put(*skb, 1)) = 0x05; /* LAPB */
+ break;
+ case ISDN_PROTO_L2_HDLC:
+ *(skb_put(*skb, 1)) = 0x02;
+ break;
+ case ISDN_PROTO_L2_TRANS:
+ /*
+ * Voice (a-law)
+ */
+ *(skb_put(*skb, 1)) = 0x06;
+ break;
+ default:
+#ifdef DEBUG
+ printk(KERN_DEBUG "Transparent\n");
+#endif
+ *(skb_put(*skb, 1)) = 0x03;
+ break;
+ }
+
+ *(skb_put(*skb, 1)) = (outgoing ? 0x02 : 0x42); /* Don't ask */
+ *(skb_put(*skb, 1)) = 0x00;
+
+ *((ushort *) skb_put(*skb, 2)) = MRU;
+
+
+ *(skb_put(*skb, 1)) = 0x08; /* Modulo */
+ *(skb_put(*skb, 1)) = 0x07; /* Max Window */
+
+ *(skb_put(*skb, 1)) = 0x01; /* No Layer3 Protocol */
+
+ /*
+ * 2 - layer3 MTU [10]
+ * - Modulo [12]
+ * - Window
+ * - layer1 proto [14]
+ * - bitrate
+ * - sub-channel [16]
+ * - layer1dataformat [17]
+ */
+
+ memset(skb_put(*skb, 8), 0, 8);
+
+ return 18;
+}
+
+
+int capi_activate_transp_req(struct pcbit_chan *chan, struct sk_buff **skb)
+{
+
+ if ((*skb = dev_alloc_skb(7)) == NULL) {
+
+ printk(KERN_WARNING "capi_activate_transp_req: alloc_skb failed\n");
+ return -1;
+ }
+
+ *((ushort *)skb_put(*skb, 2)) = chan->callref;
+
+
+ *(skb_put(*skb, 1)) = chan->layer2link; /* Layer2 id */
+ *(skb_put(*skb, 1)) = 0x00; /* Transmit by default */
+
+ *((ushort *) skb_put(*skb, 2)) = MRU;
+
+ *(skb_put(*skb, 1)) = 0x01; /* Enables reception*/
+
+ return 7;
+}
+
+int capi_tdata_req(struct pcbit_chan *chan, struct sk_buff *skb)
+{
+ ushort data_len;
+
+
+ /*
+ * callref - 2
+ * layer2link - 1
+ * wBlockLength - 2
+ * data - 4
+ * sernum - 1
+ */
+
+ data_len = skb->len;
+
+ if (skb_headroom(skb) < 10)
+ {
+ printk(KERN_CRIT "No headspace (%u) on headroom %p for capi header\n", skb_headroom(skb), skb);
+ }
+ else
+ {
+ skb_push(skb, 10);
+ }
+
+ *((u16 *) (skb->data)) = chan->callref;
+ skb->data[2] = chan->layer2link;
+ *((u16 *) (skb->data + 3)) = data_len;
+
+ chan->s_refnum = (chan->s_refnum + 1) % 8;
+ *((u32 *) (skb->data + 5)) = chan->s_refnum;
+
+ skb->data[9] = 0; /* HDLC frame number */
+
+ return 10;
+}
+
+int capi_tdata_resp(struct pcbit_chan *chan, struct sk_buff **skb)
+
+{
+ if ((*skb = dev_alloc_skb(4)) == NULL) {
+
+ printk(KERN_WARNING "capi_tdata_resp: alloc_skb failed\n");
+ return -1;
+ }
+
+ *((ushort *)skb_put(*skb, 2)) = chan->callref;
+
+ *(skb_put(*skb, 1)) = chan->layer2link;
+ *(skb_put(*skb, 1)) = chan->r_refnum;
+
+ return (*skb)->len;
+}
+
+int capi_disc_req(ushort callref, struct sk_buff **skb, u_char cause)
+{
+
+ if ((*skb = dev_alloc_skb(6)) == NULL) {
+
+ printk(KERN_WARNING "capi_disc_req: alloc_skb failed\n");
+ return -1;
+ }
+
+ *((ushort *)skb_put(*skb, 2)) = callref;
+
+ *(skb_put(*skb, 1)) = 2; /* Cause.Length = 2; */
+ *(skb_put(*skb, 1)) = 0x80;
+ *(skb_put(*skb, 1)) = 0x80 | cause;
+
+ /*
+ * Change it: we should send 'Sic transit gloria Mundi' here ;-)
+ */
+
+ *(skb_put(*skb, 1)) = 0; /* UTUS.Length = 0; */
+
+ return 6;
+}
+
+int capi_disc_resp(struct pcbit_chan *chan, struct sk_buff **skb)
+{
+ if ((*skb = dev_alloc_skb(2)) == NULL) {
+
+ printk(KERN_WARNING "capi_disc_resp: alloc_skb failed\n");
+ return -1;
+ }
+
+ *((ushort *)skb_put(*skb, 2)) = chan->callref;
+
+ return 2;
+}
+
+
+/*
+ * Decoding of CAPI messages
+ *
+ */
+
+int capi_decode_conn_ind(struct pcbit_chan *chan,
+ struct sk_buff *skb,
+ struct callb_data *info)
+{
+ int CIlen, len;
+
+ /* Call Reference [CAPI] */
+ chan->callref = *((ushort *)skb->data);
+ skb_pull(skb, 2);
+
+#ifdef DEBUG
+ printk(KERN_DEBUG "Call Reference: %04x\n", chan->callref);
+#endif
+
+ /* Channel Identification */
+
+ /* Expect
+ Len = 1
+ Octect 3 = 0100 10CC - [ 7 Basic, 4 , 2-1 chan ]
+ */
+
+ CIlen = skb->data[0];
+#ifdef DEBUG
+ if (CIlen == 1) {
+
+ if (((skb->data[1]) & 0xFC) == 0x48)
+ printk(KERN_DEBUG "decode_conn_ind: chan ok\n");
+ printk(KERN_DEBUG "phyChan = %d\n", skb->data[1] & 0x03);
+ }
+ else
+ printk(KERN_DEBUG "conn_ind: CIlen = %d\n", CIlen);
+#endif
+ skb_pull(skb, CIlen + 1);
+
+ /* Calling Party Number */
+ /* An "additional service" as far as Portugal Telecom is concerned */
+
+ len = skb->data[0];
+
+ if (len > 0) {
+ int count = 1;
+
+#ifdef DEBUG
+ printk(KERN_DEBUG "CPN: Octect 3 %02x\n", skb->data[1]);
+#endif
+ if ((skb->data[1] & 0x80) == 0)
+ count = 2;
+
+ if (!(info->data.setup.CallingPN = kmalloc(len - count + 1, GFP_ATOMIC)))
+ return -1;
+
+ skb_copy_from_linear_data_offset(skb, count + 1,
+ info->data.setup.CallingPN,
+ len - count);
+ info->data.setup.CallingPN[len - count] = 0;
+
+ }
+ else {
+ info->data.setup.CallingPN = NULL;
+ printk(KERN_DEBUG "NULL CallingPN\n");
+ }
+
+ skb_pull(skb, len + 1);
+
+ /* Calling Party Subaddress */
+ skb_pull(skb, skb->data[0] + 1);
+
+ /* Called Party Number */
+
+ len = skb->data[0];
+
+ if (len > 0) {
+ int count = 1;
+
+ if ((skb->data[1] & 0x80) == 0)
+ count = 2;
+
+ if (!(info->data.setup.CalledPN = kmalloc(len - count + 1, GFP_ATOMIC)))
+ return -1;
+
+ skb_copy_from_linear_data_offset(skb, count + 1,
+ info->data.setup.CalledPN,
+ len - count);
+ info->data.setup.CalledPN[len - count] = 0;
+
+ }
+ else {
+ info->data.setup.CalledPN = NULL;
+ printk(KERN_DEBUG "NULL CalledPN\n");
+ }
+
+ skb_pull(skb, len + 1);
+
+ /* Called Party Subaddress */
+ skb_pull(skb, skb->data[0] + 1);
+
+ /* LLC */
+ skb_pull(skb, skb->data[0] + 1);
+
+ /* HLC */
+ skb_pull(skb, skb->data[0] + 1);
+
+ /* U2U */
+ skb_pull(skb, skb->data[0] + 1);
+
+ return 0;
+}
+
+/*
+ * returns errcode
+ */
+
+int capi_decode_conn_conf(struct pcbit_chan *chan, struct sk_buff *skb,
+ int *complete)
+{
+ int errcode;
+
+ chan->callref = *((ushort *)skb->data); /* Update CallReference */
+ skb_pull(skb, 2);
+
+ errcode = *((ushort *) skb->data); /* read errcode */
+ skb_pull(skb, 2);
+
+ *complete = *(skb->data);
+ skb_pull(skb, 1);
+
+ /* FIX ME */
+ /* This is actually a firmware bug */
+ if (!*complete)
+ {
+ printk(KERN_DEBUG "complete=%02x\n", *complete);
+ *complete = 1;
+ }
+
+
+ /* Optional Bearer Capability */
+ skb_pull(skb, *(skb->data) + 1);
+
+ /* Channel Identification */
+ skb_pull(skb, *(skb->data) + 1);
+
+ /* High Layer Compatibility follows */
+ skb_pull(skb, *(skb->data) + 1);
+
+ return errcode;
+}
+
+int capi_decode_conn_actv_ind(struct pcbit_chan *chan, struct sk_buff *skb)
+{
+ ushort len;
+#ifdef DEBUG
+ char str[32];
+#endif
+
+ /* Yet Another Bearer Capability */
+ skb_pull(skb, *(skb->data) + 1);
+
+
+ /* Connected Party Number */
+ len = *(skb->data);
+
+#ifdef DEBUG
+ if (len > 1 && len < 31) {
+ skb_copy_from_linear_data_offset(skb, 2, str, len - 1);
+ str[len] = 0;
+ printk(KERN_DEBUG "Connected Party Number: %s\n", str);
+ }
+ else
+ printk(KERN_DEBUG "actv_ind CPN len = %d\n", len);
+#endif
+
+ skb_pull(skb, len + 1);
+
+ /* Connected Subaddress */
+ skb_pull(skb, *(skb->data) + 1);
+
+ /* Low Layer Capability */
+ skb_pull(skb, *(skb->data) + 1);
+
+ /* High Layer Capability */
+ skb_pull(skb, *(skb->data) + 1);
+
+ return 0;
+}
+
+int capi_decode_conn_actv_conf(struct pcbit_chan *chan, struct sk_buff *skb)
+{
+ ushort errcode;
+
+ errcode = *((ushort *)skb->data);
+ skb_pull(skb, 2);
+
+ /* Channel Identification
+ skb_pull(skb, skb->data[0] + 1);
+ */
+ return errcode;
+}
+
+
+int capi_decode_sel_proto_conf(struct pcbit_chan *chan, struct sk_buff *skb)
+{
+ ushort errcode;
+
+ chan->layer2link = *(skb->data);
+ skb_pull(skb, 1);
+
+ errcode = *((ushort *)skb->data);
+ skb_pull(skb, 2);
+
+ return errcode;
+}
+
+int capi_decode_actv_trans_conf(struct pcbit_chan *chan, struct sk_buff *skb)
+{
+ ushort errcode;
+
+ if (chan->layer2link != *(skb->data))
+ printk("capi_decode_actv_trans_conf: layer2link doesn't match\n");
+
+ skb_pull(skb, 1);
+
+ errcode = *((ushort *)skb->data);
+ skb_pull(skb, 2);
+
+ return errcode;
+}
+
+int capi_decode_disc_ind(struct pcbit_chan *chan, struct sk_buff *skb)
+{
+ ushort len;
+#ifdef DEBUG
+ int i;
+#endif
+ /* Cause */
+
+ len = *(skb->data);
+ skb_pull(skb, 1);
+
+#ifdef DEBUG
+
+ for (i = 0; i < len; i++)
+ printk(KERN_DEBUG "Cause Octect %d: %02x\n", i + 3,
+ *(skb->data + i));
+#endif
+
+ skb_pull(skb, len);
+
+ return 0;
+}
+
+#ifdef DEBUG
+int capi_decode_debug_188(u_char *hdr, ushort hdrlen)
+{
+ char str[64];
+ int len;
+
+ len = hdr[0];
+
+ if (len < 64 && len == hdrlen - 1) {
+ memcpy(str, hdr + 1, hdrlen - 1);
+ str[hdrlen - 1] = 0;
+ printk("%s\n", str);
+ }
+ else
+ printk("debug message incorrect\n");
+
+ return 0;
+}
+#endif
diff --git a/drivers/staging/i4l/pcbit/capi.h b/drivers/staging/i4l/pcbit/capi.h
new file mode 100644
index 000000000000..635f63476944
--- /dev/null
+++ b/drivers/staging/i4l/pcbit/capi.h
@@ -0,0 +1,81 @@
+/*
+ * CAPI encode/decode prototypes and defines
+ *
+ * Copyright (C) 1996 Universidade de Lisboa
+ *
+ * Written by Pedro Roque Marques (roque@di.fc.ul.pt)
+ *
+ * This software may be used and distributed according to the terms of
+ * the GNU General Public License, incorporated herein by reference.
+ */
+
+#ifndef CAPI_H
+#define CAPI_H
+
+
+#define REQ_CAUSE 0x01
+#define REQ_DISPLAY 0x04
+#define REQ_USER_TO_USER 0x08
+
+#define AppInfoMask REQ_CAUSE | REQ_DISPLAY | REQ_USER_TO_USER
+
+/* Connection Setup */
+extern int capi_conn_req(const char *calledPN, struct sk_buff **buf,
+ int proto);
+extern int capi_decode_conn_conf(struct pcbit_chan *chan, struct sk_buff *skb,
+ int *complete);
+
+extern int capi_decode_conn_ind(struct pcbit_chan *chan, struct sk_buff *skb,
+ struct callb_data *info);
+extern int capi_conn_resp(struct pcbit_chan *chan, struct sk_buff **skb);
+
+extern int capi_conn_active_req(struct pcbit_chan *chan, struct sk_buff **skb);
+extern int capi_decode_conn_actv_conf(struct pcbit_chan *chan,
+ struct sk_buff *skb);
+
+extern int capi_decode_conn_actv_ind(struct pcbit_chan *chan,
+ struct sk_buff *skb);
+extern int capi_conn_active_resp(struct pcbit_chan *chan,
+ struct sk_buff **skb);
+
+/* Data */
+extern int capi_select_proto_req(struct pcbit_chan *chan, struct sk_buff **skb,
+ int outgoing);
+extern int capi_decode_sel_proto_conf(struct pcbit_chan *chan,
+ struct sk_buff *skb);
+
+extern int capi_activate_transp_req(struct pcbit_chan *chan,
+ struct sk_buff **skb);
+extern int capi_decode_actv_trans_conf(struct pcbit_chan *chan,
+ struct sk_buff *skb);
+
+extern int capi_tdata_req(struct pcbit_chan *chan, struct sk_buff *skb);
+extern int capi_tdata_resp(struct pcbit_chan *chan, struct sk_buff **skb);
+
+/* Connection Termination */
+extern int capi_disc_req(ushort callref, struct sk_buff **skb, u_char cause);
+
+extern int capi_decode_disc_ind(struct pcbit_chan *chan, struct sk_buff *skb);
+extern int capi_disc_resp(struct pcbit_chan *chan, struct sk_buff **skb);
+
+#ifdef DEBUG
+extern int capi_decode_debug_188(u_char *hdr, ushort hdrlen);
+#endif
+
+static inline struct pcbit_chan *
+capi_channel(struct pcbit_dev *dev, struct sk_buff *skb)
+{
+ ushort callref;
+
+ callref = *((ushort *)skb->data);
+ skb_pull(skb, 2);
+
+ if (dev->b1->callref == callref)
+ return dev->b1;
+ else if (dev->b2->callref == callref)
+ return dev->b2;
+
+ return NULL;
+}
+
+#endif
diff --git a/drivers/staging/i4l/pcbit/drv.c b/drivers/staging/i4l/pcbit/drv.c
new file mode 100644
index 000000000000..4172e22ae7ed
--- /dev/null
+++ b/drivers/staging/i4l/pcbit/drv.c
@@ -0,0 +1,1077 @@
+/*
+ * PCBIT-D interface with isdn4linux
+ *
+ * Copyright (C) 1996 Universidade de Lisboa
+ *
+ * Written by Pedro Roque Marques (roque@di.fc.ul.pt)
+ *
+ * This software may be used and distributed according to the terms of
+ * the GNU General Public License, incorporated herein by reference.
+ */
+
+/*
+ * Fixes:
+ *
+ * Nuno Grilo <l38486@alfa.ist.utl.pt>
+ * fixed msn_list NULL pointer dereference.
+ *
+ */
+
+#include <linux/module.h>
+
+
+#include <linux/kernel.h>
+
+#include <linux/types.h>
+#include <linux/sched.h>
+#include <linux/slab.h>
+#include <linux/mm.h>
+#include <linux/interrupt.h>
+#include <linux/string.h>
+#include <linux/skbuff.h>
+
+#include <linux/isdnif.h>
+#include <asm/string.h>
+#include <asm/io.h>
+#include <linux/ioport.h>
+
+#include "pcbit.h"
+#include "edss1.h"
+#include "layer2.h"
+#include "capi.h"
+
+
+extern ushort last_ref_num;
+
+static int pcbit_ioctl(isdn_ctrl *ctl);
+
+static char *pcbit_devname[MAX_PCBIT_CARDS] = {
+ "pcbit0",
+ "pcbit1",
+ "pcbit2",
+ "pcbit3"
+};
+
+/*
+ * prototypes
+ */
+
+static int pcbit_command(isdn_ctrl *ctl);
+static int pcbit_stat(u_char __user *buf, int len, int, int);
+static int pcbit_xmit(int driver, int chan, int ack, struct sk_buff *skb);
+static int pcbit_writecmd(const u_char __user *, int, int, int);
+
+static int set_protocol_running(struct pcbit_dev *dev);
+
+static void pcbit_clear_msn(struct pcbit_dev *dev);
+static void pcbit_set_msn(struct pcbit_dev *dev, char *list);
+static int pcbit_check_msn(struct pcbit_dev *dev, char *msn);
+
+
+int pcbit_init_dev(int board, int mem_base, int irq)
+{
+ struct pcbit_dev *dev;
+ isdn_if *dev_if;
+
+ if ((dev = kzalloc(sizeof(struct pcbit_dev), GFP_KERNEL)) == NULL)
+ {
+ printk("pcbit_init: couldn't malloc pcbit_dev struct\n");
+ return -ENOMEM;
+ }
+
+ dev_pcbit[board] = dev;
+ init_waitqueue_head(&dev->set_running_wq);
+ spin_lock_init(&dev->lock);
+
+ if (mem_base >= 0xA0000 && mem_base <= 0xFFFFF) {
+ dev->ph_mem = mem_base;
+ if (!request_mem_region(dev->ph_mem, 4096, "PCBIT mem")) {
+ printk(KERN_WARNING
+ "PCBIT: memory region %lx-%lx already in use\n",
+ dev->ph_mem, dev->ph_mem + 4096);
+ kfree(dev);
+ dev_pcbit[board] = NULL;
+ return -EACCES;
+ }
+ dev->sh_mem = ioremap(dev->ph_mem, 4096);
+ }
+ else
+ {
+ printk("memory address invalid");
+ kfree(dev);
+ dev_pcbit[board] = NULL;
+ return -EACCES;
+ }
+
+ dev->b1 = kzalloc(sizeof(struct pcbit_chan), GFP_KERNEL);
+ if (!dev->b1) {
+ printk("pcbit_init: couldn't malloc pcbit_chan struct\n");
+ iounmap(dev->sh_mem);
+ release_mem_region(dev->ph_mem, 4096);
+ kfree(dev);
+ return -ENOMEM;
+ }
+
+ dev->b2 = kzalloc(sizeof(struct pcbit_chan), GFP_KERNEL);
+ if (!dev->b2) {
+ printk("pcbit_init: couldn't malloc pcbit_chan struct\n");
+ kfree(dev->b1);
+ iounmap(dev->sh_mem);
+ release_mem_region(dev->ph_mem, 4096);
+ kfree(dev);
+ return -ENOMEM;
+ }
+
+ dev->b2->id = 1;
+
+ INIT_WORK(&dev->qdelivery, pcbit_deliver);
+
+ /*
+ * interrupts
+ */
+
+ if (request_irq(irq, &pcbit_irq_handler, 0, pcbit_devname[board], dev) != 0)
+ {
+ kfree(dev->b1);
+ kfree(dev->b2);
+ iounmap(dev->sh_mem);
+ release_mem_region(dev->ph_mem, 4096);
+ kfree(dev);
+ dev_pcbit[board] = NULL;
+ return -EIO;
+ }
+
+ dev->irq = irq;
+
+ /* next frame to be received */
+ dev->rcv_seq = 0;
+ dev->send_seq = 0;
+ dev->unack_seq = 0;
+
+ dev->hl_hdrlen = 16;
+
+ dev_if = kmalloc(sizeof(isdn_if), GFP_KERNEL);
+
+ if (!dev_if) {
+ free_irq(irq, dev);
+ kfree(dev->b1);
+ kfree(dev->b2);
+ iounmap(dev->sh_mem);
+ release_mem_region(dev->ph_mem, 4096);
+ kfree(dev);
+ dev_pcbit[board] = NULL;
+ return -EIO;
+ }
+
+ dev->dev_if = dev_if;
+
+ dev_if->owner = THIS_MODULE;
+
+ dev_if->channels = 2;
+
+ dev_if->features = (ISDN_FEATURE_P_EURO | ISDN_FEATURE_L3_TRANS |
+ ISDN_FEATURE_L2_HDLC | ISDN_FEATURE_L2_TRANS);
+
+ dev_if->writebuf_skb = pcbit_xmit;
+ dev_if->hl_hdrlen = 16;
+
+ dev_if->maxbufsize = MAXBUFSIZE;
+ dev_if->command = pcbit_command;
+
+ dev_if->writecmd = pcbit_writecmd;
+ dev_if->readstat = pcbit_stat;
+
+
+ strcpy(dev_if->id, pcbit_devname[board]);
+
+ if (!register_isdn(dev_if)) {
+ free_irq(irq, dev);
+ kfree(dev->b1);
+ kfree(dev->b2);
+ iounmap(dev->sh_mem);
+ release_mem_region(dev->ph_mem, 4096);
+ kfree(dev);
+ dev_pcbit[board] = NULL;
+ return -EIO;
+ }
+
+ dev->id = dev_if->channels;
+
+
+ dev->l2_state = L2_DOWN;
+ dev->free = 511;
+
+ /*
+ * set_protocol_running(dev);
+ */
+
+ return 0;
+}
+
+#ifdef MODULE
+void pcbit_terminate(int board)
+{
+ struct pcbit_dev *dev;
+
+ dev = dev_pcbit[board];
+
+ if (dev) {
+ /* unregister_isdn(dev->dev_if); */
+ free_irq(dev->irq, dev);
+ pcbit_clear_msn(dev);
+ kfree(dev->dev_if);
+ if (dev->b1->fsm_timer.function)
+ del_timer(&dev->b1->fsm_timer);
+ if (dev->b2->fsm_timer.function)
+ del_timer(&dev->b2->fsm_timer);
+ kfree(dev->b1);
+ kfree(dev->b2);
+ iounmap(dev->sh_mem);
+ release_mem_region(dev->ph_mem, 4096);
+ kfree(dev);
+ }
+}
+#endif
+
+static int pcbit_command(isdn_ctrl *ctl)
+{
+ struct pcbit_dev *dev;
+ struct pcbit_chan *chan;
+ struct callb_data info;
+
+ dev = finddev(ctl->driver);
+
+ if (!dev)
+ {
+ printk("pcbit_command: unknown device\n");
+ return -1;
+ }
+
+ chan = (ctl->arg & 0x0F) ? dev->b2 : dev->b1;
+
+
+ switch (ctl->command) {
+ case ISDN_CMD_IOCTL:
+ return pcbit_ioctl(ctl);
+ break;
+ case ISDN_CMD_DIAL:
+ info.type = EV_USR_SETUP_REQ;
+ info.data.setup.CalledPN = (char *) &ctl->parm.setup.phone;
+ pcbit_fsm_event(dev, chan, EV_USR_SETUP_REQ, &info);
+ break;
+ case ISDN_CMD_ACCEPTD:
+ pcbit_fsm_event(dev, chan, EV_USR_SETUP_RESP, NULL);
+ break;
+ case ISDN_CMD_ACCEPTB:
+ printk("ISDN_CMD_ACCEPTB - not really needed\n");
+ break;
+ case ISDN_CMD_HANGUP:
+ pcbit_fsm_event(dev, chan, EV_USR_RELEASE_REQ, NULL);
+ break;
+ case ISDN_CMD_SETL2:
+ chan->proto = (ctl->arg >> 8);
+ break;
+ case ISDN_CMD_CLREAZ:
+ pcbit_clear_msn(dev);
+ break;
+ case ISDN_CMD_SETEAZ:
+ pcbit_set_msn(dev, ctl->parm.num);
+ break;
+ case ISDN_CMD_SETL3:
+ if ((ctl->arg >> 8) != ISDN_PROTO_L3_TRANS)
+ printk(KERN_DEBUG "L3 protocol unknown\n");
+ break;
+ default:
+ printk(KERN_DEBUG "pcbit_command: unknown command\n");
+ break;
+ };
+
+ return 0;
+}
+
+/*
+ * Another Hack :-(
+ * on some conditions the board stops sending TDATA_CONFs
+ * let's see if we can turn around the problem
+ */
+
+#ifdef BLOCK_TIMER
+static void pcbit_block_timer(unsigned long data)
+{
+ struct pcbit_chan *chan;
+ struct pcbit_dev *dev;
+ isdn_ctrl ictl;
+
+ chan = (struct pcbit_chan *)data;
+
+ dev = chan2dev(chan);
+
+ if (dev == NULL) {
+ printk(KERN_DEBUG "pcbit: chan2dev failed\n");
+ return;
+ }
+
+ del_timer(&chan->block_timer);
+ chan->block_timer.function = NULL;
+
+#ifdef DEBUG
+ printk(KERN_DEBUG "pcbit_block_timer\n");
+#endif
+ chan->queued = 0;
+ ictl.driver = dev->id;
+ ictl.command = ISDN_STAT_BSENT;
+ ictl.arg = chan->id;
+ dev->dev_if->statcallb(&ictl);
+}
+#endif
+
+static int pcbit_xmit(int driver, int chnum, int ack, struct sk_buff *skb)
+{
+ ushort hdrlen;
+ int refnum, len;
+ struct pcbit_chan *chan;
+ struct pcbit_dev *dev;
+
+ dev = finddev(driver);
+ if (dev == NULL)
+ {
+ printk("finddev returned NULL");
+ return -1;
+ }
+
+ chan = chnum ? dev->b2 : dev->b1;
+
+
+ if (chan->fsm_state != ST_ACTIVE)
+ return -1;
+
+ if (chan->queued >= MAX_QUEUED)
+ {
+#ifdef DEBUG_QUEUE
+ printk(KERN_DEBUG
+ "pcbit: %d packets already in queue - write fails\n",
+ chan->queued);
+#endif
+ /*
+ * packet stays on the head of the device queue
+ * since dev_start_xmit will fail
+ * see net/core/dev.c
+ */
+#ifdef BLOCK_TIMER
+ if (chan->block_timer.function == NULL) {
+ init_timer(&chan->block_timer);
+ chan->block_timer.function = &pcbit_block_timer;
+ chan->block_timer.data = (long) chan;
+ chan->block_timer.expires = jiffies + 1 * HZ;
+ add_timer(&chan->block_timer);
+ }
+#endif
+ return 0;
+ }
+
+
+ chan->queued++;
+
+ len = skb->len;
+
+ hdrlen = capi_tdata_req(chan, skb);
+
+ refnum = last_ref_num++ & 0x7fffU;
+ chan->s_refnum = refnum;
+
+ pcbit_l2_write(dev, MSG_TDATA_REQ, refnum, skb, hdrlen);
+
+ return len;
+}
+
+static int pcbit_writecmd(const u_char __user *buf, int len, int driver, int channel)
+{
+ struct pcbit_dev *dev;
+ int i, j;
+ const u_char *loadbuf;
+ u_char *ptr = NULL;
+ u_char *cbuf;
+
+ int errstat;
+
+ dev = finddev(driver);
+
+ if (!dev)
+ {
+ printk("pcbit_writecmd: couldn't find device");
+ return -ENODEV;
+ }
+
+ switch (dev->l2_state) {
+ case L2_LWMODE:
+ /* check (size <= rdp_size); write buf into board */
+ if (len < 0 || len > BANK4 + 1 || len > 1024)
+ {
+ printk("pcbit_writecmd: invalid length %d\n", len);
+ return -EINVAL;
+ }
+
+ cbuf = memdup_user(buf, len);
+ if (IS_ERR(cbuf))
+ return PTR_ERR(cbuf);
+
+ memcpy_toio(dev->sh_mem, cbuf, len);
+ kfree(cbuf);
+ return len;
+ case L2_FWMODE:
+ /* this is the hard part */
+ /* dumb board */
+ /* get it into kernel space */
+ if ((ptr = kmalloc(len, GFP_KERNEL)) == NULL)
+ return -ENOMEM;
+ if (copy_from_user(ptr, buf, len)) {
+ kfree(ptr);
+ return -EFAULT;
+ }
+ loadbuf = ptr;
+
+ errstat = 0;
+
+ for (i = 0; i < len; i++)
+ {
+ for (j = 0; j < LOAD_RETRY; j++)
+ if (!(readb(dev->sh_mem + dev->loadptr)))
+ break;
+
+ if (j == LOAD_RETRY)
+ {
+ errstat = -ETIME;
+ printk("TIMEOUT i=%d\n", i);
+ break;
+ }
+ writeb(loadbuf[i], dev->sh_mem + dev->loadptr + 1);
+ writeb(0x01, dev->sh_mem + dev->loadptr);
+
+ dev->loadptr += 2;
+ if (dev->loadptr > LOAD_ZONE_END)
+ dev->loadptr = LOAD_ZONE_START;
+ }
+ kfree(ptr);
+
+ return errstat ? errstat : len;
+ default:
+ return -EBUSY;
+ }
+}
+
+/*
+ * demultiplexing of messages
+ *
+ */
+
+void pcbit_l3_receive(struct pcbit_dev *dev, ulong msg,
+ struct sk_buff *skb,
+ ushort hdr_len, ushort refnum)
+{
+ struct pcbit_chan *chan;
+ struct sk_buff *skb2;
+ unsigned short len;
+ struct callb_data cbdata;
+ int complete, err;
+ isdn_ctrl ictl;
+
+ switch (msg) {
+
+ case MSG_TDATA_IND:
+ if (!(chan = capi_channel(dev, skb))) {
+ printk(KERN_WARNING
+ "CAPI header: unknown channel id\n");
+ break;
+ }
+ chan->r_refnum = skb->data[7];
+ skb_pull(skb, 8);
+
+ dev->dev_if->rcvcallb_skb(dev->id, chan->id, skb);
+
+ if (capi_tdata_resp(chan, &skb2) > 0)
+ pcbit_l2_write(dev, MSG_TDATA_RESP, refnum,
+ skb2, skb2->len);
+ return;
+ break;
+ case MSG_TDATA_CONF:
+ if (!(chan = capi_channel(dev, skb))) {
+ printk(KERN_WARNING
+ "CAPI header: unknown channel id\n");
+ break;
+ }
+
+#ifdef DEBUG
+ if ((*((ushort *)(skb->data + 2))) != 0) {
+ printk(KERN_DEBUG "TDATA_CONF error\n");
+ }
+#endif
+#ifdef BLOCK_TIMER
+ if (chan->queued == MAX_QUEUED) {
+ del_timer(&chan->block_timer);
+ chan->block_timer.function = NULL;
+ }
+
+#endif
+ chan->queued--;
+
+ ictl.driver = dev->id;
+ ictl.command = ISDN_STAT_BSENT;
+ ictl.arg = chan->id;
+ dev->dev_if->statcallb(&ictl);
+ break;
+
+ case MSG_CONN_IND:
+ /*
+ * channel: 1st not used will do
+ * if both are used we're in trouble
+ */
+
+ if (!dev->b1->fsm_state)
+ chan = dev->b1;
+ else if (!dev->b2->fsm_state)
+ chan = dev->b2;
+ else {
+ printk(KERN_INFO
+ "Incoming connection: no channels available");
+
+ if ((len = capi_disc_req(*(ushort *)(skb->data), &skb2, CAUSE_NOCHAN)) > 0)
+ pcbit_l2_write(dev, MSG_DISC_REQ, refnum, skb2, len);
+ break;
+ }
+
+ cbdata.data.setup.CalledPN = NULL;
+ cbdata.data.setup.CallingPN = NULL;
+
+ capi_decode_conn_ind(chan, skb, &cbdata);
+ cbdata.type = EV_NET_SETUP;
+
+ pcbit_fsm_event(dev, chan, EV_NET_SETUP, NULL);
+
+ if (pcbit_check_msn(dev, cbdata.data.setup.CallingPN))
+ pcbit_fsm_event(dev, chan, EV_USR_PROCED_REQ, &cbdata);
+ else
+ pcbit_fsm_event(dev, chan, EV_USR_RELEASE_REQ, NULL);
+
+ kfree(cbdata.data.setup.CalledPN);
+ kfree(cbdata.data.setup.CallingPN);
+ break;
+
+ case MSG_CONN_CONF:
+ /*
+ * We should be able to find the channel by the message
+ * reference number. The current version of the firmware
+ * doesn't sent the ref number correctly.
+ */
+#ifdef DEBUG
+ printk(KERN_DEBUG "refnum=%04x b1=%04x b2=%04x\n", refnum,
+ dev->b1->s_refnum,
+ dev->b2->s_refnum);
+#endif
+ /* We just try to find a channel in the right state */
+
+ if (dev->b1->fsm_state == ST_CALL_INIT)
+ chan = dev->b1;
+ else {
+ if (dev->b2->s_refnum == ST_CALL_INIT)
+ chan = dev->b2;
+ else {
+ chan = NULL;
+ printk(KERN_WARNING "Connection Confirm - no channel in Call Init state\n");
+ break;
+ }
+ }
+ if (capi_decode_conn_conf(chan, skb, &complete)) {
+ printk(KERN_DEBUG "conn_conf indicates error\n");
+ pcbit_fsm_event(dev, chan, EV_ERROR, NULL);
+ }
+ else
+ if (complete)
+ pcbit_fsm_event(dev, chan, EV_NET_CALL_PROC, NULL);
+ else
+ pcbit_fsm_event(dev, chan, EV_NET_SETUP_ACK, NULL);
+ break;
+ case MSG_CONN_ACTV_IND:
+
+ if (!(chan = capi_channel(dev, skb))) {
+ printk(KERN_WARNING
+ "CAPI header: unknown channel id\n");
+ break;
+ }
+
+ if (capi_decode_conn_actv_ind(chan, skb)) {
+ printk("error in capi_decode_conn_actv_ind\n");
+ /* pcbit_fsm_event(dev, chan, EV_ERROR, NULL); */
+ break;
+ }
+ chan->r_refnum = refnum;
+ pcbit_fsm_event(dev, chan, EV_NET_CONN, NULL);
+ break;
+ case MSG_CONN_ACTV_CONF:
+
+ if (!(chan = capi_channel(dev, skb))) {
+ printk(KERN_WARNING
+ "CAPI header: unknown channel id\n");
+ break;
+ }
+
+ if (capi_decode_conn_actv_conf(chan, skb) == 0)
+ pcbit_fsm_event(dev, chan, EV_NET_CONN_ACK, NULL);
+
+ else
+ printk(KERN_DEBUG "decode_conn_actv_conf failed\n");
+ break;
+
+ case MSG_SELP_CONF:
+
+ if (!(chan = capi_channel(dev, skb))) {
+ printk(KERN_WARNING
+ "CAPI header: unknown channel id\n");
+ break;
+ }
+
+ if (!(err = capi_decode_sel_proto_conf(chan, skb)))
+ pcbit_fsm_event(dev, chan, EV_NET_SELP_RESP, NULL);
+ else {
+ /* Error */
+ printk("error %d - capi_decode_sel_proto_conf\n", err);
+ }
+ break;
+ case MSG_ACT_TRANSP_CONF:
+ if (!(chan = capi_channel(dev, skb))) {
+ printk(KERN_WARNING
+ "CAPI header: unknown channel id\n");
+ break;
+ }
+
+ if (!capi_decode_actv_trans_conf(chan, skb))
+ pcbit_fsm_event(dev, chan, EV_NET_ACTV_RESP, NULL);
+ break;
+
+ case MSG_DISC_IND:
+
+ if (!(chan = capi_channel(dev, skb))) {
+ printk(KERN_WARNING
+ "CAPI header: unknown channel id\n");
+ break;
+ }
+
+ if (!capi_decode_disc_ind(chan, skb))
+ pcbit_fsm_event(dev, chan, EV_NET_DISC, NULL);
+ else
+ printk(KERN_WARNING "capi_decode_disc_ind - error\n");
+ break;
+ case MSG_DISC_CONF:
+ if (!(chan = capi_channel(dev, skb))) {
+ printk(KERN_WARNING
+ "CAPI header: unknown channel id\n");
+ break;
+ }
+
+ if (!capi_decode_disc_ind(chan, skb))
+ pcbit_fsm_event(dev, chan, EV_NET_RELEASE, NULL);
+ else
+ printk(KERN_WARNING "capi_decode_disc_conf - error\n");
+ break;
+ case MSG_INFO_IND:
+#ifdef DEBUG
+ printk(KERN_DEBUG "received Info Indication - discarded\n");
+#endif
+ break;
+#ifdef DEBUG
+ case MSG_DEBUG_188:
+ capi_decode_debug_188(skb->data, skb->len);
+ break;
+
+ default:
+ printk(KERN_DEBUG "pcbit_l3_receive: unknown message %08lx\n",
+ msg);
+ break;
+#endif
+ }
+
+ kfree_skb(skb);
+
+}
+
+/*
+ * Single statbuf
+ * should be a statbuf per device
+ */
+
+static char statbuf[STATBUF_LEN];
+static int stat_st = 0;
+static int stat_end = 0;
+
+static int pcbit_stat(u_char __user *buf, int len, int driver, int channel)
+{
+ int stat_count;
+ stat_count = stat_end - stat_st;
+
+ if (stat_count < 0)
+ stat_count = STATBUF_LEN - stat_st + stat_end;
+
+ /* FIXME: should we sleep and wait for more cookies ? */
+ if (len > stat_count)
+ len = stat_count;
+
+ if (stat_st < stat_end)
+ {
+ if (copy_to_user(buf, statbuf + stat_st, len))
+ return -EFAULT;
+ stat_st += len;
+ }
+ else
+ {
+ if (len > STATBUF_LEN - stat_st)
+ {
+ if (copy_to_user(buf, statbuf + stat_st,
+ STATBUF_LEN - stat_st))
+ return -EFAULT;
+ if (copy_to_user(buf, statbuf,
+ len - (STATBUF_LEN - stat_st)))
+ return -EFAULT;
+
+ stat_st = len - (STATBUF_LEN - stat_st);
+ }
+ else
+ {
+ if (copy_to_user(buf, statbuf + stat_st, len))
+ return -EFAULT;
+
+ stat_st += len;
+
+ if (stat_st == STATBUF_LEN)
+ stat_st = 0;
+ }
+ }
+
+ if (stat_st == stat_end)
+ stat_st = stat_end = 0;
+
+ return len;
+}
+
+static void pcbit_logstat(struct pcbit_dev *dev, char *str)
+{
+ int i;
+ isdn_ctrl ictl;
+
+ for (i = stat_end; i < strlen(str); i++)
+ {
+ statbuf[i] = str[i];
+ stat_end = (stat_end + 1) % STATBUF_LEN;
+ if (stat_end == stat_st)
+ stat_st = (stat_st + 1) % STATBUF_LEN;
+ }
+
+ ictl.command = ISDN_STAT_STAVAIL;
+ ictl.driver = dev->id;
+ ictl.arg = strlen(str);
+ dev->dev_if->statcallb(&ictl);
+}
+
+void pcbit_state_change(struct pcbit_dev *dev, struct pcbit_chan *chan,
+ unsigned short i, unsigned short ev, unsigned short f)
+{
+ char buf[256];
+
+ sprintf(buf, "change on device: %d channel:%d\n%s -> %s -> %s\n",
+ dev->id, chan->id,
+ isdn_state_table[i], strisdnevent(ev), isdn_state_table[f]
+ );
+
+#ifdef DEBUG
+ printk("%s", buf);
+#endif
+
+ pcbit_logstat(dev, buf);
+}
+
+static void set_running_timeout(unsigned long ptr)
+{
+ struct pcbit_dev *dev;
+
+#ifdef DEBUG
+ printk(KERN_DEBUG "set_running_timeout\n");
+#endif
+ dev = (struct pcbit_dev *) ptr;
+
+ dev->l2_state = L2_DOWN;
+ wake_up_interruptible(&dev->set_running_wq);
+}
+
+static int set_protocol_running(struct pcbit_dev *dev)
+{
+ isdn_ctrl ctl;
+
+ init_timer(&dev->set_running_timer);
+
+ dev->set_running_timer.function = &set_running_timeout;
+ dev->set_running_timer.data = (ulong) dev;
+ dev->set_running_timer.expires = jiffies + SET_RUN_TIMEOUT;
+
+ /* kick it */
+
+ dev->l2_state = L2_STARTING;
+
+ writeb((0x80U | ((dev->rcv_seq & 0x07) << 3) | (dev->send_seq & 0x07)),
+ dev->sh_mem + BANK4);
+
+ add_timer(&dev->set_running_timer);
+
+ wait_event(dev->set_running_wq, dev->l2_state == L2_RUNNING ||
+ dev->l2_state == L2_DOWN);
+
+ del_timer(&dev->set_running_timer);
+
+ if (dev->l2_state == L2_RUNNING)
+ {
+ printk(KERN_DEBUG "pcbit: running\n");
+
+ dev->unack_seq = dev->send_seq;
+
+ dev->writeptr = dev->sh_mem;
+ dev->readptr = dev->sh_mem + BANK2;
+
+ /* tell the good news to the upper layer */
+ ctl.driver = dev->id;
+ ctl.command = ISDN_STAT_RUN;
+
+ dev->dev_if->statcallb(&ctl);
+ }
+ else
+ {
+ printk(KERN_DEBUG "pcbit: initialization failed\n");
+ printk(KERN_DEBUG "pcbit: firmware not loaded\n");
+
+#ifdef DEBUG
+ printk(KERN_DEBUG "Bank3 = %02x\n",
+ readb(dev->sh_mem + BANK3));
+#endif
+ writeb(0x40, dev->sh_mem + BANK4);
+
+ /* warn the upper layer */
+ ctl.driver = dev->id;
+ ctl.command = ISDN_STAT_STOP;
+
+ dev->dev_if->statcallb(&ctl);
+
+ return -EL2HLT; /* Level 2 halted */
+ }
+
+ return 0;
+}
+
+static int pcbit_ioctl(isdn_ctrl *ctl)
+{
+ struct pcbit_dev *dev;
+ struct pcbit_ioctl *cmd;
+
+ dev = finddev(ctl->driver);
+
+ if (!dev)
+ {
+ printk(KERN_DEBUG "pcbit_ioctl: unknown device\n");
+ return -ENODEV;
+ }
+
+ cmd = (struct pcbit_ioctl *) ctl->parm.num;
+
+ switch (ctl->arg) {
+ case PCBIT_IOCTL_GETSTAT:
+ cmd->info.l2_status = dev->l2_state;
+ break;
+
+ case PCBIT_IOCTL_STRLOAD:
+ if (dev->l2_state == L2_RUNNING)
+ return -EBUSY;
+
+ dev->unack_seq = dev->send_seq = dev->rcv_seq = 0;
+
+ dev->writeptr = dev->sh_mem;
+ dev->readptr = dev->sh_mem + BANK2;
+
+ dev->l2_state = L2_LOADING;
+ break;
+
+ case PCBIT_IOCTL_LWMODE:
+ if (dev->l2_state != L2_LOADING)
+ return -EINVAL;
+
+ dev->l2_state = L2_LWMODE;
+ break;
+
+ case PCBIT_IOCTL_FWMODE:
+ if (dev->l2_state == L2_RUNNING)
+ return -EBUSY;
+ dev->loadptr = LOAD_ZONE_START;
+ dev->l2_state = L2_FWMODE;
+
+ break;
+ case PCBIT_IOCTL_ENDLOAD:
+ if (dev->l2_state == L2_RUNNING)
+ return -EBUSY;
+ dev->l2_state = L2_DOWN;
+ break;
+
+ case PCBIT_IOCTL_SETBYTE:
+ if (dev->l2_state == L2_RUNNING)
+ return -EBUSY;
+
+ /* check addr */
+ if (cmd->info.rdp_byte.addr > BANK4)
+ return -EFAULT;
+
+ writeb(cmd->info.rdp_byte.value, dev->sh_mem + cmd->info.rdp_byte.addr);
+ break;
+ case PCBIT_IOCTL_GETBYTE:
+ if (dev->l2_state == L2_RUNNING)
+ return -EBUSY;
+
+ /* check addr */
+
+ if (cmd->info.rdp_byte.addr > BANK4)
+ {
+ printk("getbyte: invalid addr %04x\n", cmd->info.rdp_byte.addr);
+ return -EFAULT;
+ }
+
+ cmd->info.rdp_byte.value = readb(dev->sh_mem + cmd->info.rdp_byte.addr);
+ break;
+ case PCBIT_IOCTL_RUNNING:
+ if (dev->l2_state == L2_RUNNING)
+ return -EBUSY;
+ return set_protocol_running(dev);
+ break;
+ case PCBIT_IOCTL_WATCH188:
+ if (dev->l2_state != L2_LOADING)
+ return -EINVAL;
+ pcbit_l2_write(dev, MSG_WATCH188, 0x0001, NULL, 0);
+ break;
+ case PCBIT_IOCTL_PING188:
+ if (dev->l2_state != L2_LOADING)
+ return -EINVAL;
+ pcbit_l2_write(dev, MSG_PING188_REQ, 0x0001, NULL, 0);
+ break;
+ case PCBIT_IOCTL_APION:
+ if (dev->l2_state != L2_LOADING)
+ return -EINVAL;
+ pcbit_l2_write(dev, MSG_API_ON, 0x0001, NULL, 0);
+ break;
+ case PCBIT_IOCTL_STOP:
+ dev->l2_state = L2_DOWN;
+ writeb(0x40, dev->sh_mem + BANK4);
+ dev->rcv_seq = 0;
+ dev->send_seq = 0;
+ dev->unack_seq = 0;
+ break;
+ default:
+ printk("error: unknown ioctl\n");
+ break;
+ };
+ return 0;
+}
+
+/*
+ * MSN list handling
+ *
+ * if null reject all calls
+ * if first entry has null MSN accept all calls
+ */
+
+static void pcbit_clear_msn(struct pcbit_dev *dev)
+{
+ struct msn_entry *ptr, *back;
+
+ for (ptr = dev->msn_list; ptr;)
+ {
+ back = ptr->next;
+ kfree(ptr);
+ ptr = back;
+ }
+
+ dev->msn_list = NULL;
+}
+
+static void pcbit_set_msn(struct pcbit_dev *dev, char *list)
+{
+ struct msn_entry *ptr;
+ struct msn_entry *back = NULL;
+ char *cp, *sp;
+ int len;
+
+ if (strlen(list) == 0) {
+ ptr = kmalloc(sizeof(struct msn_entry), GFP_ATOMIC);
+ if (!ptr) {
+ printk(KERN_WARNING "kmalloc failed\n");
+ return;
+ }
+
+ ptr->msn = NULL;
+
+ ptr->next = dev->msn_list;
+ dev->msn_list = ptr;
+
+ return;
+ }
+
+ if (dev->msn_list)
+ for (back = dev->msn_list; back->next; back = back->next);
+
+ sp = list;
+
+ do {
+ cp = strchr(sp, ',');
+ if (cp)
+ len = cp - sp;
+ else
+ len = strlen(sp);
+
+ ptr = kmalloc(sizeof(struct msn_entry), GFP_ATOMIC);
+
+ if (!ptr) {
+ printk(KERN_WARNING "kmalloc failed\n");
+ return;
+ }
+ ptr->next = NULL;
+
+ ptr->msn = kmalloc(len + 1, GFP_ATOMIC);
+ if (!ptr->msn) {
+ printk(KERN_WARNING "kmalloc failed\n");
+ kfree(ptr);
+ return;
+ }
+
+ memcpy(ptr->msn, sp, len);
+ ptr->msn[len] = 0;
+
+#ifdef DEBUG
+ printk(KERN_DEBUG "msn: %s\n", ptr->msn);
+#endif
+ if (dev->msn_list == NULL)
+ dev->msn_list = ptr;
+ else
+ back->next = ptr;
+ back = ptr;
+ sp += len;
+ } while (cp);
+}
+
+/*
+ * check if we do signal or reject an incoming call
+ */
+static int pcbit_check_msn(struct pcbit_dev *dev, char *msn)
+{
+ struct msn_entry *ptr;
+
+ for (ptr = dev->msn_list; ptr; ptr = ptr->next) {
+
+ if (ptr->msn == NULL)
+ return 1;
+
+ if (strcmp(ptr->msn, msn) == 0)
+ return 1;
+ }
+
+ return 0;
+}
diff --git a/drivers/staging/i4l/pcbit/edss1.c b/drivers/staging/i4l/pcbit/edss1.c
new file mode 100644
index 000000000000..b2262ba6f0c9
--- /dev/null
+++ b/drivers/staging/i4l/pcbit/edss1.c
@@ -0,0 +1,313 @@
+/*
+ * DSS.1 Finite State Machine
+ * base: ITU-T Rec Q.931
+ *
+ * Copyright (C) 1996 Universidade de Lisboa
+ *
+ * Written by Pedro Roque Marques (roque@di.fc.ul.pt)
+ *
+ * This software may be used and distributed according to the terms of
+ * the GNU General Public License, incorporated herein by reference.
+ */
+
+/*
+ * TODO: complete the FSM
+ * move state/event descriptions to a user space logger
+ */
+
+#include <linux/string.h>
+#include <linux/kernel.h>
+
+#include <linux/types.h>
+#include <linux/mm.h>
+#include <linux/skbuff.h>
+
+#include <linux/timer.h>
+#include <asm/io.h>
+
+#include <linux/isdnif.h>
+
+#include "pcbit.h"
+#include "edss1.h"
+#include "layer2.h"
+#include "callbacks.h"
+
+
+const char * const isdn_state_table[] = {
+ "Closed",
+ "Call initiated",
+ "Overlap sending",
+ "Outgoing call proceeding",
+ "NOT DEFINED",
+ "Call delivered",
+ "Call present",
+ "Call received",
+ "Connect request",
+ "Incoming call proceeding",
+ "Active",
+ "Disconnect request",
+ "Disconnect indication",
+ "NOT DEFINED",
+ "NOT DEFINED",
+ "Suspend request",
+ "NOT DEFINED",
+ "Resume request",
+ "NOT DEFINED",
+ "Release Request",
+ "NOT DEFINED",
+ "NOT DEFINED",
+ "NOT DEFINED",
+ "NOT DEFINED",
+ "NOT DEFINED",
+ "Overlap receiving",
+ "Select protocol on B-Channel",
+ "Activate B-channel protocol"
+};
+
+#ifdef DEBUG_ERRS
+static
+struct CauseValue {
+ byte nr;
+ char *descr;
+} cvlist[] = {
+ {0x01, "Unallocated (unassigned) number"},
+ {0x02, "No route to specified transit network"},
+ {0x03, "No route to destination"},
+ {0x04, "Send special information tone"},
+ {0x05, "Misdialled trunk prefix"},
+ {0x06, "Channel unacceptable"},
+ {0x07, "Channel awarded and being delivered in an established channel"},
+ {0x08, "Preemption"},
+ {0x09, "Preemption - circuit reserved for reuse"},
+ {0x10, "Normal call clearing"},
+ {0x11, "User busy"},
+ {0x12, "No user responding"},
+ {0x13, "No answer from user (user alerted)"},
+ {0x14, "Subscriber absent"},
+ {0x15, "Call rejected"},
+ {0x16, "Number changed"},
+ {0x1a, "non-selected user clearing"},
+ {0x1b, "Destination out of order"},
+ {0x1c, "Invalid number format (address incomplete)"},
+ {0x1d, "Facility rejected"},
+ {0x1e, "Response to Status enquiry"},
+ {0x1f, "Normal, unspecified"},
+ {0x22, "No circuit/channel available"},
+ {0x26, "Network out of order"},
+ {0x27, "Permanent frame mode connection out-of-service"},
+ {0x28, "Permanent frame mode connection operational"},
+ {0x29, "Temporary failure"},
+ {0x2a, "Switching equipment congestion"},
+ {0x2b, "Access information discarded"},
+ {0x2c, "Requested circuit/channel not available"},
+ {0x2e, "Precedence call blocked"},
+ {0x2f, "Resource unavailable, unspecified"},
+ {0x31, "Quality of service unavailable"},
+ {0x32, "Requested facility not subscribed"},
+ {0x35, "Outgoing calls barred within CUG"},
+ {0x37, "Incoming calls barred within CUG"},
+ {0x39, "Bearer capability not authorized"},
+ {0x3a, "Bearer capability not presently available"},
+ {0x3e, "Inconsistency in designated outgoing access information and subscriber class"},
+ {0x3f, "Service or option not available, unspecified"},
+ {0x41, "Bearer capability not implemented"},
+ {0x42, "Channel type not implemented"},
+ {0x43, "Requested facility not implemented"},
+ {0x44, "Only restricted digital information bearer capability is available"},
+ {0x4f, "Service or option not implemented"},
+ {0x51, "Invalid call reference value"},
+ {0x52, "Identified channel does not exist"},
+ {0x53, "A suspended call exists, but this call identity does not"},
+ {0x54, "Call identity in use"},
+ {0x55, "No call suspended"},
+ {0x56, "Call having the requested call identity has been cleared"},
+ {0x57, "User not member of CUG"},
+ {0x58, "Incompatible destination"},
+ {0x5a, "Non-existent CUG"},
+ {0x5b, "Invalid transit network selection"},
+ {0x5f, "Invalid message, unspecified"},
+ {0x60, "Mandatory information element is missing"},
+ {0x61, "Message type non-existent or not implemented"},
+ {0x62, "Message not compatible with call state or message type non-existent or not implemented"},
+ {0x63, "Information element/parameter non-existent or not implemented"},
+ {0x64, "Invalid information element contents"},
+ {0x65, "Message not compatible with call state"},
+ {0x66, "Recovery on timer expiry"},
+ {0x67, "Parameter non-existent or not implemented - passed on"},
+ {0x6e, "Message with unrecognized parameter discarded"},
+ {0x6f, "Protocol error, unspecified"},
+ {0x7f, "Interworking, unspecified"}
+};
+
+#endif
+
+static struct isdn_event_desc {
+ unsigned short ev;
+ char *desc;
+} isdn_event_table[] = {
+ {EV_USR_SETUP_REQ, "CC->L3: Setup Request"},
+ {EV_USR_SETUP_RESP, "CC->L3: Setup Response"},
+ {EV_USR_PROCED_REQ, "CC->L3: Proceeding Request"},
+ {EV_USR_RELEASE_REQ, "CC->L3: Release Request"},
+
+ {EV_NET_SETUP, "NET->TE: setup "},
+ {EV_NET_CALL_PROC, "NET->TE: call proceeding"},
+ {EV_NET_SETUP_ACK, "NET->TE: setup acknowledge (more info needed)"},
+ {EV_NET_CONN, "NET->TE: connect"},
+ {EV_NET_CONN_ACK, "NET->TE: connect acknowledge"},
+ {EV_NET_DISC, "NET->TE: disconnect indication"},
+ {EV_NET_RELEASE, "NET->TE: release"},
+ {EV_NET_RELEASE_COMP, "NET->TE: release complete"},
+ {EV_NET_SELP_RESP, "Board: Select B-channel protocol ack"},
+ {EV_NET_ACTV_RESP, "Board: Activate B-channel protocol ack"},
+ {EV_TIMER, "Timeout"},
+ {0, "NULL"}
+};
+
+char *strisdnevent(ushort ev)
+{
+ struct isdn_event_desc *entry;
+
+ for (entry = isdn_event_table; entry->ev; entry++)
+ if (entry->ev == ev)
+ break;
+
+ return entry->desc;
+}
+
+/*
+ * Euro ISDN finite state machine
+ */
+
+static struct fsm_timer_entry fsm_timers[] = {
+ {ST_CALL_PROC, 10},
+ {ST_DISC_REQ, 2},
+ {ST_ACTIVE_SELP, 5},
+ {ST_ACTIVE_ACTV, 5},
+ {ST_INCM_PROC, 10},
+ {ST_CONN_REQ, 2},
+ {0xff, 0}
+};
+
+static struct fsm_entry fsm_table[] = {
+/* Connect Phase */
+ /* Outgoing */
+ {ST_NULL, ST_CALL_INIT, EV_USR_SETUP_REQ, cb_out_1},
+
+ {ST_CALL_INIT, ST_OVER_SEND, EV_NET_SETUP_ACK, cb_notdone},
+ {ST_CALL_INIT, ST_CALL_PROC, EV_NET_CALL_PROC, NULL},
+ {ST_CALL_INIT, ST_NULL, EV_NET_DISC, cb_out_2},
+
+ {ST_CALL_PROC, ST_ACTIVE_SELP, EV_NET_CONN, cb_out_2},
+ {ST_CALL_PROC, ST_NULL, EV_NET_DISC, cb_disc_1},
+ {ST_CALL_PROC, ST_DISC_REQ, EV_USR_RELEASE_REQ, cb_disc_2},
+
+ /* Incoming */
+ {ST_NULL, ST_CALL_PRES, EV_NET_SETUP, NULL},
+
+ {ST_CALL_PRES, ST_INCM_PROC, EV_USR_PROCED_REQ, cb_in_1},
+ {ST_CALL_PRES, ST_DISC_REQ, EV_USR_RELEASE_REQ, cb_disc_2},
+
+ {ST_INCM_PROC, ST_CONN_REQ, EV_USR_SETUP_RESP, cb_in_2},
+ {ST_INCM_PROC, ST_DISC_REQ, EV_USR_RELEASE_REQ, cb_disc_2},
+
+ {ST_CONN_REQ, ST_ACTIVE_SELP, EV_NET_CONN_ACK, cb_in_3},
+
+ /* Active */
+ {ST_ACTIVE, ST_NULL, EV_NET_DISC, cb_disc_1},
+ {ST_ACTIVE, ST_DISC_REQ, EV_USR_RELEASE_REQ, cb_disc_2},
+ {ST_ACTIVE, ST_NULL, EV_NET_RELEASE, cb_disc_3},
+
+ /* Disconnect */
+
+ {ST_DISC_REQ, ST_NULL, EV_NET_DISC, cb_disc_1},
+ {ST_DISC_REQ, ST_NULL, EV_NET_RELEASE, cb_disc_3},
+
+ /* protocol selection */
+ {ST_ACTIVE_SELP, ST_ACTIVE_ACTV, EV_NET_SELP_RESP, cb_selp_1},
+ {ST_ACTIVE_SELP, ST_DISC_REQ, EV_USR_RELEASE_REQ, cb_disc_2},
+
+ {ST_ACTIVE_ACTV, ST_ACTIVE, EV_NET_ACTV_RESP, cb_open},
+ {ST_ACTIVE_ACTV, ST_DISC_REQ, EV_USR_RELEASE_REQ, cb_disc_2},
+
+ /* Timers */
+ {ST_CALL_PROC, ST_DISC_REQ, EV_TIMER, cb_disc_2},
+ {ST_DISC_REQ, ST_NULL, EV_TIMER, cb_disc_3},
+ {ST_ACTIVE_SELP, ST_DISC_REQ, EV_TIMER, cb_disc_2},
+ {ST_ACTIVE_ACTV, ST_DISC_REQ, EV_TIMER, cb_disc_2},
+ {ST_INCM_PROC, ST_DISC_REQ, EV_TIMER, cb_disc_2},
+ {ST_CONN_REQ, ST_CONN_REQ, EV_TIMER, cb_in_2},
+
+ {0xff, 0, 0, NULL}
+};
+
+
+static void pcbit_fsm_timer(unsigned long data)
+{
+ struct pcbit_dev *dev;
+ struct pcbit_chan *chan;
+
+ chan = (struct pcbit_chan *) data;
+
+ del_timer(&chan->fsm_timer);
+ chan->fsm_timer.function = NULL;
+
+ dev = chan2dev(chan);
+
+ if (dev == NULL) {
+ printk(KERN_WARNING "pcbit: timer for unknown device\n");
+ return;
+ }
+
+ pcbit_fsm_event(dev, chan, EV_TIMER, NULL);
+}
+
+
+void pcbit_fsm_event(struct pcbit_dev *dev, struct pcbit_chan *chan,
+ unsigned short event, struct callb_data *data)
+{
+ struct fsm_entry *action;
+ struct fsm_timer_entry *tentry;
+ unsigned long flags;
+
+ spin_lock_irqsave(&dev->lock, flags);
+
+ for (action = fsm_table; action->init != 0xff; action++)
+ if (action->init == chan->fsm_state && action->event == event)
+ break;
+
+ if (action->init == 0xff) {
+
+ spin_unlock_irqrestore(&dev->lock, flags);
+ printk(KERN_DEBUG "fsm error: event %x on state %x\n",
+ event, chan->fsm_state);
+ return;
+ }
+
+ if (chan->fsm_timer.function) {
+ del_timer(&chan->fsm_timer);
+ chan->fsm_timer.function = NULL;
+ }
+
+ chan->fsm_state = action->final;
+
+ pcbit_state_change(dev, chan, action->init, event, action->final);
+
+ for (tentry = fsm_timers; tentry->init != 0xff; tentry++)
+ if (tentry->init == chan->fsm_state)
+ break;
+
+ if (tentry->init != 0xff) {
+ init_timer(&chan->fsm_timer);
+ chan->fsm_timer.function = &pcbit_fsm_timer;
+ chan->fsm_timer.data = (ulong) chan;
+ chan->fsm_timer.expires = jiffies + tentry->timeout * HZ;
+ add_timer(&chan->fsm_timer);
+ }
+
+ spin_unlock_irqrestore(&dev->lock, flags);
+
+ if (action->callb)
+ action->callb(dev, chan, data);
+
+}
diff --git a/drivers/staging/i4l/pcbit/edss1.h b/drivers/staging/i4l/pcbit/edss1.h
new file mode 100644
index 000000000000..2f6b3a8edfba
--- /dev/null
+++ b/drivers/staging/i4l/pcbit/edss1.h
@@ -0,0 +1,99 @@
+/*
+ * DSS.1 module definitions
+ *
+ * Copyright (C) 1996 Universidade de Lisboa
+ *
+ * Written by Pedro Roque Marques (roque@di.fc.ul.pt)
+ *
+ * This software may be used and distributed according to the terms of
+ * the GNU General Public License, incorporated herein by reference.
+ */
+
+#ifndef EDSS1_H
+#define EDSS1_H
+
+/* ISDN states */
+
+#define ST_NULL 0
+#define ST_CALL_INIT 1 /* Call initiated */
+#define ST_OVER_SEND 2 /* Overlap sending - Requests More Info 4 call */
+#define ST_CALL_PROC 3 /* Call Proceeding */
+#define ST_CALL_DELV 4
+#define ST_CALL_PRES 6 /* Call Present - Received CONN.IND */
+#define ST_CALL_RECV 7 /* Alerting sent */
+#define ST_CONN_REQ 8 /* Answered - waiting 4 CONN.CONF */
+#define ST_INCM_PROC 9
+#define ST_ACTIVE 10
+#define ST_DISC_REQ 11
+#define ST_DISC_IND 12
+#define ST_SUSP_REQ 15
+#define ST_RESM_REQ 17
+#define ST_RELS_REQ 19
+#define ST_OVER_RECV 25
+
+#define ST_ACTIVE_SELP 26 /* Select protocol on B-Channel */
+#define ST_ACTIVE_ACTV 27 /* Activate B-channel protocol */
+
+#define MAX_STATE ST_ACTIVE_ACTV
+
+#define EV_NULL 0
+#define EV_USR_SETUP_REQ 1
+#define EV_USR_SETUP_RESP 2
+#define EV_USR_PROCED_REQ 3
+#define EV_USR_RELEASE_REQ 4
+#define EV_USR_REJECT_REQ 4
+
+#define EV_NET_SETUP 16
+#define EV_NET_CALL_PROC 17
+#define EV_NET_SETUP_ACK 18
+#define EV_NET_CONN 19
+#define EV_NET_CONN_ACK 20
+
+#define EV_NET_SELP_RESP 21
+#define EV_NET_ACTV_RESP 22
+
+#define EV_NET_DISC 23
+#define EV_NET_RELEASE 24
+#define EV_NET_RELEASE_COMP 25
+
+#define EV_TIMER 26
+#define EV_ERROR 32
+
+/*
+ * Cause values
+ * only the ones we use
+ */
+
+#define CAUSE_NORMAL 0x10U
+#define CAUSE_NOCHAN 0x22U
+
+struct callb_data {
+ unsigned short type;
+ union {
+ struct ConnInfo {
+ char *CalledPN;
+ char *CallingPN;
+ } setup;
+ unsigned short cause;
+ } data;
+};
+
+struct fsm_entry {
+ unsigned short init;
+ unsigned short final;
+ unsigned short event;
+ void (*callb)(struct pcbit_dev *, struct pcbit_chan *, struct callb_data*);
+};
+
+struct fsm_timer_entry {
+ unsigned short init;
+ unsigned long timeout; /* in seconds */
+};
+
+extern const char * const isdn_state_table[];
+
+void pcbit_fsm_event(struct pcbit_dev *, struct pcbit_chan *,
+ unsigned short event, struct callb_data *);
+char *strisdnevent(ushort ev);
+
+#endif
diff --git a/drivers/staging/i4l/pcbit/layer2.c b/drivers/staging/i4l/pcbit/layer2.c
new file mode 100644
index 000000000000..46e1240ae074
--- /dev/null
+++ b/drivers/staging/i4l/pcbit/layer2.c
@@ -0,0 +1,712 @@
+/*
+ * PCBIT-D low-layer interface
+ *
+ * Copyright (C) 1996 Universidade de Lisboa
+ *
+ * Written by Pedro Roque Marques (roque@di.fc.ul.pt)
+ *
+ * This software may be used and distributed according to the terms of
+ * the GNU General Public License, incorporated herein by reference.
+ */
+
+/*
+ * 19991203 - Fernando Carvalho - takion@superbofh.org
+ * Hacked to compile with egcs and run with current version of isdn modules
+ */
+
+/*
+ * Based on documentation provided by Inesc:
+ * - "Interface com bus do PC para o PCBIT e PCBIT-D", Inesc, Jan 93
+ */
+
+/*
+ * TODO: better handling of errors
+ * re-write/remove debug printks
+ */
+
+#include <linux/string.h>
+#include <linux/kernel.h>
+#include <linux/types.h>
+#include <linux/sched.h>
+#include <linux/slab.h>
+#include <linux/interrupt.h>
+#include <linux/workqueue.h>
+#include <linux/mm.h>
+#include <linux/skbuff.h>
+
+#include <linux/isdnif.h>
+
+#include <asm/io.h>
+
+
+#include "pcbit.h"
+#include "layer2.h"
+#include "edss1.h"
+
+#undef DEBUG_FRAG
+
+
+/*
+ * Prototypes
+ */
+
+static void pcbit_transmit(struct pcbit_dev *dev);
+
+static void pcbit_recv_ack(struct pcbit_dev *dev, unsigned char ack);
+
+static void pcbit_l2_error(struct pcbit_dev *dev);
+static void pcbit_l2_active_conf(struct pcbit_dev *dev, u_char info);
+static void pcbit_l2_err_recover(unsigned long data);
+
+static void pcbit_firmware_bug(struct pcbit_dev *dev);
+
+static __inline__ void
+pcbit_sched_delivery(struct pcbit_dev *dev)
+{
+ schedule_work(&dev->qdelivery);
+}
+
+
+/*
+ * Called from layer3
+ */
+
+int
+pcbit_l2_write(struct pcbit_dev *dev, ulong msg, ushort refnum,
+ struct sk_buff *skb, unsigned short hdr_len)
+{
+ struct frame_buf *frame,
+ *ptr;
+ unsigned long flags;
+
+ if (dev->l2_state != L2_RUNNING && dev->l2_state != L2_LOADING) {
+ dev_kfree_skb(skb);
+ return -1;
+ }
+ if ((frame = kmalloc(sizeof(struct frame_buf),
+ GFP_ATOMIC)) == NULL) {
+ dev_kfree_skb(skb);
+ return -1;
+ }
+ frame->msg = msg;
+ frame->refnum = refnum;
+ frame->copied = 0;
+ frame->hdr_len = hdr_len;
+
+ if (skb)
+ frame->dt_len = skb->len - hdr_len;
+ else
+ frame->dt_len = 0;
+
+ frame->skb = skb;
+
+ frame->next = NULL;
+
+ spin_lock_irqsave(&dev->lock, flags);
+
+ if (dev->write_queue == NULL) {
+ dev->write_queue = frame;
+ spin_unlock_irqrestore(&dev->lock, flags);
+ pcbit_transmit(dev);
+ } else {
+ for (ptr = dev->write_queue; ptr->next; ptr = ptr->next);
+ ptr->next = frame;
+
+ spin_unlock_irqrestore(&dev->lock, flags);
+ }
+ return 0;
+}
+
+static __inline__ void
+pcbit_tx_update(struct pcbit_dev *dev, ushort len)
+{
+ u_char info;
+
+ dev->send_seq = (dev->send_seq + 1) % 8;
+
+ dev->fsize[dev->send_seq] = len;
+ info = 0;
+ info |= dev->rcv_seq << 3;
+ info |= dev->send_seq;
+
+ writeb(info, dev->sh_mem + BANK4);
+
+}
+
+/*
+ * called by interrupt service routine or by write_2
+ */
+
+static void
+pcbit_transmit(struct pcbit_dev *dev)
+{
+ struct frame_buf *frame = NULL;
+ unsigned char unacked;
+ int flen; /* fragment frame length including all headers */
+ int free;
+ int count,
+ cp_len;
+ unsigned long flags;
+ unsigned short tt;
+
+ if (dev->l2_state != L2_RUNNING && dev->l2_state != L2_LOADING)
+ return;
+
+ unacked = (dev->send_seq + (8 - dev->unack_seq)) & 0x07;
+
+ spin_lock_irqsave(&dev->lock, flags);
+
+ if (dev->free > 16 && dev->write_queue && unacked < 7) {
+
+ if (!dev->w_busy)
+ dev->w_busy = 1;
+ else {
+ spin_unlock_irqrestore(&dev->lock, flags);
+ return;
+ }
+
+
+ frame = dev->write_queue;
+ free = dev->free;
+
+ spin_unlock_irqrestore(&dev->lock, flags);
+
+ if (frame->copied == 0) {
+
+ /* Type 0 frame */
+
+ ulong msg;
+
+ if (frame->skb)
+ flen = FRAME_HDR_LEN + PREHDR_LEN + frame->skb->len;
+ else
+ flen = FRAME_HDR_LEN + PREHDR_LEN;
+
+ if (flen > free)
+ flen = free;
+
+ msg = frame->msg;
+
+ /*
+ * Board level 2 header
+ */
+
+ pcbit_writew(dev, flen - FRAME_HDR_LEN);
+
+ pcbit_writeb(dev, GET_MSG_CPU(msg));
+
+ pcbit_writeb(dev, GET_MSG_PROC(msg));
+
+ /* TH */
+ pcbit_writew(dev, frame->hdr_len + PREHDR_LEN);
+
+ /* TD */
+ pcbit_writew(dev, frame->dt_len);
+
+
+ /*
+ * Board level 3 fixed-header
+ */
+
+ /* LEN = TH */
+ pcbit_writew(dev, frame->hdr_len + PREHDR_LEN);
+
+ /* XX */
+ pcbit_writew(dev, 0);
+
+ /* C + S */
+ pcbit_writeb(dev, GET_MSG_CMD(msg));
+ pcbit_writeb(dev, GET_MSG_SCMD(msg));
+
+ /* NUM */
+ pcbit_writew(dev, frame->refnum);
+
+ count = FRAME_HDR_LEN + PREHDR_LEN;
+ } else {
+ /* Type 1 frame */
+
+ flen = 2 + (frame->skb->len - frame->copied);
+
+ if (flen > free)
+ flen = free;
+
+ /* TT */
+ tt = ((ushort) (flen - 2)) | 0x8000U; /* Type 1 */
+ pcbit_writew(dev, tt);
+
+ count = 2;
+ }
+
+ if (frame->skb) {
+ cp_len = frame->skb->len - frame->copied;
+ if (cp_len > flen - count)
+ cp_len = flen - count;
+
+ memcpy_topcbit(dev, frame->skb->data + frame->copied,
+ cp_len);
+ frame->copied += cp_len;
+ }
+ /* bookkeeping */
+ dev->free -= flen;
+ pcbit_tx_update(dev, flen);
+
+ spin_lock_irqsave(&dev->lock, flags);
+
+ if (frame->skb == NULL || frame->copied == frame->skb->len) {
+
+ dev->write_queue = frame->next;
+
+ if (frame->skb != NULL) {
+ /* free frame */
+ dev_kfree_skb(frame->skb);
+ }
+ kfree(frame);
+ }
+ dev->w_busy = 0;
+ spin_unlock_irqrestore(&dev->lock, flags);
+ } else {
+ spin_unlock_irqrestore(&dev->lock, flags);
+#ifdef DEBUG
+ printk(KERN_DEBUG "unacked %d free %d write_queue %s\n",
+ unacked, dev->free, dev->write_queue ? "not empty" :
+ "empty");
+#endif
+ }
+}
+
+
+/*
+ * deliver a queued frame to the upper layer
+ */
+
+void
+pcbit_deliver(struct work_struct *work)
+{
+ struct frame_buf *frame;
+ unsigned long flags, msg;
+ struct pcbit_dev *dev =
+ container_of(work, struct pcbit_dev, qdelivery);
+
+ spin_lock_irqsave(&dev->lock, flags);
+
+ while ((frame = dev->read_queue)) {
+ dev->read_queue = frame->next;
+ spin_unlock_irqrestore(&dev->lock, flags);
+
+ msg = 0;
+ SET_MSG_CPU(msg, 0);
+ SET_MSG_PROC(msg, 0);
+ SET_MSG_CMD(msg, frame->skb->data[2]);
+ SET_MSG_SCMD(msg, frame->skb->data[3]);
+
+ frame->refnum = *((ushort *)frame->skb->data + 4);
+ frame->msg = *((ulong *)&msg);
+
+ skb_pull(frame->skb, 6);
+
+ pcbit_l3_receive(dev, frame->msg, frame->skb, frame->hdr_len,
+ frame->refnum);
+
+ kfree(frame);
+
+ spin_lock_irqsave(&dev->lock, flags);
+ }
+
+ spin_unlock_irqrestore(&dev->lock, flags);
+}
+
+/*
+ * Reads BANK 2 & Reassembles
+ */
+
+static void
+pcbit_receive(struct pcbit_dev *dev)
+{
+ unsigned short tt;
+ u_char cpu,
+ proc;
+ struct frame_buf *frame = NULL;
+ unsigned long flags;
+ u_char type1;
+
+ if (dev->l2_state != L2_RUNNING && dev->l2_state != L2_LOADING)
+ return;
+
+ tt = pcbit_readw(dev);
+
+ if ((tt & 0x7fffU) > 511) {
+ printk(KERN_INFO "pcbit: invalid frame length -> TT=%04x\n",
+ tt);
+ pcbit_l2_error(dev);
+ return;
+ }
+ if (!(tt & 0x8000U)) { /* Type 0 */
+ type1 = 0;
+
+ if (dev->read_frame) {
+ printk(KERN_DEBUG "pcbit_receive: Type 0 frame and read_frame != NULL\n");
+ /* discard previous queued frame */
+ kfree_skb(dev->read_frame->skb);
+ kfree(dev->read_frame);
+ dev->read_frame = NULL;
+ }
+ frame = kzalloc(sizeof(struct frame_buf), GFP_ATOMIC);
+
+ if (frame == NULL) {
+ printk(KERN_WARNING "kmalloc failed\n");
+ return;
+ }
+
+ cpu = pcbit_readb(dev);
+ proc = pcbit_readb(dev);
+
+
+ if (cpu != 0x06 && cpu != 0x02) {
+ printk(KERN_DEBUG "pcbit: invalid cpu value\n");
+ kfree(frame);
+ pcbit_l2_error(dev);
+ return;
+ }
+ /*
+ * we discard cpu & proc on receiving
+ * but we read it to update the pointer
+ */
+
+ frame->hdr_len = pcbit_readw(dev);
+ frame->dt_len = pcbit_readw(dev);
+
+ /*
+ * 0 sized packet
+ * I don't know if they are an error or not...
+ * But they are very frequent
+ * Not documented
+ */
+
+ if (frame->hdr_len == 0) {
+ kfree(frame);
+#ifdef DEBUG
+ printk(KERN_DEBUG "0 sized frame\n");
+#endif
+ pcbit_firmware_bug(dev);
+ return;
+ }
+ /* sanity check the length values */
+ if (frame->hdr_len > 1024 || frame->dt_len > 2048) {
+#ifdef DEBUG
+ printk(KERN_DEBUG "length problem: ");
+ printk(KERN_DEBUG "TH=%04x TD=%04x\n",
+ frame->hdr_len,
+ frame->dt_len);
+#endif
+ pcbit_l2_error(dev);
+ kfree(frame);
+ return;
+ }
+ /* minimum frame read */
+
+ frame->skb = dev_alloc_skb(frame->hdr_len + frame->dt_len +
+ ((frame->hdr_len + 15) & ~15));
+
+ if (!frame->skb) {
+ printk(KERN_DEBUG "pcbit_receive: out of memory\n");
+ kfree(frame);
+ return;
+ }
+ /* 16 byte alignment for IP */
+ if (frame->dt_len)
+ skb_reserve(frame->skb, (frame->hdr_len + 15) & ~15);
+
+ } else {
+ /* Type 1 */
+ type1 = 1;
+ tt &= 0x7fffU;
+
+ if (!(frame = dev->read_frame)) {
+ printk("Type 1 frame and no frame queued\n");
+ /* usually after an error: toss frame */
+ dev->readptr += tt;
+ if (dev->readptr > dev->sh_mem + BANK2 + BANKLEN)
+ dev->readptr -= BANKLEN;
+ return;
+
+ }
+ }
+
+ memcpy_frompcbit(dev, skb_put(frame->skb, tt), tt);
+
+ frame->copied += tt;
+ spin_lock_irqsave(&dev->lock, flags);
+ if (frame->copied == frame->hdr_len + frame->dt_len) {
+
+ if (type1) {
+ dev->read_frame = NULL;
+ }
+ if (dev->read_queue) {
+ struct frame_buf *ptr;
+ for (ptr = dev->read_queue; ptr->next; ptr = ptr->next);
+ ptr->next = frame;
+ } else
+ dev->read_queue = frame;
+
+ } else {
+ dev->read_frame = frame;
+ }
+ spin_unlock_irqrestore(&dev->lock, flags);
+}
+
+/*
+ * The board sends 0 sized frames
+ * They are TDATA_CONFs that get messed up somehow
+ * gotta send a fake acknowledgment to the upper layer somehow
+ */
+
+static __inline__ void
+pcbit_fake_conf(struct pcbit_dev *dev, struct pcbit_chan *chan)
+{
+ isdn_ctrl ictl;
+
+ if (chan->queued) {
+ chan->queued--;
+
+ ictl.driver = dev->id;
+ ictl.command = ISDN_STAT_BSENT;
+ ictl.arg = chan->id;
+ dev->dev_if->statcallb(&ictl);
+ }
+}
+
+static void
+pcbit_firmware_bug(struct pcbit_dev *dev)
+{
+ struct pcbit_chan *chan;
+
+ chan = dev->b1;
+
+ if (chan->fsm_state == ST_ACTIVE) {
+ pcbit_fake_conf(dev, chan);
+ }
+ chan = dev->b2;
+
+ if (chan->fsm_state == ST_ACTIVE) {
+ pcbit_fake_conf(dev, chan);
+ }
+}
+
+irqreturn_t
+pcbit_irq_handler(int interrupt, void *devptr)
+{
+ struct pcbit_dev *dev;
+ u_char info,
+ ack_seq,
+ read_seq;
+
+ dev = (struct pcbit_dev *) devptr;
+
+ if (!dev) {
+ printk(KERN_WARNING "pcbit_irq_handler: wrong device\n");
+ return IRQ_NONE;
+ }
+ if (dev->interrupt) {
+ printk(KERN_DEBUG "pcbit: reentering interrupt handler\n");
+ return IRQ_HANDLED;
+ }
+ dev->interrupt = 1;
+
+ info = readb(dev->sh_mem + BANK3);
+
+ if (dev->l2_state == L2_STARTING || dev->l2_state == L2_ERROR) {
+ pcbit_l2_active_conf(dev, info);
+ dev->interrupt = 0;
+ return IRQ_HANDLED;
+ }
+ if (info & 0x40U) { /* E bit set */
+#ifdef DEBUG
+ printk(KERN_DEBUG "pcbit_irq_handler: E bit on\n");
+#endif
+ pcbit_l2_error(dev);
+ dev->interrupt = 0;
+ return IRQ_HANDLED;
+ }
+ if (dev->l2_state != L2_RUNNING && dev->l2_state != L2_LOADING) {
+ dev->interrupt = 0;
+ return IRQ_HANDLED;
+ }
+ ack_seq = (info >> 3) & 0x07U;
+ read_seq = (info & 0x07U);
+
+ dev->interrupt = 0;
+
+ if (read_seq != dev->rcv_seq) {
+ while (read_seq != dev->rcv_seq) {
+ pcbit_receive(dev);
+ dev->rcv_seq = (dev->rcv_seq + 1) % 8;
+ }
+ pcbit_sched_delivery(dev);
+ }
+ if (ack_seq != dev->unack_seq) {
+ pcbit_recv_ack(dev, ack_seq);
+ }
+ info = dev->rcv_seq << 3;
+ info |= dev->send_seq;
+
+ writeb(info, dev->sh_mem + BANK4);
+ return IRQ_HANDLED;
+}
+
+
+static void
+pcbit_l2_active_conf(struct pcbit_dev *dev, u_char info)
+{
+ u_char state;
+
+ state = dev->l2_state;
+
+#ifdef DEBUG
+ printk(KERN_DEBUG "layer2_active_confirm\n");
+#endif
+
+
+ if (info & 0x80U) {
+ dev->rcv_seq = info & 0x07U;
+ dev->l2_state = L2_RUNNING;
+ } else
+ dev->l2_state = L2_DOWN;
+
+ if (state == L2_STARTING)
+ wake_up_interruptible(&dev->set_running_wq);
+
+ if (state == L2_ERROR && dev->l2_state == L2_RUNNING) {
+ pcbit_transmit(dev);
+ }
+}
+
+static void
+pcbit_l2_err_recover(unsigned long data)
+{
+
+ struct pcbit_dev *dev;
+ struct frame_buf *frame;
+
+ dev = (struct pcbit_dev *) data;
+
+ del_timer(&dev->error_recover_timer);
+ if (dev->w_busy || dev->r_busy) {
+ init_timer(&dev->error_recover_timer);
+ dev->error_recover_timer.expires = jiffies + ERRTIME;
+ add_timer(&dev->error_recover_timer);
+ return;
+ }
+ dev->w_busy = dev->r_busy = 1;
+
+ if (dev->read_frame) {
+ kfree_skb(dev->read_frame->skb);
+ kfree(dev->read_frame);
+ dev->read_frame = NULL;
+ }
+ if (dev->write_queue) {
+ frame = dev->write_queue;
+#ifdef FREE_ON_ERROR
+ dev->write_queue = dev->write_queue->next;
+
+ if (frame->skb) {
+ dev_kfree_skb(frame->skb);
+ }
+ kfree(frame);
+#else
+ frame->copied = 0;
+#endif
+ }
+ dev->rcv_seq = dev->send_seq = dev->unack_seq = 0;
+ dev->free = 511;
+ dev->l2_state = L2_ERROR;
+
+ /* this is an hack... */
+ pcbit_firmware_bug(dev);
+
+ dev->writeptr = dev->sh_mem;
+ dev->readptr = dev->sh_mem + BANK2;
+
+ writeb((0x80U | ((dev->rcv_seq & 0x07) << 3) | (dev->send_seq & 0x07)),
+ dev->sh_mem + BANK4);
+ dev->w_busy = dev->r_busy = 0;
+
+}
+
+static void
+pcbit_l2_error(struct pcbit_dev *dev)
+{
+ if (dev->l2_state == L2_RUNNING) {
+
+ printk(KERN_INFO "pcbit: layer 2 error\n");
+
+#ifdef DEBUG
+ log_state(dev);
+#endif
+
+ dev->l2_state = L2_DOWN;
+
+ init_timer(&dev->error_recover_timer);
+ dev->error_recover_timer.function = &pcbit_l2_err_recover;
+ dev->error_recover_timer.data = (ulong) dev;
+ dev->error_recover_timer.expires = jiffies + ERRTIME;
+ add_timer(&dev->error_recover_timer);
+ }
+}
+
+/*
+ * Description:
+ * if board acks frames
+ * update dev->free
+ * call pcbit_transmit to write possible queued frames
+ */
+
+static void
+pcbit_recv_ack(struct pcbit_dev *dev, unsigned char ack)
+{
+ int i,
+ count;
+ int unacked;
+
+ unacked = (dev->send_seq + (8 - dev->unack_seq)) & 0x07;
+
+ /* dev->unack_seq < ack <= dev->send_seq; */
+
+ if (unacked) {
+
+ if (dev->send_seq > dev->unack_seq) {
+ if (ack <= dev->unack_seq || ack > dev->send_seq) {
+ printk(KERN_DEBUG
+ "layer 2 ack unacceptable - dev %d",
+ dev->id);
+
+ pcbit_l2_error(dev);
+ } else if (ack > dev->send_seq && ack <= dev->unack_seq) {
+ printk(KERN_DEBUG
+ "layer 2 ack unacceptable - dev %d",
+ dev->id);
+ pcbit_l2_error(dev);
+ }
+ }
+ /* ack is acceptable */
+
+
+ i = dev->unack_seq;
+
+ do {
+ dev->unack_seq = i = (i + 1) % 8;
+ dev->free += dev->fsize[i];
+ } while (i != ack);
+
+ count = 0;
+ while (count < 7 && dev->write_queue) {
+ u8 lsend_seq = dev->send_seq;
+
+ pcbit_transmit(dev);
+
+ if (dev->send_seq == lsend_seq)
+ break;
+ count++;
+ }
+ } else
+ printk(KERN_DEBUG "recv_ack: unacked = 0\n");
+}
diff --git a/drivers/staging/i4l/pcbit/layer2.h b/drivers/staging/i4l/pcbit/layer2.h
new file mode 100644
index 000000000000..be1327bc162a
--- /dev/null
+++ b/drivers/staging/i4l/pcbit/layer2.h
@@ -0,0 +1,281 @@
+/*
+ * PCBIT-D low-layer interface definitions
+ *
+ * Copyright (C) 1996 Universidade de Lisboa
+ *
+ * Written by Pedro Roque Marques (roque@di.fc.ul.pt)
+ *
+ * This software may be used and distributed according to the terms of
+ * the GNU General Public License, incorporated herein by reference.
+ */
+
+/*
+ * 19991203 - Fernando Carvalho - takion@superbofh.org
+ * Hacked to compile with egcs and run with current version of isdn modules
+ */
+
+#ifndef LAYER2_H
+#define LAYER2_H
+
+#include <linux/interrupt.h>
+
+#include <asm/byteorder.h>
+
+#define BANK1 0x0000U /* PC -> Board */
+#define BANK2 0x01ffU /* Board -> PC */
+#define BANK3 0x03feU /* Att Board */
+#define BANK4 0x03ffU /* Att PC */
+
+#define BANKLEN 0x01FFU
+
+#define LOAD_ZONE_START 0x03f8U
+#define LOAD_ZONE_END 0x03fdU
+
+#define LOAD_RETRY 18000000
+
+
+
+/* TAM - XX - C - S - NUM */
+#define PREHDR_LEN 8
+/* TT - M - I - TH - TD */
+#define FRAME_HDR_LEN 8
+
+#define MSG_CONN_REQ 0x08000100
+#define MSG_CONN_CONF 0x00000101
+#define MSG_CONN_IND 0x00000102
+#define MSG_CONN_RESP 0x08000103
+
+#define MSG_CONN_ACTV_REQ 0x08000300
+#define MSG_CONN_ACTV_CONF 0x00000301
+#define MSG_CONN_ACTV_IND 0x00000302
+#define MSG_CONN_ACTV_RESP 0x08000303
+
+#define MSG_DISC_REQ 0x08000400
+#define MSG_DISC_CONF 0x00000401
+#define MSG_DISC_IND 0x00000402
+#define MSG_DISC_RESP 0x08000403
+
+#define MSG_TDATA_REQ 0x0908E200
+#define MSG_TDATA_CONF 0x0000E201
+#define MSG_TDATA_IND 0x0000E202
+#define MSG_TDATA_RESP 0x0908E203
+
+#define MSG_SELP_REQ 0x09004000
+#define MSG_SELP_CONF 0x00004001
+
+#define MSG_ACT_TRANSP_REQ 0x0908E000
+#define MSG_ACT_TRANSP_CONF 0x0000E001
+
+#define MSG_STPROT_REQ 0x09004100
+#define MSG_STPROT_CONF 0x00004101
+
+#define MSG_PING188_REQ 0x09030500
+#define MSG_PING188_CONF 0x000005bc
+
+#define MSG_WATCH188 0x09030400
+
+#define MSG_API_ON 0x08020102
+#define MSG_POOL_PCBIT 0x08020400
+#define MSG_POOL_PCBIT_CONF 0x00000401
+
+#define MSG_INFO_IND 0x00002602
+#define MSG_INFO_RESP 0x08002603
+
+#define MSG_DEBUG_188 0x0000ff00
+
+/*
+
+ long 4 3 2 1
+ Intel 1 2 3 4
+*/
+
+#ifdef __LITTLE_ENDIAN
+#define SET_MSG_SCMD(msg, ch) (msg = (msg & 0xffffff00) | (((ch) & 0xff)))
+#define SET_MSG_CMD(msg, ch) (msg = (msg & 0xffff00ff) | (((ch) & 0xff) << 8))
+#define SET_MSG_PROC(msg, ch) (msg = (msg & 0xff00ffff) | (((ch) & 0xff) << 16))
+#define SET_MSG_CPU(msg, ch) (msg = (msg & 0x00ffffff) | (((ch) & 0xff) << 24))
+
+#define GET_MSG_SCMD(msg) ((msg) & 0xFF)
+#define GET_MSG_CMD(msg) ((msg) >> 8 & 0xFF)
+#define GET_MSG_PROC(msg) ((msg) >> 16 & 0xFF)
+#define GET_MSG_CPU(msg) ((msg) >> 24)
+
+#else
+#error "Non-Intel CPU"
+#endif
+
+#define MAX_QUEUED 7
+
+#define SCHED_READ 0x01
+#define SCHED_WRITE 0x02
+
+#define SET_RUN_TIMEOUT 2 * HZ /* 2 seconds */
+
+struct frame_buf {
+ ulong msg;
+ unsigned int refnum;
+ unsigned int dt_len;
+ unsigned int hdr_len;
+ struct sk_buff *skb;
+ unsigned int copied;
+ struct frame_buf *next;
+};
+
+extern int pcbit_l2_write(struct pcbit_dev *dev, ulong msg, ushort refnum,
+ struct sk_buff *skb, unsigned short hdr_len);
+
+extern irqreturn_t pcbit_irq_handler(int interrupt, void *);
+
+extern struct pcbit_dev *dev_pcbit[MAX_PCBIT_CARDS];
+
+#ifdef DEBUG
+static __inline__ void log_state(struct pcbit_dev *dev) {
+ printk(KERN_DEBUG "writeptr = %ld\n",
+ (ulong) (dev->writeptr - dev->sh_mem));
+ printk(KERN_DEBUG "readptr = %ld\n",
+ (ulong) (dev->readptr - (dev->sh_mem + BANK2)));
+ printk(KERN_DEBUG "{rcv_seq=%01x, send_seq=%01x, unack_seq=%01x}\n",
+ dev->rcv_seq, dev->send_seq, dev->unack_seq);
+}
+#endif
+
+static __inline__ struct pcbit_dev *chan2dev(struct pcbit_chan *chan)
+{
+ struct pcbit_dev *dev;
+ int i;
+
+
+ for (i = 0; i < MAX_PCBIT_CARDS; i++)
+ if ((dev = dev_pcbit[i]))
+ if (dev->b1 == chan || dev->b2 == chan)
+ return dev;
+ return NULL;
+
+}
+
+static __inline__ struct pcbit_dev *finddev(int id)
+{
+ struct pcbit_dev *dev;
+ int i;
+
+ for (i = 0; i < MAX_PCBIT_CARDS; i++)
+ if ((dev = dev_pcbit[i]))
+ if (dev->id == id)
+ return dev;
+ return NULL;
+}
+
+
+/*
+ * Support routines for reading and writing in the board
+ */
+
+static __inline__ void pcbit_writeb(struct pcbit_dev *dev, unsigned char dt)
+{
+ writeb(dt, dev->writeptr++);
+ if (dev->writeptr == dev->sh_mem + BANKLEN)
+ dev->writeptr = dev->sh_mem;
+}
+
+static __inline__ void pcbit_writew(struct pcbit_dev *dev, unsigned short dt)
+{
+ int dist;
+
+ dist = BANKLEN - (dev->writeptr - dev->sh_mem);
+ switch (dist) {
+ case 2:
+ writew(dt, dev->writeptr);
+ dev->writeptr = dev->sh_mem;
+ break;
+ case 1:
+ writeb((u_char) (dt & 0x00ffU), dev->writeptr);
+ dev->writeptr = dev->sh_mem;
+ writeb((u_char) (dt >> 8), dev->writeptr++);
+ break;
+ default:
+ writew(dt, dev->writeptr);
+ dev->writeptr += 2;
+ break;
+ };
+}
+
+static __inline__ void memcpy_topcbit(struct pcbit_dev *dev, u_char *data,
+ int len)
+{
+ int diff;
+
+ diff = len - (BANKLEN - (dev->writeptr - dev->sh_mem));
+
+ if (diff > 0)
+ {
+ memcpy_toio(dev->writeptr, data, len - diff);
+ memcpy_toio(dev->sh_mem, data + (len - diff), diff);
+ dev->writeptr = dev->sh_mem + diff;
+ }
+ else
+ {
+ memcpy_toio(dev->writeptr, data, len);
+
+ dev->writeptr += len;
+ if (diff == 0)
+ dev->writeptr = dev->sh_mem;
+ }
+}
+
+static __inline__ unsigned char pcbit_readb(struct pcbit_dev *dev)
+{
+ unsigned char val;
+
+ val = readb(dev->readptr++);
+ if (dev->readptr == dev->sh_mem + BANK2 + BANKLEN)
+ dev->readptr = dev->sh_mem + BANK2;
+
+ return val;
+}
+
+static __inline__ unsigned short pcbit_readw(struct pcbit_dev *dev)
+{
+ int dist;
+ unsigned short val;
+
+ dist = BANKLEN - (dev->readptr - (dev->sh_mem + BANK2));
+ switch (dist) {
+ case 2:
+ val = readw(dev->readptr);
+ dev->readptr = dev->sh_mem + BANK2;
+ break;
+ case 1:
+ val = readb(dev->readptr);
+ dev->readptr = dev->sh_mem + BANK2;
+ val = (readb(dev->readptr++) << 8) | val;
+ break;
+ default:
+ val = readw(dev->readptr);
+ dev->readptr += 2;
+ break;
+ };
+ return val;
+}
+
+static __inline__ void memcpy_frompcbit(struct pcbit_dev *dev, u_char *data, int len)
+{
+ int diff;
+
+ diff = len - (BANKLEN - (dev->readptr - (dev->sh_mem + BANK2)));
+ if (diff > 0)
+ {
+ memcpy_fromio(data, dev->readptr, len - diff);
+ memcpy_fromio(data + (len - diff), dev->sh_mem + BANK2 , diff);
+ dev->readptr = dev->sh_mem + BANK2 + diff;
+ }
+ else
+ {
+ memcpy_fromio(data, dev->readptr, len);
+ dev->readptr += len;
+ if (diff == 0)
+ dev->readptr = dev->sh_mem + BANK2;
+ }
+}
+
+
+#endif
diff --git a/drivers/staging/i4l/pcbit/module.c b/drivers/staging/i4l/pcbit/module.c
new file mode 100644
index 000000000000..0a59bd0b8210
--- /dev/null
+++ b/drivers/staging/i4l/pcbit/module.c
@@ -0,0 +1,125 @@
+/*
+ * PCBIT-D module support
+ *
+ * Copyright (C) 1996 Universidade de Lisboa
+ *
+ * Written by Pedro Roque Marques (roque@di.fc.ul.pt)
+ *
+ * This software may be used and distributed according to the terms of
+ * the GNU General Public License, incorporated herein by reference.
+ */
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/string.h>
+#include <linux/kernel.h>
+#include <linux/skbuff.h>
+
+#include <linux/isdnif.h>
+#include "pcbit.h"
+
+MODULE_DESCRIPTION("ISDN4Linux: Driver for PCBIT-T card");
+MODULE_AUTHOR("Pedro Roque Marques");
+MODULE_LICENSE("GPL");
+
+static int mem[MAX_PCBIT_CARDS];
+static int irq[MAX_PCBIT_CARDS];
+
+module_param_array(mem, int, NULL, 0);
+module_param_array(irq, int, NULL, 0);
+
+static int num_boards;
+struct pcbit_dev *dev_pcbit[MAX_PCBIT_CARDS];
+
+static int __init pcbit_init(void)
+{
+ int board;
+
+ num_boards = 0;
+
+ printk(KERN_NOTICE
+ "PCBIT-D device driver v 0.5-fjpc0 19991204 - "
+ "Copyright (C) 1996 Universidade de Lisboa\n");
+
+ if (mem[0] || irq[0])
+ {
+ for (board = 0; board < MAX_PCBIT_CARDS && mem[board] && irq[board]; board++)
+ {
+ if (!mem[board])
+ mem[board] = 0xD0000;
+ if (!irq[board])
+ irq[board] = 5;
+
+ if (pcbit_init_dev(board, mem[board], irq[board]) == 0)
+ num_boards++;
+
+ else
+ {
+ printk(KERN_WARNING
+ "pcbit_init failed for dev %d",
+ board + 1);
+ return -EIO;
+ }
+ }
+ }
+
+ /* Hardcoded default settings detection */
+
+ if (!num_boards)
+ {
+ printk(KERN_INFO
+ "Trying to detect board using default settings\n");
+ if (pcbit_init_dev(0, 0xD0000, 5) == 0)
+ num_boards++;
+ else
+ return -EIO;
+ }
+ return 0;
+}
+
+static void __exit pcbit_exit(void)
+{
+#ifdef MODULE
+ int board;
+
+ for (board = 0; board < num_boards; board++)
+ pcbit_terminate(board);
+ printk(KERN_NOTICE
+ "PCBIT-D module unloaded\n");
+#endif
+}
+
+#ifndef MODULE
+#define MAX_PARA (MAX_PCBIT_CARDS * 2)
+static int __init pcbit_setup(char *line)
+{
+ int i, j, argc;
+ char *str;
+ int ints[MAX_PARA + 1];
+
+ str = get_options(line, MAX_PARA, ints);
+ argc = ints[0];
+ i = 0;
+ j = 1;
+
+ while (argc && (i < MAX_PCBIT_CARDS)) {
+
+ if (argc) {
+ mem[i] = ints[j];
+ j++; argc--;
+ }
+
+ if (argc) {
+ irq[i] = ints[j];
+ j++; argc--;
+ }
+
+ i++;
+ }
+ return (1);
+}
+__setup("pcbit=", pcbit_setup);
+#endif
+
+module_init(pcbit_init);
+module_exit(pcbit_exit);
diff --git a/drivers/staging/i4l/pcbit/pcbit.h b/drivers/staging/i4l/pcbit/pcbit.h
new file mode 100644
index 000000000000..0a5a99440a80
--- /dev/null
+++ b/drivers/staging/i4l/pcbit/pcbit.h
@@ -0,0 +1,177 @@
+/*
+ * PCBIT-D device driver definitions
+ *
+ * Copyright (C) 1996 Universidade de Lisboa
+ *
+ * Written by Pedro Roque Marques (roque@di.fc.ul.pt)
+ *
+ * This software may be used and distributed according to the terms of
+ * the GNU General Public License, incorporated herein by reference.
+ */
+
+#ifndef PCBIT_H
+#define PCBIT_H
+
+#include <linux/workqueue.h>
+
+#define MAX_PCBIT_CARDS 4
+
+
+#define BLOCK_TIMER
+
+#ifdef __KERNEL__
+
+struct pcbit_chan {
+ unsigned short id;
+ unsigned short callref; /* Call Reference */
+ unsigned char proto; /* layer2protocol */
+ unsigned char queued; /* unacked data messages */
+ unsigned char layer2link; /* used in TData */
+ unsigned char snum; /* used in TData */
+ unsigned short s_refnum;
+ unsigned short r_refnum;
+ unsigned short fsm_state;
+ struct timer_list fsm_timer;
+#ifdef BLOCK_TIMER
+ struct timer_list block_timer;
+#endif
+};
+
+struct msn_entry {
+ char *msn;
+ struct msn_entry *next;
+};
+
+struct pcbit_dev {
+ /* board */
+
+ volatile unsigned char __iomem *sh_mem; /* RDP address */
+ unsigned long ph_mem;
+ unsigned int irq;
+ unsigned int id;
+ unsigned int interrupt; /* set during interrupt
+ processing */
+ spinlock_t lock;
+ /* isdn4linux */
+
+ struct msn_entry *msn_list; /* ISDN address list */
+
+ isdn_if *dev_if;
+
+ ushort ll_hdrlen;
+ ushort hl_hdrlen;
+
+ /* link layer */
+ unsigned char l2_state;
+
+ struct frame_buf *read_queue;
+ struct frame_buf *read_frame;
+ struct frame_buf *write_queue;
+
+ /* Protocol start */
+ wait_queue_head_t set_running_wq;
+ struct timer_list set_running_timer;
+
+ struct timer_list error_recover_timer;
+
+ struct work_struct qdelivery;
+
+ u_char w_busy;
+ u_char r_busy;
+
+ volatile unsigned char __iomem *readptr;
+ volatile unsigned char __iomem *writeptr;
+
+ ushort loadptr;
+
+ unsigned short fsize[8]; /* sent layer2 frames size */
+
+ unsigned char send_seq;
+ unsigned char rcv_seq;
+ unsigned char unack_seq;
+
+ unsigned short free;
+
+ /* channels */
+
+ struct pcbit_chan *b1;
+ struct pcbit_chan *b2;
+};
+
+#define STATS_TIMER (10 * HZ)
+#define ERRTIME (HZ / 10)
+
+/* MRU */
+#define MAXBUFSIZE 1534
+#define MRU MAXBUFSIZE
+
+#define STATBUF_LEN 2048
+/*
+ *
+ */
+
+#endif /* __KERNEL__ */
+
+/* isdn_ctrl only allows a long sized argument */
+
+struct pcbit_ioctl {
+ union {
+ struct byte_op {
+ ushort addr;
+ ushort value;
+ } rdp_byte;
+ unsigned long l2_status;
+ } info;
+};
+
+
+
+#define PCBIT_IOCTL_GETSTAT 0x01 /* layer2 status */
+#define PCBIT_IOCTL_LWMODE 0x02 /* linear write mode */
+#define PCBIT_IOCTL_STRLOAD 0x03 /* start load mode */
+#define PCBIT_IOCTL_ENDLOAD 0x04 /* end load mode */
+#define PCBIT_IOCTL_SETBYTE 0x05 /* set byte */
+#define PCBIT_IOCTL_GETBYTE 0x06 /* get byte */
+#define PCBIT_IOCTL_RUNNING 0x07 /* set protocol running */
+#define PCBIT_IOCTL_WATCH188 0x08 /* set watch 188 */
+#define PCBIT_IOCTL_PING188 0x09 /* ping 188 */
+#define PCBIT_IOCTL_FWMODE 0x0A /* firmware write mode */
+#define PCBIT_IOCTL_STOP 0x0B /* stop protocol */
+#define PCBIT_IOCTL_APION 0x0C /* issue API_ON */
+
+#ifndef __KERNEL__
+
+#define PCBIT_GETSTAT (PCBIT_IOCTL_GETSTAT + IIOCDRVCTL)
+#define PCBIT_LWMODE (PCBIT_IOCTL_LWMODE + IIOCDRVCTL)
+#define PCBIT_STRLOAD (PCBIT_IOCTL_STRLOAD + IIOCDRVCTL)
+#define PCBIT_ENDLOAD (PCBIT_IOCTL_ENDLOAD + IIOCDRVCTL)
+#define PCBIT_SETBYTE (PCBIT_IOCTL_SETBYTE + IIOCDRVCTL)
+#define PCBIT_GETBYTE (PCBIT_IOCTL_GETBYTE + IIOCDRVCTL)
+#define PCBIT_RUNNING (PCBIT_IOCTL_RUNNING + IIOCDRVCTL)
+#define PCBIT_WATCH188 (PCBIT_IOCTL_WATCH188 + IIOCDRVCTL)
+#define PCBIT_PING188 (PCBIT_IOCTL_PING188 + IIOCDRVCTL)
+#define PCBIT_FWMODE (PCBIT_IOCTL_FWMODE + IIOCDRVCTL)
+#define PCBIT_STOP (PCBIT_IOCTL_STOP + IIOCDRVCTL)
+#define PCBIT_APION (PCBIT_IOCTL_APION + IIOCDRVCTL)
+
+#define MAXSUPERLINE 3000
+
+#endif
+
+#define L2_DOWN 0
+#define L2_LOADING 1
+#define L2_LWMODE 2
+#define L2_FWMODE 3
+#define L2_STARTING 4
+#define L2_RUNNING 5
+#define L2_ERROR 6
+
+void pcbit_deliver(struct work_struct *work);
+int pcbit_init_dev(int board, int mem_base, int irq);
+void pcbit_terminate(int board);
+void pcbit_l3_receive(struct pcbit_dev *dev, ulong msg, struct sk_buff *skb,
+ ushort hdr_len, ushort refnum);
+void pcbit_state_change(struct pcbit_dev *dev, struct pcbit_chan *chan,
+ unsigned short i, unsigned short ev, unsigned short f);
+
+#endif