diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-06 01:02:30 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-06 01:02:30 +0000 |
commit | 76cb841cb886eef6b3bee341a2266c76578724ad (patch) | |
tree | f5892e5ba6cc11949952a6ce4ecbe6d516d6ce58 /drivers/isdn/hisax | |
parent | Initial commit. (diff) | |
download | linux-76cb841cb886eef6b3bee341a2266c76578724ad.tar.xz linux-76cb841cb886eef6b3bee341a2266c76578724ad.zip |
Adding upstream version 4.19.249.upstream/4.19.249
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'drivers/isdn/hisax')
100 files changed, 55478 insertions, 0 deletions
diff --git a/drivers/isdn/hisax/Kconfig b/drivers/isdn/hisax/Kconfig new file mode 100644 index 000000000..38cfc8baa --- /dev/null +++ b/drivers/isdn/hisax/Kconfig @@ -0,0 +1,422 @@ + +menu "Passive cards" + +config ISDN_DRV_HISAX + tristate "HiSax SiemensChipSet driver support" + select CRC_CCITT + ---help--- + This is a driver supporting the Siemens chipset on various + ISDN-cards (like AVM A1, Elsa ISDN cards, Teles S0-16.0, Teles + S0-16.3, Teles S0-8, Teles/Creatix PnP, ITK micro ix1 and many + compatibles). + + HiSax is just the name of this driver, not the name of any hardware. + + If you have a card with such a chipset, you should say Y here and + also to the configuration option of the driver for your particular + card, below. + +if ISDN_DRV_HISAX + +comment "D-channel protocol features" + +config HISAX_EURO + bool "HiSax Support for EURO/DSS1" + help + Say Y or N according to the D-channel protocol which your local + telephone service company provides. + + The call control protocol E-DSS1 is used in most European countries. + If unsure, say Y. + +config DE_AOC + bool "Support for german chargeinfo" + depends on HISAX_EURO + help + If you want that the HiSax hardware driver sends messages to the + upper level of the isdn code on each AOCD (Advice Of Charge, During + the call -- transmission of the fee information during a call) and + on each AOCE (Advice Of Charge, at the End of the call -- + transmission of fee information at the end of the call), say Y here. + This works only in Germany. + +config HISAX_NO_SENDCOMPLETE + bool "Disable sending complete" + depends on HISAX_EURO + help + If you have trouble with some ugly exchanges or you live in + Australia select this option. + +config HISAX_NO_LLC + bool "Disable sending low layer compatibility" + depends on HISAX_EURO + help + If you have trouble with some ugly exchanges try to select this + option. + +config HISAX_NO_KEYPAD + bool "Disable keypad protocol option" + depends on HISAX_EURO + help + If you like to send special dial strings including * or # without + using the keypad protocol, select this option. + +config HISAX_1TR6 + bool "HiSax Support for german 1TR6" + help + Say Y or N according to the D-channel protocol which your local + telephone service company provides. + + 1TR6 is an old call control protocol which was used in Germany + before E-DSS1 was established. Nowadays, all new lines in Germany + use E-DSS1. + +config HISAX_NI1 + bool "HiSax Support for US NI1" + help + Enable this if you like to use ISDN in US on a NI1 basic rate + interface. + +config HISAX_MAX_CARDS + int "Maximum number of cards supported by HiSax" + default "8" + help + This option allows you to specify the maximum number of cards which + the HiSax driver will be able to handle. + +comment "HiSax supported cards" + +config HISAX_16_0 + bool "Teles 16.0/8.0" + depends on ISA + help + This enables HiSax support for the Teles ISDN-cards S0-16.0, S0-8 + and many compatibles. + + See <file:Documentation/isdn/README.HiSax> on how to configure it + using the different cards, a different D-channel protocol, or + non-standard IRQ/port/shmem settings. + +config HISAX_16_3 + bool "Teles 16.3 or PNP or PCMCIA" + help + This enables HiSax support for the Teles ISDN-cards S0-16.3 the + Teles/Creatix PnP and the Teles PCMCIA. + + See <file:Documentation/isdn/README.HiSax> on how to configure it + using the different cards, a different D-channel protocol, or + non-standard IRQ/port settings. + +config HISAX_TELESPCI + bool "Teles PCI" + depends on PCI && (BROKEN || !(SPARC || PPC || PARISC || M68K || (MIPS && !CPU_LITTLE_ENDIAN) || (XTENSA && !CPU_LITTLE_ENDIAN))) + help + This enables HiSax support for the Teles PCI. + See <file:Documentation/isdn/README.HiSax> on how to configure it. + +config HISAX_S0BOX + bool "Teles S0Box" + help + This enables HiSax support for the Teles/Creatix parallel port + S0BOX. See <file:Documentation/isdn/README.HiSax> on how to + configure it. + +config HISAX_AVM_A1 + bool "AVM A1 (Fritz)" + depends on ISA + help + This enables HiSax support for the AVM A1 (aka "Fritz"). + + See <file:Documentation/isdn/README.HiSax> on how to configure it + using the different cards, a different D-channel protocol, or + non-standard IRQ/port settings. + +config HISAX_FRITZPCI + bool "AVM PnP/PCI (Fritz!PnP/PCI)" + depends on BROKEN || !PPC64 + help + This enables HiSax support for the AVM "Fritz!PnP" and "Fritz!PCI". + See <file:Documentation/isdn/README.HiSax> on how to configure it. + +config HISAX_AVM_A1_PCMCIA + bool "AVM A1 PCMCIA (Fritz)" + help + This enables HiSax support for the AVM A1 "Fritz!PCMCIA"). + See <file:Documentation/isdn/README.HiSax> on how to configure it. + +config HISAX_ELSA + bool "Elsa cards" + help + This enables HiSax support for the Elsa Mircolink ISA cards, for the + Elsa Quickstep series cards and Elsa PCMCIA. + + See <file:Documentation/isdn/README.HiSax> on how to configure it + using the different cards, a different D-channel protocol, or + non-standard IRQ/port settings. + +config HISAX_IX1MICROR2 + bool "ITK ix1-micro Revision 2" + depends on ISA + help + This enables HiSax support for the ITK ix1-micro Revision 2 card. + + See <file:Documentation/isdn/README.HiSax> on how to configure it + using the different cards, a different D-channel protocol, or + non-standard IRQ/port settings. + +config HISAX_DIEHLDIVA + bool "Eicon.Diehl Diva cards" + help + This enables HiSax support for the Eicon.Diehl Diva none PRO + versions passive ISDN cards. + + See <file:Documentation/isdn/README.HiSax> on how to configure it + using the different cards, a different D-channel protocol, or + non-standard IRQ/port settings. + +config HISAX_ASUSCOM + bool "ASUSCOM ISA cards" + depends on ISA + help + This enables HiSax support for the AsusCom and their OEM versions + passive ISDN ISA cards. + + See <file:Documentation/isdn/README.HiSax> on how to configure it + using the different cards, a different D-channel protocol, or + non-standard IRQ/port settings. + +config HISAX_TELEINT + bool "TELEINT cards" + depends on ISA + help + This enables HiSax support for the TELEINT SA1 semiactiv ISDN card. + + See <file:Documentation/isdn/README.HiSax> on how to configure it + using the different cards, a different D-channel protocol, or + non-standard IRQ/port settings. + +config HISAX_HFCS + bool "HFC-S based cards" + depends on ISA + help + This enables HiSax support for the HFC-S 2BDS0 based cards, like + teles 16.3c. + + See <file:Documentation/isdn/README.HiSax> on how to configure it + using the different cards, a different D-channel protocol, or + non-standard IRQ/port settings. + +config HISAX_SEDLBAUER + bool "Sedlbauer cards" + help + This enables HiSax support for the Sedlbauer passive ISDN cards. + + See <file:Documentation/isdn/README.HiSax> on how to configure it + using the different cards, a different D-channel protocol, or + non-standard IRQ/port settings. + +config HISAX_SPORTSTER + bool "USR Sportster internal TA" + depends on ISA + help + This enables HiSax support for the USR Sportster internal TA card. + + See <file:Documentation/isdn/README.HiSax> on how to configure it + using a different D-channel protocol, or non-standard IRQ/port + settings. + +config HISAX_MIC + bool "MIC card" + depends on ISA + help + This enables HiSax support for the ITH MIC card. + + See <file:Documentation/isdn/README.HiSax> on how to configure it + using a different D-channel protocol, or non-standard IRQ/port + settings. + +config HISAX_NETJET + bool "NETjet card" + depends on PCI && (BROKEN || !(PPC || PARISC || M68K || (MIPS && !CPU_LITTLE_ENDIAN) || (XTENSA && !CPU_LITTLE_ENDIAN) || MICROBLAZE)) + depends on VIRT_TO_BUS + help + This enables HiSax support for the NetJet from Traverse + Technologies. + + See <file:Documentation/isdn/README.HiSax> on how to configure it + using a different D-channel protocol, or non-standard IRQ/port + settings. + +config HISAX_NETJET_U + bool "NETspider U card" + depends on PCI && (BROKEN || !(PPC || PARISC || M68K || (MIPS && !CPU_LITTLE_ENDIAN) || (XTENSA && !CPU_LITTLE_ENDIAN) || MICROBLAZE)) + depends on VIRT_TO_BUS + help + This enables HiSax support for the Netspider U interface ISDN card + from Traverse Technologies. + See <file:Documentation/isdn/README.HiSax> on how to configure it + using a different D-channel protocol, or non-standard IRQ/port + settings. + +config HISAX_NICCY + bool "Niccy PnP/PCI card" + help + This enables HiSax support for the Dr. Neuhaus Niccy PnP or PCI. + + See <file:Documentation/isdn/README.HiSax> on how to configure it + using a different D-channel protocol, or non-standard IRQ/port + settings. + +config HISAX_ISURF + bool "Siemens I-Surf card" + depends on ISA + help + This enables HiSax support for the Siemens I-Talk/I-Surf card with + ISAR chip. + See <file:Documentation/isdn/README.HiSax> on how to configure it + using a different D-channel protocol, or non-standard IRQ/port + settings. + +config HISAX_HSTSAPHIR + bool "HST Saphir card" + depends on ISA + help + This enables HiSax support for the HST Saphir card. + + See <file:Documentation/isdn/README.HiSax> on how to configure it + using a different D-channel protocol, or non-standard IRQ/port + settings. + +config HISAX_BKM_A4T + bool "Telekom A4T card" + depends on PCI + help + This enables HiSax support for the Telekom A4T card. + + See <file:Documentation/isdn/README.HiSax> on how to configure it + using a different D-channel protocol, or non-standard IRQ/port + settings. + +config HISAX_SCT_QUADRO + bool "Scitel Quadro card" + depends on PCI + help + This enables HiSax support for the Scitel Quadro card. + + See <file:Documentation/isdn/README.HiSax> on how to configure it + using a different D-channel protocol, or non-standard IRQ/port + settings. + +config HISAX_GAZEL + bool "Gazel cards" + help + This enables HiSax support for the Gazel cards. + + See <file:Documentation/isdn/README.HiSax> on how to configure it + using a different D-channel protocol, or non-standard IRQ/port + settings. + +config HISAX_HFC_PCI + bool "HFC PCI-Bus cards" + depends on PCI && (BROKEN || !(SPARC || PPC || PARISC || M68K || (MIPS && !CPU_LITTLE_ENDIAN) || (XTENSA && !CPU_LITTLE_ENDIAN))) + help + This enables HiSax support for the HFC-S PCI 2BDS0 based cards. + + For more information see under + <file:Documentation/isdn/README.hfc-pci>. + +config HISAX_W6692 + bool "Winbond W6692 based cards" + depends on PCI + help + This enables HiSax support for Winbond W6692 based PCI ISDN cards. + + See <file:Documentation/isdn/README.HiSax> on how to configure it + using a different D-channel protocol, or non-standard IRQ/port + settings. + +config HISAX_HFC_SX + bool "HFC-S+, HFC-SP, HFC-PCMCIA cards" + help + This enables HiSax support for the HFC-S+, HFC-SP and HFC-PCMCIA + cards. This code is not finished yet. + +config HISAX_ENTERNOW_PCI + bool "Formula-n enter:now PCI card" + depends on HISAX_NETJET && PCI && (BROKEN || !(SPARC || PPC || PARISC || M68K || (MIPS && !CPU_LITTLE_ENDIAN) || (XTENSA && !CPU_LITTLE_ENDIAN))) + help + This enables HiSax support for the Formula-n enter:now PCI + ISDN card. + +config HISAX_DEBUG + bool "HiSax debugging" + help + This enables debugging code in the new-style HiSax drivers, i.e. + the ST5481 USB driver currently. + If in doubt, say yes. + +comment "HiSax PCMCIA card service modules" + +config HISAX_SEDLBAUER_CS + tristate "Sedlbauer PCMCIA cards" + depends on PCMCIA && HISAX_SEDLBAUER + help + This enables the PCMCIA client driver for the Sedlbauer Speed Star + and Speed Star II cards. + +config HISAX_ELSA_CS + tristate "ELSA PCMCIA MicroLink cards" + depends on PCMCIA && HISAX_ELSA + help + This enables the PCMCIA client driver for the Elsa PCMCIA MicroLink + card. + +config HISAX_AVM_A1_CS + tristate "AVM A1 PCMCIA cards" + depends on PCMCIA && ISDN_DRV_HISAX + help + This enables the PCMCIA client driver for the AVM A1 / Fritz!Card + PCMCIA cards. + +config HISAX_TELES_CS + tristate "TELES PCMCIA cards" + depends on PCMCIA && HISAX_16_3 + help + This enables the PCMCIA client driver for the Teles PCMCIA cards. + +comment "HiSax sub driver modules" + +config HISAX_ST5481 + tristate "ST5481 USB ISDN modem" + depends on USB + select ISDN_HDLC + select CRC_CCITT + select BITREVERSE + help + This enables the driver for ST5481 based USB ISDN adapters, + e.g. the BeWan Gazel 128 USB + +config HISAX_HFCUSB + tristate "HFC USB based ISDN modems" + depends on USB + help + This enables the driver for HFC USB based ISDN modems. + +config HISAX_HFC4S8S + tristate "HFC-4S/8S based ISDN cards" + help + This enables the driver for HFC-4S/8S based ISDN cards. + +config HISAX_FRITZ_PCIPNP + tristate "AVM Fritz!Card PCI/PCIv2/PnP support" + depends on PCI + help + This enables the driver for the AVM Fritz!Card PCI, + Fritz!Card PCI v2 and Fritz!Card PnP. + (the latter also needs you to select "ISA Plug and Play support" + from the menu "Plug and Play configuration") + +endif + +endmenu + diff --git a/drivers/isdn/hisax/Makefile b/drivers/isdn/hisax/Makefile new file mode 100644 index 000000000..3eca9d23f --- /dev/null +++ b/drivers/isdn/hisax/Makefile @@ -0,0 +1,60 @@ +# SPDX-License-Identifier: GPL-2.0 +# Makefile for the hisax ISDN device driver + +# The target object and module list name. + +# Define maximum number of cards + +ccflags-y := -DHISAX_MAX_CARDS=$(CONFIG_HISAX_MAX_CARDS) + +obj-$(CONFIG_ISDN_DRV_HISAX) += hisax.o +obj-$(CONFIG_HISAX_SEDLBAUER_CS) += sedlbauer_cs.o +obj-$(CONFIG_HISAX_ELSA_CS) += elsa_cs.o +obj-$(CONFIG_HISAX_AVM_A1_CS) += avma1_cs.o +obj-$(CONFIG_HISAX_TELES_CS) += teles_cs.o +obj-$(CONFIG_HISAX_ST5481) += hisax_st5481.o +obj-$(CONFIG_HISAX_HFCUSB) += hfc_usb.o +obj-$(CONFIG_HISAX_HFC4S8S) += hfc4s8s_l1.o +obj-$(CONFIG_HISAX_FRITZ_PCIPNP) += hisax_isac.o hisax_fcpcipnp.o + +# Multipart objects. + +hisax_st5481-y := st5481_init.o st5481_usb.o st5481_d.o \ + st5481_b.o + +hisax-y := config.o isdnl1.o tei.o isdnl2.o isdnl3.o \ + lmgr.o q931.o callc.o fsm.o +hisax-$(CONFIG_HISAX_EURO) += l3dss1.o +hisax-$(CONFIG_HISAX_NI1) += l3ni1.o +hisax-$(CONFIG_HISAX_1TR6) += l3_1tr6.o + +hisax-$(CONFIG_HISAX_16_0) += teles0.o isac.o arcofi.o hscx.o +hisax-$(CONFIG_HISAX_16_3) += teles3.o isac.o arcofi.o hscx.o +hisax-$(CONFIG_HISAX_TELESPCI) += telespci.o isac.o arcofi.o hscx.o +hisax-$(CONFIG_HISAX_S0BOX) += s0box.o isac.o arcofi.o hscx.o +hisax-$(CONFIG_HISAX_AVM_A1) += avm_a1.o isac.o arcofi.o hscx.o +hisax-$(CONFIG_HISAX_AVM_A1_PCMCIA) += avm_a1p.o isac.o arcofi.o hscx.o +hisax-$(CONFIG_HISAX_FRITZPCI) += avm_pci.o isac.o arcofi.o +hisax-$(CONFIG_HISAX_ELSA) += elsa.o isac.o arcofi.o hscx.o +hisax-$(CONFIG_HISAX_IX1MICROR2) += ix1_micro.o isac.o arcofi.o hscx.o +hisax-$(CONFIG_HISAX_DIEHLDIVA) += diva.o isac.o arcofi.o hscx.o ipacx.o +hisax-$(CONFIG_HISAX_ASUSCOM) += asuscom.o isac.o arcofi.o hscx.o +hisax-$(CONFIG_HISAX_TELEINT) += teleint.o isac.o arcofi.o hfc_2bs0.o +hisax-$(CONFIG_HISAX_SEDLBAUER) += sedlbauer.o isac.o arcofi.o hscx.o \ + isar.o +hisax-$(CONFIG_HISAX_SPORTSTER) += sportster.o isac.o arcofi.o hscx.o +hisax-$(CONFIG_HISAX_MIC) += mic.o isac.o arcofi.o hscx.o +hisax-$(CONFIG_HISAX_NETJET) += nj_s.o netjet.o isac.o arcofi.o +hisax-$(CONFIG_HISAX_NETJET_U) += nj_u.o netjet.o icc.o +hisax-$(CONFIG_HISAX_HFCS) += hfcscard.o hfc_2bds0.o +hisax-$(CONFIG_HISAX_HFC_PCI) += hfc_pci.o +hisax-$(CONFIG_HISAX_HFC_SX) += hfc_sx.o +hisax-$(CONFIG_HISAX_NICCY) += niccy.o isac.o arcofi.o hscx.o +hisax-$(CONFIG_HISAX_ISURF) += isurf.o isac.o arcofi.o isar.o +hisax-$(CONFIG_HISAX_HSTSAPHIR) += saphir.o isac.o arcofi.o hscx.o +hisax-$(CONFIG_HISAX_BKM_A4T) += bkm_a4t.o isac.o arcofi.o jade.o +hisax-$(CONFIG_HISAX_SCT_QUADRO) += bkm_a8.o isac.o arcofi.o hscx.o +hisax-$(CONFIG_HISAX_GAZEL) += gazel.o isac.o arcofi.o hscx.o +hisax-$(CONFIG_HISAX_W6692) += w6692.o +hisax-$(CONFIG_HISAX_ENTERNOW_PCI) += enternow_pci.o amd7930_fn.o + diff --git a/drivers/isdn/hisax/amd7930_fn.c b/drivers/isdn/hisax/amd7930_fn.c new file mode 100644 index 000000000..77debda22 --- /dev/null +++ b/drivers/isdn/hisax/amd7930_fn.c @@ -0,0 +1,794 @@ +/* gerdes_amd7930.c,v 0.99 2001/10/02 + * + * gerdes_amd7930.c Amd 79C30A and 79C32A specific routines + * (based on HiSax driver by Karsten Keil) + * + * Author Christoph Ersfeld <info@formula-n.de> + * Formula-n Europe AG (www.formula-n.com) + * previously Gerdes AG + * + * + * This file is (c) under GNU PUBLIC LICENSE + * + * + * Notes: + * Version 0.99 is the first release of this driver and there are + * certainly a few bugs. + * + * Please don't report any malfunction to me without sending + * (compressed) debug-logs. + * It would be nearly impossible to retrace it. + * + * Log D-channel-processing as follows: + * + * 1. Load hisax with card-specific parameters, this example ist for + * Formula-n enter:now ISDN PCI and compatible + * (f.e. Gerdes Power ISDN PCI) + * + * modprobe hisax type=41 protocol=2 id=gerdes + * + * if you chose an other value for id, you need to modify the + * code below, too. + * + * 2. set debug-level + * + * hisaxctrl gerdes 1 0x3ff + * hisaxctrl gerdes 11 0x4f + * cat /dev/isdnctrl >> ~/log & + * + * Please take also a look into /var/log/messages if there is + * anything importand concerning HISAX. + * + * + * Credits: + * Programming the driver for Formula-n enter:now ISDN PCI and + * necessary this driver for the used Amd 7930 D-channel-controller + * was spnsored by Formula-n Europe AG. + * Thanks to Karsten Keil and Petr Novak, who gave me support in + * Hisax-specific questions. + * I want so say special thanks to Carl-Friedrich Braun, who had to + * answer a lot of questions about generally ISDN and about handling + * of the Amd-Chip. + * + */ + + +#include "hisax.h" +#include "isdnl1.h" +#include "isac.h" +#include "amd7930_fn.h" +#include <linux/interrupt.h> +#include <linux/init.h> +#include <linux/gfp.h> + +static void Amd7930_new_ph(struct IsdnCardState *cs); + +static WORD initAMD[] = { + 0x0100, + + 0x00A5, 3, 0x01, 0x40, 0x58, // LPR, LMR1, LMR2 + 0x0086, 1, 0x0B, // DMR1 (D-Buffer TH-Interrupts on) + 0x0087, 1, 0xFF, // DMR2 + 0x0092, 1, 0x03, // EFCR (extended mode d-channel-fifo on) + 0x0090, 4, 0xFE, 0xFF, 0x02, 0x0F, // FRAR4, SRAR4, DMR3, DMR4 (address recognition ) + 0x0084, 2, 0x80, 0x00, // DRLR + 0x00C0, 1, 0x47, // PPCR1 + 0x00C8, 1, 0x01, // PPCR2 + + 0x0102, + 0x0107, + 0x01A1, 1, + 0x0121, 1, + 0x0189, 2, + + 0x0045, 4, 0x61, 0x72, 0x00, 0x00, // MCR1, MCR2, MCR3, MCR4 + 0x0063, 2, 0x08, 0x08, // GX + 0x0064, 2, 0x08, 0x08, // GR + 0x0065, 2, 0x99, 0x00, // GER + 0x0066, 2, 0x7C, 0x8B, // STG + 0x0067, 2, 0x00, 0x00, // FTGR1, FTGR2 + 0x0068, 2, 0x20, 0x20, // ATGR1, ATGR2 + 0x0069, 1, 0x4F, // MMR1 + 0x006A, 1, 0x00, // MMR2 + 0x006C, 1, 0x40, // MMR3 + 0x0021, 1, 0x02, // INIT + 0x00A3, 1, 0x40, // LMR1 + + 0xFFFF +}; + + +static void /* macro wWordAMD */ +WriteWordAmd7930(struct IsdnCardState *cs, BYTE reg, WORD val) +{ + wByteAMD(cs, 0x00, reg); + wByteAMD(cs, 0x01, LOBYTE(val)); + wByteAMD(cs, 0x01, HIBYTE(val)); +} + +static WORD /* macro rWordAMD */ +ReadWordAmd7930(struct IsdnCardState *cs, BYTE reg) +{ + WORD res; + /* direct access register */ + if (reg < 8) { + res = rByteAMD(cs, reg); + res += 256 * rByteAMD(cs, reg); + } + /* indirect access register */ + else { + wByteAMD(cs, 0x00, reg); + res = rByteAMD(cs, 0x01); + res += 256 * rByteAMD(cs, 0x01); + } + return (res); +} + + +static void +Amd7930_ph_command(struct IsdnCardState *cs, u_char command, char *s) +{ + if (cs->debug & L1_DEB_ISAC) + debugl1(cs, "AMD7930: %s: ph_command 0x%02X", s, command); + + cs->dc.amd7930.lmr1 = command; + wByteAMD(cs, 0xA3, command); +} + + + +static BYTE i430States[] = { +// to reset F3 F4 F5 F6 F7 F8 AR from + 0x01, 0x02, 0x00, 0x00, 0x00, 0x07, 0x05, 0x00, // init + 0x01, 0x02, 0x00, 0x00, 0x00, 0x07, 0x05, 0x00, // reset + 0x01, 0x02, 0x00, 0x00, 0x00, 0x09, 0x05, 0x04, // F3 + 0x01, 0x02, 0x00, 0x00, 0x1B, 0x00, 0x00, 0x00, // F4 + 0x01, 0x02, 0x00, 0x00, 0x1B, 0x00, 0x00, 0x00, // F5 + 0x01, 0x03, 0x00, 0x00, 0x00, 0x06, 0x05, 0x00, // F6 + 0x11, 0x13, 0x00, 0x00, 0x1B, 0x00, 0x15, 0x00, // F7 + 0x01, 0x03, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, // F8 + 0x01, 0x03, 0x00, 0x00, 0x00, 0x09, 0x00, 0x0A}; // AR + + +/* Row init - reset F3 F4 F5 F6 F7 F8 AR */ +static BYTE stateHelper[] = { 0x00, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08 }; + + + + +static void +Amd7930_get_state(struct IsdnCardState *cs) { + BYTE lsr = rByteAMD(cs, 0xA1); + cs->dc.amd7930.ph_state = (lsr & 0x7) + 2; + Amd7930_new_ph(cs); +} + + + +static void +Amd7930_new_ph(struct IsdnCardState *cs) +{ + u_char index = stateHelper[cs->dc.amd7930.old_state] * 8 + stateHelper[cs->dc.amd7930.ph_state] - 1; + u_char message = i430States[index]; + + if (cs->debug & L1_DEB_ISAC) + debugl1(cs, "AMD7930: new_ph %d, old_ph %d, message %d, index %d", + cs->dc.amd7930.ph_state, cs->dc.amd7930.old_state, message & 0x0f, index); + + cs->dc.amd7930.old_state = cs->dc.amd7930.ph_state; + + /* abort transmit if nessesary */ + if ((message & 0xf0) && (cs->tx_skb)) { + wByteAMD(cs, 0x21, 0xC2); + wByteAMD(cs, 0x21, 0x02); + } + + switch (message & 0x0f) { + + case (1): + l1_msg(cs, HW_RESET | INDICATION, NULL); + Amd7930_get_state(cs); + break; + case (2): /* init, Card starts in F3 */ + l1_msg(cs, HW_DEACTIVATE | CONFIRM, NULL); + break; + case (3): + l1_msg(cs, HW_DEACTIVATE | INDICATION, NULL); + break; + case (4): + l1_msg(cs, HW_POWERUP | CONFIRM, NULL); + Amd7930_ph_command(cs, 0x50, "HW_ENABLE REQUEST"); + break; + case (5): + l1_msg(cs, HW_RSYNC | INDICATION, NULL); + break; + case (6): + l1_msg(cs, HW_INFO4_P8 | INDICATION, NULL); + break; + case (7): /* init, Card starts in F7 */ + l1_msg(cs, HW_RSYNC | INDICATION, NULL); + l1_msg(cs, HW_INFO4_P8 | INDICATION, NULL); + break; + case (8): + l1_msg(cs, HW_POWERUP | CONFIRM, NULL); + /* fall through */ + case (9): + Amd7930_ph_command(cs, 0x40, "HW_ENABLE REQ cleared if set"); + l1_msg(cs, HW_RSYNC | INDICATION, NULL); + l1_msg(cs, HW_INFO2 | INDICATION, NULL); + l1_msg(cs, HW_INFO4_P8 | INDICATION, NULL); + break; + case (10): + Amd7930_ph_command(cs, 0x40, "T3 expired, HW_ENABLE REQ cleared"); + cs->dc.amd7930.old_state = 3; + break; + case (11): + l1_msg(cs, HW_INFO2 | INDICATION, NULL); + break; + default: + break; + } +} + + + +static void +Amd7930_bh(struct work_struct *work) +{ + struct IsdnCardState *cs = + container_of(work, struct IsdnCardState, tqueue); + struct PStack *stptr; + + if (test_and_clear_bit(D_CLEARBUSY, &cs->event)) { + if (cs->debug) + debugl1(cs, "Amd7930: bh, D-Channel Busy cleared"); + stptr = cs->stlist; + while (stptr != NULL) { + stptr->l1.l1l2(stptr, PH_PAUSE | CONFIRM, NULL); + stptr = stptr->next; + } + } + if (test_and_clear_bit(D_L1STATECHANGE, &cs->event)) { + if (cs->debug & L1_DEB_ISAC) + debugl1(cs, "AMD7930: bh, D_L1STATECHANGE"); + Amd7930_new_ph(cs); + } + + if (test_and_clear_bit(D_RCVBUFREADY, &cs->event)) { + if (cs->debug & L1_DEB_ISAC) + debugl1(cs, "AMD7930: bh, D_RCVBUFREADY"); + DChannel_proc_rcv(cs); + } + + if (test_and_clear_bit(D_XMTBUFREADY, &cs->event)) { + if (cs->debug & L1_DEB_ISAC) + debugl1(cs, "AMD7930: bh, D_XMTBUFREADY"); + DChannel_proc_xmt(cs); + } +} + +static void +Amd7930_empty_Dfifo(struct IsdnCardState *cs, int flag) +{ + + BYTE stat, der; + BYTE *ptr; + struct sk_buff *skb; + + + if ((cs->debug & L1_DEB_ISAC) && !(cs->debug & L1_DEB_ISAC_FIFO)) + debugl1(cs, "Amd7930: empty_Dfifo"); + + + ptr = cs->rcvbuf + cs->rcvidx; + + /* AMD interrupts off */ + AmdIrqOff(cs); + + /* read D-Channel-Fifo*/ + stat = rByteAMD(cs, 0x07); // DSR2 + + /* while Data in Fifo ... */ + while ((stat & 2) && ((ptr-cs->rcvbuf) < MAX_DFRAME_LEN_L1)) { + *ptr = rByteAMD(cs, 0x04); // DCRB + ptr++; + stat = rByteAMD(cs, 0x07); // DSR2 + cs->rcvidx = ptr - cs->rcvbuf; + + /* Paket ready? */ + if (stat & 1) { + + der = rWordAMD(cs, 0x03); + + /* no errors, packet ok */ + if (!der && !flag) { + rWordAMD(cs, 0x89); // clear DRCR + + if ((cs->rcvidx) > 0) { + if (!(skb = alloc_skb(cs->rcvidx, GFP_ATOMIC))) + printk(KERN_WARNING "HiSax: Amd7930: empty_Dfifo, D receive out of memory!\n"); + else { + /* Debugging */ + if (cs->debug & L1_DEB_ISAC_FIFO) { + char *t = cs->dlog; + + t += sprintf(t, "Amd7930: empty_Dfifo cnt: %d |", cs->rcvidx); + QuickHex(t, cs->rcvbuf, cs->rcvidx); + debugl1(cs, "%s", cs->dlog); + } + /* moves received data in sk-buffer */ + skb_put_data(skb, cs->rcvbuf, + cs->rcvidx); + skb_queue_tail(&cs->rq, skb); + } + } + + } + /* throw damaged packets away, reset receive-buffer, indicate RX */ + ptr = cs->rcvbuf; + cs->rcvidx = 0; + schedule_event(cs, D_RCVBUFREADY); + } + } + /* Packet to long, overflow */ + if (cs->rcvidx >= MAX_DFRAME_LEN_L1) { + if (cs->debug & L1_DEB_WARN) + debugl1(cs, "AMD7930: empty_Dfifo L2-Framelength overrun"); + cs->rcvidx = 0; + return; + } + /* AMD interrupts on */ + AmdIrqOn(cs); +} + + +static void +Amd7930_fill_Dfifo(struct IsdnCardState *cs) +{ + + WORD dtcrr, dtcrw, len, count; + BYTE txstat, dmr3; + BYTE *ptr, *deb_ptr; + + if ((cs->debug & L1_DEB_ISAC) && !(cs->debug & L1_DEB_ISAC_FIFO)) + debugl1(cs, "Amd7930: fill_Dfifo"); + + if ((!cs->tx_skb) || (cs->tx_skb->len <= 0)) + return; + + dtcrw = 0; + if (!cs->dc.amd7930.tx_xmtlen) + /* new Frame */ + len = dtcrw = cs->tx_skb->len; + /* continue frame */ + else len = cs->dc.amd7930.tx_xmtlen; + + + /* AMD interrupts off */ + AmdIrqOff(cs); + + deb_ptr = ptr = cs->tx_skb->data; + + /* while free place in tx-fifo available and data in sk-buffer */ + txstat = 0x10; + while ((txstat & 0x10) && (cs->tx_cnt < len)) { + wByteAMD(cs, 0x04, *ptr); + ptr++; + cs->tx_cnt++; + txstat = rByteAMD(cs, 0x07); + } + count = ptr - cs->tx_skb->data; + skb_pull(cs->tx_skb, count); + + + dtcrr = rWordAMD(cs, 0x85); // DTCR + dmr3 = rByteAMD(cs, 0x8E); + + if (cs->debug & L1_DEB_ISAC) { + debugl1(cs, "Amd7930: fill_Dfifo, DMR3: 0x%02X, DTCR read: 0x%04X write: 0x%02X 0x%02X", dmr3, dtcrr, LOBYTE(dtcrw), HIBYTE(dtcrw)); + } + + /* writeing of dtcrw starts transmit */ + if (!cs->dc.amd7930.tx_xmtlen) { + wWordAMD(cs, 0x85, dtcrw); + cs->dc.amd7930.tx_xmtlen = dtcrw; + } + + if (test_and_set_bit(FLG_DBUSY_TIMER, &cs->HW_Flags)) { + debugl1(cs, "Amd7930: fill_Dfifo dbusytimer running"); + del_timer(&cs->dbusytimer); + } + cs->dbusytimer.expires = jiffies + ((DBUSY_TIMER_VALUE * HZ) / 1000); + add_timer(&cs->dbusytimer); + + if (cs->debug & L1_DEB_ISAC_FIFO) { + char *t = cs->dlog; + + t += sprintf(t, "Amd7930: fill_Dfifo cnt: %d |", count); + QuickHex(t, deb_ptr, count); + debugl1(cs, "%s", cs->dlog); + } + /* AMD interrupts on */ + AmdIrqOn(cs); +} + + +void Amd7930_interrupt(struct IsdnCardState *cs, BYTE irflags) +{ + BYTE dsr1, dsr2, lsr; + WORD der; + + while (irflags) + { + + dsr1 = rByteAMD(cs, 0x02); + der = rWordAMD(cs, 0x03); + dsr2 = rByteAMD(cs, 0x07); + lsr = rByteAMD(cs, 0xA1); + + if (cs->debug & L1_DEB_ISAC) + debugl1(cs, "Amd7930: interrupt: flags: 0x%02X, DSR1: 0x%02X, DSR2: 0x%02X, LSR: 0x%02X, DER=0x%04X", irflags, dsr1, dsr2, lsr, der); + + /* D error -> read DER and DSR2 bit 2 */ + if (der || (dsr2 & 4)) { + + if (cs->debug & L1_DEB_WARN) + debugl1(cs, "Amd7930: interrupt: D error DER=0x%04X", der); + + /* RX, TX abort if collision detected */ + if (der & 2) { + wByteAMD(cs, 0x21, 0xC2); + wByteAMD(cs, 0x21, 0x02); + if (test_and_clear_bit(FLG_DBUSY_TIMER, &cs->HW_Flags)) + del_timer(&cs->dbusytimer); + if (test_and_clear_bit(FLG_L1_DBUSY, &cs->HW_Flags)) + schedule_event(cs, D_CLEARBUSY); + /* restart frame */ + if (cs->tx_skb) { + skb_push(cs->tx_skb, cs->tx_cnt); + cs->tx_cnt = 0; + cs->dc.amd7930.tx_xmtlen = 0; + Amd7930_fill_Dfifo(cs); + } else { + printk(KERN_WARNING "HiSax: Amd7930 D-Collision, no skb\n"); + debugl1(cs, "Amd7930: interrupt: D-Collision, no skb"); + } + } + /* remove damaged data from fifo */ + Amd7930_empty_Dfifo(cs, 1); + + if (test_and_clear_bit(FLG_DBUSY_TIMER, &cs->HW_Flags)) + del_timer(&cs->dbusytimer); + if (test_and_clear_bit(FLG_L1_DBUSY, &cs->HW_Flags)) + schedule_event(cs, D_CLEARBUSY); + /* restart TX-Frame */ + if (cs->tx_skb) { + skb_push(cs->tx_skb, cs->tx_cnt); + cs->tx_cnt = 0; + cs->dc.amd7930.tx_xmtlen = 0; + Amd7930_fill_Dfifo(cs); + } + } + + /* D TX FIFO empty -> fill */ + if (irflags & 1) { + if (cs->debug & L1_DEB_ISAC) + debugl1(cs, "Amd7930: interrupt: clear Timer and fill D-TX-FIFO if data"); + + /* AMD interrupts off */ + AmdIrqOff(cs); + + if (test_and_clear_bit(FLG_DBUSY_TIMER, &cs->HW_Flags)) + del_timer(&cs->dbusytimer); + if (test_and_clear_bit(FLG_L1_DBUSY, &cs->HW_Flags)) + schedule_event(cs, D_CLEARBUSY); + if (cs->tx_skb) { + if (cs->tx_skb->len) + Amd7930_fill_Dfifo(cs); + } + /* AMD interrupts on */ + AmdIrqOn(cs); + } + + + /* D RX FIFO full or tiny packet in Fifo -> empty */ + if ((irflags & 2) || (dsr1 & 2)) { + if (cs->debug & L1_DEB_ISAC) + debugl1(cs, "Amd7930: interrupt: empty D-FIFO"); + Amd7930_empty_Dfifo(cs, 0); + } + + + /* D-Frame transmit complete */ + if (dsr1 & 64) { + if (cs->debug & L1_DEB_ISAC) { + debugl1(cs, "Amd7930: interrupt: transmit packet ready"); + } + /* AMD interrupts off */ + AmdIrqOff(cs); + + if (test_and_clear_bit(FLG_DBUSY_TIMER, &cs->HW_Flags)) + del_timer(&cs->dbusytimer); + if (test_and_clear_bit(FLG_L1_DBUSY, &cs->HW_Flags)) + schedule_event(cs, D_CLEARBUSY); + + if (cs->tx_skb) { + if (cs->debug & L1_DEB_ISAC) + debugl1(cs, "Amd7930: interrupt: TX-Packet ready, freeing skb"); + dev_kfree_skb_irq(cs->tx_skb); + cs->tx_cnt = 0; + cs->dc.amd7930.tx_xmtlen = 0; + cs->tx_skb = NULL; + } + if ((cs->tx_skb = skb_dequeue(&cs->sq))) { + if (cs->debug & L1_DEB_ISAC) + debugl1(cs, "Amd7930: interrupt: TX-Packet ready, next packet dequeued"); + cs->tx_cnt = 0; + cs->dc.amd7930.tx_xmtlen = 0; + Amd7930_fill_Dfifo(cs); + } + else + schedule_event(cs, D_XMTBUFREADY); + /* AMD interrupts on */ + AmdIrqOn(cs); + } + + /* LIU status interrupt -> read LSR, check statechanges */ + if (lsr & 0x38) { + /* AMD interrupts off */ + AmdIrqOff(cs); + + if (cs->debug & L1_DEB_ISAC) + debugl1(cs, "Amd: interrupt: LSR=0x%02X, LIU is in state %d", lsr, ((lsr & 0x7) + 2)); + + cs->dc.amd7930.ph_state = (lsr & 0x7) + 2; + + schedule_event(cs, D_L1STATECHANGE); + /* AMD interrupts on */ + AmdIrqOn(cs); + } + + /* reads Interrupt-Register again. If there is a new interrupt-flag: restart handler */ + irflags = rByteAMD(cs, 0x00); + } + +} + +static void +Amd7930_l1hw(struct PStack *st, int pr, void *arg) +{ + struct IsdnCardState *cs = (struct IsdnCardState *) st->l1.hardware; + struct sk_buff *skb = arg; + u_long flags; + + if (cs->debug & L1_DEB_ISAC) + debugl1(cs, "Amd7930: l1hw called, pr: 0x%04X", pr); + + switch (pr) { + case (PH_DATA | REQUEST): + if (cs->debug & DEB_DLOG_HEX) + LogFrame(cs, skb->data, skb->len); + if (cs->debug & DEB_DLOG_VERBOSE) + dlogframe(cs, skb, 0); + spin_lock_irqsave(&cs->lock, flags); + if (cs->tx_skb) { + skb_queue_tail(&cs->sq, skb); +#ifdef L2FRAME_DEBUG /* psa */ + if (cs->debug & L1_DEB_LAPD) + Logl2Frame(cs, skb, "Amd7930: l1hw: PH_DATA Queued", 0); +#endif + } else { + cs->tx_skb = skb; + cs->tx_cnt = 0; + cs->dc.amd7930.tx_xmtlen = 0; +#ifdef L2FRAME_DEBUG /* psa */ + if (cs->debug & L1_DEB_LAPD) + Logl2Frame(cs, skb, "Amd7930: l1hw: PH_DATA", 0); +#endif + Amd7930_fill_Dfifo(cs); + } + spin_unlock_irqrestore(&cs->lock, flags); + break; + case (PH_PULL | INDICATION): + spin_lock_irqsave(&cs->lock, flags); + if (cs->tx_skb) { + if (cs->debug & L1_DEB_WARN) + debugl1(cs, "Amd7930: l1hw: l2l1 tx_skb exist this shouldn't happen"); + skb_queue_tail(&cs->sq, skb); + spin_unlock_irqrestore(&cs->lock, flags); + break; + } + if (cs->debug & DEB_DLOG_HEX) + LogFrame(cs, skb->data, skb->len); + if (cs->debug & DEB_DLOG_VERBOSE) + dlogframe(cs, skb, 0); + cs->tx_skb = skb; + cs->tx_cnt = 0; + cs->dc.amd7930.tx_xmtlen = 0; +#ifdef L2FRAME_DEBUG /* psa */ + if (cs->debug & L1_DEB_LAPD) + Logl2Frame(cs, skb, "Amd7930: l1hw: PH_DATA_PULLED", 0); +#endif + Amd7930_fill_Dfifo(cs); + spin_unlock_irqrestore(&cs->lock, flags); + break; + case (PH_PULL | REQUEST): +#ifdef L2FRAME_DEBUG /* psa */ + if (cs->debug & L1_DEB_LAPD) + debugl1(cs, "Amd7930: l1hw: -> PH_REQUEST_PULL, skb: %s", (cs->tx_skb) ? "yes" : "no"); +#endif + if (!cs->tx_skb) { + test_and_clear_bit(FLG_L1_PULL_REQ, &st->l1.Flags); + st->l1.l1l2(st, PH_PULL | CONFIRM, NULL); + } else + test_and_set_bit(FLG_L1_PULL_REQ, &st->l1.Flags); + break; + case (HW_RESET | REQUEST): + spin_lock_irqsave(&cs->lock, flags); + if ((cs->dc.amd7930.ph_state == 8)) { + /* b-channels off, PH-AR cleared + * change to F3 */ + Amd7930_ph_command(cs, 0x20, "HW_RESET REQUEST"); //LMR1 bit 5 + spin_unlock_irqrestore(&cs->lock, flags); + } else { + Amd7930_ph_command(cs, 0x40, "HW_RESET REQUEST"); + cs->dc.amd7930.ph_state = 2; + spin_unlock_irqrestore(&cs->lock, flags); + Amd7930_new_ph(cs); + } + break; + case (HW_ENABLE | REQUEST): + cs->dc.amd7930.ph_state = 9; + Amd7930_new_ph(cs); + break; + case (HW_INFO3 | REQUEST): + // automatic + break; + case (HW_TESTLOOP | REQUEST): + /* not implemented yet */ + break; + case (HW_DEACTIVATE | RESPONSE): + skb_queue_purge(&cs->rq); + skb_queue_purge(&cs->sq); + if (cs->tx_skb) { + dev_kfree_skb(cs->tx_skb); + cs->tx_skb = NULL; + } + if (test_and_clear_bit(FLG_DBUSY_TIMER, &cs->HW_Flags)) + del_timer(&cs->dbusytimer); + if (test_and_clear_bit(FLG_L1_DBUSY, &cs->HW_Flags)) + schedule_event(cs, D_CLEARBUSY); + break; + default: + if (cs->debug & L1_DEB_WARN) + debugl1(cs, "Amd7930: l1hw: unknown %04x", pr); + break; + } +} + +static void +setstack_Amd7930(struct PStack *st, struct IsdnCardState *cs) +{ + + if (cs->debug & L1_DEB_ISAC) + debugl1(cs, "Amd7930: setstack called"); + + st->l1.l1hw = Amd7930_l1hw; +} + + +static void +DC_Close_Amd7930(struct IsdnCardState *cs) { + if (cs->debug & L1_DEB_ISAC) + debugl1(cs, "Amd7930: DC_Close called"); +} + + +static void +dbusy_timer_handler(struct timer_list *t) +{ + struct IsdnCardState *cs = from_timer(cs, t, dbusytimer); + u_long flags; + struct PStack *stptr; + WORD dtcr, der; + BYTE dsr1, dsr2; + + + if (cs->debug & L1_DEB_ISAC) + debugl1(cs, "Amd7930: dbusy_timer expired!"); + + if (test_bit(FLG_DBUSY_TIMER, &cs->HW_Flags)) { + spin_lock_irqsave(&cs->lock, flags); + /* D Transmit Byte Count Register: + * Counts down packet's number of Bytes, 0 if packet ready */ + dtcr = rWordAMD(cs, 0x85); + dsr1 = rByteAMD(cs, 0x02); + dsr2 = rByteAMD(cs, 0x07); + der = rWordAMD(cs, 0x03); + + if (cs->debug & L1_DEB_ISAC) + debugl1(cs, "Amd7930: dbusy_timer_handler: DSR1=0x%02X, DSR2=0x%02X, DER=0x%04X, cs->tx_skb->len=%u, tx_stat=%u, dtcr=%u, cs->tx_cnt=%u", dsr1, dsr2, der, cs->tx_skb->len, cs->dc.amd7930.tx_xmtlen, dtcr, cs->tx_cnt); + + if ((cs->dc.amd7930.tx_xmtlen - dtcr) < cs->tx_cnt) { /* D-Channel Busy */ + test_and_set_bit(FLG_L1_DBUSY, &cs->HW_Flags); + stptr = cs->stlist; + spin_unlock_irqrestore(&cs->lock, flags); + while (stptr != NULL) { + stptr->l1.l1l2(stptr, PH_PAUSE | INDICATION, NULL); + stptr = stptr->next; + } + + } else { + /* discard frame; reset transceiver */ + test_and_clear_bit(FLG_DBUSY_TIMER, &cs->HW_Flags); + if (cs->tx_skb) { + dev_kfree_skb_any(cs->tx_skb); + cs->tx_cnt = 0; + cs->tx_skb = NULL; + cs->dc.amd7930.tx_xmtlen = 0; + } else { + printk(KERN_WARNING "HiSax: Amd7930: D-Channel Busy no skb\n"); + debugl1(cs, "Amd7930: D-Channel Busy no skb"); + + } + /* Transmitter reset, abort transmit */ + wByteAMD(cs, 0x21, 0x82); + wByteAMD(cs, 0x21, 0x02); + spin_unlock_irqrestore(&cs->lock, flags); + cs->irq_func(cs->irq, cs); + + if (cs->debug & L1_DEB_ISAC) + debugl1(cs, "Amd7930: dbusy_timer_handler: Transmitter reset"); + } + } +} + + + +void Amd7930_init(struct IsdnCardState *cs) +{ + WORD *ptr; + BYTE cmd, cnt; + + if (cs->debug & L1_DEB_ISAC) + debugl1(cs, "Amd7930: initamd called"); + + cs->dc.amd7930.tx_xmtlen = 0; + cs->dc.amd7930.old_state = 0; + cs->dc.amd7930.lmr1 = 0x40; + cs->dc.amd7930.ph_command = Amd7930_ph_command; + cs->setstack_d = setstack_Amd7930; + cs->DC_Close = DC_Close_Amd7930; + + /* AMD Initialisation */ + for (ptr = initAMD; *ptr != 0xFFFF; ) { + cmd = LOBYTE(*ptr); + + /* read */ + if (*ptr++ >= 0x100) { + if (cmd < 8) + /* reset register */ + rByteAMD(cs, cmd); + else { + wByteAMD(cs, 0x00, cmd); + for (cnt = *ptr++; cnt > 0; cnt--) + rByteAMD(cs, 0x01); + } + } + /* write */ + else if (cmd < 8) + wByteAMD(cs, cmd, LOBYTE(*ptr++)); + + else { + wByteAMD(cs, 0x00, cmd); + for (cnt = *ptr++; cnt > 0; cnt--) + wByteAMD(cs, 0x01, LOBYTE(*ptr++)); + } + } +} + +void setup_Amd7930(struct IsdnCardState *cs) +{ + INIT_WORK(&cs->tqueue, Amd7930_bh); + timer_setup(&cs->dbusytimer, dbusy_timer_handler, 0); +} diff --git a/drivers/isdn/hisax/amd7930_fn.h b/drivers/isdn/hisax/amd7930_fn.h new file mode 100644 index 000000000..1f4d80c5e --- /dev/null +++ b/drivers/isdn/hisax/amd7930_fn.h @@ -0,0 +1,37 @@ +/* drivers/isdn/hisax/amd7930_fn.h + * + * gerdes_amd7930.h Header-file included by + * gerdes_amd7930.c + * + * Author Christoph Ersfeld <info@formula-n.de> + * Formula-n Europe AG (www.formula-n.com) + * previously Gerdes AG + * + * + * This file is (c) under GNU PUBLIC LICENSE + */ + + + + +#define BYTE unsigned char +#define WORD unsigned int +#define rByteAMD(cs, reg) cs->readisac(cs, reg) +#define wByteAMD(cs, reg, val) cs->writeisac(cs, reg, val) +#define rWordAMD(cs, reg) ReadWordAmd7930(cs, reg) +#define wWordAMD(cs, reg, val) WriteWordAmd7930(cs, reg, val) +#define HIBYTE(w) ((unsigned char)((w & 0xff00) / 256)) +#define LOBYTE(w) ((unsigned char)(w & 0x00ff)) + +#define AmdIrqOff(cs) cs->dc.amd7930.setIrqMask(cs, 0) +#define AmdIrqOn(cs) cs->dc.amd7930.setIrqMask(cs, 1) + +#define AMD_CR 0x00 +#define AMD_DR 0x01 + + +#define DBUSY_TIMER_VALUE 80 + +extern void Amd7930_interrupt(struct IsdnCardState *, unsigned char); +extern void Amd7930_init(struct IsdnCardState *); +extern void setup_Amd7930(struct IsdnCardState *); diff --git a/drivers/isdn/hisax/arcofi.c b/drivers/isdn/hisax/arcofi.c new file mode 100644 index 000000000..2f784f96d --- /dev/null +++ b/drivers/isdn/hisax/arcofi.c @@ -0,0 +1,131 @@ +/* $Id: arcofi.c,v 1.14.2.3 2004/01/13 14:31:24 keil Exp $ + * + * Ansteuerung ARCOFI 2165 + * + * Author Karsten Keil + * Copyright by Karsten Keil <keil@isdn4linux.de> + * + * This software may be used and distributed according to the terms + * of the GNU General Public License, incorporated herein by reference. + * + */ + +#include <linux/sched.h> +#include "hisax.h" +#include "isdnl1.h" +#include "isac.h" +#include "arcofi.h" + +#define ARCOFI_TIMER_VALUE 20 + +static void +add_arcofi_timer(struct IsdnCardState *cs) { + if (test_and_set_bit(FLG_ARCOFI_TIMER, &cs->HW_Flags)) { + del_timer(&cs->dc.isac.arcofitimer); + } + cs->dc.isac.arcofitimer.expires = jiffies + ((ARCOFI_TIMER_VALUE * HZ) / 1000); + add_timer(&cs->dc.isac.arcofitimer); +} + +static void +send_arcofi(struct IsdnCardState *cs) { + add_arcofi_timer(cs); + cs->dc.isac.mon_txp = 0; + cs->dc.isac.mon_txc = cs->dc.isac.arcofi_list->len; + memcpy(cs->dc.isac.mon_tx, cs->dc.isac.arcofi_list->msg, cs->dc.isac.mon_txc); + switch (cs->dc.isac.arcofi_bc) { + case 0: break; + case 1: cs->dc.isac.mon_tx[1] |= 0x40; + break; + default: break; + } + cs->dc.isac.mocr &= 0x0f; + cs->dc.isac.mocr |= 0xa0; + cs->writeisac(cs, ISAC_MOCR, cs->dc.isac.mocr); + (void) cs->readisac(cs, ISAC_MOSR); + cs->writeisac(cs, ISAC_MOX1, cs->dc.isac.mon_tx[cs->dc.isac.mon_txp++]); + cs->dc.isac.mocr |= 0x10; + cs->writeisac(cs, ISAC_MOCR, cs->dc.isac.mocr); +} + +int +arcofi_fsm(struct IsdnCardState *cs, int event, void *data) { + if (cs->debug & L1_DEB_MONITOR) { + debugl1(cs, "arcofi state %d event %d", cs->dc.isac.arcofi_state, event); + } + if (event == ARCOFI_TIMEOUT) { + cs->dc.isac.arcofi_state = ARCOFI_NOP; + test_and_set_bit(FLG_ARCOFI_ERROR, &cs->HW_Flags); + wake_up(&cs->dc.isac.arcofi_wait); + return (1); + } + switch (cs->dc.isac.arcofi_state) { + case ARCOFI_NOP: + if (event == ARCOFI_START) { + cs->dc.isac.arcofi_list = data; + cs->dc.isac.arcofi_state = ARCOFI_TRANSMIT; + send_arcofi(cs); + } + break; + case ARCOFI_TRANSMIT: + if (event == ARCOFI_TX_END) { + if (cs->dc.isac.arcofi_list->receive) { + add_arcofi_timer(cs); + cs->dc.isac.arcofi_state = ARCOFI_RECEIVE; + } else { + if (cs->dc.isac.arcofi_list->next) { + cs->dc.isac.arcofi_list = + cs->dc.isac.arcofi_list->next; + send_arcofi(cs); + } else { + if (test_and_clear_bit(FLG_ARCOFI_TIMER, &cs->HW_Flags)) { + del_timer(&cs->dc.isac.arcofitimer); + } + cs->dc.isac.arcofi_state = ARCOFI_NOP; + wake_up(&cs->dc.isac.arcofi_wait); + } + } + } + break; + case ARCOFI_RECEIVE: + if (event == ARCOFI_RX_END) { + if (cs->dc.isac.arcofi_list->next) { + cs->dc.isac.arcofi_list = + cs->dc.isac.arcofi_list->next; + cs->dc.isac.arcofi_state = ARCOFI_TRANSMIT; + send_arcofi(cs); + } else { + if (test_and_clear_bit(FLG_ARCOFI_TIMER, &cs->HW_Flags)) { + del_timer(&cs->dc.isac.arcofitimer); + } + cs->dc.isac.arcofi_state = ARCOFI_NOP; + wake_up(&cs->dc.isac.arcofi_wait); + } + } + break; + default: + debugl1(cs, "Arcofi unknown state %x", cs->dc.isac.arcofi_state); + return (2); + } + return (0); +} + +static void +arcofi_timer(struct timer_list *t) { + struct IsdnCardState *cs = from_timer(cs, t, dc.isac.arcofitimer); + arcofi_fsm(cs, ARCOFI_TIMEOUT, NULL); +} + +void +clear_arcofi(struct IsdnCardState *cs) { + if (test_and_clear_bit(FLG_ARCOFI_TIMER, &cs->HW_Flags)) { + del_timer(&cs->dc.isac.arcofitimer); + } +} + +void +init_arcofi(struct IsdnCardState *cs) { + timer_setup(&cs->dc.isac.arcofitimer, arcofi_timer, 0); + init_waitqueue_head(&cs->dc.isac.arcofi_wait); + test_and_set_bit(HW_ARCOFI, &cs->HW_Flags); +} diff --git a/drivers/isdn/hisax/arcofi.h b/drivers/isdn/hisax/arcofi.h new file mode 100644 index 000000000..b9c77529f --- /dev/null +++ b/drivers/isdn/hisax/arcofi.h @@ -0,0 +1,27 @@ +/* $Id: arcofi.h,v 1.6.6.2 2001/09/23 22:24:46 kai Exp $ + * + * Ansteuerung ARCOFI 2165 + * + * Author Karsten Keil + * Copyright by Karsten Keil <keil@isdn4linux.de> + * + * This software may be used and distributed according to the terms + * of the GNU General Public License, incorporated herein by reference. + * + */ + +#define ARCOFI_USE 1 + +/* states */ +#define ARCOFI_NOP 0 +#define ARCOFI_TRANSMIT 1 +#define ARCOFI_RECEIVE 2 +/* events */ +#define ARCOFI_START 1 +#define ARCOFI_TX_END 2 +#define ARCOFI_RX_END 3 +#define ARCOFI_TIMEOUT 4 + +extern int arcofi_fsm(struct IsdnCardState *cs, int event, void *data); +extern void init_arcofi(struct IsdnCardState *cs); +extern void clear_arcofi(struct IsdnCardState *cs); diff --git a/drivers/isdn/hisax/asuscom.c b/drivers/isdn/hisax/asuscom.c new file mode 100644 index 000000000..74c871495 --- /dev/null +++ b/drivers/isdn/hisax/asuscom.c @@ -0,0 +1,423 @@ +/* $Id: asuscom.c,v 1.14.2.4 2004/01/13 23:48:39 keil Exp $ + * + * low level stuff for ASUSCOM NETWORK INC. ISDNLink cards + * + * Author Karsten Keil + * Copyright by Karsten Keil <keil@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 ASUSCOM NETWORK INC. Taiwan and Dynalink NL for information + * + */ + +#include <linux/init.h> +#include <linux/isapnp.h> +#include "hisax.h" +#include "isac.h" +#include "ipac.h" +#include "hscx.h" +#include "isdnl1.h" + +static const char *Asuscom_revision = "$Revision: 1.14.2.4 $"; + +#define byteout(addr, val) outb(val, addr) +#define bytein(addr) inb(addr) + +#define ASUS_ISAC 0 +#define ASUS_HSCX 1 +#define ASUS_ADR 2 +#define ASUS_CTRL_U7 3 +#define ASUS_CTRL_POTS 5 + +#define ASUS_IPAC_ALE 0 +#define ASUS_IPAC_DATA 1 + +#define ASUS_ISACHSCX 1 +#define ASUS_IPAC 2 + +/* CARD_ADR (Write) */ +#define ASUS_RESET 0x80 /* Bit 7 Reset-Leitung */ + +static inline u_char +readreg(unsigned int ale, unsigned int adr, u_char off) +{ + register u_char ret; + + byteout(ale, off); + ret = bytein(adr); + return (ret); +} + +static inline void +readfifo(unsigned int ale, unsigned int adr, u_char off, u_char *data, int size) +{ + byteout(ale, off); + insb(adr, data, size); +} + + +static inline void +writereg(unsigned int ale, unsigned int adr, u_char off, u_char data) +{ + byteout(ale, off); + byteout(adr, data); +} + +static inline void +writefifo(unsigned int ale, unsigned int adr, u_char off, u_char *data, int size) +{ + byteout(ale, off); + outsb(adr, data, size); +} + +/* Interface functions */ + +static u_char +ReadISAC(struct IsdnCardState *cs, u_char offset) +{ + return (readreg(cs->hw.asus.adr, cs->hw.asus.isac, offset)); +} + +static void +WriteISAC(struct IsdnCardState *cs, u_char offset, u_char value) +{ + writereg(cs->hw.asus.adr, cs->hw.asus.isac, offset, value); +} + +static void +ReadISACfifo(struct IsdnCardState *cs, u_char *data, int size) +{ + readfifo(cs->hw.asus.adr, cs->hw.asus.isac, 0, data, size); +} + +static void +WriteISACfifo(struct IsdnCardState *cs, u_char *data, int size) +{ + writefifo(cs->hw.asus.adr, cs->hw.asus.isac, 0, data, size); +} + +static u_char +ReadISAC_IPAC(struct IsdnCardState *cs, u_char offset) +{ + return (readreg(cs->hw.asus.adr, cs->hw.asus.isac, offset | 0x80)); +} + +static void +WriteISAC_IPAC(struct IsdnCardState *cs, u_char offset, u_char value) +{ + writereg(cs->hw.asus.adr, cs->hw.asus.isac, offset | 0x80, value); +} + +static void +ReadISACfifo_IPAC(struct IsdnCardState *cs, u_char *data, int size) +{ + readfifo(cs->hw.asus.adr, cs->hw.asus.isac, 0x80, data, size); +} + +static void +WriteISACfifo_IPAC(struct IsdnCardState *cs, u_char *data, int size) +{ + writefifo(cs->hw.asus.adr, cs->hw.asus.isac, 0x80, data, size); +} + +static u_char +ReadHSCX(struct IsdnCardState *cs, int hscx, u_char offset) +{ + return (readreg(cs->hw.asus.adr, + cs->hw.asus.hscx, offset + (hscx ? 0x40 : 0))); +} + +static void +WriteHSCX(struct IsdnCardState *cs, int hscx, u_char offset, u_char value) +{ + writereg(cs->hw.asus.adr, + cs->hw.asus.hscx, offset + (hscx ? 0x40 : 0), value); +} + +/* + * fast interrupt HSCX stuff goes here + */ + +#define READHSCX(cs, nr, reg) readreg(cs->hw.asus.adr, \ + cs->hw.asus.hscx, reg + (nr ? 0x40 : 0)) +#define WRITEHSCX(cs, nr, reg, data) writereg(cs->hw.asus.adr, \ + cs->hw.asus.hscx, reg + (nr ? 0x40 : 0), data) + +#define READHSCXFIFO(cs, nr, ptr, cnt) readfifo(cs->hw.asus.adr, \ + cs->hw.asus.hscx, (nr ? 0x40 : 0), ptr, cnt) + +#define WRITEHSCXFIFO(cs, nr, ptr, cnt) writefifo(cs->hw.asus.adr, \ + cs->hw.asus.hscx, (nr ? 0x40 : 0), ptr, cnt) + +#include "hscx_irq.c" + +static irqreturn_t +asuscom_interrupt(int intno, void *dev_id) +{ + struct IsdnCardState *cs = dev_id; + u_char val; + u_long flags; + + spin_lock_irqsave(&cs->lock, flags); + val = readreg(cs->hw.asus.adr, cs->hw.asus.hscx, HSCX_ISTA + 0x40); +Start_HSCX: + if (val) + hscx_int_main(cs, val); + val = readreg(cs->hw.asus.adr, cs->hw.asus.isac, ISAC_ISTA); +Start_ISAC: + if (val) + isac_interrupt(cs, val); + val = readreg(cs->hw.asus.adr, cs->hw.asus.hscx, HSCX_ISTA + 0x40); + if (val) { + if (cs->debug & L1_DEB_HSCX) + debugl1(cs, "HSCX IntStat after IntRoutine"); + goto Start_HSCX; + } + val = readreg(cs->hw.asus.adr, cs->hw.asus.isac, ISAC_ISTA); + if (val) { + if (cs->debug & L1_DEB_ISAC) + debugl1(cs, "ISAC IntStat after IntRoutine"); + goto Start_ISAC; + } + writereg(cs->hw.asus.adr, cs->hw.asus.hscx, HSCX_MASK, 0xFF); + writereg(cs->hw.asus.adr, cs->hw.asus.hscx, HSCX_MASK + 0x40, 0xFF); + writereg(cs->hw.asus.adr, cs->hw.asus.isac, ISAC_MASK, 0xFF); + writereg(cs->hw.asus.adr, cs->hw.asus.isac, ISAC_MASK, 0x0); + writereg(cs->hw.asus.adr, cs->hw.asus.hscx, HSCX_MASK, 0x0); + writereg(cs->hw.asus.adr, cs->hw.asus.hscx, HSCX_MASK + 0x40, 0x0); + spin_unlock_irqrestore(&cs->lock, flags); + return IRQ_HANDLED; +} + +static irqreturn_t +asuscom_interrupt_ipac(int intno, void *dev_id) +{ + struct IsdnCardState *cs = dev_id; + u_char ista, val, icnt = 5; + u_long flags; + + spin_lock_irqsave(&cs->lock, flags); + ista = readreg(cs->hw.asus.adr, cs->hw.asus.isac, IPAC_ISTA); +Start_IPAC: + if (cs->debug & L1_DEB_IPAC) + debugl1(cs, "IPAC ISTA %02X", ista); + if (ista & 0x0f) { + val = readreg(cs->hw.asus.adr, cs->hw.asus.hscx, HSCX_ISTA + 0x40); + if (ista & 0x01) + val |= 0x01; + if (ista & 0x04) + val |= 0x02; + if (ista & 0x08) + val |= 0x04; + if (val) + hscx_int_main(cs, val); + } + if (ista & 0x20) { + val = 0xfe & readreg(cs->hw.asus.adr, cs->hw.asus.isac, ISAC_ISTA | 0x80); + if (val) { + isac_interrupt(cs, val); + } + } + if (ista & 0x10) { + val = 0x01; + isac_interrupt(cs, val); + } + ista = readreg(cs->hw.asus.adr, cs->hw.asus.isac, IPAC_ISTA); + if ((ista & 0x3f) && icnt) { + icnt--; + goto Start_IPAC; + } + if (!icnt) + printk(KERN_WARNING "ASUS IRQ LOOP\n"); + writereg(cs->hw.asus.adr, cs->hw.asus.isac, IPAC_MASK, 0xFF); + writereg(cs->hw.asus.adr, cs->hw.asus.isac, IPAC_MASK, 0xC0); + spin_unlock_irqrestore(&cs->lock, flags); + return IRQ_HANDLED; +} + +static void +release_io_asuscom(struct IsdnCardState *cs) +{ + int bytecnt = 8; + + if (cs->hw.asus.cfg_reg) + release_region(cs->hw.asus.cfg_reg, bytecnt); +} + +static void +reset_asuscom(struct IsdnCardState *cs) +{ + if (cs->subtyp == ASUS_IPAC) + writereg(cs->hw.asus.adr, cs->hw.asus.isac, IPAC_POTA2, 0x20); + else + byteout(cs->hw.asus.adr, ASUS_RESET); /* Reset On */ + mdelay(10); + if (cs->subtyp == ASUS_IPAC) + writereg(cs->hw.asus.adr, cs->hw.asus.isac, IPAC_POTA2, 0x0); + else + byteout(cs->hw.asus.adr, 0); /* Reset Off */ + mdelay(10); + if (cs->subtyp == ASUS_IPAC) { + writereg(cs->hw.asus.adr, cs->hw.asus.isac, IPAC_CONF, 0x0); + writereg(cs->hw.asus.adr, cs->hw.asus.isac, IPAC_ACFG, 0xff); + writereg(cs->hw.asus.adr, cs->hw.asus.isac, IPAC_AOE, 0x0); + writereg(cs->hw.asus.adr, cs->hw.asus.isac, IPAC_MASK, 0xc0); + writereg(cs->hw.asus.adr, cs->hw.asus.isac, IPAC_PCFG, 0x12); + } +} + +static int +Asus_card_msg(struct IsdnCardState *cs, int mt, void *arg) +{ + u_long flags; + + switch (mt) { + case CARD_RESET: + spin_lock_irqsave(&cs->lock, flags); + reset_asuscom(cs); + spin_unlock_irqrestore(&cs->lock, flags); + return (0); + case CARD_RELEASE: + release_io_asuscom(cs); + return (0); + case CARD_INIT: + spin_lock_irqsave(&cs->lock, flags); + cs->debug |= L1_DEB_IPAC; + inithscxisac(cs, 3); + spin_unlock_irqrestore(&cs->lock, flags); + return (0); + case CARD_TEST: + return (0); + } + return (0); +} + +#ifdef __ISAPNP__ +static struct isapnp_device_id asus_ids[] = { + { ISAPNP_VENDOR('A', 'S', 'U'), ISAPNP_FUNCTION(0x1688), + ISAPNP_VENDOR('A', 'S', 'U'), ISAPNP_FUNCTION(0x1688), + (unsigned long) "Asus1688 PnP" }, + { ISAPNP_VENDOR('A', 'S', 'U'), ISAPNP_FUNCTION(0x1690), + ISAPNP_VENDOR('A', 'S', 'U'), ISAPNP_FUNCTION(0x1690), + (unsigned long) "Asus1690 PnP" }, + { ISAPNP_VENDOR('S', 'I', 'E'), ISAPNP_FUNCTION(0x0020), + ISAPNP_VENDOR('S', 'I', 'E'), ISAPNP_FUNCTION(0x0020), + (unsigned long) "Isurf2 PnP" }, + { ISAPNP_VENDOR('E', 'L', 'F'), ISAPNP_FUNCTION(0x0000), + ISAPNP_VENDOR('E', 'L', 'F'), ISAPNP_FUNCTION(0x0000), + (unsigned long) "Iscas TE320" }, + { 0, } +}; + +static struct isapnp_device_id *ipid = &asus_ids[0]; +static struct pnp_card *pnp_c = NULL; +#endif + +int setup_asuscom(struct IsdnCard *card) +{ + int bytecnt; + struct IsdnCardState *cs = card->cs; + u_char val; + char tmp[64]; + + strcpy(tmp, Asuscom_revision); + printk(KERN_INFO "HiSax: Asuscom ISDNLink driver Rev. %s\n", HiSax_getrev(tmp)); + if (cs->typ != ISDN_CTYPE_ASUSCOM) + return (0); +#ifdef __ISAPNP__ + if (!card->para[1] && isapnp_present()) { + struct pnp_dev *pnp_d; + while (ipid->card_vendor) { + if ((pnp_c = pnp_find_card(ipid->card_vendor, + ipid->card_device, pnp_c))) { + pnp_d = NULL; + if ((pnp_d = pnp_find_dev(pnp_c, + ipid->vendor, ipid->function, pnp_d))) { + int err; + + printk(KERN_INFO "HiSax: %s detected\n", + (char *)ipid->driver_data); + pnp_disable_dev(pnp_d); + err = pnp_activate_dev(pnp_d); + if (err < 0) { + printk(KERN_WARNING "%s: pnp_activate_dev ret(%d)\n", + __func__, err); + return (0); + } + card->para[1] = pnp_port_start(pnp_d, 0); + card->para[0] = pnp_irq(pnp_d, 0); + if (card->para[0] == -1 || !card->para[1]) { + printk(KERN_ERR "AsusPnP:some resources are missing %ld/%lx\n", + card->para[0], card->para[1]); + pnp_disable_dev(pnp_d); + return (0); + } + break; + } else { + printk(KERN_ERR "AsusPnP: PnP error card found, no device\n"); + } + } + ipid++; + pnp_c = NULL; + } + if (!ipid->card_vendor) { + printk(KERN_INFO "AsusPnP: no ISAPnP card found\n"); + return (0); + } + } +#endif + bytecnt = 8; + cs->hw.asus.cfg_reg = card->para[1]; + cs->irq = card->para[0]; + if (!request_region(cs->hw.asus.cfg_reg, bytecnt, "asuscom isdn")) { + printk(KERN_WARNING + "HiSax: ISDNLink config port %x-%x already in use\n", + cs->hw.asus.cfg_reg, + cs->hw.asus.cfg_reg + bytecnt); + return (0); + } + printk(KERN_INFO "ISDNLink: defined at 0x%x IRQ %d\n", + cs->hw.asus.cfg_reg, cs->irq); + setup_isac(cs); + cs->BC_Read_Reg = &ReadHSCX; + cs->BC_Write_Reg = &WriteHSCX; + cs->BC_Send_Data = &hscx_fill_fifo; + cs->cardmsg = &Asus_card_msg; + val = readreg(cs->hw.asus.cfg_reg + ASUS_IPAC_ALE, + cs->hw.asus.cfg_reg + ASUS_IPAC_DATA, IPAC_ID); + if ((val == 1) || (val == 2)) { + cs->subtyp = ASUS_IPAC; + cs->hw.asus.adr = cs->hw.asus.cfg_reg + ASUS_IPAC_ALE; + cs->hw.asus.isac = cs->hw.asus.cfg_reg + ASUS_IPAC_DATA; + cs->hw.asus.hscx = cs->hw.asus.cfg_reg + ASUS_IPAC_DATA; + test_and_set_bit(HW_IPAC, &cs->HW_Flags); + cs->readisac = &ReadISAC_IPAC; + cs->writeisac = &WriteISAC_IPAC; + cs->readisacfifo = &ReadISACfifo_IPAC; + cs->writeisacfifo = &WriteISACfifo_IPAC; + cs->irq_func = &asuscom_interrupt_ipac; + printk(KERN_INFO "Asus: IPAC version %x\n", val); + } else { + cs->subtyp = ASUS_ISACHSCX; + cs->hw.asus.adr = cs->hw.asus.cfg_reg + ASUS_ADR; + cs->hw.asus.isac = cs->hw.asus.cfg_reg + ASUS_ISAC; + cs->hw.asus.hscx = cs->hw.asus.cfg_reg + ASUS_HSCX; + cs->hw.asus.u7 = cs->hw.asus.cfg_reg + ASUS_CTRL_U7; + cs->hw.asus.pots = cs->hw.asus.cfg_reg + ASUS_CTRL_POTS; + cs->readisac = &ReadISAC; + cs->writeisac = &WriteISAC; + cs->readisacfifo = &ReadISACfifo; + cs->writeisacfifo = &WriteISACfifo; + cs->irq_func = &asuscom_interrupt; + ISACVersion(cs, "ISDNLink:"); + if (HscxVersion(cs, "ISDNLink:")) { + printk(KERN_WARNING + "ISDNLink: wrong HSCX versions check IO address\n"); + release_io_asuscom(cs); + return (0); + } + } + return (1); +} diff --git a/drivers/isdn/hisax/avm_a1.c b/drivers/isdn/hisax/avm_a1.c new file mode 100644 index 000000000..7dd74087a --- /dev/null +++ b/drivers/isdn/hisax/avm_a1.c @@ -0,0 +1,307 @@ +/* $Id: avm_a1.c,v 2.15.2.4 2004/01/13 21:46:03 keil Exp $ + * + * low level stuff for AVM A1 (Fritz) isdn cards + * + * Author Karsten Keil + * Copyright by Karsten Keil <keil@isdn4linux.de> + * + * This software may be used and distributed according to the terms + * of the GNU General Public License, incorporated herein by reference. + * + */ + +#include <linux/init.h> +#include "hisax.h" +#include "isac.h" +#include "hscx.h" +#include "isdnl1.h" + +static const char *avm_revision = "$Revision: 2.15.2.4 $"; + +#define AVM_A1_STAT_ISAC 0x01 +#define AVM_A1_STAT_HSCX 0x02 +#define AVM_A1_STAT_TIMER 0x04 + +#define byteout(addr, val) outb(val, addr) +#define bytein(addr) inb(addr) + +static inline u_char +readreg(unsigned int adr, u_char off) +{ + return (bytein(adr + off)); +} + +static inline void +writereg(unsigned int adr, u_char off, u_char data) +{ + byteout(adr + off, data); +} + + +static inline void +read_fifo(unsigned int adr, u_char *data, int size) +{ + insb(adr, data, size); +} + +static void +write_fifo(unsigned int adr, u_char *data, int size) +{ + outsb(adr, data, size); +} + +/* Interface functions */ + +static u_char +ReadISAC(struct IsdnCardState *cs, u_char offset) +{ + return (readreg(cs->hw.avm.isac, offset)); +} + +static void +WriteISAC(struct IsdnCardState *cs, u_char offset, u_char value) +{ + writereg(cs->hw.avm.isac, offset, value); +} + +static void +ReadISACfifo(struct IsdnCardState *cs, u_char *data, int size) +{ + read_fifo(cs->hw.avm.isacfifo, data, size); +} + +static void +WriteISACfifo(struct IsdnCardState *cs, u_char *data, int size) +{ + write_fifo(cs->hw.avm.isacfifo, data, size); +} + +static u_char +ReadHSCX(struct IsdnCardState *cs, int hscx, u_char offset) +{ + return (readreg(cs->hw.avm.hscx[hscx], offset)); +} + +static void +WriteHSCX(struct IsdnCardState *cs, int hscx, u_char offset, u_char value) +{ + writereg(cs->hw.avm.hscx[hscx], offset, value); +} + +/* + * fast interrupt HSCX stuff goes here + */ + +#define READHSCX(cs, nr, reg) readreg(cs->hw.avm.hscx[nr], reg) +#define WRITEHSCX(cs, nr, reg, data) writereg(cs->hw.avm.hscx[nr], reg, data) +#define READHSCXFIFO(cs, nr, ptr, cnt) read_fifo(cs->hw.avm.hscxfifo[nr], ptr, cnt) +#define WRITEHSCXFIFO(cs, nr, ptr, cnt) write_fifo(cs->hw.avm.hscxfifo[nr], ptr, cnt) + +#include "hscx_irq.c" + +static irqreturn_t +avm_a1_interrupt(int intno, void *dev_id) +{ + struct IsdnCardState *cs = dev_id; + u_char val, sval; + u_long flags; + + spin_lock_irqsave(&cs->lock, flags); + while (((sval = bytein(cs->hw.avm.cfg_reg)) & 0xf) != 0x7) { + if (!(sval & AVM_A1_STAT_TIMER)) { + byteout(cs->hw.avm.cfg_reg, 0x1E); + sval = bytein(cs->hw.avm.cfg_reg); + } else if (cs->debug & L1_DEB_INTSTAT) + debugl1(cs, "avm IntStatus %x", sval); + if (!(sval & AVM_A1_STAT_HSCX)) { + val = readreg(cs->hw.avm.hscx[1], HSCX_ISTA); + if (val) + hscx_int_main(cs, val); + } + if (!(sval & AVM_A1_STAT_ISAC)) { + val = readreg(cs->hw.avm.isac, ISAC_ISTA); + if (val) + isac_interrupt(cs, val); + } + } + writereg(cs->hw.avm.hscx[0], HSCX_MASK, 0xFF); + writereg(cs->hw.avm.hscx[1], HSCX_MASK, 0xFF); + writereg(cs->hw.avm.isac, ISAC_MASK, 0xFF); + writereg(cs->hw.avm.isac, ISAC_MASK, 0x0); + writereg(cs->hw.avm.hscx[0], HSCX_MASK, 0x0); + writereg(cs->hw.avm.hscx[1], HSCX_MASK, 0x0); + spin_unlock_irqrestore(&cs->lock, flags); + return IRQ_HANDLED; +} + +static inline void +release_ioregs(struct IsdnCardState *cs, int mask) +{ + release_region(cs->hw.avm.cfg_reg, 8); + if (mask & 1) + release_region(cs->hw.avm.isac + 32, 32); + if (mask & 2) + release_region(cs->hw.avm.isacfifo, 1); + if (mask & 4) + release_region(cs->hw.avm.hscx[0] + 32, 32); + if (mask & 8) + release_region(cs->hw.avm.hscxfifo[0], 1); + if (mask & 0x10) + release_region(cs->hw.avm.hscx[1] + 32, 32); + if (mask & 0x20) + release_region(cs->hw.avm.hscxfifo[1], 1); +} + +static int +AVM_card_msg(struct IsdnCardState *cs, int mt, void *arg) +{ + u_long flags; + + switch (mt) { + case CARD_RESET: + return (0); + case CARD_RELEASE: + release_ioregs(cs, 0x3f); + return (0); + case CARD_INIT: + spin_lock_irqsave(&cs->lock, flags); + inithscxisac(cs, 1); + byteout(cs->hw.avm.cfg_reg, 0x16); + byteout(cs->hw.avm.cfg_reg, 0x1E); + inithscxisac(cs, 2); + spin_unlock_irqrestore(&cs->lock, flags); + return (0); + case CARD_TEST: + return (0); + } + return (0); +} + +int setup_avm_a1(struct IsdnCard *card) +{ + u_char val; + struct IsdnCardState *cs = card->cs; + char tmp[64]; + + strcpy(tmp, avm_revision); + printk(KERN_INFO "HiSax: AVM driver Rev. %s\n", HiSax_getrev(tmp)); + if (cs->typ != ISDN_CTYPE_A1) + return (0); + + cs->hw.avm.cfg_reg = card->para[1] + 0x1800; + cs->hw.avm.isac = card->para[1] + 0x1400 - 0x20; + cs->hw.avm.hscx[0] = card->para[1] + 0x400 - 0x20; + cs->hw.avm.hscx[1] = card->para[1] + 0xc00 - 0x20; + cs->hw.avm.isacfifo = card->para[1] + 0x1000; + cs->hw.avm.hscxfifo[0] = card->para[1]; + cs->hw.avm.hscxfifo[1] = card->para[1] + 0x800; + cs->irq = card->para[0]; + if (!request_region(cs->hw.avm.cfg_reg, 8, "avm cfg")) { + printk(KERN_WARNING + "HiSax: AVM A1 config port %x-%x already in use\n", + cs->hw.avm.cfg_reg, + cs->hw.avm.cfg_reg + 8); + return (0); + } + if (!request_region(cs->hw.avm.isac + 32, 32, "HiSax isac")) { + printk(KERN_WARNING + "HiSax: AVM A1 isac ports %x-%x already in use\n", + cs->hw.avm.isac + 32, + cs->hw.avm.isac + 64); + release_ioregs(cs, 0); + return (0); + } + if (!request_region(cs->hw.avm.isacfifo, 1, "HiSax isac fifo")) { + printk(KERN_WARNING + "HiSax: AVM A1 isac fifo port %x already in use\n", + cs->hw.avm.isacfifo); + release_ioregs(cs, 1); + return (0); + } + if (!request_region(cs->hw.avm.hscx[0] + 32, 32, "HiSax hscx A")) { + printk(KERN_WARNING + "HiSax: AVM A1 hscx A ports %x-%x already in use\n", + cs->hw.avm.hscx[0] + 32, + cs->hw.avm.hscx[0] + 64); + release_ioregs(cs, 3); + return (0); + } + if (!request_region(cs->hw.avm.hscxfifo[0], 1, "HiSax hscx A fifo")) { + printk(KERN_WARNING + "HiSax: AVM A1 hscx A fifo port %x already in use\n", + cs->hw.avm.hscxfifo[0]); + release_ioregs(cs, 7); + return (0); + } + if (!request_region(cs->hw.avm.hscx[1] + 32, 32, "HiSax hscx B")) { + printk(KERN_WARNING + "HiSax: AVM A1 hscx B ports %x-%x already in use\n", + cs->hw.avm.hscx[1] + 32, + cs->hw.avm.hscx[1] + 64); + release_ioregs(cs, 0xf); + return (0); + } + if (!request_region(cs->hw.avm.hscxfifo[1], 1, "HiSax hscx B fifo")) { + printk(KERN_WARNING + "HiSax: AVM A1 hscx B fifo port %x already in use\n", + cs->hw.avm.hscxfifo[1]); + release_ioregs(cs, 0x1f); + return (0); + } + byteout(cs->hw.avm.cfg_reg, 0x0); + HZDELAY(HZ / 5 + 1); + byteout(cs->hw.avm.cfg_reg, 0x1); + HZDELAY(HZ / 5 + 1); + byteout(cs->hw.avm.cfg_reg, 0x0); + HZDELAY(HZ / 5 + 1); + val = cs->irq; + if (val == 9) + val = 2; + byteout(cs->hw.avm.cfg_reg + 1, val); + HZDELAY(HZ / 5 + 1); + byteout(cs->hw.avm.cfg_reg, 0x0); + HZDELAY(HZ / 5 + 1); + + val = bytein(cs->hw.avm.cfg_reg); + printk(KERN_INFO "AVM A1: Byte at %x is %x\n", + cs->hw.avm.cfg_reg, val); + val = bytein(cs->hw.avm.cfg_reg + 3); + printk(KERN_INFO "AVM A1: Byte at %x is %x\n", + cs->hw.avm.cfg_reg + 3, val); + val = bytein(cs->hw.avm.cfg_reg + 2); + printk(KERN_INFO "AVM A1: Byte at %x is %x\n", + cs->hw.avm.cfg_reg + 2, val); + val = bytein(cs->hw.avm.cfg_reg); + printk(KERN_INFO "AVM A1: Byte at %x is %x\n", + cs->hw.avm.cfg_reg, val); + + printk(KERN_INFO "HiSax: AVM A1 config irq:%d cfg:0x%X\n", + cs->irq, + cs->hw.avm.cfg_reg); + printk(KERN_INFO + "HiSax: isac:0x%X/0x%X\n", + cs->hw.avm.isac + 32, cs->hw.avm.isacfifo); + printk(KERN_INFO + "HiSax: hscx A:0x%X/0x%X hscx B:0x%X/0x%X\n", + cs->hw.avm.hscx[0] + 32, cs->hw.avm.hscxfifo[0], + cs->hw.avm.hscx[1] + 32, cs->hw.avm.hscxfifo[1]); + + cs->readisac = &ReadISAC; + cs->writeisac = &WriteISAC; + cs->readisacfifo = &ReadISACfifo; + cs->writeisacfifo = &WriteISACfifo; + cs->BC_Read_Reg = &ReadHSCX; + cs->BC_Write_Reg = &WriteHSCX; + cs->BC_Send_Data = &hscx_fill_fifo; + setup_isac(cs); + cs->cardmsg = &AVM_card_msg; + cs->irq_func = &avm_a1_interrupt; + ISACVersion(cs, "AVM A1:"); + if (HscxVersion(cs, "AVM A1:")) { + printk(KERN_WARNING + "AVM A1: wrong HSCX versions check IO address\n"); + release_ioregs(cs, 0x3f); + return (0); + } + return (1); +} diff --git a/drivers/isdn/hisax/avm_a1p.c b/drivers/isdn/hisax/avm_a1p.c new file mode 100644 index 000000000..bc52d54ff --- /dev/null +++ b/drivers/isdn/hisax/avm_a1p.c @@ -0,0 +1,267 @@ +/* $Id: avm_a1p.c,v 2.9.2.5 2004/01/24 20:47:19 keil Exp $ + * + * low level stuff for the following AVM cards: + * A1 PCMCIA + * FRITZ!Card PCMCIA + * FRITZ!Card PCMCIA 2.0 + * + * Author Carsten Paeth + * Copyright by Carsten Paeth <calle@calle.de> + * + * This software may be used and distributed according to the terms + * of the GNU General Public License, incorporated herein by reference. + * + */ + +#include <linux/init.h> +#include "hisax.h" +#include "isac.h" +#include "hscx.h" +#include "isdnl1.h" + +/* register offsets */ +#define ADDRREG_OFFSET 0x02 +#define DATAREG_OFFSET 0x03 +#define ASL0_OFFSET 0x04 +#define ASL1_OFFSET 0x05 +#define MODREG_OFFSET 0x06 +#define VERREG_OFFSET 0x07 + +/* address offsets */ +#define ISAC_FIFO_OFFSET 0x00 +#define ISAC_REG_OFFSET 0x20 +#define HSCX_CH_DIFF 0x40 +#define HSCX_FIFO_OFFSET 0x80 +#define HSCX_REG_OFFSET 0xa0 + +/* read bits ASL0 */ +#define ASL0_R_TIMER 0x10 /* active low */ +#define ASL0_R_ISAC 0x20 /* active low */ +#define ASL0_R_HSCX 0x40 /* active low */ +#define ASL0_R_TESTBIT 0x80 +#define ASL0_R_IRQPENDING (ASL0_R_ISAC | ASL0_R_HSCX | ASL0_R_TIMER) + +/* write bits ASL0 */ +#define ASL0_W_RESET 0x01 +#define ASL0_W_TDISABLE 0x02 +#define ASL0_W_TRESET 0x04 +#define ASL0_W_IRQENABLE 0x08 +#define ASL0_W_TESTBIT 0x80 + +/* write bits ASL1 */ +#define ASL1_W_LED0 0x10 +#define ASL1_W_LED1 0x20 +#define ASL1_W_ENABLE_S0 0xC0 + +#define byteout(addr, val) outb(val, addr) +#define bytein(addr) inb(addr) + +static const char *avm_revision = "$Revision: 2.9.2.5 $"; + +static inline u_char +ReadISAC(struct IsdnCardState *cs, u_char offset) +{ + u_char ret; + + offset -= 0x20; + byteout(cs->hw.avm.cfg_reg + ADDRREG_OFFSET, ISAC_REG_OFFSET + offset); + ret = bytein(cs->hw.avm.cfg_reg + DATAREG_OFFSET); + return ret; +} + +static inline void +WriteISAC(struct IsdnCardState *cs, u_char offset, u_char value) +{ + offset -= 0x20; + byteout(cs->hw.avm.cfg_reg + ADDRREG_OFFSET, ISAC_REG_OFFSET + offset); + byteout(cs->hw.avm.cfg_reg + DATAREG_OFFSET, value); +} + +static inline void +ReadISACfifo(struct IsdnCardState *cs, u_char *data, int size) +{ + byteout(cs->hw.avm.cfg_reg + ADDRREG_OFFSET, ISAC_FIFO_OFFSET); + insb(cs->hw.avm.cfg_reg + DATAREG_OFFSET, data, size); +} + +static inline void +WriteISACfifo(struct IsdnCardState *cs, u_char *data, int size) +{ + byteout(cs->hw.avm.cfg_reg + ADDRREG_OFFSET, ISAC_FIFO_OFFSET); + outsb(cs->hw.avm.cfg_reg + DATAREG_OFFSET, data, size); +} + +static inline u_char +ReadHSCX(struct IsdnCardState *cs, int hscx, u_char offset) +{ + u_char ret; + + offset -= 0x20; + byteout(cs->hw.avm.cfg_reg + ADDRREG_OFFSET, + HSCX_REG_OFFSET + hscx * HSCX_CH_DIFF + offset); + ret = bytein(cs->hw.avm.cfg_reg + DATAREG_OFFSET); + return ret; +} + +static inline void +WriteHSCX(struct IsdnCardState *cs, int hscx, u_char offset, u_char value) +{ + offset -= 0x20; + byteout(cs->hw.avm.cfg_reg + ADDRREG_OFFSET, + HSCX_REG_OFFSET + hscx * HSCX_CH_DIFF + offset); + byteout(cs->hw.avm.cfg_reg + DATAREG_OFFSET, value); +} + +static inline void +ReadHSCXfifo(struct IsdnCardState *cs, int hscx, u_char *data, int size) +{ + byteout(cs->hw.avm.cfg_reg + ADDRREG_OFFSET, + HSCX_FIFO_OFFSET + hscx * HSCX_CH_DIFF); + insb(cs->hw.avm.cfg_reg + DATAREG_OFFSET, data, size); +} + +static inline void +WriteHSCXfifo(struct IsdnCardState *cs, int hscx, u_char *data, int size) +{ + byteout(cs->hw.avm.cfg_reg + ADDRREG_OFFSET, + HSCX_FIFO_OFFSET + hscx * HSCX_CH_DIFF); + outsb(cs->hw.avm.cfg_reg + DATAREG_OFFSET, data, size); +} + +/* + * fast interrupt HSCX stuff goes here + */ + +#define READHSCX(cs, nr, reg) ReadHSCX(cs, nr, reg) +#define WRITEHSCX(cs, nr, reg, data) WriteHSCX(cs, nr, reg, data) +#define READHSCXFIFO(cs, nr, ptr, cnt) ReadHSCXfifo(cs, nr, ptr, cnt) +#define WRITEHSCXFIFO(cs, nr, ptr, cnt) WriteHSCXfifo(cs, nr, ptr, cnt) + +#include "hscx_irq.c" + +static irqreturn_t +avm_a1p_interrupt(int intno, void *dev_id) +{ + struct IsdnCardState *cs = dev_id; + u_char val, sval; + u_long flags; + + spin_lock_irqsave(&cs->lock, flags); + while ((sval = (~bytein(cs->hw.avm.cfg_reg + ASL0_OFFSET) & ASL0_R_IRQPENDING))) { + if (cs->debug & L1_DEB_INTSTAT) + debugl1(cs, "avm IntStatus %x", sval); + if (sval & ASL0_R_HSCX) { + val = ReadHSCX(cs, 1, HSCX_ISTA); + if (val) + hscx_int_main(cs, val); + } + if (sval & ASL0_R_ISAC) { + val = ReadISAC(cs, ISAC_ISTA); + if (val) + isac_interrupt(cs, val); + } + } + WriteHSCX(cs, 0, HSCX_MASK, 0xff); + WriteHSCX(cs, 1, HSCX_MASK, 0xff); + WriteISAC(cs, ISAC_MASK, 0xff); + WriteISAC(cs, ISAC_MASK, 0x00); + WriteHSCX(cs, 0, HSCX_MASK, 0x00); + WriteHSCX(cs, 1, HSCX_MASK, 0x00); + spin_unlock_irqrestore(&cs->lock, flags); + return IRQ_HANDLED; +} + +static int +AVM_card_msg(struct IsdnCardState *cs, int mt, void *arg) +{ + u_long flags; + + switch (mt) { + case CARD_RESET: + spin_lock_irqsave(&cs->lock, flags); + byteout(cs->hw.avm.cfg_reg + ASL0_OFFSET, 0x00); + HZDELAY(HZ / 5 + 1); + byteout(cs->hw.avm.cfg_reg + ASL0_OFFSET, ASL0_W_RESET); + HZDELAY(HZ / 5 + 1); + byteout(cs->hw.avm.cfg_reg + ASL0_OFFSET, 0x00); + spin_unlock_irqrestore(&cs->lock, flags); + return 0; + + case CARD_RELEASE: + /* free_irq is done in HiSax_closecard(). */ + /* free_irq(cs->irq, cs); */ + return 0; + + case CARD_INIT: + spin_lock_irqsave(&cs->lock, flags); + byteout(cs->hw.avm.cfg_reg + ASL0_OFFSET, ASL0_W_TDISABLE | ASL0_W_TRESET | ASL0_W_IRQENABLE); + clear_pending_isac_ints(cs); + clear_pending_hscx_ints(cs); + inithscxisac(cs, 1); + inithscxisac(cs, 2); + spin_unlock_irqrestore(&cs->lock, flags); + return 0; + + case CARD_TEST: + /* we really don't need it for the PCMCIA Version */ + return 0; + + default: + /* all card drivers ignore others, so we do the same */ + return 0; + } + return 0; +} + +int setup_avm_a1_pcmcia(struct IsdnCard *card) +{ + u_char model, vers; + struct IsdnCardState *cs = card->cs; + char tmp[64]; + + + strcpy(tmp, avm_revision); + printk(KERN_INFO "HiSax: AVM A1 PCMCIA driver Rev. %s\n", + HiSax_getrev(tmp)); + if (cs->typ != ISDN_CTYPE_A1_PCMCIA) + return (0); + + cs->hw.avm.cfg_reg = card->para[1]; + cs->irq = card->para[0]; + + + byteout(cs->hw.avm.cfg_reg + ASL1_OFFSET, ASL1_W_ENABLE_S0); + byteout(cs->hw.avm.cfg_reg + ASL0_OFFSET, 0x00); + HZDELAY(HZ / 5 + 1); + byteout(cs->hw.avm.cfg_reg + ASL0_OFFSET, ASL0_W_RESET); + HZDELAY(HZ / 5 + 1); + byteout(cs->hw.avm.cfg_reg + ASL0_OFFSET, 0x00); + + byteout(cs->hw.avm.cfg_reg + ASL0_OFFSET, ASL0_W_TDISABLE | ASL0_W_TRESET); + + model = bytein(cs->hw.avm.cfg_reg + MODREG_OFFSET); + vers = bytein(cs->hw.avm.cfg_reg + VERREG_OFFSET); + + printk(KERN_INFO "AVM A1 PCMCIA: io 0x%x irq %d model %d version %d\n", + cs->hw.avm.cfg_reg, cs->irq, model, vers); + + setup_isac(cs); + cs->readisac = &ReadISAC; + cs->writeisac = &WriteISAC; + cs->readisacfifo = &ReadISACfifo; + cs->writeisacfifo = &WriteISACfifo; + cs->BC_Read_Reg = &ReadHSCX; + cs->BC_Write_Reg = &WriteHSCX; + cs->BC_Send_Data = &hscx_fill_fifo; + cs->cardmsg = &AVM_card_msg; + cs->irq_flags = IRQF_SHARED; + cs->irq_func = &avm_a1p_interrupt; + + ISACVersion(cs, "AVM A1 PCMCIA:"); + if (HscxVersion(cs, "AVM A1 PCMCIA:")) { + printk(KERN_WARNING + "AVM A1 PCMCIA: wrong HSCX versions check IO address\n"); + return (0); + } + return (1); +} diff --git a/drivers/isdn/hisax/avm_pci.c b/drivers/isdn/hisax/avm_pci.c new file mode 100644 index 000000000..b161456c9 --- /dev/null +++ b/drivers/isdn/hisax/avm_pci.c @@ -0,0 +1,904 @@ +/* $Id: avm_pci.c,v 1.29.2.4 2004/02/11 13:21:32 keil Exp $ + * + * low level stuff for AVM Fritz!PCI and ISA PnP isdn cards + * + * Author Karsten Keil + * Copyright by Karsten Keil <keil@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 AVM, Berlin for information + * + */ + +#include <linux/init.h> +#include "hisax.h" +#include "isac.h" +#include "isdnl1.h" +#include <linux/pci.h> +#include <linux/slab.h> +#include <linux/isapnp.h> +#include <linux/interrupt.h> + +static const char *avm_pci_rev = "$Revision: 1.29.2.4 $"; + +#define AVM_FRITZ_PCI 1 +#define AVM_FRITZ_PNP 2 + +#define HDLC_FIFO 0x0 +#define HDLC_STATUS 0x4 + +#define AVM_HDLC_1 0x00 +#define AVM_HDLC_2 0x01 +#define AVM_ISAC_FIFO 0x02 +#define AVM_ISAC_REG_LOW 0x04 +#define AVM_ISAC_REG_HIGH 0x06 + +#define AVM_STATUS0_IRQ_ISAC 0x01 +#define AVM_STATUS0_IRQ_HDLC 0x02 +#define AVM_STATUS0_IRQ_TIMER 0x04 +#define AVM_STATUS0_IRQ_MASK 0x07 + +#define AVM_STATUS0_RESET 0x01 +#define AVM_STATUS0_DIS_TIMER 0x02 +#define AVM_STATUS0_RES_TIMER 0x04 +#define AVM_STATUS0_ENA_IRQ 0x08 +#define AVM_STATUS0_TESTBIT 0x10 + +#define AVM_STATUS1_INT_SEL 0x0f +#define AVM_STATUS1_ENA_IOM 0x80 + +#define HDLC_MODE_ITF_FLG 0x01 +#define HDLC_MODE_TRANS 0x02 +#define HDLC_MODE_CCR_7 0x04 +#define HDLC_MODE_CCR_16 0x08 +#define HDLC_MODE_TESTLOOP 0x80 + +#define HDLC_INT_XPR 0x80 +#define HDLC_INT_XDU 0x40 +#define HDLC_INT_RPR 0x20 +#define HDLC_INT_MASK 0xE0 + +#define HDLC_STAT_RME 0x01 +#define HDLC_STAT_RDO 0x10 +#define HDLC_STAT_CRCVFRRAB 0x0E +#define HDLC_STAT_CRCVFR 0x06 +#define HDLC_STAT_RML_MASK 0x3f00 + +#define HDLC_CMD_XRS 0x80 +#define HDLC_CMD_XME 0x01 +#define HDLC_CMD_RRS 0x20 +#define HDLC_CMD_XML_MASK 0x3f00 + + +/* Interface functions */ + +static u_char +ReadISAC(struct IsdnCardState *cs, u_char offset) +{ + register u_char idx = (offset > 0x2f) ? AVM_ISAC_REG_HIGH : AVM_ISAC_REG_LOW; + register u_char val; + + outb(idx, cs->hw.avm.cfg_reg + 4); + val = inb(cs->hw.avm.isac + (offset & 0xf)); + return (val); +} + +static void +WriteISAC(struct IsdnCardState *cs, u_char offset, u_char value) +{ + register u_char idx = (offset > 0x2f) ? AVM_ISAC_REG_HIGH : AVM_ISAC_REG_LOW; + + outb(idx, cs->hw.avm.cfg_reg + 4); + outb(value, cs->hw.avm.isac + (offset & 0xf)); +} + +static void +ReadISACfifo(struct IsdnCardState *cs, u_char *data, int size) +{ + outb(AVM_ISAC_FIFO, cs->hw.avm.cfg_reg + 4); + insb(cs->hw.avm.isac, data, size); +} + +static void +WriteISACfifo(struct IsdnCardState *cs, u_char *data, int size) +{ + outb(AVM_ISAC_FIFO, cs->hw.avm.cfg_reg + 4); + outsb(cs->hw.avm.isac, data, size); +} + +static inline u_int +ReadHDLCPCI(struct IsdnCardState *cs, int chan, u_char offset) +{ + register u_int idx = chan ? AVM_HDLC_2 : AVM_HDLC_1; + register u_int val; + + outl(idx, cs->hw.avm.cfg_reg + 4); + val = inl(cs->hw.avm.isac + offset); + return (val); +} + +static inline void +WriteHDLCPCI(struct IsdnCardState *cs, int chan, u_char offset, u_int value) +{ + register u_int idx = chan ? AVM_HDLC_2 : AVM_HDLC_1; + + outl(idx, cs->hw.avm.cfg_reg + 4); + outl(value, cs->hw.avm.isac + offset); +} + +static inline u_char +ReadHDLCPnP(struct IsdnCardState *cs, int chan, u_char offset) +{ + register u_char idx = chan ? AVM_HDLC_2 : AVM_HDLC_1; + register u_char val; + + outb(idx, cs->hw.avm.cfg_reg + 4); + val = inb(cs->hw.avm.isac + offset); + return (val); +} + +static inline void +WriteHDLCPnP(struct IsdnCardState *cs, int chan, u_char offset, u_char value) +{ + register u_char idx = chan ? AVM_HDLC_2 : AVM_HDLC_1; + + outb(idx, cs->hw.avm.cfg_reg + 4); + outb(value, cs->hw.avm.isac + offset); +} + +static u_char +ReadHDLC_s(struct IsdnCardState *cs, int chan, u_char offset) +{ + return (0xff & ReadHDLCPCI(cs, chan, offset)); +} + +static void +WriteHDLC_s(struct IsdnCardState *cs, int chan, u_char offset, u_char value) +{ + WriteHDLCPCI(cs, chan, offset, value); +} + +static inline +struct BCState *Sel_BCS(struct IsdnCardState *cs, int channel) +{ + if (cs->bcs[0].mode && (cs->bcs[0].channel == channel)) + return (&cs->bcs[0]); + else if (cs->bcs[1].mode && (cs->bcs[1].channel == channel)) + return (&cs->bcs[1]); + else + return (NULL); +} + +static void +write_ctrl(struct BCState *bcs, int which) { + + if (bcs->cs->debug & L1_DEB_HSCX) + debugl1(bcs->cs, "hdlc %c wr%x ctrl %x", + 'A' + bcs->channel, which, bcs->hw.hdlc.ctrl.ctrl); + if (bcs->cs->subtyp == AVM_FRITZ_PCI) { + WriteHDLCPCI(bcs->cs, bcs->channel, HDLC_STATUS, bcs->hw.hdlc.ctrl.ctrl); + } else { + if (which & 4) + WriteHDLCPnP(bcs->cs, bcs->channel, HDLC_STATUS + 2, + bcs->hw.hdlc.ctrl.sr.mode); + if (which & 2) + WriteHDLCPnP(bcs->cs, bcs->channel, HDLC_STATUS + 1, + bcs->hw.hdlc.ctrl.sr.xml); + if (which & 1) + WriteHDLCPnP(bcs->cs, bcs->channel, HDLC_STATUS, + bcs->hw.hdlc.ctrl.sr.cmd); + } +} + +static void +modehdlc(struct BCState *bcs, int mode, int bc) +{ + struct IsdnCardState *cs = bcs->cs; + int hdlc = bcs->channel; + + if (cs->debug & L1_DEB_HSCX) + debugl1(cs, "hdlc %c mode %d --> %d ichan %d --> %d", + 'A' + hdlc, bcs->mode, mode, hdlc, bc); + bcs->hw.hdlc.ctrl.ctrl = 0; + switch (mode) { + case (-1): /* used for init */ + bcs->mode = 1; + bcs->channel = bc; + bc = 0; + /* fall through */ + case (L1_MODE_NULL): + if (bcs->mode == L1_MODE_NULL) + return; + bcs->hw.hdlc.ctrl.sr.cmd = HDLC_CMD_XRS | HDLC_CMD_RRS; + bcs->hw.hdlc.ctrl.sr.mode = HDLC_MODE_TRANS; + write_ctrl(bcs, 5); + bcs->mode = L1_MODE_NULL; + bcs->channel = bc; + break; + case (L1_MODE_TRANS): + bcs->mode = mode; + bcs->channel = bc; + bcs->hw.hdlc.ctrl.sr.cmd = HDLC_CMD_XRS | HDLC_CMD_RRS; + bcs->hw.hdlc.ctrl.sr.mode = HDLC_MODE_TRANS; + write_ctrl(bcs, 5); + bcs->hw.hdlc.ctrl.sr.cmd = HDLC_CMD_XRS; + write_ctrl(bcs, 1); + bcs->hw.hdlc.ctrl.sr.cmd = 0; + schedule_event(bcs, B_XMTBUFREADY); + break; + case (L1_MODE_HDLC): + bcs->mode = mode; + bcs->channel = bc; + bcs->hw.hdlc.ctrl.sr.cmd = HDLC_CMD_XRS | HDLC_CMD_RRS; + bcs->hw.hdlc.ctrl.sr.mode = HDLC_MODE_ITF_FLG; + write_ctrl(bcs, 5); + bcs->hw.hdlc.ctrl.sr.cmd = HDLC_CMD_XRS; + write_ctrl(bcs, 1); + bcs->hw.hdlc.ctrl.sr.cmd = 0; + schedule_event(bcs, B_XMTBUFREADY); + break; + } +} + +static inline void +hdlc_empty_fifo(struct BCState *bcs, int count) +{ + register u_int *ptr; + u_char *p; + u_char idx = bcs->channel ? AVM_HDLC_2 : AVM_HDLC_1; + int cnt = 0; + struct IsdnCardState *cs = bcs->cs; + + if ((cs->debug & L1_DEB_HSCX) && !(cs->debug & L1_DEB_HSCX_FIFO)) + debugl1(cs, "hdlc_empty_fifo %d", count); + if (bcs->hw.hdlc.rcvidx + count > HSCX_BUFMAX) { + if (cs->debug & L1_DEB_WARN) + debugl1(cs, "hdlc_empty_fifo: incoming packet too large"); + return; + } + p = bcs->hw.hdlc.rcvbuf + bcs->hw.hdlc.rcvidx; + ptr = (u_int *)p; + bcs->hw.hdlc.rcvidx += count; + if (cs->subtyp == AVM_FRITZ_PCI) { + outl(idx, cs->hw.avm.cfg_reg + 4); + while (cnt < count) { +#ifdef __powerpc__ + *ptr++ = in_be32((unsigned *)(cs->hw.avm.isac + _IO_BASE)); +#else + *ptr++ = inl(cs->hw.avm.isac); +#endif /* __powerpc__ */ + cnt += 4; + } + } else { + outb(idx, cs->hw.avm.cfg_reg + 4); + while (cnt < count) { + *p++ = inb(cs->hw.avm.isac); + cnt++; + } + } + if (cs->debug & L1_DEB_HSCX_FIFO) { + char *t = bcs->blog; + + if (cs->subtyp == AVM_FRITZ_PNP) + p = (u_char *) ptr; + t += sprintf(t, "hdlc_empty_fifo %c cnt %d", + bcs->channel ? 'B' : 'A', count); + QuickHex(t, p, count); + debugl1(cs, "%s", bcs->blog); + } +} + +static inline void +hdlc_fill_fifo(struct BCState *bcs) +{ + struct IsdnCardState *cs = bcs->cs; + int count, cnt = 0; + int fifo_size = 32; + u_char *p; + u_int *ptr; + + if ((cs->debug & L1_DEB_HSCX) && !(cs->debug & L1_DEB_HSCX_FIFO)) + debugl1(cs, "hdlc_fill_fifo"); + if (!bcs->tx_skb) + return; + if (bcs->tx_skb->len <= 0) + return; + + bcs->hw.hdlc.ctrl.sr.cmd &= ~HDLC_CMD_XME; + if (bcs->tx_skb->len > fifo_size) { + count = fifo_size; + } else { + count = bcs->tx_skb->len; + if (bcs->mode != L1_MODE_TRANS) + bcs->hw.hdlc.ctrl.sr.cmd |= HDLC_CMD_XME; + } + if ((cs->debug & L1_DEB_HSCX) && !(cs->debug & L1_DEB_HSCX_FIFO)) + debugl1(cs, "hdlc_fill_fifo %d/%u", count, bcs->tx_skb->len); + p = bcs->tx_skb->data; + ptr = (u_int *)p; + skb_pull(bcs->tx_skb, count); + bcs->tx_cnt -= count; + bcs->hw.hdlc.count += count; + bcs->hw.hdlc.ctrl.sr.xml = ((count == fifo_size) ? 0 : count); + write_ctrl(bcs, 3); /* sets the correct index too */ + if (cs->subtyp == AVM_FRITZ_PCI) { + while (cnt < count) { +#ifdef __powerpc__ + out_be32((unsigned *)(cs->hw.avm.isac + _IO_BASE), *ptr++); +#else + outl(*ptr++, cs->hw.avm.isac); +#endif /* __powerpc__ */ + cnt += 4; + } + } else { + while (cnt < count) { + outb(*p++, cs->hw.avm.isac); + cnt++; + } + } + if (cs->debug & L1_DEB_HSCX_FIFO) { + char *t = bcs->blog; + + if (cs->subtyp == AVM_FRITZ_PNP) + p = (u_char *) ptr; + t += sprintf(t, "hdlc_fill_fifo %c cnt %d", + bcs->channel ? 'B' : 'A', count); + QuickHex(t, p, count); + debugl1(cs, "%s", bcs->blog); + } +} + +static void +HDLC_irq(struct BCState *bcs, u_int stat) { + int len; + struct sk_buff *skb; + + if (bcs->cs->debug & L1_DEB_HSCX) + debugl1(bcs->cs, "ch%d stat %#x", bcs->channel, stat); + if (stat & HDLC_INT_RPR) { + if (stat & HDLC_STAT_RDO) { + if (bcs->cs->debug & L1_DEB_HSCX) + debugl1(bcs->cs, "RDO"); + else + debugl1(bcs->cs, "ch%d stat %#x", bcs->channel, stat); + bcs->hw.hdlc.ctrl.sr.xml = 0; + bcs->hw.hdlc.ctrl.sr.cmd |= HDLC_CMD_RRS; + write_ctrl(bcs, 1); + bcs->hw.hdlc.ctrl.sr.cmd &= ~HDLC_CMD_RRS; + write_ctrl(bcs, 1); + bcs->hw.hdlc.rcvidx = 0; + } else { + if (!(len = (stat & HDLC_STAT_RML_MASK) >> 8)) + len = 32; + hdlc_empty_fifo(bcs, len); + if ((stat & HDLC_STAT_RME) || (bcs->mode == L1_MODE_TRANS)) { + if (((stat & HDLC_STAT_CRCVFRRAB) == HDLC_STAT_CRCVFR) || + (bcs->mode == L1_MODE_TRANS)) { + if (!(skb = dev_alloc_skb(bcs->hw.hdlc.rcvidx))) + printk(KERN_WARNING "HDLC: receive out of memory\n"); + else { + skb_put_data(skb, + bcs->hw.hdlc.rcvbuf, + bcs->hw.hdlc.rcvidx); + skb_queue_tail(&bcs->rqueue, skb); + } + bcs->hw.hdlc.rcvidx = 0; + schedule_event(bcs, B_RCVBUFREADY); + } else { + if (bcs->cs->debug & L1_DEB_HSCX) + debugl1(bcs->cs, "invalid frame"); + else + debugl1(bcs->cs, "ch%d invalid frame %#x", bcs->channel, stat); + bcs->hw.hdlc.rcvidx = 0; + } + } + } + } + if (stat & HDLC_INT_XDU) { + /* Here we lost an TX interrupt, so + * restart transmitting the whole frame. + */ + if (bcs->tx_skb) { + skb_push(bcs->tx_skb, bcs->hw.hdlc.count); + bcs->tx_cnt += bcs->hw.hdlc.count; + bcs->hw.hdlc.count = 0; + if (bcs->cs->debug & L1_DEB_WARN) + debugl1(bcs->cs, "ch%d XDU", bcs->channel); + } else if (bcs->cs->debug & L1_DEB_WARN) + debugl1(bcs->cs, "ch%d XDU without skb", bcs->channel); + bcs->hw.hdlc.ctrl.sr.xml = 0; + bcs->hw.hdlc.ctrl.sr.cmd |= HDLC_CMD_XRS; + write_ctrl(bcs, 1); + bcs->hw.hdlc.ctrl.sr.cmd &= ~HDLC_CMD_XRS; + write_ctrl(bcs, 1); + hdlc_fill_fifo(bcs); + } else if (stat & HDLC_INT_XPR) { + if (bcs->tx_skb) { + if (bcs->tx_skb->len) { + hdlc_fill_fifo(bcs); + return; + } else { + if (test_bit(FLG_LLI_L1WAKEUP, &bcs->st->lli.flag) && + (PACKET_NOACK != bcs->tx_skb->pkt_type)) { + u_long flags; + spin_lock_irqsave(&bcs->aclock, flags); + bcs->ackcnt += bcs->hw.hdlc.count; + spin_unlock_irqrestore(&bcs->aclock, flags); + schedule_event(bcs, B_ACKPENDING); + } + dev_kfree_skb_irq(bcs->tx_skb); + bcs->hw.hdlc.count = 0; + bcs->tx_skb = NULL; + } + } + if ((bcs->tx_skb = skb_dequeue(&bcs->squeue))) { + bcs->hw.hdlc.count = 0; + test_and_set_bit(BC_FLG_BUSY, &bcs->Flag); + hdlc_fill_fifo(bcs); + } else { + test_and_clear_bit(BC_FLG_BUSY, &bcs->Flag); + schedule_event(bcs, B_XMTBUFREADY); + } + } +} + +static inline void +HDLC_irq_main(struct IsdnCardState *cs) +{ + u_int stat; + struct BCState *bcs; + + if (cs->subtyp == AVM_FRITZ_PCI) { + stat = ReadHDLCPCI(cs, 0, HDLC_STATUS); + } else { + stat = ReadHDLCPnP(cs, 0, HDLC_STATUS); + if (stat & HDLC_INT_RPR) + stat |= (ReadHDLCPnP(cs, 0, HDLC_STATUS + 1)) << 8; + } + if (stat & HDLC_INT_MASK) { + if (!(bcs = Sel_BCS(cs, 0))) { + if (cs->debug) + debugl1(cs, "hdlc spurious channel 0 IRQ"); + } else + HDLC_irq(bcs, stat); + } + if (cs->subtyp == AVM_FRITZ_PCI) { + stat = ReadHDLCPCI(cs, 1, HDLC_STATUS); + } else { + stat = ReadHDLCPnP(cs, 1, HDLC_STATUS); + if (stat & HDLC_INT_RPR) + stat |= (ReadHDLCPnP(cs, 1, HDLC_STATUS + 1)) << 8; + } + if (stat & HDLC_INT_MASK) { + if (!(bcs = Sel_BCS(cs, 1))) { + if (cs->debug) + debugl1(cs, "hdlc spurious channel 1 IRQ"); + } else + HDLC_irq(bcs, stat); + } +} + +static void +hdlc_l2l1(struct PStack *st, int pr, void *arg) +{ + struct BCState *bcs = st->l1.bcs; + struct sk_buff *skb = arg; + u_long flags; + + switch (pr) { + case (PH_DATA | REQUEST): + spin_lock_irqsave(&bcs->cs->lock, flags); + if (bcs->tx_skb) { + skb_queue_tail(&bcs->squeue, skb); + } else { + bcs->tx_skb = skb; + test_and_set_bit(BC_FLG_BUSY, &bcs->Flag); + bcs->hw.hdlc.count = 0; + bcs->cs->BC_Send_Data(bcs); + } + spin_unlock_irqrestore(&bcs->cs->lock, flags); + break; + case (PH_PULL | INDICATION): + spin_lock_irqsave(&bcs->cs->lock, flags); + if (bcs->tx_skb) { + printk(KERN_WARNING "hdlc_l2l1: this shouldn't happen\n"); + } else { + test_and_set_bit(BC_FLG_BUSY, &bcs->Flag); + bcs->tx_skb = skb; + bcs->hw.hdlc.count = 0; + bcs->cs->BC_Send_Data(bcs); + } + spin_unlock_irqrestore(&bcs->cs->lock, flags); + break; + case (PH_PULL | REQUEST): + if (!bcs->tx_skb) { + test_and_clear_bit(FLG_L1_PULL_REQ, &st->l1.Flags); + st->l1.l1l2(st, PH_PULL | CONFIRM, NULL); + } else + test_and_set_bit(FLG_L1_PULL_REQ, &st->l1.Flags); + break; + case (PH_ACTIVATE | REQUEST): + spin_lock_irqsave(&bcs->cs->lock, flags); + test_and_set_bit(BC_FLG_ACTIV, &bcs->Flag); + modehdlc(bcs, st->l1.mode, st->l1.bc); + spin_unlock_irqrestore(&bcs->cs->lock, flags); + l1_msg_b(st, pr, arg); + break; + case (PH_DEACTIVATE | REQUEST): + l1_msg_b(st, pr, arg); + break; + case (PH_DEACTIVATE | CONFIRM): + spin_lock_irqsave(&bcs->cs->lock, flags); + test_and_clear_bit(BC_FLG_ACTIV, &bcs->Flag); + test_and_clear_bit(BC_FLG_BUSY, &bcs->Flag); + modehdlc(bcs, 0, st->l1.bc); + spin_unlock_irqrestore(&bcs->cs->lock, flags); + st->l1.l1l2(st, PH_DEACTIVATE | CONFIRM, NULL); + break; + } +} + +static void +close_hdlcstate(struct BCState *bcs) +{ + modehdlc(bcs, 0, 0); + if (test_and_clear_bit(BC_FLG_INIT, &bcs->Flag)) { + kfree(bcs->hw.hdlc.rcvbuf); + bcs->hw.hdlc.rcvbuf = NULL; + kfree(bcs->blog); + bcs->blog = NULL; + skb_queue_purge(&bcs->rqueue); + skb_queue_purge(&bcs->squeue); + if (bcs->tx_skb) { + dev_kfree_skb_any(bcs->tx_skb); + bcs->tx_skb = NULL; + test_and_clear_bit(BC_FLG_BUSY, &bcs->Flag); + } + } +} + +static int +open_hdlcstate(struct IsdnCardState *cs, struct BCState *bcs) +{ + if (!test_and_set_bit(BC_FLG_INIT, &bcs->Flag)) { + if (!(bcs->hw.hdlc.rcvbuf = kmalloc(HSCX_BUFMAX, GFP_ATOMIC))) { + printk(KERN_WARNING + "HiSax: No memory for hdlc.rcvbuf\n"); + return (1); + } + if (!(bcs->blog = kmalloc(MAX_BLOG_SPACE, GFP_ATOMIC))) { + printk(KERN_WARNING + "HiSax: No memory for bcs->blog\n"); + test_and_clear_bit(BC_FLG_INIT, &bcs->Flag); + kfree(bcs->hw.hdlc.rcvbuf); + bcs->hw.hdlc.rcvbuf = NULL; + return (2); + } + skb_queue_head_init(&bcs->rqueue); + skb_queue_head_init(&bcs->squeue); + } + bcs->tx_skb = NULL; + test_and_clear_bit(BC_FLG_BUSY, &bcs->Flag); + bcs->event = 0; + bcs->hw.hdlc.rcvidx = 0; + bcs->tx_cnt = 0; + return (0); +} + +static int +setstack_hdlc(struct PStack *st, struct BCState *bcs) +{ + bcs->channel = st->l1.bc; + if (open_hdlcstate(st->l1.hardware, bcs)) + return (-1); + st->l1.bcs = bcs; + st->l2.l2l1 = hdlc_l2l1; + setstack_manager(st); + bcs->st = st; + setstack_l1_B(st); + return (0); +} + +#if 0 +void __init +clear_pending_hdlc_ints(struct IsdnCardState *cs) +{ + u_int val; + + if (cs->subtyp == AVM_FRITZ_PCI) { + val = ReadHDLCPCI(cs, 0, HDLC_STATUS); + debugl1(cs, "HDLC 1 STA %x", val); + val = ReadHDLCPCI(cs, 1, HDLC_STATUS); + debugl1(cs, "HDLC 2 STA %x", val); + } else { + val = ReadHDLCPnP(cs, 0, HDLC_STATUS); + debugl1(cs, "HDLC 1 STA %x", val); + val = ReadHDLCPnP(cs, 0, HDLC_STATUS + 1); + debugl1(cs, "HDLC 1 RML %x", val); + val = ReadHDLCPnP(cs, 0, HDLC_STATUS + 2); + debugl1(cs, "HDLC 1 MODE %x", val); + val = ReadHDLCPnP(cs, 0, HDLC_STATUS + 3); + debugl1(cs, "HDLC 1 VIN %x", val); + val = ReadHDLCPnP(cs, 1, HDLC_STATUS); + debugl1(cs, "HDLC 2 STA %x", val); + val = ReadHDLCPnP(cs, 1, HDLC_STATUS + 1); + debugl1(cs, "HDLC 2 RML %x", val); + val = ReadHDLCPnP(cs, 1, HDLC_STATUS + 2); + debugl1(cs, "HDLC 2 MODE %x", val); + val = ReadHDLCPnP(cs, 1, HDLC_STATUS + 3); + debugl1(cs, "HDLC 2 VIN %x", val); + } +} +#endif /* 0 */ + +static void +inithdlc(struct IsdnCardState *cs) +{ + cs->bcs[0].BC_SetStack = setstack_hdlc; + cs->bcs[1].BC_SetStack = setstack_hdlc; + cs->bcs[0].BC_Close = close_hdlcstate; + cs->bcs[1].BC_Close = close_hdlcstate; + modehdlc(cs->bcs, -1, 0); + modehdlc(cs->bcs + 1, -1, 1); +} + +static irqreturn_t +avm_pcipnp_interrupt(int intno, void *dev_id) +{ + struct IsdnCardState *cs = dev_id; + u_long flags; + u_char val; + u_char sval; + + spin_lock_irqsave(&cs->lock, flags); + sval = inb(cs->hw.avm.cfg_reg + 2); + if ((sval & AVM_STATUS0_IRQ_MASK) == AVM_STATUS0_IRQ_MASK) { + /* possible a shared IRQ reqest */ + spin_unlock_irqrestore(&cs->lock, flags); + return IRQ_NONE; + } + if (!(sval & AVM_STATUS0_IRQ_ISAC)) { + val = ReadISAC(cs, ISAC_ISTA); + isac_interrupt(cs, val); + } + if (!(sval & AVM_STATUS0_IRQ_HDLC)) { + HDLC_irq_main(cs); + } + WriteISAC(cs, ISAC_MASK, 0xFF); + WriteISAC(cs, ISAC_MASK, 0x0); + spin_unlock_irqrestore(&cs->lock, flags); + return IRQ_HANDLED; +} + +static void +reset_avmpcipnp(struct IsdnCardState *cs) +{ + printk(KERN_INFO "AVM PCI/PnP: reset\n"); + outb(AVM_STATUS0_RESET | AVM_STATUS0_DIS_TIMER, cs->hw.avm.cfg_reg + 2); + mdelay(10); + outb(AVM_STATUS0_DIS_TIMER | AVM_STATUS0_RES_TIMER | AVM_STATUS0_ENA_IRQ, cs->hw.avm.cfg_reg + 2); + outb(AVM_STATUS1_ENA_IOM | cs->irq, cs->hw.avm.cfg_reg + 3); + mdelay(10); + printk(KERN_INFO "AVM PCI/PnP: S1 %x\n", inb(cs->hw.avm.cfg_reg + 3)); +} + +static int +AVM_card_msg(struct IsdnCardState *cs, int mt, void *arg) +{ + u_long flags; + + switch (mt) { + case CARD_RESET: + spin_lock_irqsave(&cs->lock, flags); + reset_avmpcipnp(cs); + spin_unlock_irqrestore(&cs->lock, flags); + return (0); + case CARD_RELEASE: + outb(0, cs->hw.avm.cfg_reg + 2); + release_region(cs->hw.avm.cfg_reg, 32); + return (0); + case CARD_INIT: + spin_lock_irqsave(&cs->lock, flags); + reset_avmpcipnp(cs); + clear_pending_isac_ints(cs); + initisac(cs); + inithdlc(cs); + outb(AVM_STATUS0_DIS_TIMER | AVM_STATUS0_RES_TIMER, + cs->hw.avm.cfg_reg + 2); + WriteISAC(cs, ISAC_MASK, 0); + outb(AVM_STATUS0_DIS_TIMER | AVM_STATUS0_RES_TIMER | + AVM_STATUS0_ENA_IRQ, cs->hw.avm.cfg_reg + 2); + /* RESET Receiver and Transmitter */ + WriteISAC(cs, ISAC_CMDR, 0x41); + spin_unlock_irqrestore(&cs->lock, flags); + return (0); + case CARD_TEST: + return (0); + } + return (0); +} + +static int avm_setup_rest(struct IsdnCardState *cs) +{ + u_int val, ver; + + cs->hw.avm.isac = cs->hw.avm.cfg_reg + 0x10; + if (!request_region(cs->hw.avm.cfg_reg, 32, + (cs->subtyp == AVM_FRITZ_PCI) ? "avm PCI" : "avm PnP")) { + printk(KERN_WARNING + "HiSax: Fritz!PCI/PNP config port %x-%x already in use\n", + cs->hw.avm.cfg_reg, + cs->hw.avm.cfg_reg + 31); + return (0); + } + switch (cs->subtyp) { + case AVM_FRITZ_PCI: + val = inl(cs->hw.avm.cfg_reg); + printk(KERN_INFO "AVM PCI: stat %#x\n", val); + printk(KERN_INFO "AVM PCI: Class %X Rev %d\n", + val & 0xff, (val >> 8) & 0xff); + cs->BC_Read_Reg = &ReadHDLC_s; + cs->BC_Write_Reg = &WriteHDLC_s; + break; + case AVM_FRITZ_PNP: + val = inb(cs->hw.avm.cfg_reg); + ver = inb(cs->hw.avm.cfg_reg + 1); + printk(KERN_INFO "AVM PnP: Class %X Rev %d\n", val, ver); + cs->BC_Read_Reg = &ReadHDLCPnP; + cs->BC_Write_Reg = &WriteHDLCPnP; + break; + default: + printk(KERN_WARNING "AVM unknown subtype %d\n", cs->subtyp); + return (0); + } + printk(KERN_INFO "HiSax: %s config irq:%d base:0x%X\n", + (cs->subtyp == AVM_FRITZ_PCI) ? "AVM Fritz!PCI" : "AVM Fritz!PnP", + cs->irq, cs->hw.avm.cfg_reg); + + setup_isac(cs); + cs->readisac = &ReadISAC; + cs->writeisac = &WriteISAC; + cs->readisacfifo = &ReadISACfifo; + cs->writeisacfifo = &WriteISACfifo; + cs->BC_Send_Data = &hdlc_fill_fifo; + cs->cardmsg = &AVM_card_msg; + cs->irq_func = &avm_pcipnp_interrupt; + cs->writeisac(cs, ISAC_MASK, 0xFF); + ISACVersion(cs, (cs->subtyp == AVM_FRITZ_PCI) ? "AVM PCI:" : "AVM PnP:"); + return (1); +} + +#ifndef __ISAPNP__ + +static int avm_pnp_setup(struct IsdnCardState *cs) +{ + return (1); /* no-op: success */ +} + +#else + +static struct pnp_card *pnp_avm_c = NULL; + +static int avm_pnp_setup(struct IsdnCardState *cs) +{ + struct pnp_dev *pnp_avm_d = NULL; + + if (!isapnp_present()) + return (1); /* no-op: success */ + + if ((pnp_avm_c = pnp_find_card( + ISAPNP_VENDOR('A', 'V', 'M'), + ISAPNP_FUNCTION(0x0900), pnp_avm_c))) { + if ((pnp_avm_d = pnp_find_dev(pnp_avm_c, + ISAPNP_VENDOR('A', 'V', 'M'), + ISAPNP_FUNCTION(0x0900), pnp_avm_d))) { + int err; + + pnp_disable_dev(pnp_avm_d); + err = pnp_activate_dev(pnp_avm_d); + if (err < 0) { + printk(KERN_WARNING "%s: pnp_activate_dev ret(%d)\n", + __func__, err); + return (0); + } + cs->hw.avm.cfg_reg = + pnp_port_start(pnp_avm_d, 0); + cs->irq = pnp_irq(pnp_avm_d, 0); + if (cs->irq == -1) { + printk(KERN_ERR "FritzPnP:No IRQ\n"); + return (0); + } + if (!cs->hw.avm.cfg_reg) { + printk(KERN_ERR "FritzPnP:No IO address\n"); + return (0); + } + cs->subtyp = AVM_FRITZ_PNP; + + return (2); /* goto 'ready' label */ + } + } + + return (1); +} + +#endif /* __ISAPNP__ */ + +#ifndef CONFIG_PCI + +static int avm_pci_setup(struct IsdnCardState *cs) +{ + return (1); /* no-op: success */ +} + +#else + +static struct pci_dev *dev_avm = NULL; + +static int avm_pci_setup(struct IsdnCardState *cs) +{ + if ((dev_avm = hisax_find_pci_device(PCI_VENDOR_ID_AVM, + PCI_DEVICE_ID_AVM_A1, dev_avm))) { + + if (pci_enable_device(dev_avm)) + return (0); + + cs->irq = dev_avm->irq; + if (!cs->irq) { + printk(KERN_ERR "FritzPCI: No IRQ for PCI card found\n"); + return (0); + } + + cs->hw.avm.cfg_reg = pci_resource_start(dev_avm, 1); + if (!cs->hw.avm.cfg_reg) { + printk(KERN_ERR "FritzPCI: No IO-Adr for PCI card found\n"); + return (0); + } + + cs->subtyp = AVM_FRITZ_PCI; + } else { + printk(KERN_WARNING "FritzPCI: No PCI card found\n"); + return (0); + } + + cs->irq_flags |= IRQF_SHARED; + + return (1); +} + +#endif /* CONFIG_PCI */ + +int setup_avm_pcipnp(struct IsdnCard *card) +{ + struct IsdnCardState *cs = card->cs; + char tmp[64]; + int rc; + + strcpy(tmp, avm_pci_rev); + printk(KERN_INFO "HiSax: AVM PCI driver Rev. %s\n", HiSax_getrev(tmp)); + + if (cs->typ != ISDN_CTYPE_FRITZPCI) + return (0); + + if (card->para[1]) { + /* old manual method */ + cs->hw.avm.cfg_reg = card->para[1]; + cs->irq = card->para[0]; + cs->subtyp = AVM_FRITZ_PNP; + goto ready; + } + + rc = avm_pnp_setup(cs); + if (rc < 1) + return (0); + if (rc == 2) + goto ready; + + rc = avm_pci_setup(cs); + if (rc < 1) + return (0); + +ready: + return avm_setup_rest(cs); +} diff --git a/drivers/isdn/hisax/avma1_cs.c b/drivers/isdn/hisax/avma1_cs.c new file mode 100644 index 000000000..baad94ec1 --- /dev/null +++ b/drivers/isdn/hisax/avma1_cs.c @@ -0,0 +1,162 @@ +/* + * PCMCIA client driver for AVM A1 / Fritz!PCMCIA + * + * Author Carsten Paeth + * Copyright 1998-2001 by Carsten Paeth <calle@calle.in-berlin.de> + * + * 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/kernel.h> +#include <linux/init.h> +#include <linux/ptrace.h> +#include <linux/slab.h> +#include <linux/string.h> +#include <asm/io.h> + +#include <pcmcia/cistpl.h> +#include <pcmcia/ds.h> +#include "hisax_cfg.h" + +MODULE_DESCRIPTION("ISDN4Linux: PCMCIA client driver for AVM A1/Fritz!PCMCIA cards"); +MODULE_AUTHOR("Carsten Paeth"); +MODULE_LICENSE("GPL"); + + +/*====================================================================*/ + +/* Parameters that can be set with 'insmod' */ + +static int isdnprot = 2; + +module_param(isdnprot, int, 0); + +/*====================================================================*/ + +static int avma1cs_config(struct pcmcia_device *link); +static void avma1cs_release(struct pcmcia_device *link); +static void avma1cs_detach(struct pcmcia_device *p_dev); + +static int avma1cs_probe(struct pcmcia_device *p_dev) +{ + dev_dbg(&p_dev->dev, "avma1cs_attach()\n"); + + /* General socket configuration */ + p_dev->config_flags |= CONF_ENABLE_IRQ | CONF_AUTO_SET_IO; + p_dev->config_index = 1; + p_dev->config_regs = PRESENT_OPTION; + + return avma1cs_config(p_dev); +} /* avma1cs_attach */ + +static void avma1cs_detach(struct pcmcia_device *link) +{ + dev_dbg(&link->dev, "avma1cs_detach(0x%p)\n", link); + avma1cs_release(link); + kfree(link->priv); +} /* avma1cs_detach */ + +static int avma1cs_configcheck(struct pcmcia_device *p_dev, void *priv_data) +{ + p_dev->resource[0]->end = 16; + p_dev->resource[0]->flags &= ~IO_DATA_PATH_WIDTH; + p_dev->resource[0]->flags |= IO_DATA_PATH_WIDTH_8; + p_dev->io_lines = 5; + + return pcmcia_request_io(p_dev); +} + + +static int avma1cs_config(struct pcmcia_device *link) +{ + int i = -1; + char devname[128]; + IsdnCard_t icard; + int busy = 0; + + dev_dbg(&link->dev, "avma1cs_config(0x%p)\n", link); + + devname[0] = 0; + if (link->prod_id[1]) + strlcpy(devname, link->prod_id[1], sizeof(devname)); + + if (pcmcia_loop_config(link, avma1cs_configcheck, NULL)) + return -ENODEV; + + do { + /* + * allocate an interrupt line + */ + if (!link->irq) { + /* undo */ + pcmcia_disable_device(link); + break; + } + + /* + * configure the PCMCIA socket + */ + i = pcmcia_enable_device(link); + if (i != 0) { + pcmcia_disable_device(link); + break; + } + + } while (0); + + /* If any step failed, release any partially configured state */ + if (i != 0) { + avma1cs_release(link); + return -ENODEV; + } + + icard.para[0] = link->irq; + icard.para[1] = link->resource[0]->start; + icard.protocol = isdnprot; + icard.typ = ISDN_CTYPE_A1_PCMCIA; + + i = hisax_init_pcmcia(link, &busy, &icard); + if (i < 0) { + printk(KERN_ERR "avma1_cs: failed to initialize AVM A1 " + "PCMCIA %d at i/o %#x\n", i, + (unsigned int) link->resource[0]->start); + avma1cs_release(link); + return -ENODEV; + } + link->priv = (void *) (unsigned long) i; + + return 0; +} /* avma1cs_config */ + +static void avma1cs_release(struct pcmcia_device *link) +{ + unsigned long minor = (unsigned long) link->priv; + + dev_dbg(&link->dev, "avma1cs_release(0x%p)\n", link); + + /* now unregister function with hisax */ + HiSax_closecard(minor); + + pcmcia_disable_device(link); +} /* avma1cs_release */ + +static const struct pcmcia_device_id avma1cs_ids[] = { + PCMCIA_DEVICE_PROD_ID12("AVM", "ISDN A", 0x95d42008, 0xadc9d4bb), + PCMCIA_DEVICE_PROD_ID12("ISDN", "CARD", 0x8d9761c8, 0x01c5aa7b), + PCMCIA_DEVICE_NULL +}; +MODULE_DEVICE_TABLE(pcmcia, avma1cs_ids); + +static struct pcmcia_driver avma1cs_driver = { + .owner = THIS_MODULE, + .name = "avma1_cs", + .probe = avma1cs_probe, + .remove = avma1cs_detach, + .id_table = avma1cs_ids, +}; +module_pcmcia_driver(avma1cs_driver); diff --git a/drivers/isdn/hisax/bkm_a4t.c b/drivers/isdn/hisax/bkm_a4t.c new file mode 100644 index 000000000..c360164bd --- /dev/null +++ b/drivers/isdn/hisax/bkm_a4t.c @@ -0,0 +1,358 @@ +/* $Id: bkm_a4t.c,v 1.22.2.4 2004/01/14 16:04:48 keil Exp $ + * + * low level stuff for T-Berkom A4T + * + * Author Roland Klabunde + * Copyright by Roland Klabunde <R.Klabunde@Berkom.de> + * + * This software may be used and distributed according to the terms + * of the GNU General Public License, incorporated herein by reference. + * + */ + + +#include <linux/init.h> +#include "hisax.h" +#include "isac.h" +#include "hscx.h" +#include "jade.h" +#include "isdnl1.h" +#include <linux/pci.h> +#include "bkm_ax.h" + +static const char *bkm_a4t_revision = "$Revision: 1.22.2.4 $"; + + +static inline u_char +readreg(unsigned int ale, unsigned long adr, u_char off) +{ + register u_int ret; + unsigned int *po = (unsigned int *) adr; /* Postoffice */ + + *po = (GCS_2 | PO_WRITE | off); + __WAITI20__(po); + *po = (ale | PO_READ); + __WAITI20__(po); + ret = *po; + return ((unsigned char) ret); +} + + +static inline void +readfifo(unsigned int ale, unsigned long adr, u_char off, u_char *data, int size) +{ + int i; + for (i = 0; i < size; i++) + *data++ = readreg(ale, adr, off); +} + + +static inline void +writereg(unsigned int ale, unsigned long adr, u_char off, u_char data) +{ + unsigned int *po = (unsigned int *) adr; /* Postoffice */ + *po = (GCS_2 | PO_WRITE | off); + __WAITI20__(po); + *po = (ale | PO_WRITE | data); + __WAITI20__(po); +} + + +static inline void +writefifo(unsigned int ale, unsigned long adr, u_char off, u_char *data, int size) +{ + int i; + + for (i = 0; i < size; i++) + writereg(ale, adr, off, *data++); +} + + +/* Interface functions */ + +static u_char +ReadISAC(struct IsdnCardState *cs, u_char offset) +{ + return (readreg(cs->hw.ax.isac_ale, cs->hw.ax.isac_adr, offset)); +} + +static void +WriteISAC(struct IsdnCardState *cs, u_char offset, u_char value) +{ + writereg(cs->hw.ax.isac_ale, cs->hw.ax.isac_adr, offset, value); +} + +static void +ReadISACfifo(struct IsdnCardState *cs, u_char *data, int size) +{ + readfifo(cs->hw.ax.isac_ale, cs->hw.ax.isac_adr, 0, data, size); +} + +static void +WriteISACfifo(struct IsdnCardState *cs, u_char *data, int size) +{ + writefifo(cs->hw.ax.isac_ale, cs->hw.ax.isac_adr, 0, data, size); +} + +static u_char +ReadJADE(struct IsdnCardState *cs, int jade, u_char offset) +{ + return (readreg(cs->hw.ax.jade_ale, cs->hw.ax.jade_adr, offset + (jade == -1 ? 0 : (jade ? 0xC0 : 0x80)))); +} + +static void +WriteJADE(struct IsdnCardState *cs, int jade, u_char offset, u_char value) +{ + writereg(cs->hw.ax.jade_ale, cs->hw.ax.jade_adr, offset + (jade == -1 ? 0 : (jade ? 0xC0 : 0x80)), value); +} + +/* + * fast interrupt JADE stuff goes here + */ + +#define READJADE(cs, nr, reg) readreg(cs->hw.ax.jade_ale, \ + cs->hw.ax.jade_adr, reg + (nr == -1 ? 0 : (nr ? 0xC0 : 0x80))) +#define WRITEJADE(cs, nr, reg, data) writereg(cs->hw.ax.jade_ale, \ + cs->hw.ax.jade_adr, reg + (nr == -1 ? 0 : (nr ? 0xC0 : 0x80)), data) + +#define READJADEFIFO(cs, nr, ptr, cnt) readfifo(cs->hw.ax.jade_ale, \ + cs->hw.ax.jade_adr, (nr == -1 ? 0 : (nr ? 0xC0 : 0x80)), ptr, cnt) +#define WRITEJADEFIFO(cs, nr, ptr, cnt) writefifo(cs->hw.ax.jade_ale, \ + cs->hw.ax.jade_adr, (nr == -1 ? 0 : (nr ? 0xC0 : 0x80)), ptr, cnt) + +#include "jade_irq.c" + +static irqreturn_t +bkm_interrupt(int intno, void *dev_id) +{ + struct IsdnCardState *cs = dev_id; + u_char val = 0; + u_long flags; + I20_REGISTER_FILE *pI20_Regs; + + spin_lock_irqsave(&cs->lock, flags); + pI20_Regs = (I20_REGISTER_FILE *) (cs->hw.ax.base); + + /* ISDN interrupt pending? */ + if (pI20_Regs->i20IntStatus & intISDN) { + /* Reset the ISDN interrupt */ + pI20_Regs->i20IntStatus = intISDN; + /* Disable ISDN interrupt */ + pI20_Regs->i20IntCtrl &= ~intISDN; + /* Channel A first */ + val = readreg(cs->hw.ax.jade_ale, cs->hw.ax.jade_adr, jade_HDLC_ISR + 0x80); + if (val) { + jade_int_main(cs, val, 0); + } + /* Channel B */ + val = readreg(cs->hw.ax.jade_ale, cs->hw.ax.jade_adr, jade_HDLC_ISR + 0xC0); + if (val) { + jade_int_main(cs, val, 1); + } + /* D-Channel */ + val = readreg(cs->hw.ax.isac_ale, cs->hw.ax.isac_adr, ISAC_ISTA); + if (val) { + isac_interrupt(cs, val); + } + /* Reenable ISDN interrupt */ + pI20_Regs->i20IntCtrl |= intISDN; + spin_unlock_irqrestore(&cs->lock, flags); + return IRQ_HANDLED; + } else { + spin_unlock_irqrestore(&cs->lock, flags); + return IRQ_NONE; + } +} + +static void +release_io_bkm(struct IsdnCardState *cs) +{ + if (cs->hw.ax.base) { + iounmap((void *) cs->hw.ax.base); + cs->hw.ax.base = 0; + } +} + +static void +enable_bkm_int(struct IsdnCardState *cs, unsigned bEnable) +{ + if (cs->typ == ISDN_CTYPE_BKM_A4T) { + I20_REGISTER_FILE *pI20_Regs = (I20_REGISTER_FILE *) (cs->hw.ax.base); + if (bEnable) + pI20_Regs->i20IntCtrl |= (intISDN | intPCI); + else + /* CAUTION: This disables the video capture driver too */ + pI20_Regs->i20IntCtrl &= ~(intISDN | intPCI); + } +} + +static void +reset_bkm(struct IsdnCardState *cs) +{ + if (cs->typ == ISDN_CTYPE_BKM_A4T) { + I20_REGISTER_FILE *pI20_Regs = (I20_REGISTER_FILE *) (cs->hw.ax.base); + /* Issue the I20 soft reset */ + pI20_Regs->i20SysControl = 0xFF; /* all in */ + mdelay(10); + /* Remove the soft reset */ + pI20_Regs->i20SysControl = sysRESET | 0xFF; + mdelay(10); + /* Set our configuration */ + pI20_Regs->i20SysControl = sysRESET | sysCFG; + /* Issue ISDN reset */ + pI20_Regs->i20GuestControl = guestWAIT_CFG | + g_A4T_JADE_RES | + g_A4T_ISAR_RES | + g_A4T_ISAC_RES | + g_A4T_JADE_BOOTR | + g_A4T_ISAR_BOOTR; + mdelay(10); + + /* Remove RESET state from ISDN */ + pI20_Regs->i20GuestControl &= ~(g_A4T_ISAC_RES | + g_A4T_JADE_RES | + g_A4T_ISAR_RES); + mdelay(10); + } +} + +static int +BKM_card_msg(struct IsdnCardState *cs, int mt, void *arg) +{ + u_long flags; + + switch (mt) { + case CARD_RESET: + /* Disable ints */ + spin_lock_irqsave(&cs->lock, flags); + enable_bkm_int(cs, 0); + reset_bkm(cs); + spin_unlock_irqrestore(&cs->lock, flags); + return (0); + case CARD_RELEASE: + /* Sanity */ + spin_lock_irqsave(&cs->lock, flags); + enable_bkm_int(cs, 0); + reset_bkm(cs); + spin_unlock_irqrestore(&cs->lock, flags); + release_io_bkm(cs); + return (0); + case CARD_INIT: + spin_lock_irqsave(&cs->lock, flags); + clear_pending_isac_ints(cs); + clear_pending_jade_ints(cs); + initisac(cs); + initjade(cs); + /* Enable ints */ + enable_bkm_int(cs, 1); + spin_unlock_irqrestore(&cs->lock, flags); + return (0); + case CARD_TEST: + return (0); + } + return (0); +} + +static int a4t_pci_probe(struct pci_dev *dev_a4t, struct IsdnCardState *cs, + u_int *found, u_int *pci_memaddr) +{ + u16 sub_sys; + u16 sub_vendor; + + sub_vendor = dev_a4t->subsystem_vendor; + sub_sys = dev_a4t->subsystem_device; + if ((sub_sys == PCI_DEVICE_ID_BERKOM_A4T) && (sub_vendor == PCI_VENDOR_ID_BERKOM)) { + if (pci_enable_device(dev_a4t)) + return (0); /* end loop & function */ + *found = 1; + *pci_memaddr = pci_resource_start(dev_a4t, 0); + cs->irq = dev_a4t->irq; + return (1); /* end loop */ + } + + return (-1); /* continue looping */ +} + +static int a4t_cs_init(struct IsdnCard *card, struct IsdnCardState *cs, + u_int pci_memaddr) +{ + I20_REGISTER_FILE *pI20_Regs; + + if (!cs->irq) { /* IRQ range check ?? */ + printk(KERN_WARNING "HiSax: Telekom A4T: No IRQ\n"); + return (0); + } + cs->hw.ax.base = (long) ioremap(pci_memaddr, 4096); + /* Check suspecious address */ + pI20_Regs = (I20_REGISTER_FILE *) (cs->hw.ax.base); + if ((pI20_Regs->i20IntStatus & 0x8EFFFFFF) != 0) { + printk(KERN_WARNING "HiSax: Telekom A4T address " + "%lx-%lx suspicious\n", + cs->hw.ax.base, cs->hw.ax.base + 4096); + iounmap((void *) cs->hw.ax.base); + cs->hw.ax.base = 0; + return (0); + } + cs->hw.ax.isac_adr = cs->hw.ax.base + PO_OFFSET; + cs->hw.ax.jade_adr = cs->hw.ax.base + PO_OFFSET; + cs->hw.ax.isac_ale = GCS_1; + cs->hw.ax.jade_ale = GCS_3; + + printk(KERN_INFO "HiSax: Telekom A4T: Card configured at " + "0x%lX IRQ %d\n", + cs->hw.ax.base, cs->irq); + + setup_isac(cs); + cs->readisac = &ReadISAC; + cs->writeisac = &WriteISAC; + cs->readisacfifo = &ReadISACfifo; + cs->writeisacfifo = &WriteISACfifo; + cs->BC_Read_Reg = &ReadJADE; + cs->BC_Write_Reg = &WriteJADE; + cs->BC_Send_Data = &jade_fill_fifo; + cs->cardmsg = &BKM_card_msg; + cs->irq_func = &bkm_interrupt; + cs->irq_flags |= IRQF_SHARED; + ISACVersion(cs, "Telekom A4T:"); + /* Jade version */ + JadeVersion(cs, "Telekom A4T:"); + + return (1); +} + +static struct pci_dev *dev_a4t = NULL; + +int setup_bkm_a4t(struct IsdnCard *card) +{ + struct IsdnCardState *cs = card->cs; + char tmp[64]; + u_int pci_memaddr = 0, found = 0; + int ret; + + strcpy(tmp, bkm_a4t_revision); + printk(KERN_INFO "HiSax: T-Berkom driver Rev. %s\n", HiSax_getrev(tmp)); + if (cs->typ == ISDN_CTYPE_BKM_A4T) { + cs->subtyp = BKM_A4T; + } else + return (0); + + while ((dev_a4t = hisax_find_pci_device(PCI_VENDOR_ID_ZORAN, + PCI_DEVICE_ID_ZORAN_36120, dev_a4t))) { + ret = a4t_pci_probe(dev_a4t, cs, &found, &pci_memaddr); + if (!ret) + return (0); + if (ret > 0) + break; + } + if (!found) { + printk(KERN_WARNING "HiSax: Telekom A4T: Card not found\n"); + return (0); + } + if (!pci_memaddr) { + printk(KERN_WARNING "HiSax: Telekom A4T: " + "No Memory base address\n"); + return (0); + } + + return a4t_cs_init(card, cs, pci_memaddr); +} diff --git a/drivers/isdn/hisax/bkm_a8.c b/drivers/isdn/hisax/bkm_a8.c new file mode 100644 index 000000000..dd663ea57 --- /dev/null +++ b/drivers/isdn/hisax/bkm_a8.c @@ -0,0 +1,433 @@ +/* $Id: bkm_a8.c,v 1.22.2.4 2004/01/15 14:02:34 keil Exp $ + * + * low level stuff for Scitel Quadro (4*S0, passive) + * + * Author Roland Klabunde + * Copyright by Roland Klabunde <R.Klabunde@Berkom.de> + * + * This software may be used and distributed according to the terms + * of the GNU General Public License, incorporated herein by reference. + * + */ + + +#include <linux/init.h> +#include "hisax.h" +#include "isac.h" +#include "ipac.h" +#include "hscx.h" +#include "isdnl1.h" +#include <linux/pci.h> +#include "bkm_ax.h" + +#define ATTEMPT_PCI_REMAPPING /* Required for PLX rev 1 */ + +static const char sct_quadro_revision[] = "$Revision: 1.22.2.4 $"; + +static const char *sct_quadro_subtypes[] = +{ + "", + "#1", + "#2", + "#3", + "#4" +}; + + +#define wordout(addr, val) outw(val, addr) +#define wordin(addr) inw(addr) + +static inline u_char +readreg(unsigned int ale, unsigned int adr, u_char off) +{ + register u_char ret; + wordout(ale, off); + ret = wordin(adr) & 0xFF; + return (ret); +} + +static inline void +readfifo(unsigned int ale, unsigned int adr, u_char off, u_char *data, int size) +{ + int i; + wordout(ale, off); + for (i = 0; i < size; i++) + data[i] = wordin(adr) & 0xFF; +} + + +static inline void +writereg(unsigned int ale, unsigned int adr, u_char off, u_char data) +{ + wordout(ale, off); + wordout(adr, data); +} + +static inline void +writefifo(unsigned int ale, unsigned int adr, u_char off, u_char *data, int size) +{ + int i; + wordout(ale, off); + for (i = 0; i < size; i++) + wordout(adr, data[i]); +} + +/* Interface functions */ + +static u_char +ReadISAC(struct IsdnCardState *cs, u_char offset) +{ + return (readreg(cs->hw.ax.base, cs->hw.ax.data_adr, offset | 0x80)); +} + +static void +WriteISAC(struct IsdnCardState *cs, u_char offset, u_char value) +{ + writereg(cs->hw.ax.base, cs->hw.ax.data_adr, offset | 0x80, value); +} + +static void +ReadISACfifo(struct IsdnCardState *cs, u_char *data, int size) +{ + readfifo(cs->hw.ax.base, cs->hw.ax.data_adr, 0x80, data, size); +} + +static void +WriteISACfifo(struct IsdnCardState *cs, u_char *data, int size) +{ + writefifo(cs->hw.ax.base, cs->hw.ax.data_adr, 0x80, data, size); +} + + +static u_char +ReadHSCX(struct IsdnCardState *cs, int hscx, u_char offset) +{ + return (readreg(cs->hw.ax.base, cs->hw.ax.data_adr, offset + (hscx ? 0x40 : 0))); +} + +static void +WriteHSCX(struct IsdnCardState *cs, int hscx, u_char offset, u_char value) +{ + writereg(cs->hw.ax.base, cs->hw.ax.data_adr, offset + (hscx ? 0x40 : 0), value); +} + +/* Set the specific ipac to active */ +static void +set_ipac_active(struct IsdnCardState *cs, u_int active) +{ + /* set irq mask */ + writereg(cs->hw.ax.base, cs->hw.ax.data_adr, IPAC_MASK, + active ? 0xc0 : 0xff); +} + +/* + * fast interrupt HSCX stuff goes here + */ + +#define READHSCX(cs, nr, reg) readreg(cs->hw.ax.base, \ + cs->hw.ax.data_adr, reg + (nr ? 0x40 : 0)) +#define WRITEHSCX(cs, nr, reg, data) writereg(cs->hw.ax.base, \ + cs->hw.ax.data_adr, reg + (nr ? 0x40 : 0), data) +#define READHSCXFIFO(cs, nr, ptr, cnt) readfifo(cs->hw.ax.base, \ + cs->hw.ax.data_adr, (nr ? 0x40 : 0), ptr, cnt) +#define WRITEHSCXFIFO(cs, nr, ptr, cnt) writefifo(cs->hw.ax.base, \ + cs->hw.ax.data_adr, (nr ? 0x40 : 0), ptr, cnt) + +#include "hscx_irq.c" + +static irqreturn_t +bkm_interrupt_ipac(int intno, void *dev_id) +{ + struct IsdnCardState *cs = dev_id; + u_char ista, val, icnt = 5; + u_long flags; + + spin_lock_irqsave(&cs->lock, flags); + ista = readreg(cs->hw.ax.base, cs->hw.ax.data_adr, IPAC_ISTA); + if (!(ista & 0x3f)) { /* not this IPAC */ + spin_unlock_irqrestore(&cs->lock, flags); + return IRQ_NONE; + } +Start_IPAC: + if (cs->debug & L1_DEB_IPAC) + debugl1(cs, "IPAC ISTA %02X", ista); + if (ista & 0x0f) { + val = readreg(cs->hw.ax.base, cs->hw.ax.data_adr, HSCX_ISTA + 0x40); + if (ista & 0x01) + val |= 0x01; + if (ista & 0x04) + val |= 0x02; + if (ista & 0x08) + val |= 0x04; + if (val) { + hscx_int_main(cs, val); + } + } + if (ista & 0x20) { + val = 0xfe & readreg(cs->hw.ax.base, cs->hw.ax.data_adr, ISAC_ISTA | 0x80); + if (val) { + isac_interrupt(cs, val); + } + } + if (ista & 0x10) { + val = 0x01; + isac_interrupt(cs, val); + } + ista = readreg(cs->hw.ax.base, cs->hw.ax.data_adr, IPAC_ISTA); + if ((ista & 0x3f) && icnt) { + icnt--; + goto Start_IPAC; + } + if (!icnt) + printk(KERN_WARNING "HiSax: Scitel Quadro (%s) IRQ LOOP\n", + sct_quadro_subtypes[cs->subtyp]); + writereg(cs->hw.ax.base, cs->hw.ax.data_adr, IPAC_MASK, 0xFF); + writereg(cs->hw.ax.base, cs->hw.ax.data_adr, IPAC_MASK, 0xC0); + spin_unlock_irqrestore(&cs->lock, flags); + return IRQ_HANDLED; +} + +static void +release_io_sct_quadro(struct IsdnCardState *cs) +{ + release_region(cs->hw.ax.base & 0xffffffc0, 128); + if (cs->subtyp == SCT_1) + release_region(cs->hw.ax.plx_adr, 64); +} + +static void +enable_bkm_int(struct IsdnCardState *cs, unsigned bEnable) +{ + if (cs->typ == ISDN_CTYPE_SCT_QUADRO) { + if (bEnable) + wordout(cs->hw.ax.plx_adr + 0x4C, (wordin(cs->hw.ax.plx_adr + 0x4C) | 0x41)); + else + wordout(cs->hw.ax.plx_adr + 0x4C, (wordin(cs->hw.ax.plx_adr + 0x4C) & ~0x41)); + } +} + +static void +reset_bkm(struct IsdnCardState *cs) +{ + if (cs->subtyp == SCT_1) { + wordout(cs->hw.ax.plx_adr + 0x50, (wordin(cs->hw.ax.plx_adr + 0x50) & ~4)); + mdelay(10); + /* Remove the soft reset */ + wordout(cs->hw.ax.plx_adr + 0x50, (wordin(cs->hw.ax.plx_adr + 0x50) | 4)); + mdelay(10); + } +} + +static int +BKM_card_msg(struct IsdnCardState *cs, int mt, void *arg) +{ + u_long flags; + + switch (mt) { + case CARD_RESET: + spin_lock_irqsave(&cs->lock, flags); + /* Disable ints */ + set_ipac_active(cs, 0); + enable_bkm_int(cs, 0); + reset_bkm(cs); + spin_unlock_irqrestore(&cs->lock, flags); + return (0); + case CARD_RELEASE: + /* Sanity */ + spin_lock_irqsave(&cs->lock, flags); + set_ipac_active(cs, 0); + enable_bkm_int(cs, 0); + spin_unlock_irqrestore(&cs->lock, flags); + release_io_sct_quadro(cs); + return (0); + case CARD_INIT: + spin_lock_irqsave(&cs->lock, flags); + cs->debug |= L1_DEB_IPAC; + set_ipac_active(cs, 1); + inithscxisac(cs, 3); + /* Enable ints */ + enable_bkm_int(cs, 1); + spin_unlock_irqrestore(&cs->lock, flags); + return (0); + case CARD_TEST: + return (0); + } + return (0); +} + +static int sct_alloc_io(u_int adr, u_int len) +{ + if (!request_region(adr, len, "scitel")) { + printk(KERN_WARNING + "HiSax: Scitel port %#x-%#x already in use\n", + adr, adr + len); + return (1); + } + return (0); +} + +static struct pci_dev *dev_a8 = NULL; +static u16 sub_vendor_id = 0; +static u16 sub_sys_id = 0; +static u_char pci_bus = 0; +static u_char pci_device_fn = 0; +static u_char pci_irq = 0; + +int setup_sct_quadro(struct IsdnCard *card) +{ + struct IsdnCardState *cs = card->cs; + char tmp[64]; + u_int found = 0; + u_int pci_ioaddr1, pci_ioaddr2, pci_ioaddr3, pci_ioaddr4, pci_ioaddr5; + + strcpy(tmp, sct_quadro_revision); + printk(KERN_INFO "HiSax: T-Berkom driver Rev. %s\n", HiSax_getrev(tmp)); + if (cs->typ == ISDN_CTYPE_SCT_QUADRO) { + cs->subtyp = SCT_1; /* Preset */ + } else + return (0); + + /* Identify subtype by para[0] */ + if (card->para[0] >= SCT_1 && card->para[0] <= SCT_4) + cs->subtyp = card->para[0]; + else { + printk(KERN_WARNING "HiSax: Scitel Quadro: Invalid " + "subcontroller in configuration, default to 1\n"); + return (0); + } + if ((cs->subtyp != SCT_1) && ((sub_sys_id != PCI_DEVICE_ID_BERKOM_SCITEL_QUADRO) || + (sub_vendor_id != PCI_VENDOR_ID_BERKOM))) + return (0); + if (cs->subtyp == SCT_1) { + while ((dev_a8 = hisax_find_pci_device(PCI_VENDOR_ID_PLX, + PCI_DEVICE_ID_PLX_9050, dev_a8))) { + + sub_vendor_id = dev_a8->subsystem_vendor; + sub_sys_id = dev_a8->subsystem_device; + if ((sub_sys_id == PCI_DEVICE_ID_BERKOM_SCITEL_QUADRO) && + (sub_vendor_id == PCI_VENDOR_ID_BERKOM)) { + if (pci_enable_device(dev_a8)) + return (0); + pci_ioaddr1 = pci_resource_start(dev_a8, 1); + pci_irq = dev_a8->irq; + pci_bus = dev_a8->bus->number; + pci_device_fn = dev_a8->devfn; + found = 1; + break; + } + } + if (!found) { + printk(KERN_WARNING "HiSax: Scitel Quadro (%s): " + "Card not found\n", + sct_quadro_subtypes[cs->subtyp]); + return (0); + } +#ifdef ATTEMPT_PCI_REMAPPING +/* HACK: PLX revision 1 bug: PLX address bit 7 must not be set */ + if ((pci_ioaddr1 & 0x80) && (dev_a8->revision == 1)) { + printk(KERN_WARNING "HiSax: Scitel Quadro (%s): " + "PLX rev 1, remapping required!\n", + sct_quadro_subtypes[cs->subtyp]); + /* Restart PCI negotiation */ + pci_write_config_dword(dev_a8, PCI_BASE_ADDRESS_1, (u_int)-1); + /* Move up by 0x80 byte */ + pci_ioaddr1 += 0x80; + pci_ioaddr1 &= PCI_BASE_ADDRESS_IO_MASK; + pci_write_config_dword(dev_a8, PCI_BASE_ADDRESS_1, pci_ioaddr1); + dev_a8->resource[1].start = pci_ioaddr1; + } +#endif /* End HACK */ + } + if (!pci_irq) { /* IRQ range check ?? */ + printk(KERN_WARNING "HiSax: Scitel Quadro (%s): No IRQ\n", + sct_quadro_subtypes[cs->subtyp]); + return (0); + } + pci_read_config_dword(dev_a8, PCI_BASE_ADDRESS_1, &pci_ioaddr1); + pci_read_config_dword(dev_a8, PCI_BASE_ADDRESS_2, &pci_ioaddr2); + pci_read_config_dword(dev_a8, PCI_BASE_ADDRESS_3, &pci_ioaddr3); + pci_read_config_dword(dev_a8, PCI_BASE_ADDRESS_4, &pci_ioaddr4); + pci_read_config_dword(dev_a8, PCI_BASE_ADDRESS_5, &pci_ioaddr5); + if (!pci_ioaddr1 || !pci_ioaddr2 || !pci_ioaddr3 || !pci_ioaddr4 || !pci_ioaddr5) { + printk(KERN_WARNING "HiSax: Scitel Quadro (%s): " + "No IO base address(es)\n", + sct_quadro_subtypes[cs->subtyp]); + return (0); + } + pci_ioaddr1 &= PCI_BASE_ADDRESS_IO_MASK; + pci_ioaddr2 &= PCI_BASE_ADDRESS_IO_MASK; + pci_ioaddr3 &= PCI_BASE_ADDRESS_IO_MASK; + pci_ioaddr4 &= PCI_BASE_ADDRESS_IO_MASK; + pci_ioaddr5 &= PCI_BASE_ADDRESS_IO_MASK; + /* Take over */ + cs->irq = pci_irq; + cs->irq_flags |= IRQF_SHARED; + /* pci_ioaddr1 is unique to all subdevices */ + /* pci_ioaddr2 is for the fourth subdevice only */ + /* pci_ioaddr3 is for the third subdevice only */ + /* pci_ioaddr4 is for the second subdevice only */ + /* pci_ioaddr5 is for the first subdevice only */ + cs->hw.ax.plx_adr = pci_ioaddr1; + /* Enter all ipac_base addresses */ + switch (cs->subtyp) { + case 1: + cs->hw.ax.base = pci_ioaddr5 + 0x00; + if (sct_alloc_io(pci_ioaddr1, 128)) + return (0); + if (sct_alloc_io(pci_ioaddr5, 64)) + return (0); + /* disable all IPAC */ + writereg(pci_ioaddr5, pci_ioaddr5 + 4, + IPAC_MASK, 0xFF); + writereg(pci_ioaddr4 + 0x08, pci_ioaddr4 + 0x0c, + IPAC_MASK, 0xFF); + writereg(pci_ioaddr3 + 0x10, pci_ioaddr3 + 0x14, + IPAC_MASK, 0xFF); + writereg(pci_ioaddr2 + 0x20, pci_ioaddr2 + 0x24, + IPAC_MASK, 0xFF); + break; + case 2: + cs->hw.ax.base = pci_ioaddr4 + 0x08; + if (sct_alloc_io(pci_ioaddr4, 64)) + return (0); + break; + case 3: + cs->hw.ax.base = pci_ioaddr3 + 0x10; + if (sct_alloc_io(pci_ioaddr3, 64)) + return (0); + break; + case 4: + cs->hw.ax.base = pci_ioaddr2 + 0x20; + if (sct_alloc_io(pci_ioaddr2, 64)) + return (0); + break; + } + /* For isac and hscx data path */ + cs->hw.ax.data_adr = cs->hw.ax.base + 4; + + printk(KERN_INFO "HiSax: Scitel Quadro (%s) configured at " + "0x%.4lX, 0x%.4lX, 0x%.4lX and IRQ %d\n", + sct_quadro_subtypes[cs->subtyp], + cs->hw.ax.plx_adr, + cs->hw.ax.base, + cs->hw.ax.data_adr, + cs->irq); + + test_and_set_bit(HW_IPAC, &cs->HW_Flags); + + cs->readisac = &ReadISAC; + cs->writeisac = &WriteISAC; + cs->readisacfifo = &ReadISACfifo; + cs->writeisacfifo = &WriteISACfifo; + + cs->BC_Read_Reg = &ReadHSCX; + cs->BC_Write_Reg = &WriteHSCX; + cs->BC_Send_Data = &hscx_fill_fifo; + cs->cardmsg = &BKM_card_msg; + cs->irq_func = &bkm_interrupt_ipac; + + printk(KERN_INFO "HiSax: Scitel Quadro (%s): IPAC Version %d\n", + sct_quadro_subtypes[cs->subtyp], + readreg(cs->hw.ax.base, cs->hw.ax.data_adr, IPAC_ID)); + return (1); +} diff --git a/drivers/isdn/hisax/bkm_ax.h b/drivers/isdn/hisax/bkm_ax.h new file mode 100644 index 000000000..27ff8a886 --- /dev/null +++ b/drivers/isdn/hisax/bkm_ax.h @@ -0,0 +1,119 @@ +/* $Id: bkm_ax.h,v 1.5.6.3 2001/09/23 22:24:46 kai Exp $ + * + * low level decls for T-Berkom cards A4T and Scitel Quadro (4*S0, passive) + * + * Author Roland Klabunde + * Copyright by Roland Klabunde <R.Klabunde@Berkom.de> + * + * This software may be used and distributed according to the terms + * of the GNU General Public License, incorporated herein by reference. + * + */ + +#ifndef __BKM_AX_H__ +#define __BKM_AX_H__ + +/* Supported boards (subtypes) */ +#define SCT_1 1 +#define SCT_2 2 +#define SCT_3 3 +#define SCT_4 4 +#define BKM_A4T 5 + +#define PLX_ADDR_PLX 0x14 /* Addr PLX configuration */ +#define PLX_ADDR_ISAC 0x18 /* Addr ISAC */ +#define PLX_ADDR_HSCX 0x1C /* Addr HSCX */ +#define PLX_ADDR_ALE 0x20 /* Addr ALE */ +#define PLX_ADDR_ALEPLUS 0x24 /* Next Addr behind ALE */ + +#define PLX_SUBVEN 0x2C /* Offset SubVendor */ +#define PLX_SUBSYS 0x2E /* Offset SubSystem */ + + +/* Application specific registers I20 (Siemens SZB6120H) */ +typedef struct { + /* Video front end horizontal configuration register */ + volatile u_int i20VFEHorzCfg; /* Offset 00 */ + /* Video front end vertical configuration register */ + volatile u_int i20VFEVertCfg; /* Offset 04 */ + /* Video front end scaler and pixel format register */ + volatile u_int i20VFEScaler; /* Offset 08 */ + /* Video display top register */ + volatile u_int i20VDispTop; /* Offset 0C */ + /* Video display bottom register */ + volatile u_int i20VDispBottom; /* Offset 10 */ + /* Video stride, status and frame grab register */ + volatile u_int i20VidFrameGrab;/* Offset 14 */ + /* Video display configuration register */ + volatile u_int i20VDispCfg; /* Offset 18 */ + /* Video masking map top */ + volatile u_int i20VMaskTop; /* Offset 1C */ + /* Video masking map bottom */ + volatile u_int i20VMaskBottom; /* Offset 20 */ + /* Overlay control register */ + volatile u_int i20OvlyControl; /* Offset 24 */ + /* System, PCI and general purpose pins control register */ + volatile u_int i20SysControl; /* Offset 28 */ +#define sysRESET 0x01000000 /* bit 24:Softreset (Low) */ + /* GPIO 4...0: Output fixed for our cfg! */ +#define sysCFG 0x000000E0 /* GPIO 7,6,5: Input */ + /* General purpose pins and guest bus control register */ + volatile u_int i20GuestControl;/* Offset 2C */ +#define guestWAIT_CFG 0x00005555 /* 4 PCI waits for all */ +#define guestISDN_INT_E 0x01000000 /* ISDN Int en (low) */ +#define guestVID_INT_E 0x02000000 /* Video interrupt en (low) */ +#define guestADI1_INT_R 0x04000000 /* ADI #1 int req (low) */ +#define guestADI2_INT_R 0x08000000 /* ADI #2 int req (low) */ +#define guestISDN_RES 0x10000000 /* ISDN reset bit (high) */ +#define guestADI1_INT_S 0x20000000 /* ADI #1 int pending (low) */ +#define guestADI2_INT_S 0x40000000 /* ADI #2 int pending (low) */ +#define guestISDN_INT_S 0x80000000 /* ISAC int pending (low) */ + +#define g_A4T_JADE_RES 0x01000000 /* JADE Reset (High) */ +#define g_A4T_ISAR_RES 0x02000000 /* ISAR Reset (High) */ +#define g_A4T_ISAC_RES 0x04000000 /* ISAC Reset (High) */ +#define g_A4T_JADE_BOOTR 0x08000000 /* JADE enable boot SRAM (Low) NOT USED */ +#define g_A4T_ISAR_BOOTR 0x10000000 /* ISAR enable boot SRAM (Low) NOT USED */ +#define g_A4T_JADE_INT_S 0x20000000 /* JADE interrupt pnd (Low) */ +#define g_A4T_ISAR_INT_S 0x40000000 /* ISAR interrupt pnd (Low) */ +#define g_A4T_ISAC_INT_S 0x80000000 /* ISAC interrupt pnd (Low) */ + + volatile u_int i20CodeSource; /* Offset 30 */ + volatile u_int i20CodeXferCtrl;/* Offset 34 */ + volatile u_int i20CodeMemPtr; /* Offset 38 */ + + volatile u_int i20IntStatus; /* Offset 3C */ + volatile u_int i20IntCtrl; /* Offset 40 */ +#define intISDN 0x40000000 /* GIRQ1En (ISAC/ADI) (High) */ +#define intVID 0x20000000 /* GIRQ0En (VSYNC) (High) */ +#define intCOD 0x10000000 /* CodRepIrqEn (High) */ +#define intPCI 0x01000000 /* PCI IntA enable (High) */ + + volatile u_int i20I2CCtrl; /* Offset 44 */ +} I20_REGISTER_FILE, *PI20_REGISTER_FILE; + +/* + * Postoffice structure for A4T + * + */ +#define PO_OFFSET 0x00000200 /* Postoffice offset from base */ + +#define GCS_0 0x00000000 /* Guest bus chip selects */ +#define GCS_1 0x00100000 +#define GCS_2 0x00200000 +#define GCS_3 0x00300000 + +#define PO_READ 0x00000000 /* R/W from/to guest bus */ +#define PO_WRITE 0x00800000 + +#define PO_PEND 0x02000000 + +#define POSTOFFICE(postoffice) *(volatile unsigned int *)(postoffice) + +/* Wait unlimited (don't worry) */ +#define __WAITI20__(postoffice) \ + do { \ + while ((POSTOFFICE(postoffice) & PO_PEND)) ; \ + } while (0) + +#endif /* __BKM_AX_H__ */ diff --git a/drivers/isdn/hisax/callc.c b/drivers/isdn/hisax/callc.c new file mode 100644 index 000000000..9ee063287 --- /dev/null +++ b/drivers/isdn/hisax/callc.c @@ -0,0 +1,1792 @@ +/* $Id: callc.c,v 2.59.2.4 2004/02/11 13:21:32 keil Exp $ + * + * Author Karsten Keil + * Copyright by Karsten Keil <keil@isdn4linux.de> + * + * This software may be used and distributed according to the terms + * of the GNU General Public License, incorporated herein by reference. + * + * For changes and modifications please read + * Documentation/isdn/HiSax.cert + * + * based on the teles driver from Jan den Ouden + * + * Thanks to Jan den Ouden + * Fritz Elfert + * + */ + +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/init.h> +#include "hisax.h" +#include <linux/isdn/capicmd.h> + +const char *lli_revision = "$Revision: 2.59.2.4 $"; + +extern struct IsdnCard cards[]; + +static int init_b_st(struct Channel *chanp, int incoming); +static void release_b_st(struct Channel *chanp); + +static struct Fsm callcfsm; +static int chancount; + +/* experimental REJECT after ALERTING for CALLBACK to beat the 4s delay */ +#define ALERT_REJECT 0 + +/* Value to delay the sending of the first B-channel packet after CONNECT + * here is no value given by ITU, but experience shows that 300 ms will + * work on many networks, if you or your other side is behind local exchanges + * a greater value may be recommented. If the delay is to short the first paket + * will be lost and autodetect on many comercial routers goes wrong ! + * You can adjust this value on runtime with + * hisaxctrl <id> 2 <value> + * value is in milliseconds + */ +#define DEFAULT_B_DELAY 300 + +/* Flags for remembering action done in lli */ + +#define FLG_START_B 0 + +/* + * Find card with given driverId + */ +static inline struct IsdnCardState * +hisax_findcard(int driverid) +{ + int i; + + for (i = 0; i < nrcards; i++) + if (cards[i].cs) + if (cards[i].cs->myid == driverid) + return (cards[i].cs); + return (struct IsdnCardState *) 0; +} + +static __printf(3, 4) void + link_debug(struct Channel *chanp, int direction, char *fmt, ...) +{ + va_list args; + char tmp[16]; + + va_start(args, fmt); + sprintf(tmp, "Ch%d %s ", chanp->chan, + direction ? "LL->HL" : "HL->LL"); + VHiSax_putstatus(chanp->cs, tmp, fmt, args); + va_end(args); +} + +enum { + ST_NULL, /* 0 inactive */ + ST_OUT_DIAL, /* 1 outgoing, SETUP send; awaiting confirm */ + ST_IN_WAIT_LL, /* 2 incoming call received; wait for LL confirm */ + ST_IN_ALERT_SENT, /* 3 incoming call received; ALERT send */ + ST_IN_WAIT_CONN_ACK, /* 4 incoming CONNECT send; awaiting CONN_ACK */ + ST_WAIT_BCONN, /* 5 CONNECT/CONN_ACK received, awaiting b-channel prot. estbl. */ + ST_ACTIVE, /* 6 active, b channel prot. established */ + ST_WAIT_BRELEASE, /* 7 call clear. (initiator), awaiting b channel prot. rel. */ + ST_WAIT_BREL_DISC, /* 8 call clear. (receiver), DISCONNECT req. received */ + ST_WAIT_DCOMMAND, /* 9 call clear. (receiver), awaiting DCHANNEL message */ + ST_WAIT_DRELEASE, /* 10 DISCONNECT sent, awaiting RELEASE */ + ST_WAIT_D_REL_CNF, /* 11 RELEASE sent, awaiting RELEASE confirm */ + ST_IN_PROCEED_SEND, /* 12 incoming call, proceeding send */ +}; + + +#define STATE_COUNT (ST_IN_PROCEED_SEND + 1) + +static char *strState[] = +{ + "ST_NULL", + "ST_OUT_DIAL", + "ST_IN_WAIT_LL", + "ST_IN_ALERT_SENT", + "ST_IN_WAIT_CONN_ACK", + "ST_WAIT_BCONN", + "ST_ACTIVE", + "ST_WAIT_BRELEASE", + "ST_WAIT_BREL_DISC", + "ST_WAIT_DCOMMAND", + "ST_WAIT_DRELEASE", + "ST_WAIT_D_REL_CNF", + "ST_IN_PROCEED_SEND", +}; + +enum { + EV_DIAL, /* 0 */ + EV_SETUP_CNF, /* 1 */ + EV_ACCEPTB, /* 2 */ + EV_DISCONNECT_IND, /* 3 */ + EV_RELEASE, /* 4 */ + EV_LEASED, /* 5 */ + EV_LEASED_REL, /* 6 */ + EV_SETUP_IND, /* 7 */ + EV_ACCEPTD, /* 8 */ + EV_SETUP_CMPL_IND, /* 9 */ + EV_BC_EST, /* 10 */ + EV_WRITEBUF, /* 11 */ + EV_HANGUP, /* 12 */ + EV_BC_REL, /* 13 */ + EV_CINF, /* 14 */ + EV_SUSPEND, /* 15 */ + EV_RESUME, /* 16 */ + EV_NOSETUP_RSP, /* 17 */ + EV_SETUP_ERR, /* 18 */ + EV_CONNECT_ERR, /* 19 */ + EV_PROCEED, /* 20 */ + EV_ALERT, /* 21 */ + EV_REDIR, /* 22 */ +}; + +#define EVENT_COUNT (EV_REDIR + 1) + +static char *strEvent[] = +{ + "EV_DIAL", + "EV_SETUP_CNF", + "EV_ACCEPTB", + "EV_DISCONNECT_IND", + "EV_RELEASE", + "EV_LEASED", + "EV_LEASED_REL", + "EV_SETUP_IND", + "EV_ACCEPTD", + "EV_SETUP_CMPL_IND", + "EV_BC_EST", + "EV_WRITEBUF", + "EV_HANGUP", + "EV_BC_REL", + "EV_CINF", + "EV_SUSPEND", + "EV_RESUME", + "EV_NOSETUP_RSP", + "EV_SETUP_ERR", + "EV_CONNECT_ERR", + "EV_PROCEED", + "EV_ALERT", + "EV_REDIR", +}; + + +static inline void +HL_LL(struct Channel *chanp, int command) +{ + isdn_ctrl ic; + + ic.driver = chanp->cs->myid; + ic.command = command; + ic.arg = chanp->chan; + chanp->cs->iif.statcallb(&ic); +} + +static inline void +lli_deliver_cause(struct Channel *chanp) +{ + isdn_ctrl ic; + + if (!chanp->proc) + return; + if (chanp->proc->para.cause == NO_CAUSE) + return; + ic.driver = chanp->cs->myid; + ic.command = ISDN_STAT_CAUSE; + ic.arg = chanp->chan; + if (chanp->cs->protocol == ISDN_PTYPE_EURO) + sprintf(ic.parm.num, "E%02X%02X", chanp->proc->para.loc & 0x7f, + chanp->proc->para.cause & 0x7f); + else + sprintf(ic.parm.num, "%02X%02X", chanp->proc->para.loc & 0x7f, + chanp->proc->para.cause & 0x7f); + chanp->cs->iif.statcallb(&ic); +} + +static inline void +lli_close(struct FsmInst *fi) +{ + struct Channel *chanp = fi->userdata; + + FsmChangeState(fi, ST_NULL); + chanp->Flags = 0; + chanp->cs->cardmsg(chanp->cs, MDL_INFO_REL, (void *) (long)chanp->chan); +} + +static void +lli_leased_in(struct FsmInst *fi, int event, void *arg) +{ + struct Channel *chanp = fi->userdata; + isdn_ctrl ic; + int ret; + + if (!chanp->leased) + return; + chanp->cs->cardmsg(chanp->cs, MDL_INFO_SETUP, (void *) (long)chanp->chan); + FsmChangeState(fi, ST_IN_WAIT_LL); + if (chanp->debug & 1) + link_debug(chanp, 0, "STAT_ICALL_LEASED"); + ic.driver = chanp->cs->myid; + ic.command = ((chanp->chan < 2) ? ISDN_STAT_ICALL : ISDN_STAT_ICALLW); + ic.arg = chanp->chan; + ic.parm.setup.si1 = 7; + ic.parm.setup.si2 = 0; + ic.parm.setup.plan = 0; + ic.parm.setup.screen = 0; + sprintf(ic.parm.setup.eazmsn, "%d", chanp->chan + 1); + sprintf(ic.parm.setup.phone, "LEASED%d", chanp->cs->myid); + ret = chanp->cs->iif.statcallb(&ic); + if (chanp->debug & 1) + link_debug(chanp, 1, "statcallb ret=%d", ret); + if (!ret) { + chanp->cs->cardmsg(chanp->cs, MDL_INFO_REL, (void *) (long)chanp->chan); + FsmChangeState(fi, ST_NULL); + } +} + + +/* + * Dial out + */ +static void +lli_init_bchan_out(struct FsmInst *fi, int event, void *arg) +{ + struct Channel *chanp = fi->userdata; + + FsmChangeState(fi, ST_WAIT_BCONN); + if (chanp->debug & 1) + link_debug(chanp, 0, "STAT_DCONN"); + HL_LL(chanp, ISDN_STAT_DCONN); + init_b_st(chanp, 0); + chanp->b_st->lli.l4l3(chanp->b_st, DL_ESTABLISH | REQUEST, NULL); +} + +static void +lli_prep_dialout(struct FsmInst *fi, int event, void *arg) +{ + struct Channel *chanp = fi->userdata; + + FsmDelTimer(&chanp->drel_timer, 60); + FsmDelTimer(&chanp->dial_timer, 73); + chanp->l2_active_protocol = chanp->l2_protocol; + chanp->incoming = 0; + chanp->cs->cardmsg(chanp->cs, MDL_INFO_SETUP, (void *) (long)chanp->chan); + if (chanp->leased) { + lli_init_bchan_out(fi, event, arg); + } else { + FsmChangeState(fi, ST_OUT_DIAL); + chanp->d_st->lli.l4l3(chanp->d_st, CC_SETUP | REQUEST, chanp); + } +} + +static void +lli_resume(struct FsmInst *fi, int event, void *arg) +{ + struct Channel *chanp = fi->userdata; + + FsmDelTimer(&chanp->drel_timer, 60); + FsmDelTimer(&chanp->dial_timer, 73); + chanp->l2_active_protocol = chanp->l2_protocol; + chanp->incoming = 0; + chanp->cs->cardmsg(chanp->cs, MDL_INFO_SETUP, (void *) (long)chanp->chan); + if (chanp->leased) { + lli_init_bchan_out(fi, event, arg); + } else { + FsmChangeState(fi, ST_OUT_DIAL); + chanp->d_st->lli.l4l3(chanp->d_st, CC_RESUME | REQUEST, chanp); + } +} + +static void +lli_go_active(struct FsmInst *fi, int event, void *arg) +{ + struct Channel *chanp = fi->userdata; + isdn_ctrl ic; + + + FsmChangeState(fi, ST_ACTIVE); + chanp->data_open = !0; + if (chanp->bcs->conmsg) + strcpy(ic.parm.num, chanp->bcs->conmsg); + else + ic.parm.num[0] = 0; + if (chanp->debug & 1) + link_debug(chanp, 0, "STAT_BCONN %s", ic.parm.num); + ic.driver = chanp->cs->myid; + ic.command = ISDN_STAT_BCONN; + ic.arg = chanp->chan; + chanp->cs->iif.statcallb(&ic); + chanp->cs->cardmsg(chanp->cs, MDL_INFO_CONN, (void *) (long)chanp->chan); +} + + +/* + * RESUME + */ + +/* incoming call */ + +static void +lli_deliver_call(struct FsmInst *fi, int event, void *arg) +{ + struct Channel *chanp = fi->userdata; + isdn_ctrl ic; + int ret; + + chanp->cs->cardmsg(chanp->cs, MDL_INFO_SETUP, (void *) (long)chanp->chan); + /* + * Report incoming calls only once to linklevel, use CallFlags + * which is set to 3 with each broadcast message in isdnl1.c + * and resetted if a interface answered the STAT_ICALL. + */ + if (1) { /* for only one TEI */ + FsmChangeState(fi, ST_IN_WAIT_LL); + if (chanp->debug & 1) + link_debug(chanp, 0, (chanp->chan < 2) ? "STAT_ICALL" : "STAT_ICALLW"); + ic.driver = chanp->cs->myid; + ic.command = ((chanp->chan < 2) ? ISDN_STAT_ICALL : ISDN_STAT_ICALLW); + + ic.arg = chanp->chan; + /* + * No need to return "unknown" for calls without OAD, + * cause that's handled in linklevel now (replaced by '0') + */ + memcpy(&ic.parm.setup, &chanp->proc->para.setup, sizeof(setup_parm)); + ret = chanp->cs->iif.statcallb(&ic); + if (chanp->debug & 1) + link_debug(chanp, 1, "statcallb ret=%d", ret); + + switch (ret) { + case 1: /* OK, someone likes this call */ + FsmDelTimer(&chanp->drel_timer, 61); + FsmChangeState(fi, ST_IN_ALERT_SENT); + chanp->d_st->lli.l4l3(chanp->d_st, CC_ALERTING | REQUEST, chanp->proc); + break; + case 5: /* direct redirect */ + case 4: /* Proceeding desired */ + FsmDelTimer(&chanp->drel_timer, 61); + FsmChangeState(fi, ST_IN_PROCEED_SEND); + chanp->d_st->lli.l4l3(chanp->d_st, CC_PROCEED_SEND | REQUEST, chanp->proc); + if (ret == 5) { + memcpy(&chanp->setup, &ic.parm.setup, sizeof(setup_parm)); + chanp->d_st->lli.l4l3(chanp->d_st, CC_REDIR | REQUEST, chanp->proc); + } + break; + case 2: /* Rejecting Call */ + break; + case 3: /* incomplete number */ + FsmDelTimer(&chanp->drel_timer, 61); + chanp->d_st->lli.l4l3(chanp->d_st, CC_MORE_INFO | REQUEST, chanp->proc); + break; + case 0: /* OK, nobody likes this call */ + default: /* statcallb problems */ + chanp->d_st->lli.l4l3(chanp->d_st, CC_IGNORE | REQUEST, chanp->proc); + chanp->cs->cardmsg(chanp->cs, MDL_INFO_REL, (void *) (long)chanp->chan); + FsmChangeState(fi, ST_NULL); + break; + } + } else { + chanp->d_st->lli.l4l3(chanp->d_st, CC_IGNORE | REQUEST, chanp->proc); + chanp->cs->cardmsg(chanp->cs, MDL_INFO_REL, (void *) (long)chanp->chan); + } +} + +static void +lli_send_dconnect(struct FsmInst *fi, int event, void *arg) +{ + struct Channel *chanp = fi->userdata; + + FsmChangeState(fi, ST_IN_WAIT_CONN_ACK); + chanp->d_st->lli.l4l3(chanp->d_st, CC_SETUP | RESPONSE, chanp->proc); +} + +static void +lli_send_alert(struct FsmInst *fi, int event, void *arg) +{ + struct Channel *chanp = fi->userdata; + + FsmChangeState(fi, ST_IN_ALERT_SENT); + chanp->d_st->lli.l4l3(chanp->d_st, CC_ALERTING | REQUEST, chanp->proc); +} + +static void +lli_send_redir(struct FsmInst *fi, int event, void *arg) +{ + struct Channel *chanp = fi->userdata; + + chanp->d_st->lli.l4l3(chanp->d_st, CC_REDIR | REQUEST, chanp->proc); +} + +static void +lli_init_bchan_in(struct FsmInst *fi, int event, void *arg) +{ + struct Channel *chanp = fi->userdata; + + FsmChangeState(fi, ST_WAIT_BCONN); + if (chanp->debug & 1) + link_debug(chanp, 0, "STAT_DCONN"); + HL_LL(chanp, ISDN_STAT_DCONN); + chanp->l2_active_protocol = chanp->l2_protocol; + chanp->incoming = !0; + init_b_st(chanp, !0); + chanp->b_st->lli.l4l3(chanp->b_st, DL_ESTABLISH | REQUEST, NULL); +} + +static void +lli_setup_rsp(struct FsmInst *fi, int event, void *arg) +{ + struct Channel *chanp = fi->userdata; + + if (chanp->leased) { + lli_init_bchan_in(fi, event, arg); + } else { + FsmChangeState(fi, ST_IN_WAIT_CONN_ACK); +#ifdef WANT_ALERT + chanp->d_st->lli.l4l3(chanp->d_st, CC_ALERTING | REQUEST, chanp->proc); +#endif + chanp->d_st->lli.l4l3(chanp->d_st, CC_SETUP | RESPONSE, chanp->proc); + } +} + +/* Call suspend */ + +static void +lli_suspend(struct FsmInst *fi, int event, void *arg) +{ + struct Channel *chanp = fi->userdata; + + chanp->d_st->lli.l4l3(chanp->d_st, CC_SUSPEND | REQUEST, chanp->proc); +} + +/* Call clearing */ + +static void +lli_leased_hup(struct FsmInst *fi, struct Channel *chanp) +{ + isdn_ctrl ic; + + ic.driver = chanp->cs->myid; + ic.command = ISDN_STAT_CAUSE; + ic.arg = chanp->chan; + sprintf(ic.parm.num, "L0010"); + chanp->cs->iif.statcallb(&ic); + if (chanp->debug & 1) + link_debug(chanp, 0, "STAT_DHUP"); + HL_LL(chanp, ISDN_STAT_DHUP); + lli_close(fi); +} + +static void +lli_disconnect_req(struct FsmInst *fi, int event, void *arg) +{ + struct Channel *chanp = fi->userdata; + + if (chanp->leased) { + lli_leased_hup(fi, chanp); + } else { + FsmChangeState(fi, ST_WAIT_DRELEASE); + if (chanp->proc) + chanp->proc->para.cause = 0x10; /* Normal Call Clearing */ + chanp->d_st->lli.l4l3(chanp->d_st, CC_DISCONNECT | REQUEST, + chanp->proc); + } +} + +static void +lli_disconnect_reject(struct FsmInst *fi, int event, void *arg) +{ + struct Channel *chanp = fi->userdata; + + if (chanp->leased) { + lli_leased_hup(fi, chanp); + } else { + FsmChangeState(fi, ST_WAIT_DRELEASE); + if (chanp->proc) + chanp->proc->para.cause = 0x15; /* Call Rejected */ + chanp->d_st->lli.l4l3(chanp->d_st, CC_DISCONNECT | REQUEST, + chanp->proc); + } +} + +static void +lli_dhup_close(struct FsmInst *fi, int event, void *arg) +{ + struct Channel *chanp = fi->userdata; + + if (chanp->leased) { + lli_leased_hup(fi, chanp); + } else { + if (chanp->debug & 1) + link_debug(chanp, 0, "STAT_DHUP"); + lli_deliver_cause(chanp); + HL_LL(chanp, ISDN_STAT_DHUP); + lli_close(fi); + } +} + +static void +lli_reject_req(struct FsmInst *fi, int event, void *arg) +{ + struct Channel *chanp = fi->userdata; + + if (chanp->leased) { + lli_leased_hup(fi, chanp); + return; + } +#ifndef ALERT_REJECT + if (chanp->proc) + chanp->proc->para.cause = 0x15; /* Call Rejected */ + chanp->d_st->lli.l4l3(chanp->d_st, CC_REJECT | REQUEST, chanp->proc); + lli_dhup_close(fi, event, arg); +#else + FsmRestartTimer(&chanp->drel_timer, 40, EV_HANGUP, NULL, 63); + FsmChangeState(fi, ST_IN_ALERT_SENT); + chanp->d_st->lli.l4l3(chanp->d_st, CC_ALERTING | REQUEST, chanp->proc); +#endif +} + +static void +lli_disconn_bchan(struct FsmInst *fi, int event, void *arg) +{ + struct Channel *chanp = fi->userdata; + + chanp->data_open = 0; + FsmChangeState(fi, ST_WAIT_BRELEASE); + chanp->b_st->lli.l4l3(chanp->b_st, DL_RELEASE | REQUEST, NULL); +} + +static void +lli_start_disc(struct FsmInst *fi, int event, void *arg) +{ + struct Channel *chanp = fi->userdata; + + if (chanp->leased) { + lli_leased_hup(fi, chanp); + } else { + lli_disconnect_req(fi, event, arg); + } +} + +static void +lli_rel_b_disc(struct FsmInst *fi, int event, void *arg) +{ + struct Channel *chanp = fi->userdata; + + release_b_st(chanp); + lli_start_disc(fi, event, arg); +} + +static void +lli_bhup_disc(struct FsmInst *fi, int event, void *arg) +{ + struct Channel *chanp = fi->userdata; + + if (chanp->debug & 1) + link_debug(chanp, 0, "STAT_BHUP"); + HL_LL(chanp, ISDN_STAT_BHUP); + lli_rel_b_disc(fi, event, arg); +} + +static void +lli_bhup_rel_b(struct FsmInst *fi, int event, void *arg) +{ + struct Channel *chanp = fi->userdata; + + FsmChangeState(fi, ST_WAIT_DCOMMAND); + chanp->data_open = 0; + if (chanp->debug & 1) + link_debug(chanp, 0, "STAT_BHUP"); + HL_LL(chanp, ISDN_STAT_BHUP); + release_b_st(chanp); +} + +static void +lli_release_bchan(struct FsmInst *fi, int event, void *arg) +{ + struct Channel *chanp = fi->userdata; + + chanp->data_open = 0; + FsmChangeState(fi, ST_WAIT_BREL_DISC); + chanp->b_st->lli.l4l3(chanp->b_st, DL_RELEASE | REQUEST, NULL); +} + + +static void +lli_rel_b_dhup(struct FsmInst *fi, int event, void *arg) +{ + struct Channel *chanp = fi->userdata; + + release_b_st(chanp); + lli_dhup_close(fi, event, arg); +} + +static void +lli_bhup_dhup(struct FsmInst *fi, int event, void *arg) +{ + struct Channel *chanp = fi->userdata; + + if (chanp->debug & 1) + link_debug(chanp, 0, "STAT_BHUP"); + HL_LL(chanp, ISDN_STAT_BHUP); + lli_rel_b_dhup(fi, event, arg); +} + +static void +lli_abort(struct FsmInst *fi, int event, void *arg) +{ + struct Channel *chanp = fi->userdata; + + chanp->data_open = 0; + chanp->b_st->lli.l4l3(chanp->b_st, DL_RELEASE | REQUEST, NULL); + lli_bhup_dhup(fi, event, arg); +} + +static void +lli_release_req(struct FsmInst *fi, int event, void *arg) +{ + struct Channel *chanp = fi->userdata; + + if (chanp->leased) { + lli_leased_hup(fi, chanp); + } else { + FsmChangeState(fi, ST_WAIT_D_REL_CNF); + chanp->d_st->lli.l4l3(chanp->d_st, CC_RELEASE | REQUEST, + chanp->proc); + } +} + +static void +lli_rel_b_release_req(struct FsmInst *fi, int event, void *arg) +{ + struct Channel *chanp = fi->userdata; + + release_b_st(chanp); + lli_release_req(fi, event, arg); +} + +static void +lli_bhup_release_req(struct FsmInst *fi, int event, void *arg) +{ + struct Channel *chanp = fi->userdata; + + if (chanp->debug & 1) + link_debug(chanp, 0, "STAT_BHUP"); + HL_LL(chanp, ISDN_STAT_BHUP); + lli_rel_b_release_req(fi, event, arg); +} + + +/* processing charge info */ +static void +lli_charge_info(struct FsmInst *fi, int event, void *arg) +{ + struct Channel *chanp = fi->userdata; + isdn_ctrl ic; + + ic.driver = chanp->cs->myid; + ic.command = ISDN_STAT_CINF; + ic.arg = chanp->chan; + sprintf(ic.parm.num, "%d", chanp->proc->para.chargeinfo); + chanp->cs->iif.statcallb(&ic); +} + +/* error procedures */ + +static void +lli_dchan_not_ready(struct FsmInst *fi, int event, void *arg) +{ + struct Channel *chanp = fi->userdata; + + if (chanp->debug & 1) + link_debug(chanp, 0, "STAT_DHUP"); + HL_LL(chanp, ISDN_STAT_DHUP); +} + +static void +lli_no_setup_rsp(struct FsmInst *fi, int event, void *arg) +{ + struct Channel *chanp = fi->userdata; + + if (chanp->debug & 1) + link_debug(chanp, 0, "STAT_DHUP"); + HL_LL(chanp, ISDN_STAT_DHUP); + lli_close(fi); +} + +static void +lli_error(struct FsmInst *fi, int event, void *arg) +{ + FsmChangeState(fi, ST_WAIT_DRELEASE); +} + +static void +lli_failure_l(struct FsmInst *fi, int event, void *arg) +{ + struct Channel *chanp = fi->userdata; + isdn_ctrl ic; + + FsmChangeState(fi, ST_NULL); + ic.driver = chanp->cs->myid; + ic.command = ISDN_STAT_CAUSE; + ic.arg = chanp->chan; + sprintf(ic.parm.num, "L%02X%02X", 0, 0x2f); + chanp->cs->iif.statcallb(&ic); + HL_LL(chanp, ISDN_STAT_DHUP); + chanp->Flags = 0; + chanp->cs->cardmsg(chanp->cs, MDL_INFO_REL, (void *) (long)chanp->chan); +} + +static void +lli_rel_b_fail(struct FsmInst *fi, int event, void *arg) +{ + struct Channel *chanp = fi->userdata; + + release_b_st(chanp); + lli_failure_l(fi, event, arg); +} + +static void +lli_bhup_fail(struct FsmInst *fi, int event, void *arg) +{ + struct Channel *chanp = fi->userdata; + + if (chanp->debug & 1) + link_debug(chanp, 0, "STAT_BHUP"); + HL_LL(chanp, ISDN_STAT_BHUP); + lli_rel_b_fail(fi, event, arg); +} + +static void +lli_failure_a(struct FsmInst *fi, int event, void *arg) +{ + struct Channel *chanp = fi->userdata; + + chanp->data_open = 0; + chanp->b_st->lli.l4l3(chanp->b_st, DL_RELEASE | REQUEST, NULL); + lli_bhup_fail(fi, event, arg); +} + +/* *INDENT-OFF* */ +static struct FsmNode fnlist[] __initdata = +{ + {ST_NULL, EV_DIAL, lli_prep_dialout}, + {ST_NULL, EV_RESUME, lli_resume}, + {ST_NULL, EV_SETUP_IND, lli_deliver_call}, + {ST_NULL, EV_LEASED, lli_leased_in}, + {ST_OUT_DIAL, EV_SETUP_CNF, lli_init_bchan_out}, + {ST_OUT_DIAL, EV_HANGUP, lli_disconnect_req}, + {ST_OUT_DIAL, EV_DISCONNECT_IND, lli_release_req}, + {ST_OUT_DIAL, EV_RELEASE, lli_dhup_close}, + {ST_OUT_DIAL, EV_NOSETUP_RSP, lli_no_setup_rsp}, + {ST_OUT_DIAL, EV_SETUP_ERR, lli_error}, + {ST_IN_WAIT_LL, EV_LEASED_REL, lli_failure_l}, + {ST_IN_WAIT_LL, EV_ACCEPTD, lli_setup_rsp}, + {ST_IN_WAIT_LL, EV_HANGUP, lli_reject_req}, + {ST_IN_WAIT_LL, EV_DISCONNECT_IND, lli_release_req}, + {ST_IN_WAIT_LL, EV_RELEASE, lli_dhup_close}, + {ST_IN_WAIT_LL, EV_SETUP_IND, lli_deliver_call}, + {ST_IN_WAIT_LL, EV_SETUP_ERR, lli_error}, + {ST_IN_ALERT_SENT, EV_SETUP_CMPL_IND, lli_init_bchan_in}, + {ST_IN_ALERT_SENT, EV_ACCEPTD, lli_send_dconnect}, + {ST_IN_ALERT_SENT, EV_HANGUP, lli_disconnect_reject}, + {ST_IN_ALERT_SENT, EV_DISCONNECT_IND, lli_release_req}, + {ST_IN_ALERT_SENT, EV_RELEASE, lli_dhup_close}, + {ST_IN_ALERT_SENT, EV_REDIR, lli_send_redir}, + {ST_IN_PROCEED_SEND, EV_REDIR, lli_send_redir}, + {ST_IN_PROCEED_SEND, EV_ALERT, lli_send_alert}, + {ST_IN_PROCEED_SEND, EV_ACCEPTD, lli_send_dconnect}, + {ST_IN_PROCEED_SEND, EV_HANGUP, lli_disconnect_reject}, + {ST_IN_PROCEED_SEND, EV_DISCONNECT_IND, lli_dhup_close}, + {ST_IN_ALERT_SENT, EV_RELEASE, lli_dhup_close}, + {ST_IN_WAIT_CONN_ACK, EV_SETUP_CMPL_IND, lli_init_bchan_in}, + {ST_IN_WAIT_CONN_ACK, EV_HANGUP, lli_disconnect_req}, + {ST_IN_WAIT_CONN_ACK, EV_DISCONNECT_IND, lli_release_req}, + {ST_IN_WAIT_CONN_ACK, EV_RELEASE, lli_dhup_close}, + {ST_IN_WAIT_CONN_ACK, EV_CONNECT_ERR, lli_error}, + {ST_WAIT_BCONN, EV_BC_EST, lli_go_active}, + {ST_WAIT_BCONN, EV_BC_REL, lli_rel_b_disc}, + {ST_WAIT_BCONN, EV_HANGUP, lli_rel_b_disc}, + {ST_WAIT_BCONN, EV_DISCONNECT_IND, lli_rel_b_release_req}, + {ST_WAIT_BCONN, EV_RELEASE, lli_rel_b_dhup}, + {ST_WAIT_BCONN, EV_LEASED_REL, lli_rel_b_fail}, + {ST_WAIT_BCONN, EV_CINF, lli_charge_info}, + {ST_ACTIVE, EV_CINF, lli_charge_info}, + {ST_ACTIVE, EV_BC_REL, lli_bhup_rel_b}, + {ST_ACTIVE, EV_SUSPEND, lli_suspend}, + {ST_ACTIVE, EV_HANGUP, lli_disconn_bchan}, + {ST_ACTIVE, EV_DISCONNECT_IND, lli_release_bchan}, + {ST_ACTIVE, EV_RELEASE, lli_abort}, + {ST_ACTIVE, EV_LEASED_REL, lli_failure_a}, + {ST_WAIT_BRELEASE, EV_BC_REL, lli_bhup_disc}, + {ST_WAIT_BRELEASE, EV_DISCONNECT_IND, lli_bhup_release_req}, + {ST_WAIT_BRELEASE, EV_RELEASE, lli_bhup_dhup}, + {ST_WAIT_BRELEASE, EV_LEASED_REL, lli_bhup_fail}, + {ST_WAIT_BREL_DISC, EV_BC_REL, lli_bhup_release_req}, + {ST_WAIT_BREL_DISC, EV_RELEASE, lli_bhup_dhup}, + {ST_WAIT_DCOMMAND, EV_HANGUP, lli_start_disc}, + {ST_WAIT_DCOMMAND, EV_DISCONNECT_IND, lli_release_req}, + {ST_WAIT_DCOMMAND, EV_RELEASE, lli_dhup_close}, + {ST_WAIT_DCOMMAND, EV_LEASED_REL, lli_failure_l}, + {ST_WAIT_DRELEASE, EV_RELEASE, lli_dhup_close}, + {ST_WAIT_DRELEASE, EV_DIAL, lli_dchan_not_ready}, + /* ETS 300-104 16.1 */ + {ST_WAIT_D_REL_CNF, EV_RELEASE, lli_dhup_close}, + {ST_WAIT_D_REL_CNF, EV_DIAL, lli_dchan_not_ready}, +}; +/* *INDENT-ON* */ + +int __init +CallcNew(void) +{ + callcfsm.state_count = STATE_COUNT; + callcfsm.event_count = EVENT_COUNT; + callcfsm.strEvent = strEvent; + callcfsm.strState = strState; + return FsmNew(&callcfsm, fnlist, ARRAY_SIZE(fnlist)); +} + +void +CallcFree(void) +{ + FsmFree(&callcfsm); +} + +static void +release_b_st(struct Channel *chanp) +{ + struct PStack *st = chanp->b_st; + + if (test_and_clear_bit(FLG_START_B, &chanp->Flags)) { + chanp->bcs->BC_Close(chanp->bcs); + switch (chanp->l2_active_protocol) { + case (ISDN_PROTO_L2_X75I): + releasestack_isdnl2(st); + break; + case (ISDN_PROTO_L2_HDLC): + case (ISDN_PROTO_L2_HDLC_56K): + case (ISDN_PROTO_L2_TRANS): + case (ISDN_PROTO_L2_MODEM): + case (ISDN_PROTO_L2_FAX): + releasestack_transl2(st); + break; + } + } +} + +static struct Channel +*selectfreechannel(struct PStack *st, int bch) +{ + struct IsdnCardState *cs = st->l1.hardware; + struct Channel *chanp = st->lli.userdata; + int i; + + if (test_bit(FLG_TWO_DCHAN, &cs->HW_Flags)) + i = 1; + else + i = 0; + + if (!bch) { + i = 2; /* virtual channel */ + chanp += 2; + } + + while (i < ((bch) ? cs->chanlimit : (2 + MAX_WAITING_CALLS))) { + if (chanp->fi.state == ST_NULL) + return (chanp); + chanp++; + i++; + } + + if (bch) /* number of channels is limited */ { + i = 2; /* virtual channel */ + chanp = st->lli.userdata; + chanp += i; + while (i < (2 + MAX_WAITING_CALLS)) { + if (chanp->fi.state == ST_NULL) + return (chanp); + chanp++; + i++; + } + } + return (NULL); +} + +static void stat_redir_result(struct IsdnCardState *cs, int chan, ulong result) +{ isdn_ctrl ic; + + ic.driver = cs->myid; + ic.command = ISDN_STAT_REDIR; + ic.arg = chan; + ic.parm.num[0] = result; + cs->iif.statcallb(&ic); +} /* stat_redir_result */ + +static void +dchan_l3l4(struct PStack *st, int pr, void *arg) +{ + struct l3_process *pc = arg; + struct IsdnCardState *cs = st->l1.hardware; + struct Channel *chanp; + + if (!pc) + return; + + if (pr == (CC_SETUP | INDICATION)) { + if (!(chanp = selectfreechannel(pc->st, pc->para.bchannel))) { + pc->para.cause = 0x11; /* User busy */ + pc->st->lli.l4l3(pc->st, CC_REJECT | REQUEST, pc); + } else { + chanp->proc = pc; + pc->chan = chanp; + FsmEvent(&chanp->fi, EV_SETUP_IND, NULL); + } + return; + } + if (!(chanp = pc->chan)) + return; + + switch (pr) { + case (CC_MORE_INFO | INDICATION): + FsmEvent(&chanp->fi, EV_SETUP_IND, NULL); + break; + case (CC_DISCONNECT | INDICATION): + FsmEvent(&chanp->fi, EV_DISCONNECT_IND, NULL); + break; + case (CC_RELEASE | CONFIRM): + FsmEvent(&chanp->fi, EV_RELEASE, NULL); + break; + case (CC_SUSPEND | CONFIRM): + FsmEvent(&chanp->fi, EV_RELEASE, NULL); + break; + case (CC_RESUME | CONFIRM): + FsmEvent(&chanp->fi, EV_SETUP_CNF, NULL); + break; + case (CC_RESUME_ERR): + FsmEvent(&chanp->fi, EV_RELEASE, NULL); + break; + case (CC_RELEASE | INDICATION): + FsmEvent(&chanp->fi, EV_RELEASE, NULL); + break; + case (CC_SETUP_COMPL | INDICATION): + FsmEvent(&chanp->fi, EV_SETUP_CMPL_IND, NULL); + break; + case (CC_SETUP | CONFIRM): + FsmEvent(&chanp->fi, EV_SETUP_CNF, NULL); + break; + case (CC_CHARGE | INDICATION): + FsmEvent(&chanp->fi, EV_CINF, NULL); + break; + case (CC_NOSETUP_RSP): + FsmEvent(&chanp->fi, EV_NOSETUP_RSP, NULL); + break; + case (CC_SETUP_ERR): + FsmEvent(&chanp->fi, EV_SETUP_ERR, NULL); + break; + case (CC_CONNECT_ERR): + FsmEvent(&chanp->fi, EV_CONNECT_ERR, NULL); + break; + case (CC_RELEASE_ERR): + FsmEvent(&chanp->fi, EV_RELEASE, NULL); + break; + case (CC_PROCEED_SEND | INDICATION): + case (CC_PROCEEDING | INDICATION): + case (CC_ALERTING | INDICATION): + case (CC_PROGRESS | INDICATION): + case (CC_NOTIFY | INDICATION): + break; + case (CC_REDIR | INDICATION): + stat_redir_result(cs, chanp->chan, pc->redir_result); + break; + default: + if (chanp->debug & 0x800) { + HiSax_putstatus(chanp->cs, "Ch", + "%d L3->L4 unknown primitiv %#x", + chanp->chan, pr); + } + } +} + +static void +dummy_pstack(struct PStack *st, int pr, void *arg) { + printk(KERN_WARNING"call to dummy_pstack pr=%04x arg %lx\n", pr, (long)arg); +} + +static int +init_PStack(struct PStack **stp) { + *stp = kmalloc(sizeof(struct PStack), GFP_KERNEL); + if (!*stp) + return -ENOMEM; + (*stp)->next = NULL; + (*stp)->l1.l1l2 = dummy_pstack; + (*stp)->l1.l1hw = dummy_pstack; + (*stp)->l1.l1tei = dummy_pstack; + (*stp)->l2.l2tei = dummy_pstack; + (*stp)->l2.l2l1 = dummy_pstack; + (*stp)->l2.l2l3 = dummy_pstack; + (*stp)->l3.l3l2 = dummy_pstack; + (*stp)->l3.l3ml3 = dummy_pstack; + (*stp)->l3.l3l4 = dummy_pstack; + (*stp)->lli.l4l3 = dummy_pstack; + (*stp)->ma.layer = dummy_pstack; + return 0; +} + +static int +init_d_st(struct Channel *chanp) +{ + struct PStack *st; + struct IsdnCardState *cs = chanp->cs; + char tmp[16]; + int err; + + err = init_PStack(&chanp->d_st); + if (err) + return err; + st = chanp->d_st; + st->next = NULL; + HiSax_addlist(cs, st); + setstack_HiSax(st, cs); + st->l2.sap = 0; + st->l2.tei = -1; + st->l2.flag = 0; + test_and_set_bit(FLG_MOD128, &st->l2.flag); + test_and_set_bit(FLG_LAPD, &st->l2.flag); + test_and_set_bit(FLG_ORIG, &st->l2.flag); + st->l2.maxlen = MAX_DFRAME_LEN; + st->l2.window = 1; + st->l2.T200 = 1000; /* 1000 milliseconds */ + st->l2.N200 = 3; /* try 3 times */ + st->l2.T203 = 10000; /* 10000 milliseconds */ + if (test_bit(FLG_TWO_DCHAN, &cs->HW_Flags)) + sprintf(tmp, "DCh%d Q.921 ", chanp->chan); + else + sprintf(tmp, "DCh Q.921 "); + setstack_isdnl2(st, tmp); + setstack_l3dc(st, chanp); + st->lli.userdata = chanp; + st->l3.l3l4 = dchan_l3l4; + + return 0; +} + +static __printf(2, 3) void + callc_debug(struct FsmInst *fi, char *fmt, ...) +{ + va_list args; + struct Channel *chanp = fi->userdata; + char tmp[16]; + + va_start(args, fmt); + sprintf(tmp, "Ch%d callc ", chanp->chan); + VHiSax_putstatus(chanp->cs, tmp, fmt, args); + va_end(args); +} + +static int +init_chan(int chan, struct IsdnCardState *csta) +{ + struct Channel *chanp = csta->channel + chan; + int err; + + chanp->cs = csta; + chanp->bcs = csta->bcs + chan; + chanp->chan = chan; + chanp->incoming = 0; + chanp->debug = 0; + chanp->Flags = 0; + chanp->leased = 0; + err = init_PStack(&chanp->b_st); + if (err) + return err; + chanp->b_st->l1.delay = DEFAULT_B_DELAY; + chanp->fi.fsm = &callcfsm; + chanp->fi.state = ST_NULL; + chanp->fi.debug = 0; + chanp->fi.userdata = chanp; + chanp->fi.printdebug = callc_debug; + FsmInitTimer(&chanp->fi, &chanp->dial_timer); + FsmInitTimer(&chanp->fi, &chanp->drel_timer); + if (!chan || (test_bit(FLG_TWO_DCHAN, &csta->HW_Flags) && chan < 2)) { + err = init_d_st(chanp); + if (err) + return err; + } else { + chanp->d_st = csta->channel->d_st; + } + chanp->data_open = 0; + return 0; +} + +int +CallcNewChan(struct IsdnCardState *csta) { + int i, err; + + chancount += 2; + err = init_chan(0, csta); + if (err) + return err; + err = init_chan(1, csta); + if (err) + return err; + printk(KERN_INFO "HiSax: 2 channels added\n"); + + for (i = 0; i < MAX_WAITING_CALLS; i++) { + err = init_chan(i + 2, csta); + if (err) + return err; + } + printk(KERN_INFO "HiSax: MAX_WAITING_CALLS added\n"); + if (test_bit(FLG_PTP, &csta->channel->d_st->l2.flag)) { + printk(KERN_INFO "LAYER2 WATCHING ESTABLISH\n"); + csta->channel->d_st->lli.l4l3(csta->channel->d_st, + DL_ESTABLISH | REQUEST, NULL); + } + return (0); +} + +static void +release_d_st(struct Channel *chanp) +{ + struct PStack *st = chanp->d_st; + + if (!st) + return; + releasestack_isdnl2(st); + releasestack_isdnl3(st); + HiSax_rmlist(st->l1.hardware, st); + kfree(st); + chanp->d_st = NULL; +} + +void +CallcFreeChan(struct IsdnCardState *csta) +{ + int i; + + for (i = 0; i < 2; i++) { + FsmDelTimer(&csta->channel[i].drel_timer, 74); + FsmDelTimer(&csta->channel[i].dial_timer, 75); + if (i || test_bit(FLG_TWO_DCHAN, &csta->HW_Flags)) + release_d_st(csta->channel + i); + if (csta->channel[i].b_st) { + release_b_st(csta->channel + i); + kfree(csta->channel[i].b_st); + csta->channel[i].b_st = NULL; + } else + printk(KERN_WARNING "CallcFreeChan b_st ch%d already freed\n", i); + if (i || test_bit(FLG_TWO_DCHAN, &csta->HW_Flags)) { + release_d_st(csta->channel + i); + } else + csta->channel[i].d_st = NULL; + } +} + +static void +lldata_handler(struct PStack *st, int pr, void *arg) +{ + struct Channel *chanp = (struct Channel *) st->lli.userdata; + struct sk_buff *skb = arg; + + switch (pr) { + case (DL_DATA | INDICATION): + if (chanp->data_open) { + if (chanp->debug & 0x800) + link_debug(chanp, 0, "lldata: %d", skb->len); + chanp->cs->iif.rcvcallb_skb(chanp->cs->myid, chanp->chan, skb); + } else { + link_debug(chanp, 0, "lldata: channel not open"); + dev_kfree_skb(skb); + } + break; + case (DL_ESTABLISH | INDICATION): + case (DL_ESTABLISH | CONFIRM): + FsmEvent(&chanp->fi, EV_BC_EST, NULL); + break; + case (DL_RELEASE | INDICATION): + case (DL_RELEASE | CONFIRM): + FsmEvent(&chanp->fi, EV_BC_REL, NULL); + break; + default: + printk(KERN_WARNING "lldata_handler unknown primitive %#x\n", + pr); + break; + } +} + +static void +lltrans_handler(struct PStack *st, int pr, void *arg) +{ + struct Channel *chanp = (struct Channel *) st->lli.userdata; + struct sk_buff *skb = arg; + + switch (pr) { + case (PH_DATA | INDICATION): + if (chanp->data_open) { + if (chanp->debug & 0x800) + link_debug(chanp, 0, "lltrans: %d", skb->len); + chanp->cs->iif.rcvcallb_skb(chanp->cs->myid, chanp->chan, skb); + } else { + link_debug(chanp, 0, "lltrans: channel not open"); + dev_kfree_skb(skb); + } + break; + case (PH_ACTIVATE | INDICATION): + case (PH_ACTIVATE | CONFIRM): + FsmEvent(&chanp->fi, EV_BC_EST, NULL); + break; + case (PH_DEACTIVATE | INDICATION): + case (PH_DEACTIVATE | CONFIRM): + FsmEvent(&chanp->fi, EV_BC_REL, NULL); + break; + default: + printk(KERN_WARNING "lltrans_handler unknown primitive %#x\n", + pr); + break; + } +} + +void +lli_writewakeup(struct PStack *st, int len) +{ + struct Channel *chanp = st->lli.userdata; + isdn_ctrl ic; + + if (chanp->debug & 0x800) + link_debug(chanp, 0, "llwakeup: %d", len); + ic.driver = chanp->cs->myid; + ic.command = ISDN_STAT_BSENT; + ic.arg = chanp->chan; + ic.parm.length = len; + chanp->cs->iif.statcallb(&ic); +} + +static int +init_b_st(struct Channel *chanp, int incoming) +{ + struct PStack *st = chanp->b_st; + struct IsdnCardState *cs = chanp->cs; + char tmp[16]; + + st->l1.hardware = cs; + if (chanp->leased) + st->l1.bc = chanp->chan & 1; + else + st->l1.bc = chanp->proc->para.bchannel - 1; + switch (chanp->l2_active_protocol) { + case (ISDN_PROTO_L2_X75I): + case (ISDN_PROTO_L2_HDLC): + st->l1.mode = L1_MODE_HDLC; + break; + case (ISDN_PROTO_L2_HDLC_56K): + st->l1.mode = L1_MODE_HDLC_56K; + break; + case (ISDN_PROTO_L2_TRANS): + st->l1.mode = L1_MODE_TRANS; + break; + case (ISDN_PROTO_L2_MODEM): + st->l1.mode = L1_MODE_V32; + break; + case (ISDN_PROTO_L2_FAX): + st->l1.mode = L1_MODE_FAX; + break; + } + chanp->bcs->conmsg = NULL; + if (chanp->bcs->BC_SetStack(st, chanp->bcs)) + return (-1); + st->l2.flag = 0; + test_and_set_bit(FLG_LAPB, &st->l2.flag); + st->l2.maxlen = MAX_DATA_SIZE; + if (!incoming) + test_and_set_bit(FLG_ORIG, &st->l2.flag); + st->l2.T200 = 1000; /* 1000 milliseconds */ + st->l2.window = 7; + st->l2.N200 = 4; /* try 4 times */ + st->l2.T203 = 5000; /* 5000 milliseconds */ + st->l3.debug = 0; + switch (chanp->l2_active_protocol) { + case (ISDN_PROTO_L2_X75I): + sprintf(tmp, "Ch%d X.75", chanp->chan); + setstack_isdnl2(st, tmp); + setstack_l3bc(st, chanp); + st->l2.l2l3 = lldata_handler; + st->lli.userdata = chanp; + test_and_clear_bit(FLG_LLI_L1WAKEUP, &st->lli.flag); + test_and_set_bit(FLG_LLI_L2WAKEUP, &st->lli.flag); + st->l2.l2m.debug = chanp->debug & 16; + st->l2.debug = chanp->debug & 64; + break; + case (ISDN_PROTO_L2_HDLC): + case (ISDN_PROTO_L2_HDLC_56K): + case (ISDN_PROTO_L2_TRANS): + case (ISDN_PROTO_L2_MODEM): + case (ISDN_PROTO_L2_FAX): + st->l1.l1l2 = lltrans_handler; + st->lli.userdata = chanp; + test_and_set_bit(FLG_LLI_L1WAKEUP, &st->lli.flag); + test_and_clear_bit(FLG_LLI_L2WAKEUP, &st->lli.flag); + setstack_transl2(st); + setstack_l3bc(st, chanp); + break; + } + test_and_set_bit(FLG_START_B, &chanp->Flags); + return (0); +} + +static void +leased_l4l3(struct PStack *st, int pr, void *arg) +{ + struct Channel *chanp = (struct Channel *) st->lli.userdata; + struct sk_buff *skb = arg; + + switch (pr) { + case (DL_DATA | REQUEST): + link_debug(chanp, 0, "leased line d-channel DATA"); + dev_kfree_skb(skb); + break; + case (DL_ESTABLISH | REQUEST): + st->l2.l2l1(st, PH_ACTIVATE | REQUEST, NULL); + break; + case (DL_RELEASE | REQUEST): + break; + default: + printk(KERN_WARNING "transd_l4l3 unknown primitive %#x\n", + pr); + break; + } +} + +static void +leased_l1l2(struct PStack *st, int pr, void *arg) +{ + struct Channel *chanp = (struct Channel *) st->lli.userdata; + struct sk_buff *skb = arg; + int i, event = EV_LEASED_REL; + + switch (pr) { + case (PH_DATA | INDICATION): + link_debug(chanp, 0, "leased line d-channel DATA"); + dev_kfree_skb(skb); + break; + case (PH_ACTIVATE | INDICATION): + case (PH_ACTIVATE | CONFIRM): + event = EV_LEASED; + /* fall through */ + case (PH_DEACTIVATE | INDICATION): + case (PH_DEACTIVATE | CONFIRM): + if (test_bit(FLG_TWO_DCHAN, &chanp->cs->HW_Flags)) + i = 1; + else + i = 0; + while (i < 2) { + FsmEvent(&chanp->fi, event, NULL); + chanp++; + i++; + } + break; + default: + printk(KERN_WARNING + "transd_l1l2 unknown primitive %#x\n", pr); + break; + } +} + +static void +distr_debug(struct IsdnCardState *csta, int debugflags) +{ + int i; + struct Channel *chanp = csta->channel; + + for (i = 0; i < (2 + MAX_WAITING_CALLS); i++) { + chanp[i].debug = debugflags; + chanp[i].fi.debug = debugflags & 2; + chanp[i].d_st->l2.l2m.debug = debugflags & 8; + chanp[i].b_st->l2.l2m.debug = debugflags & 0x10; + chanp[i].d_st->l2.debug = debugflags & 0x20; + chanp[i].b_st->l2.debug = debugflags & 0x40; + chanp[i].d_st->l3.l3m.debug = debugflags & 0x80; + chanp[i].b_st->l3.l3m.debug = debugflags & 0x100; + chanp[i].b_st->ma.tei_m.debug = debugflags & 0x200; + chanp[i].b_st->ma.debug = debugflags & 0x200; + chanp[i].d_st->l1.l1m.debug = debugflags & 0x1000; + chanp[i].b_st->l1.l1m.debug = debugflags & 0x2000; + } + if (debugflags & 4) + csta->debug |= DEB_DLOG_HEX; + else + csta->debug &= ~DEB_DLOG_HEX; +} + +static char tmpbuf[256]; + +static void +capi_debug(struct Channel *chanp, capi_msg *cm) +{ + char *t = tmpbuf; + + t += QuickHex(t, (u_char *)cm, (cm->Length > 50) ? 50 : cm->Length); + t--; + *t = 0; + HiSax_putstatus(chanp->cs, "Ch", "%d CAPIMSG %s", chanp->chan, tmpbuf); +} + +static void +lli_got_fac_req(struct Channel *chanp, capi_msg *cm) { + if ((cm->para[0] != 3) || (cm->para[1] != 0)) + return; + if (cm->para[2] < 3) + return; + if (cm->para[4] != 0) + return; + switch (cm->para[3]) { + case 4: /* Suspend */ + strncpy(chanp->setup.phone, &cm->para[5], cm->para[5] + 1); + FsmEvent(&chanp->fi, EV_SUSPEND, cm); + break; + case 5: /* Resume */ + strncpy(chanp->setup.phone, &cm->para[5], cm->para[5] + 1); + if (chanp->fi.state == ST_NULL) { + FsmEvent(&chanp->fi, EV_RESUME, cm); + } else { + FsmDelTimer(&chanp->dial_timer, 72); + FsmAddTimer(&chanp->dial_timer, 80, EV_RESUME, cm, 73); + } + break; + } +} + +static void +lli_got_manufacturer(struct Channel *chanp, struct IsdnCardState *cs, capi_msg *cm) { + if ((cs->typ == ISDN_CTYPE_ELSA) || (cs->typ == ISDN_CTYPE_ELSA_PNP) || + (cs->typ == ISDN_CTYPE_ELSA_PCI)) { + if (cs->hw.elsa.MFlag) { + cs->cardmsg(cs, CARD_AUX_IND, cm->para); + } + } +} + + +/***************************************************************/ +/* Limit the available number of channels for the current card */ +/***************************************************************/ +static int +set_channel_limit(struct IsdnCardState *cs, int chanmax) +{ + isdn_ctrl ic; + int i, ii; + + if ((chanmax < 0) || (chanmax > 2)) + return (-EINVAL); + cs->chanlimit = 0; + for (ii = 0; ii < 2; ii++) { + ic.driver = cs->myid; + ic.command = ISDN_STAT_DISCH; + ic.arg = ii; + if (ii >= chanmax) + ic.parm.num[0] = 0; /* disabled */ + else + ic.parm.num[0] = 1; /* enabled */ + i = cs->iif.statcallb(&ic); + if (i) return (-EINVAL); + if (ii < chanmax) + cs->chanlimit++; + } + return (0); +} /* set_channel_limit */ + +int +HiSax_command(isdn_ctrl *ic) +{ + struct IsdnCardState *csta = hisax_findcard(ic->driver); + struct PStack *st; + struct Channel *chanp; + int i; + u_int num; + + if (!csta) { + printk(KERN_ERR + "HiSax: if_command %d called with invalid driverId %d!\n", + ic->command, ic->driver); + return -ENODEV; + } + switch (ic->command) { + case (ISDN_CMD_SETEAZ): + chanp = csta->channel + ic->arg; + break; + case (ISDN_CMD_SETL2): + chanp = csta->channel + (ic->arg & 0xff); + if (chanp->debug & 1) + link_debug(chanp, 1, "SETL2 card %d %ld", + csta->cardnr + 1, ic->arg >> 8); + chanp->l2_protocol = ic->arg >> 8; + break; + case (ISDN_CMD_SETL3): + chanp = csta->channel + (ic->arg & 0xff); + if (chanp->debug & 1) + link_debug(chanp, 1, "SETL3 card %d %ld", + csta->cardnr + 1, ic->arg >> 8); + chanp->l3_protocol = ic->arg >> 8; + break; + case (ISDN_CMD_DIAL): + chanp = csta->channel + (ic->arg & 0xff); + if (chanp->debug & 1) + link_debug(chanp, 1, "DIAL %s -> %s (%d,%d)", + ic->parm.setup.eazmsn, ic->parm.setup.phone, + ic->parm.setup.si1, ic->parm.setup.si2); + memcpy(&chanp->setup, &ic->parm.setup, sizeof(setup_parm)); + if (!strcmp(chanp->setup.eazmsn, "0")) + chanp->setup.eazmsn[0] = '\0'; + /* this solution is dirty and may be change, if + * we make a callreference based callmanager */ + if (chanp->fi.state == ST_NULL) { + FsmEvent(&chanp->fi, EV_DIAL, NULL); + } else { + FsmDelTimer(&chanp->dial_timer, 70); + FsmAddTimer(&chanp->dial_timer, 50, EV_DIAL, NULL, 71); + } + break; + case (ISDN_CMD_ACCEPTB): + chanp = csta->channel + ic->arg; + if (chanp->debug & 1) + link_debug(chanp, 1, "ACCEPTB"); + FsmEvent(&chanp->fi, EV_ACCEPTB, NULL); + break; + case (ISDN_CMD_ACCEPTD): + chanp = csta->channel + ic->arg; + memcpy(&chanp->setup, &ic->parm.setup, sizeof(setup_parm)); + if (chanp->debug & 1) + link_debug(chanp, 1, "ACCEPTD"); + FsmEvent(&chanp->fi, EV_ACCEPTD, NULL); + break; + case (ISDN_CMD_HANGUP): + chanp = csta->channel + ic->arg; + if (chanp->debug & 1) + link_debug(chanp, 1, "HANGUP"); + FsmEvent(&chanp->fi, EV_HANGUP, NULL); + break; + case (CAPI_PUT_MESSAGE): + chanp = csta->channel + ic->arg; + if (chanp->debug & 1) + capi_debug(chanp, &ic->parm.cmsg); + if (ic->parm.cmsg.Length < 8) + break; + switch (ic->parm.cmsg.Command) { + case CAPI_FACILITY: + if (ic->parm.cmsg.Subcommand == CAPI_REQ) + lli_got_fac_req(chanp, &ic->parm.cmsg); + break; + case CAPI_MANUFACTURER: + if (ic->parm.cmsg.Subcommand == CAPI_REQ) + lli_got_manufacturer(chanp, csta, &ic->parm.cmsg); + break; + default: + break; + } + break; + case (ISDN_CMD_IOCTL): + switch (ic->arg) { + case (0): + num = *(unsigned int *) ic->parm.num; + HiSax_reportcard(csta->cardnr, num); + break; + case (1): + num = *(unsigned int *) ic->parm.num; + distr_debug(csta, num); + printk(KERN_DEBUG "HiSax: debugging flags card %d set to %x\n", + csta->cardnr + 1, num); + HiSax_putstatus(csta, "debugging flags ", + "card %d set to %x", csta->cardnr + 1, num); + break; + case (2): + num = *(unsigned int *) ic->parm.num; + csta->channel[0].b_st->l1.delay = num; + csta->channel[1].b_st->l1.delay = num; + HiSax_putstatus(csta, "delay ", "card %d set to %d ms", + csta->cardnr + 1, num); + printk(KERN_DEBUG "HiSax: delay card %d set to %d ms\n", + csta->cardnr + 1, num); + break; + case (5): /* set card in leased mode */ + num = *(unsigned int *) ic->parm.num; + if ((num < 1) || (num > 2)) { + HiSax_putstatus(csta, "Set LEASED ", + "wrong channel %d", num); + printk(KERN_WARNING "HiSax: Set LEASED wrong channel %d\n", + num); + } else { + num--; + chanp = csta->channel + num; + chanp->leased = 1; + HiSax_putstatus(csta, "Card", + "%d channel %d set leased mode\n", + csta->cardnr + 1, num + 1); + chanp->d_st->l1.l1l2 = leased_l1l2; + chanp->d_st->lli.l4l3 = leased_l4l3; + chanp->d_st->lli.l4l3(chanp->d_st, + DL_ESTABLISH | REQUEST, NULL); + } + break; + case (6): /* set B-channel test loop */ + num = *(unsigned int *) ic->parm.num; + if (csta->stlist) + csta->stlist->l2.l2l1(csta->stlist, + PH_TESTLOOP | REQUEST, (void *) (long)num); + break; + case (7): /* set card in PTP mode */ + num = *(unsigned int *) ic->parm.num; + if (test_bit(FLG_TWO_DCHAN, &csta->HW_Flags)) { + printk(KERN_ERR "HiSax PTP mode only with one TEI possible\n"); + } else if (num) { + test_and_set_bit(FLG_PTP, &csta->channel[0].d_st->l2.flag); + test_and_set_bit(FLG_FIXED_TEI, &csta->channel[0].d_st->l2.flag); + csta->channel[0].d_st->l2.tei = 0; + HiSax_putstatus(csta, "set card ", "in PTP mode"); + printk(KERN_DEBUG "HiSax: set card in PTP mode\n"); + printk(KERN_INFO "LAYER2 WATCHING ESTABLISH\n"); + csta->channel[0].d_st->lli.l4l3(csta->channel[0].d_st, + DL_ESTABLISH | REQUEST, NULL); + } else { + test_and_clear_bit(FLG_PTP, &csta->channel[0].d_st->l2.flag); + test_and_clear_bit(FLG_FIXED_TEI, &csta->channel[0].d_st->l2.flag); + HiSax_putstatus(csta, "set card ", "in PTMP mode"); + printk(KERN_DEBUG "HiSax: set card in PTMP mode\n"); + } + break; + case (8): /* set card in FIXED TEI mode */ + num = *(unsigned int *)ic->parm.num; + chanp = csta->channel + (num & 1); + num = num >> 1; + if (num == 127) { + test_and_clear_bit(FLG_FIXED_TEI, &chanp->d_st->l2.flag); + chanp->d_st->l2.tei = -1; + HiSax_putstatus(csta, "set card ", "in VAR TEI mode"); + printk(KERN_DEBUG "HiSax: set card in VAR TEI mode\n"); + } else { + test_and_set_bit(FLG_FIXED_TEI, &chanp->d_st->l2.flag); + chanp->d_st->l2.tei = num; + HiSax_putstatus(csta, "set card ", "in FIXED TEI (%d) mode", num); + printk(KERN_DEBUG "HiSax: set card in FIXED TEI (%d) mode\n", + num); + } + chanp->d_st->lli.l4l3(chanp->d_st, + DL_ESTABLISH | REQUEST, NULL); + break; + case (11): + num = csta->debug & DEB_DLOG_HEX; + csta->debug = *(unsigned int *) ic->parm.num; + csta->debug |= num; + HiSax_putstatus(cards[0].cs, "l1 debugging ", + "flags card %d set to %x", + csta->cardnr + 1, csta->debug); + printk(KERN_DEBUG "HiSax: l1 debugging flags card %d set to %x\n", + csta->cardnr + 1, csta->debug); + break; + case (13): + csta->channel[0].d_st->l3.debug = *(unsigned int *) ic->parm.num; + csta->channel[1].d_st->l3.debug = *(unsigned int *) ic->parm.num; + HiSax_putstatus(cards[0].cs, "l3 debugging ", + "flags card %d set to %x\n", csta->cardnr + 1, + *(unsigned int *) ic->parm.num); + printk(KERN_DEBUG "HiSax: l3 debugging flags card %d set to %x\n", + csta->cardnr + 1, *(unsigned int *) ic->parm.num); + break; + case (10): + i = *(unsigned int *) ic->parm.num; + return (set_channel_limit(csta, i)); + default: + if (csta->auxcmd) + return (csta->auxcmd(csta, ic)); + printk(KERN_DEBUG "HiSax: invalid ioctl %d\n", + (int) ic->arg); + return (-EINVAL); + } + break; + + case (ISDN_CMD_PROCEED): + chanp = csta->channel + ic->arg; + if (chanp->debug & 1) + link_debug(chanp, 1, "PROCEED"); + FsmEvent(&chanp->fi, EV_PROCEED, NULL); + break; + + case (ISDN_CMD_ALERT): + chanp = csta->channel + ic->arg; + if (chanp->debug & 1) + link_debug(chanp, 1, "ALERT"); + FsmEvent(&chanp->fi, EV_ALERT, NULL); + break; + + case (ISDN_CMD_REDIR): + chanp = csta->channel + ic->arg; + if (chanp->debug & 1) + link_debug(chanp, 1, "REDIR"); + memcpy(&chanp->setup, &ic->parm.setup, sizeof(setup_parm)); + FsmEvent(&chanp->fi, EV_REDIR, NULL); + break; + + /* protocol specific io commands */ + case (ISDN_CMD_PROT_IO): + for (st = csta->stlist; st; st = st->next) + if (st->protocol == (ic->arg & 0xFF)) + return (st->lli.l4l3_proto(st, ic)); + return (-EINVAL); + break; + default: + if (csta->auxcmd) + return (csta->auxcmd(csta, ic)); + return (-EINVAL); + } + return (0); +} + +int +HiSax_writebuf_skb(int id, int chan, int ack, struct sk_buff *skb) +{ + struct IsdnCardState *csta = hisax_findcard(id); + struct Channel *chanp; + struct PStack *st; + int len = skb->len; + struct sk_buff *nskb; + + if (!csta) { + printk(KERN_ERR + "HiSax: if_sendbuf called with invalid driverId!\n"); + return -ENODEV; + } + chanp = csta->channel + chan; + st = chanp->b_st; + if (!chanp->data_open) { + link_debug(chanp, 1, "writebuf: channel not open"); + return -EIO; + } + if (len > MAX_DATA_SIZE) { + link_debug(chanp, 1, "writebuf: packet too large (%d bytes)", len); + printk(KERN_WARNING "HiSax_writebuf: packet too large (%d bytes) !\n", + len); + return -EINVAL; + } + if (len) { + if ((len + chanp->bcs->tx_cnt) > MAX_DATA_MEM) { + /* Must return 0 here, since this is not an error + * but a temporary lack of resources. + */ + if (chanp->debug & 0x800) + link_debug(chanp, 1, "writebuf: no buffers for %d bytes", len); + return 0; + } else if (chanp->debug & 0x800) + link_debug(chanp, 1, "writebuf %d/%d/%d", len, chanp->bcs->tx_cnt, MAX_DATA_MEM); + nskb = skb_clone(skb, GFP_ATOMIC); + if (nskb) { + nskb->truesize = nskb->len; + if (!ack) + nskb->pkt_type = PACKET_NOACK; + if (chanp->l2_active_protocol == ISDN_PROTO_L2_X75I) + st->l3.l3l2(st, DL_DATA | REQUEST, nskb); + else { + chanp->bcs->tx_cnt += len; + st->l2.l2l1(st, PH_DATA | REQUEST, nskb); + } + dev_kfree_skb(skb); + } else + len = 0; + } + return (len); +} diff --git a/drivers/isdn/hisax/config.c b/drivers/isdn/hisax/config.c new file mode 100644 index 000000000..b12e6cae2 --- /dev/null +++ b/drivers/isdn/hisax/config.c @@ -0,0 +1,1993 @@ +/* $Id: config.c,v 2.84.2.5 2004/02/11 13:21:33 keil Exp $ + * + * Author Karsten Keil + * Copyright by Karsten Keil <keil@isdn4linux.de> + * by Kai Germaschewski <kai.germaschewski@gmx.de> + * + * This software may be used and distributed according to the terms + * of the GNU General Public License, incorporated herein by reference. + * + * For changes and modifications please read + * Documentation/isdn/HiSax.cert + * + * based on the teles driver from Jan den Ouden + * + */ + +#include <linux/types.h> +#include <linux/stddef.h> +#include <linux/timer.h> +#include <linux/init.h> +#include "hisax.h" +#include <linux/module.h> +#include <linux/kernel_stat.h> +#include <linux/workqueue.h> +#include <linux/interrupt.h> +#include <linux/slab.h> +#define HISAX_STATUS_BUFSIZE 4096 + +/* + * This structure array contains one entry per card. An entry looks + * like this: + * + * { type, protocol, p0, p1, p2, NULL } + * + * type + * 1 Teles 16.0 p0=irq p1=membase p2=iobase + * 2 Teles 8.0 p0=irq p1=membase + * 3 Teles 16.3 p0=irq p1=iobase + * 4 Creatix PNP p0=irq p1=IO0 (ISAC) p2=IO1 (HSCX) + * 5 AVM A1 (Fritz) p0=irq p1=iobase + * 6 ELSA PC [p0=iobase] or nothing (autodetect) + * 7 ELSA Quickstep p0=irq p1=iobase + * 8 Teles PCMCIA p0=irq p1=iobase + * 9 ITK ix1-micro p0=irq p1=iobase + * 10 ELSA PCMCIA p0=irq p1=iobase + * 11 Eicon.Diehl Diva p0=irq p1=iobase + * 12 Asuscom ISDNLink p0=irq p1=iobase + * 13 Teleint p0=irq p1=iobase + * 14 Teles 16.3c p0=irq p1=iobase + * 15 Sedlbauer speed p0=irq p1=iobase + * 15 Sedlbauer PC/104 p0=irq p1=iobase + * 15 Sedlbauer speed pci no parameter + * 16 USR Sportster internal p0=irq p1=iobase + * 17 MIC card p0=irq p1=iobase + * 18 ELSA Quickstep 1000PCI no parameter + * 19 Compaq ISDN S0 ISA card p0=irq p1=IO0 (HSCX) p2=IO1 (ISAC) p3=IO2 + * 20 Travers Technologies NETjet-S PCI card + * 21 TELES PCI no parameter + * 22 Sedlbauer Speed Star p0=irq p1=iobase + * 23 reserved + * 24 Dr Neuhaus Niccy PnP/PCI card p0=irq p1=IO0 p2=IO1 (PnP only) + * 25 Teles S0Box p0=irq p1=iobase (from isapnp setup) + * 26 AVM A1 PCMCIA (Fritz) p0=irq p1=iobase + * 27 AVM PnP/PCI p0=irq p1=iobase (PCI no parameter) + * 28 Sedlbauer Speed Fax+ p0=irq p1=iobase (from isapnp setup) + * 29 Siemens I-Surf p0=irq p1=iobase p2=memory (from isapnp setup) + * 30 ACER P10 p0=irq p1=iobase (from isapnp setup) + * 31 HST Saphir p0=irq p1=iobase + * 32 Telekom A4T none + * 33 Scitel Quadro p0=subcontroller (4*S0, subctrl 1...4) + * 34 Gazel ISDN cards + * 35 HFC 2BDS0 PCI none + * 36 Winbond 6692 PCI none + * 37 HFC 2BDS0 S+/SP p0=irq p1=iobase + * 38 Travers Technologies NETspider-U PCI card + * 39 HFC 2BDS0-SP PCMCIA p0=irq p1=iobase + * 40 hotplug interface + * 41 Formula-n enter:now ISDN PCI a/b none + * + * protocol can be either ISDN_PTYPE_EURO or ISDN_PTYPE_1TR6 or ISDN_PTYPE_NI1 + * + * + */ + +const char *CardType[] = { + "No Card", "Teles 16.0", "Teles 8.0", "Teles 16.3", + "Creatix/Teles PnP", "AVM A1", "Elsa ML", "Elsa Quickstep", + "Teles PCMCIA", "ITK ix1-micro Rev.2", "Elsa PCMCIA", + "Eicon.Diehl Diva", "ISDNLink", "TeleInt", "Teles 16.3c", + "Sedlbauer Speed Card", "USR Sportster", "ith mic Linux", + "Elsa PCI", "Compaq ISA", "NETjet-S", "Teles PCI", + "Sedlbauer Speed Star (PCMCIA)", "AMD 7930", "NICCY", "S0Box", + "AVM A1 (PCMCIA)", "AVM Fritz PnP/PCI", "Sedlbauer Speed Fax +", + "Siemens I-Surf", "Acer P10", "HST Saphir", "Telekom A4T", + "Scitel Quadro", "Gazel", "HFC 2BDS0 PCI", "Winbond 6692", + "HFC 2BDS0 SX", "NETspider-U", "HFC-2BDS0-SP PCMCIA", + "Hotplug", "Formula-n enter:now PCI a/b", +}; + +#ifdef CONFIG_HISAX_ELSA +#define DEFAULT_CARD ISDN_CTYPE_ELSA +#define DEFAULT_CFG {0, 0, 0, 0} +#endif + +#ifdef CONFIG_HISAX_AVM_A1 +#undef DEFAULT_CARD +#undef DEFAULT_CFG +#define DEFAULT_CARD ISDN_CTYPE_A1 +#define DEFAULT_CFG {10, 0x340, 0, 0} +#endif + +#ifdef CONFIG_HISAX_AVM_A1_PCMCIA +#undef DEFAULT_CARD +#undef DEFAULT_CFG +#define DEFAULT_CARD ISDN_CTYPE_A1_PCMCIA +#define DEFAULT_CFG {11, 0x170, 0, 0} +#endif + +#ifdef CONFIG_HISAX_FRITZPCI +#undef DEFAULT_CARD +#undef DEFAULT_CFG +#define DEFAULT_CARD ISDN_CTYPE_FRITZPCI +#define DEFAULT_CFG {0, 0, 0, 0} +#endif + +#ifdef CONFIG_HISAX_16_3 +#undef DEFAULT_CARD +#undef DEFAULT_CFG +#define DEFAULT_CARD ISDN_CTYPE_16_3 +#define DEFAULT_CFG {15, 0x180, 0, 0} +#endif + +#ifdef CONFIG_HISAX_S0BOX +#undef DEFAULT_CARD +#undef DEFAULT_CFG +#define DEFAULT_CARD ISDN_CTYPE_S0BOX +#define DEFAULT_CFG {7, 0x378, 0, 0} +#endif + +#ifdef CONFIG_HISAX_16_0 +#undef DEFAULT_CARD +#undef DEFAULT_CFG +#define DEFAULT_CARD ISDN_CTYPE_16_0 +#define DEFAULT_CFG {15, 0xd0000, 0xd80, 0} +#endif + +#ifdef CONFIG_HISAX_TELESPCI +#undef DEFAULT_CARD +#undef DEFAULT_CFG +#define DEFAULT_CARD ISDN_CTYPE_TELESPCI +#define DEFAULT_CFG {0, 0, 0, 0} +#endif + +#ifdef CONFIG_HISAX_IX1MICROR2 +#undef DEFAULT_CARD +#undef DEFAULT_CFG +#define DEFAULT_CARD ISDN_CTYPE_IX1MICROR2 +#define DEFAULT_CFG {5, 0x390, 0, 0} +#endif + +#ifdef CONFIG_HISAX_DIEHLDIVA +#undef DEFAULT_CARD +#undef DEFAULT_CFG +#define DEFAULT_CARD ISDN_CTYPE_DIEHLDIVA +#define DEFAULT_CFG {0, 0x0, 0, 0} +#endif + +#ifdef CONFIG_HISAX_ASUSCOM +#undef DEFAULT_CARD +#undef DEFAULT_CFG +#define DEFAULT_CARD ISDN_CTYPE_ASUSCOM +#define DEFAULT_CFG {5, 0x200, 0, 0} +#endif + +#ifdef CONFIG_HISAX_TELEINT +#undef DEFAULT_CARD +#undef DEFAULT_CFG +#define DEFAULT_CARD ISDN_CTYPE_TELEINT +#define DEFAULT_CFG {5, 0x300, 0, 0} +#endif + +#ifdef CONFIG_HISAX_SEDLBAUER +#undef DEFAULT_CARD +#undef DEFAULT_CFG +#define DEFAULT_CARD ISDN_CTYPE_SEDLBAUER +#define DEFAULT_CFG {11, 0x270, 0, 0} +#endif + +#ifdef CONFIG_HISAX_SPORTSTER +#undef DEFAULT_CARD +#undef DEFAULT_CFG +#define DEFAULT_CARD ISDN_CTYPE_SPORTSTER +#define DEFAULT_CFG {7, 0x268, 0, 0} +#endif + +#ifdef CONFIG_HISAX_MIC +#undef DEFAULT_CARD +#undef DEFAULT_CFG +#define DEFAULT_CARD ISDN_CTYPE_MIC +#define DEFAULT_CFG {12, 0x3e0, 0, 0} +#endif + +#ifdef CONFIG_HISAX_NETJET +#undef DEFAULT_CARD +#undef DEFAULT_CFG +#define DEFAULT_CARD ISDN_CTYPE_NETJET_S +#define DEFAULT_CFG {0, 0, 0, 0} +#endif + +#ifdef CONFIG_HISAX_HFCS +#undef DEFAULT_CARD +#undef DEFAULT_CFG +#define DEFAULT_CARD ISDN_CTYPE_TELES3C +#define DEFAULT_CFG {5, 0x500, 0, 0} +#endif + +#ifdef CONFIG_HISAX_HFC_PCI +#undef DEFAULT_CARD +#undef DEFAULT_CFG +#define DEFAULT_CARD ISDN_CTYPE_HFC_PCI +#define DEFAULT_CFG {0, 0, 0, 0} +#endif + +#ifdef CONFIG_HISAX_HFC_SX +#undef DEFAULT_CARD +#undef DEFAULT_CFG +#define DEFAULT_CARD ISDN_CTYPE_HFC_SX +#define DEFAULT_CFG {5, 0x2E0, 0, 0} +#endif + +#ifdef CONFIG_HISAX_NICCY +#undef DEFAULT_CARD +#undef DEFAULT_CFG +#define DEFAULT_CARD ISDN_CTYPE_NICCY +#define DEFAULT_CFG {0, 0x0, 0, 0} +#endif + +#ifdef CONFIG_HISAX_ISURF +#undef DEFAULT_CARD +#undef DEFAULT_CFG +#define DEFAULT_CARD ISDN_CTYPE_ISURF +#define DEFAULT_CFG {5, 0x100, 0xc8000, 0} +#endif + +#ifdef CONFIG_HISAX_HSTSAPHIR +#undef DEFAULT_CARD +#undef DEFAULT_CFG +#define DEFAULT_CARD ISDN_CTYPE_HSTSAPHIR +#define DEFAULT_CFG {5, 0x250, 0, 0} +#endif + +#ifdef CONFIG_HISAX_BKM_A4T +#undef DEFAULT_CARD +#undef DEFAULT_CFG +#define DEFAULT_CARD ISDN_CTYPE_BKM_A4T +#define DEFAULT_CFG {0, 0x0, 0, 0} +#endif + +#ifdef CONFIG_HISAX_SCT_QUADRO +#undef DEFAULT_CARD +#undef DEFAULT_CFG +#define DEFAULT_CARD ISDN_CTYPE_SCT_QUADRO +#define DEFAULT_CFG {1, 0x0, 0, 0} +#endif + +#ifdef CONFIG_HISAX_GAZEL +#undef DEFAULT_CARD +#undef DEFAULT_CFG +#define DEFAULT_CARD ISDN_CTYPE_GAZEL +#define DEFAULT_CFG {15, 0x180, 0, 0} +#endif + +#ifdef CONFIG_HISAX_W6692 +#undef DEFAULT_CARD +#undef DEFAULT_CFG +#define DEFAULT_CARD ISDN_CTYPE_W6692 +#define DEFAULT_CFG {0, 0, 0, 0} +#endif + +#ifdef CONFIG_HISAX_NETJET_U +#undef DEFAULT_CARD +#undef DEFAULT_CFG +#define DEFAULT_CARD ISDN_CTYPE_NETJET_U +#define DEFAULT_CFG {0, 0, 0, 0} +#endif + +#ifdef CONFIG_HISAX_1TR6 +#define DEFAULT_PROTO ISDN_PTYPE_1TR6 +#define DEFAULT_PROTO_NAME "1TR6" +#endif +#ifdef CONFIG_HISAX_NI1 +#undef DEFAULT_PROTO +#define DEFAULT_PROTO ISDN_PTYPE_NI1 +#undef DEFAULT_PROTO_NAME +#define DEFAULT_PROTO_NAME "NI1" +#endif +#ifdef CONFIG_HISAX_EURO +#undef DEFAULT_PROTO +#define DEFAULT_PROTO ISDN_PTYPE_EURO +#undef DEFAULT_PROTO_NAME +#define DEFAULT_PROTO_NAME "EURO" +#endif +#ifndef DEFAULT_PROTO +#define DEFAULT_PROTO ISDN_PTYPE_UNKNOWN +#define DEFAULT_PROTO_NAME "UNKNOWN" +#endif +#ifndef DEFAULT_CARD +#define DEFAULT_CARD 0 +#define DEFAULT_CFG {0, 0, 0, 0} +#endif + +#define FIRST_CARD { \ + DEFAULT_CARD, \ + DEFAULT_PROTO, \ + DEFAULT_CFG, \ + NULL, \ + } + +struct IsdnCard cards[HISAX_MAX_CARDS] = { + FIRST_CARD, +}; + +#define HISAX_IDSIZE (HISAX_MAX_CARDS * 8) +static char HiSaxID[HISAX_IDSIZE] = { 0, }; + +static char *HiSax_id = HiSaxID; +#ifdef MODULE +/* Variables for insmod */ +static int type[HISAX_MAX_CARDS] = { 0, }; +static int protocol[HISAX_MAX_CARDS] = { 0, }; +static int io[HISAX_MAX_CARDS] = { 0, }; +#undef IO0_IO1 +#ifdef CONFIG_HISAX_16_3 +#define IO0_IO1 +#endif +#ifdef CONFIG_HISAX_NICCY +#undef IO0_IO1 +#define IO0_IO1 +#endif +#ifdef IO0_IO1 +static int io0[HISAX_MAX_CARDS] = { 0, }; +static int io1[HISAX_MAX_CARDS] = { 0, }; +#endif +static int irq[HISAX_MAX_CARDS] = { 0, }; +static int mem[HISAX_MAX_CARDS] = { 0, }; +static char *id = HiSaxID; + +MODULE_DESCRIPTION("ISDN4Linux: Driver for passive ISDN cards"); +MODULE_AUTHOR("Karsten Keil"); +MODULE_LICENSE("GPL"); +module_param_array(type, int, NULL, 0); +module_param_array(protocol, int, NULL, 0); +module_param_hw_array(io, int, ioport, NULL, 0); +module_param_hw_array(irq, int, irq, NULL, 0); +module_param_hw_array(mem, int, iomem, NULL, 0); +module_param(id, charp, 0); +#ifdef IO0_IO1 +module_param_hw_array(io0, int, ioport, NULL, 0); +module_param_hw_array(io1, int, ioport, NULL, 0); +#endif +#endif /* MODULE */ + +int nrcards; + +char *HiSax_getrev(const char *revision) +{ + char *rev; + char *p; + + if ((p = strchr(revision, ':'))) { + rev = p + 2; + p = strchr(rev, '$'); + *--p = 0; + } else + rev = "???"; + return rev; +} + +static void __init HiSaxVersion(void) +{ + char tmp[64]; + + printk(KERN_INFO "HiSax: Linux Driver for passive ISDN cards\n"); +#ifdef MODULE + printk(KERN_INFO "HiSax: Version 3.5 (module)\n"); +#else + printk(KERN_INFO "HiSax: Version 3.5 (kernel)\n"); +#endif + strcpy(tmp, l1_revision); + printk(KERN_INFO "HiSax: Layer1 Revision %s\n", HiSax_getrev(tmp)); + strcpy(tmp, l2_revision); + printk(KERN_INFO "HiSax: Layer2 Revision %s\n", HiSax_getrev(tmp)); + strcpy(tmp, tei_revision); + printk(KERN_INFO "HiSax: TeiMgr Revision %s\n", HiSax_getrev(tmp)); + strcpy(tmp, l3_revision); + printk(KERN_INFO "HiSax: Layer3 Revision %s\n", HiSax_getrev(tmp)); + strcpy(tmp, lli_revision); + printk(KERN_INFO "HiSax: LinkLayer Revision %s\n", + HiSax_getrev(tmp)); +} + +#ifndef MODULE +#define MAX_ARG (HISAX_MAX_CARDS * 5) +static int __init HiSax_setup(char *line) +{ + int i, j, argc; + int ints[MAX_ARG + 1]; + char *str; + + str = get_options(line, MAX_ARG, ints); + argc = ints[0]; + printk(KERN_DEBUG "HiSax_setup: argc(%d) str(%s)\n", argc, str); + i = 0; + j = 1; + while (argc && (i < HISAX_MAX_CARDS)) { + cards[i].protocol = DEFAULT_PROTO; + if (argc) { + cards[i].typ = ints[j]; + j++; + argc--; + } + if (argc) { + cards[i].protocol = ints[j]; + j++; + argc--; + } + if (argc) { + cards[i].para[0] = ints[j]; + j++; + argc--; + } + if (argc) { + cards[i].para[1] = ints[j]; + j++; + argc--; + } + if (argc) { + cards[i].para[2] = ints[j]; + j++; + argc--; + } + i++; + } + if (str && *str) { + if (strlen(str) < HISAX_IDSIZE) + strcpy(HiSaxID, str); + else + printk(KERN_WARNING "HiSax: ID too long!"); + } else + strcpy(HiSaxID, "HiSax"); + + HiSax_id = HiSaxID; + return 1; +} + +__setup("hisax=", HiSax_setup); +#endif /* MODULES */ + +#if CARD_TELES0 +extern int setup_teles0(struct IsdnCard *card); +#endif + +#if CARD_TELES3 +extern int setup_teles3(struct IsdnCard *card); +#endif + +#if CARD_S0BOX +extern int setup_s0box(struct IsdnCard *card); +#endif + +#if CARD_TELESPCI +extern int setup_telespci(struct IsdnCard *card); +#endif + +#if CARD_AVM_A1 +extern int setup_avm_a1(struct IsdnCard *card); +#endif + +#if CARD_AVM_A1_PCMCIA +extern int setup_avm_a1_pcmcia(struct IsdnCard *card); +#endif + +#if CARD_FRITZPCI +extern int setup_avm_pcipnp(struct IsdnCard *card); +#endif + +#if CARD_ELSA +extern int setup_elsa(struct IsdnCard *card); +#endif + +#if CARD_IX1MICROR2 +extern int setup_ix1micro(struct IsdnCard *card); +#endif + +#if CARD_DIEHLDIVA +extern int setup_diva(struct IsdnCard *card); +#endif + +#if CARD_ASUSCOM +extern int setup_asuscom(struct IsdnCard *card); +#endif + +#if CARD_TELEINT +extern int setup_TeleInt(struct IsdnCard *card); +#endif + +#if CARD_SEDLBAUER +extern int setup_sedlbauer(struct IsdnCard *card); +#endif + +#if CARD_SPORTSTER +extern int setup_sportster(struct IsdnCard *card); +#endif + +#if CARD_MIC +extern int setup_mic(struct IsdnCard *card); +#endif + +#if CARD_NETJET_S +extern int setup_netjet_s(struct IsdnCard *card); +#endif + +#if CARD_HFCS +extern int setup_hfcs(struct IsdnCard *card); +#endif + +#if CARD_HFC_PCI +extern int setup_hfcpci(struct IsdnCard *card); +#endif + +#if CARD_HFC_SX +extern int setup_hfcsx(struct IsdnCard *card); +#endif + +#if CARD_NICCY +extern int setup_niccy(struct IsdnCard *card); +#endif + +#if CARD_ISURF +extern int setup_isurf(struct IsdnCard *card); +#endif + +#if CARD_HSTSAPHIR +extern int setup_saphir(struct IsdnCard *card); +#endif + +#if CARD_BKM_A4T +extern int setup_bkm_a4t(struct IsdnCard *card); +#endif + +#if CARD_SCT_QUADRO +extern int setup_sct_quadro(struct IsdnCard *card); +#endif + +#if CARD_GAZEL +extern int setup_gazel(struct IsdnCard *card); +#endif + +#if CARD_W6692 +extern int setup_w6692(struct IsdnCard *card); +#endif + +#if CARD_NETJET_U +extern int setup_netjet_u(struct IsdnCard *card); +#endif + +#if CARD_FN_ENTERNOW_PCI +extern int setup_enternow_pci(struct IsdnCard *card); +#endif + +/* + * Find card with given driverId + */ +static inline struct IsdnCardState *hisax_findcard(int driverid) +{ + int i; + + for (i = 0; i < nrcards; i++) + if (cards[i].cs) + if (cards[i].cs->myid == driverid) + return cards[i].cs; + return NULL; +} + +/* + * Find card with given card number + */ +#if 0 +struct IsdnCardState *hisax_get_card(int cardnr) +{ + if ((cardnr <= nrcards) && (cardnr > 0)) + if (cards[cardnr - 1].cs) + return cards[cardnr - 1].cs; + return NULL; +} +#endif /* 0 */ + +static int HiSax_readstatus(u_char __user *buf, int len, int id, int channel) +{ + int count, cnt; + u_char __user *p = buf; + struct IsdnCardState *cs = hisax_findcard(id); + + if (cs) { + if (len > HISAX_STATUS_BUFSIZE) { + printk(KERN_WARNING + "HiSax: status overflow readstat %d/%d\n", + len, HISAX_STATUS_BUFSIZE); + } + count = cs->status_end - cs->status_read + 1; + if (count >= len) + count = len; + if (copy_to_user(p, cs->status_read, count)) + return -EFAULT; + cs->status_read += count; + if (cs->status_read > cs->status_end) + cs->status_read = cs->status_buf; + p += count; + count = len - count; + while (count) { + if (count > HISAX_STATUS_BUFSIZE) + cnt = HISAX_STATUS_BUFSIZE; + else + cnt = count; + if (copy_to_user(p, cs->status_read, cnt)) + return -EFAULT; + p += cnt; + cs->status_read += cnt % HISAX_STATUS_BUFSIZE; + count -= cnt; + } + return len; + } else { + printk(KERN_ERR + "HiSax: if_readstatus called with invalid driverId!\n"); + return -ENODEV; + } +} + +int jiftime(char *s, long mark) +{ + s += 8; + + *s-- = '\0'; + *s-- = mark % 10 + '0'; + mark /= 10; + *s-- = mark % 10 + '0'; + mark /= 10; + *s-- = '.'; + *s-- = mark % 10 + '0'; + mark /= 10; + *s-- = mark % 6 + '0'; + mark /= 6; + *s-- = ':'; + *s-- = mark % 10 + '0'; + mark /= 10; + *s-- = mark % 10 + '0'; + return 8; +} + +static u_char tmpbuf[HISAX_STATUS_BUFSIZE]; + +void VHiSax_putstatus(struct IsdnCardState *cs, char *head, const char *fmt, + va_list args) +{ + /* if head == NULL the fmt contains the full info */ + + u_long flags; + int count, i; + u_char *p; + isdn_ctrl ic; + int len; + const u_char *data; + + if (!cs) { + printk(KERN_WARNING "HiSax: No CardStatus for message"); + return; + } + spin_lock_irqsave(&cs->statlock, flags); + if (head) { + p = tmpbuf; + p += jiftime(p, jiffies); + p += sprintf(p, " %s", head); + p += vsprintf(p, fmt, args); + *p++ = '\n'; + *p = 0; + len = p - tmpbuf; + data = tmpbuf; + } else { + data = fmt; + len = strlen(fmt); + } + if (len > HISAX_STATUS_BUFSIZE) { + spin_unlock_irqrestore(&cs->statlock, flags); + printk(KERN_WARNING "HiSax: status overflow %d/%d\n", + len, HISAX_STATUS_BUFSIZE); + return; + } + count = len; + i = cs->status_end - cs->status_write + 1; + if (i >= len) + i = len; + len -= i; + memcpy(cs->status_write, data, i); + cs->status_write += i; + if (cs->status_write > cs->status_end) + cs->status_write = cs->status_buf; + if (len) { + memcpy(cs->status_write, data + i, len); + cs->status_write += len; + } +#ifdef KERNELSTACK_DEBUG + i = (ulong) & len - current->kernel_stack_page; + sprintf(tmpbuf, "kstack %s %lx use %ld\n", current->comm, + current->kernel_stack_page, i); + len = strlen(tmpbuf); + for (p = tmpbuf, i = len; i > 0; i--, p++) { + *cs->status_write++ = *p; + if (cs->status_write > cs->status_end) + cs->status_write = cs->status_buf; + count++; + } +#endif + spin_unlock_irqrestore(&cs->statlock, flags); + if (count) { + ic.command = ISDN_STAT_STAVAIL; + ic.driver = cs->myid; + ic.arg = count; + cs->iif.statcallb(&ic); + } +} + +void HiSax_putstatus(struct IsdnCardState *cs, char *head, const char *fmt, ...) +{ + va_list args; + + va_start(args, fmt); + VHiSax_putstatus(cs, head, fmt, args); + va_end(args); +} + +int ll_run(struct IsdnCardState *cs, int addfeatures) +{ + isdn_ctrl ic; + + ic.driver = cs->myid; + ic.command = ISDN_STAT_RUN; + cs->iif.features |= addfeatures; + cs->iif.statcallb(&ic); + return 0; +} + +static void ll_stop(struct IsdnCardState *cs) +{ + isdn_ctrl ic; + + ic.command = ISDN_STAT_STOP; + ic.driver = cs->myid; + cs->iif.statcallb(&ic); + // CallcFreeChan(cs); +} + +static void ll_unload(struct IsdnCardState *cs) +{ + isdn_ctrl ic; + + ic.command = ISDN_STAT_UNLOAD; + ic.driver = cs->myid; + cs->iif.statcallb(&ic); + kfree(cs->status_buf); + cs->status_read = NULL; + cs->status_write = NULL; + cs->status_end = NULL; + kfree(cs->dlog); + cs->dlog = NULL; +} + +static void closecard(int cardnr) +{ + struct IsdnCardState *csta = cards[cardnr].cs; + + if (csta->bcs->BC_Close != NULL) { + csta->bcs->BC_Close(csta->bcs + 1); + csta->bcs->BC_Close(csta->bcs); + } + + skb_queue_purge(&csta->rq); + skb_queue_purge(&csta->sq); + kfree(csta->rcvbuf); + csta->rcvbuf = NULL; + if (csta->tx_skb) { + dev_kfree_skb(csta->tx_skb); + csta->tx_skb = NULL; + } + if (csta->DC_Close != NULL) { + csta->DC_Close(csta); + } + if (csta->cardmsg) + csta->cardmsg(csta, CARD_RELEASE, NULL); + if (csta->dbusytimer.function != NULL) // FIXME? + del_timer(&csta->dbusytimer); + ll_unload(csta); +} + +static irqreturn_t card_irq(int intno, void *dev_id) +{ + struct IsdnCardState *cs = dev_id; + irqreturn_t ret = cs->irq_func(intno, cs); + + if (ret == IRQ_HANDLED) + cs->irq_cnt++; + return ret; +} + +static int init_card(struct IsdnCardState *cs) +{ + int irq_cnt, cnt = 3, ret; + + if (!cs->irq) { + ret = cs->cardmsg(cs, CARD_INIT, NULL); + return (ret); + } + irq_cnt = cs->irq_cnt = 0; + printk(KERN_INFO "%s: IRQ %d count %d\n", CardType[cs->typ], + cs->irq, irq_cnt); + if (request_irq(cs->irq, card_irq, cs->irq_flags, "HiSax", cs)) { + printk(KERN_WARNING "HiSax: couldn't get interrupt %d\n", + cs->irq); + return 1; + } + while (cnt) { + cs->cardmsg(cs, CARD_INIT, NULL); + /* Timeout 10ms */ + msleep(10); + printk(KERN_INFO "%s: IRQ %d count %d\n", + CardType[cs->typ], cs->irq, cs->irq_cnt); + if (cs->irq_cnt == irq_cnt) { + printk(KERN_WARNING + "%s: IRQ(%d) getting no interrupts during init %d\n", + CardType[cs->typ], cs->irq, 4 - cnt); + if (cnt == 1) { + free_irq(cs->irq, cs); + return 2; + } else { + cs->cardmsg(cs, CARD_RESET, NULL); + cnt--; + } + } else { + cs->cardmsg(cs, CARD_TEST, NULL); + return 0; + } + } + return 3; +} + +static int hisax_cs_setup_card(struct IsdnCard *card) +{ + int ret; + + switch (card->typ) { +#if CARD_TELES0 + case ISDN_CTYPE_16_0: + case ISDN_CTYPE_8_0: + ret = setup_teles0(card); + break; +#endif +#if CARD_TELES3 + case ISDN_CTYPE_16_3: + case ISDN_CTYPE_PNP: + case ISDN_CTYPE_TELESPCMCIA: + case ISDN_CTYPE_COMPAQ_ISA: + ret = setup_teles3(card); + break; +#endif +#if CARD_S0BOX + case ISDN_CTYPE_S0BOX: + ret = setup_s0box(card); + break; +#endif +#if CARD_TELESPCI + case ISDN_CTYPE_TELESPCI: + ret = setup_telespci(card); + break; +#endif +#if CARD_AVM_A1 + case ISDN_CTYPE_A1: + ret = setup_avm_a1(card); + break; +#endif +#if CARD_AVM_A1_PCMCIA + case ISDN_CTYPE_A1_PCMCIA: + ret = setup_avm_a1_pcmcia(card); + break; +#endif +#if CARD_FRITZPCI + case ISDN_CTYPE_FRITZPCI: + ret = setup_avm_pcipnp(card); + break; +#endif +#if CARD_ELSA + case ISDN_CTYPE_ELSA: + case ISDN_CTYPE_ELSA_PNP: + case ISDN_CTYPE_ELSA_PCMCIA: + case ISDN_CTYPE_ELSA_PCI: + ret = setup_elsa(card); + break; +#endif +#if CARD_IX1MICROR2 + case ISDN_CTYPE_IX1MICROR2: + ret = setup_ix1micro(card); + break; +#endif +#if CARD_DIEHLDIVA + case ISDN_CTYPE_DIEHLDIVA: + ret = setup_diva(card); + break; +#endif +#if CARD_ASUSCOM + case ISDN_CTYPE_ASUSCOM: + ret = setup_asuscom(card); + break; +#endif +#if CARD_TELEINT + case ISDN_CTYPE_TELEINT: + ret = setup_TeleInt(card); + break; +#endif +#if CARD_SEDLBAUER + case ISDN_CTYPE_SEDLBAUER: + case ISDN_CTYPE_SEDLBAUER_PCMCIA: + case ISDN_CTYPE_SEDLBAUER_FAX: + ret = setup_sedlbauer(card); + break; +#endif +#if CARD_SPORTSTER + case ISDN_CTYPE_SPORTSTER: + ret = setup_sportster(card); + break; +#endif +#if CARD_MIC + case ISDN_CTYPE_MIC: + ret = setup_mic(card); + break; +#endif +#if CARD_NETJET_S + case ISDN_CTYPE_NETJET_S: + ret = setup_netjet_s(card); + break; +#endif +#if CARD_HFCS + case ISDN_CTYPE_TELES3C: + case ISDN_CTYPE_ACERP10: + ret = setup_hfcs(card); + break; +#endif +#if CARD_HFC_PCI + case ISDN_CTYPE_HFC_PCI: + ret = setup_hfcpci(card); + break; +#endif +#if CARD_HFC_SX + case ISDN_CTYPE_HFC_SX: + ret = setup_hfcsx(card); + break; +#endif +#if CARD_NICCY + case ISDN_CTYPE_NICCY: + ret = setup_niccy(card); + break; +#endif +#if CARD_ISURF + case ISDN_CTYPE_ISURF: + ret = setup_isurf(card); + break; +#endif +#if CARD_HSTSAPHIR + case ISDN_CTYPE_HSTSAPHIR: + ret = setup_saphir(card); + break; +#endif +#if CARD_BKM_A4T + case ISDN_CTYPE_BKM_A4T: + ret = setup_bkm_a4t(card); + break; +#endif +#if CARD_SCT_QUADRO + case ISDN_CTYPE_SCT_QUADRO: + ret = setup_sct_quadro(card); + break; +#endif +#if CARD_GAZEL + case ISDN_CTYPE_GAZEL: + ret = setup_gazel(card); + break; +#endif +#if CARD_W6692 + case ISDN_CTYPE_W6692: + ret = setup_w6692(card); + break; +#endif +#if CARD_NETJET_U + case ISDN_CTYPE_NETJET_U: + ret = setup_netjet_u(card); + break; +#endif +#if CARD_FN_ENTERNOW_PCI + case ISDN_CTYPE_ENTERNOW: + ret = setup_enternow_pci(card); + break; +#endif + case ISDN_CTYPE_DYNAMIC: + ret = 2; + break; + default: + printk(KERN_WARNING + "HiSax: Support for %s Card not selected\n", + CardType[card->typ]); + ret = 0; + break; + } + + return ret; +} + +static int hisax_cs_new(int cardnr, char *id, struct IsdnCard *card, + struct IsdnCardState **cs_out, int *busy_flag, + struct module *lockowner) +{ + struct IsdnCardState *cs; + + *cs_out = NULL; + + cs = kzalloc(sizeof(struct IsdnCardState), GFP_KERNEL); + if (!cs) { + printk(KERN_WARNING + "HiSax: No memory for IsdnCardState(card %d)\n", + cardnr + 1); + goto out; + } + card->cs = cs; + spin_lock_init(&cs->statlock); + spin_lock_init(&cs->lock); + cs->chanlimit = 2; /* maximum B-channel number */ + cs->logecho = 0; /* No echo logging */ + cs->cardnr = cardnr; + cs->debug = L1_DEB_WARN; + cs->HW_Flags = 0; + cs->busy_flag = busy_flag; + cs->irq_flags = I4L_IRQ_FLAG; +#if TEI_PER_CARD + if (card->protocol == ISDN_PTYPE_NI1) + test_and_set_bit(FLG_TWO_DCHAN, &cs->HW_Flags); +#else + test_and_set_bit(FLG_TWO_DCHAN, &cs->HW_Flags); +#endif + cs->protocol = card->protocol; + + if (card->typ <= 0 || card->typ > ISDN_CTYPE_COUNT) { + printk(KERN_WARNING + "HiSax: Card Type %d out of range\n", card->typ); + goto outf_cs; + } + if (!(cs->dlog = kmalloc(MAX_DLOG_SPACE, GFP_KERNEL))) { + printk(KERN_WARNING + "HiSax: No memory for dlog(card %d)\n", cardnr + 1); + goto outf_cs; + } + if (!(cs->status_buf = kmalloc(HISAX_STATUS_BUFSIZE, GFP_KERNEL))) { + printk(KERN_WARNING + "HiSax: No memory for status_buf(card %d)\n", + cardnr + 1); + goto outf_dlog; + } + cs->stlist = NULL; + cs->status_read = cs->status_buf; + cs->status_write = cs->status_buf; + cs->status_end = cs->status_buf + HISAX_STATUS_BUFSIZE - 1; + cs->typ = card->typ; +#ifdef MODULE + cs->iif.owner = lockowner; +#endif + strcpy(cs->iif.id, id); + cs->iif.channels = 2; + cs->iif.maxbufsize = MAX_DATA_SIZE; + cs->iif.hl_hdrlen = MAX_HEADER_LEN; + cs->iif.features = + ISDN_FEATURE_L2_X75I | + ISDN_FEATURE_L2_HDLC | + ISDN_FEATURE_L2_HDLC_56K | + ISDN_FEATURE_L2_TRANS | + ISDN_FEATURE_L3_TRANS | +#ifdef CONFIG_HISAX_1TR6 + ISDN_FEATURE_P_1TR6 | +#endif +#ifdef CONFIG_HISAX_EURO + ISDN_FEATURE_P_EURO | +#endif +#ifdef CONFIG_HISAX_NI1 + ISDN_FEATURE_P_NI1 | +#endif + 0; + + cs->iif.command = HiSax_command; + cs->iif.writecmd = NULL; + cs->iif.writebuf_skb = HiSax_writebuf_skb; + cs->iif.readstat = HiSax_readstatus; + register_isdn(&cs->iif); + cs->myid = cs->iif.channels; + + *cs_out = cs; + return 1; /* success */ + +outf_dlog: + kfree(cs->dlog); +outf_cs: + kfree(cs); + card->cs = NULL; +out: + return 0; /* error */ +} + +static int hisax_cs_setup(int cardnr, struct IsdnCard *card, + struct IsdnCardState *cs) +{ + int ret; + + if (!(cs->rcvbuf = kmalloc(MAX_DFRAME_LEN_L1, GFP_KERNEL))) { + printk(KERN_WARNING "HiSax: No memory for isac rcvbuf\n"); + ll_unload(cs); + goto outf_cs; + } + cs->rcvidx = 0; + cs->tx_skb = NULL; + cs->tx_cnt = 0; + cs->event = 0; + + skb_queue_head_init(&cs->rq); + skb_queue_head_init(&cs->sq); + + init_bcstate(cs, 0); + init_bcstate(cs, 1); + + /* init_card only handles interrupts which are not */ + /* used here for the loadable driver */ + switch (card->typ) { + case ISDN_CTYPE_DYNAMIC: + ret = 0; + break; + default: + ret = init_card(cs); + break; + } + if (ret) { + closecard(cardnr); + goto outf_cs; + } + init_tei(cs, cs->protocol); + ret = CallcNewChan(cs); + if (ret) { + closecard(cardnr); + goto outf_cs; + } + /* ISAR needs firmware download first */ + if (!test_bit(HW_ISAR, &cs->HW_Flags)) + ll_run(cs, 0); + + return 1; + +outf_cs: + kfree(cs); + card->cs = NULL; + return 0; +} + +static int checkcard(int cardnr, char *id, int *busy_flag, + struct module *lockowner, hisax_setup_func_t card_setup) +{ + int ret; + struct IsdnCard *card = cards + cardnr; + struct IsdnCardState *cs; + + ret = hisax_cs_new(cardnr, id, card, &cs, busy_flag, lockowner); + if (!ret) + return 0; + + printk(KERN_INFO + "HiSax: Card %d Protocol %s Id=%s (%d)\n", cardnr + 1, + (card->protocol == ISDN_PTYPE_1TR6) ? "1TR6" : + (card->protocol == ISDN_PTYPE_EURO) ? "EDSS1" : + (card->protocol == ISDN_PTYPE_LEASED) ? "LEASED" : + (card->protocol == ISDN_PTYPE_NI1) ? "NI1" : + "NONE", cs->iif.id, cs->myid); + + ret = card_setup(card); + if (!ret) { + ll_unload(cs); + goto outf_cs; + } + + ret = hisax_cs_setup(cardnr, card, cs); + goto out; + +outf_cs: + kfree(cs); + card->cs = NULL; +out: + return ret; +} + +static void HiSax_shiftcards(int idx) +{ + int i; + + for (i = idx; i < (HISAX_MAX_CARDS - 1); i++) + memcpy(&cards[i], &cards[i + 1], sizeof(cards[i])); +} + +static int __init HiSax_inithardware(int *busy_flag) +{ + int foundcards = 0; + int i = 0; + int t = ','; + int flg = 0; + char *id; + char *next_id = HiSax_id; + char ids[20]; + + if (strchr(HiSax_id, ',')) + t = ','; + else if (strchr(HiSax_id, '%')) + t = '%'; + + while (i < nrcards) { + if (cards[i].typ < 1) + break; + id = next_id; + if ((next_id = strchr(id, t))) { + *next_id++ = 0; + strcpy(ids, id); + flg = i + 1; + } else { + next_id = id; + if (flg >= i) + strcpy(ids, id); + else + sprintf(ids, "%s%d", id, i); + } + if (checkcard(i, ids, busy_flag, THIS_MODULE, + hisax_cs_setup_card)) { + foundcards++; + i++; + } else { + /* make sure we don't oops the module */ + if (cards[i].typ > 0 && cards[i].typ <= ISDN_CTYPE_COUNT) { + printk(KERN_WARNING + "HiSax: Card %s not installed !\n", + CardType[cards[i].typ]); + } + HiSax_shiftcards(i); + nrcards--; + } + } + return foundcards; +} + +void HiSax_closecard(int cardnr) +{ + int i, last = nrcards - 1; + + if (cardnr > last || cardnr < 0) + return; + if (cards[cardnr].cs) { + ll_stop(cards[cardnr].cs); + release_tei(cards[cardnr].cs); + CallcFreeChan(cards[cardnr].cs); + + closecard(cardnr); + if (cards[cardnr].cs->irq) + free_irq(cards[cardnr].cs->irq, cards[cardnr].cs); + kfree((void *) cards[cardnr].cs); + cards[cardnr].cs = NULL; + } + i = cardnr; + while (i <= last) { + cards[i] = cards[i + 1]; + i++; + } + nrcards--; +} + +void HiSax_reportcard(int cardnr, int sel) +{ + struct IsdnCardState *cs = cards[cardnr].cs; + + printk(KERN_DEBUG "HiSax: reportcard No %d\n", cardnr + 1); + printk(KERN_DEBUG "HiSax: Type %s\n", CardType[cs->typ]); + printk(KERN_DEBUG "HiSax: debuglevel %x\n", cs->debug); + printk(KERN_DEBUG "HiSax: HiSax_reportcard address 0x%lX\n", + (ulong) & HiSax_reportcard); + printk(KERN_DEBUG "HiSax: cs 0x%lX\n", (ulong) cs); + printk(KERN_DEBUG "HiSax: HW_Flags %lx bc0 flg %lx bc1 flg %lx\n", + cs->HW_Flags, cs->bcs[0].Flag, cs->bcs[1].Flag); + printk(KERN_DEBUG "HiSax: bcs 0 mode %d ch%d\n", + cs->bcs[0].mode, cs->bcs[0].channel); + printk(KERN_DEBUG "HiSax: bcs 1 mode %d ch%d\n", + cs->bcs[1].mode, cs->bcs[1].channel); +#ifdef ERROR_STATISTIC + printk(KERN_DEBUG "HiSax: dc errors(rx,crc,tx) %d,%d,%d\n", + cs->err_rx, cs->err_crc, cs->err_tx); + printk(KERN_DEBUG + "HiSax: bc0 errors(inv,rdo,crc,tx) %d,%d,%d,%d\n", + cs->bcs[0].err_inv, cs->bcs[0].err_rdo, cs->bcs[0].err_crc, + cs->bcs[0].err_tx); + printk(KERN_DEBUG + "HiSax: bc1 errors(inv,rdo,crc,tx) %d,%d,%d,%d\n", + cs->bcs[1].err_inv, cs->bcs[1].err_rdo, cs->bcs[1].err_crc, + cs->bcs[1].err_tx); + if (sel == 99) { + cs->err_rx = 0; + cs->err_crc = 0; + cs->err_tx = 0; + cs->bcs[0].err_inv = 0; + cs->bcs[0].err_rdo = 0; + cs->bcs[0].err_crc = 0; + cs->bcs[0].err_tx = 0; + cs->bcs[1].err_inv = 0; + cs->bcs[1].err_rdo = 0; + cs->bcs[1].err_crc = 0; + cs->bcs[1].err_tx = 0; + } +#endif +} + +static int __init HiSax_init(void) +{ + int i, retval; +#ifdef MODULE + int j; + int nzproto = 0; +#endif + + HiSaxVersion(); + retval = CallcNew(); + if (retval) + goto out; + retval = Isdnl3New(); + if (retval) + goto out_callc; + retval = Isdnl2New(); + if (retval) + goto out_isdnl3; + retval = TeiNew(); + if (retval) + goto out_isdnl2; + retval = Isdnl1New(); + if (retval) + goto out_tei; + +#ifdef MODULE + if (!type[0]) { + /* We 'll register drivers later, but init basic functions */ + for (i = 0; i < HISAX_MAX_CARDS; i++) + cards[i].typ = 0; + return 0; + } +#ifdef CONFIG_HISAX_ELSA + if (type[0] == ISDN_CTYPE_ELSA_PCMCIA) { + /* we have exported and return in this case */ + return 0; + } +#endif +#ifdef CONFIG_HISAX_SEDLBAUER + if (type[0] == ISDN_CTYPE_SEDLBAUER_PCMCIA) { + /* we have to export and return in this case */ + return 0; + } +#endif +#ifdef CONFIG_HISAX_AVM_A1_PCMCIA + if (type[0] == ISDN_CTYPE_A1_PCMCIA) { + /* we have to export and return in this case */ + return 0; + } +#endif +#ifdef CONFIG_HISAX_HFC_SX + if (type[0] == ISDN_CTYPE_HFC_SP_PCMCIA) { + /* we have to export and return in this case */ + return 0; + } +#endif +#endif + nrcards = 0; +#ifdef MODULE + if (id) /* If id= string used */ + HiSax_id = id; + for (i = j = 0; j < HISAX_MAX_CARDS; i++) { + cards[j].typ = type[i]; + if (protocol[i]) { + cards[j].protocol = protocol[i]; + nzproto++; + } else { + cards[j].protocol = DEFAULT_PROTO; + } + switch (type[i]) { + case ISDN_CTYPE_16_0: + cards[j].para[0] = irq[i]; + cards[j].para[1] = mem[i]; + cards[j].para[2] = io[i]; + break; + + case ISDN_CTYPE_8_0: + cards[j].para[0] = irq[i]; + cards[j].para[1] = mem[i]; + break; + +#ifdef IO0_IO1 + case ISDN_CTYPE_PNP: + case ISDN_CTYPE_NICCY: + cards[j].para[0] = irq[i]; + cards[j].para[1] = io0[i]; + cards[j].para[2] = io1[i]; + break; + case ISDN_CTYPE_COMPAQ_ISA: + cards[j].para[0] = irq[i]; + cards[j].para[1] = io0[i]; + cards[j].para[2] = io1[i]; + cards[j].para[3] = io[i]; + break; +#endif + case ISDN_CTYPE_ELSA: + case ISDN_CTYPE_HFC_PCI: + cards[j].para[0] = io[i]; + break; + case ISDN_CTYPE_16_3: + case ISDN_CTYPE_TELESPCMCIA: + case ISDN_CTYPE_A1: + case ISDN_CTYPE_A1_PCMCIA: + case ISDN_CTYPE_ELSA_PNP: + case ISDN_CTYPE_ELSA_PCMCIA: + case ISDN_CTYPE_IX1MICROR2: + case ISDN_CTYPE_DIEHLDIVA: + case ISDN_CTYPE_ASUSCOM: + case ISDN_CTYPE_TELEINT: + case ISDN_CTYPE_SEDLBAUER: + case ISDN_CTYPE_SEDLBAUER_PCMCIA: + case ISDN_CTYPE_SEDLBAUER_FAX: + case ISDN_CTYPE_SPORTSTER: + case ISDN_CTYPE_MIC: + case ISDN_CTYPE_TELES3C: + case ISDN_CTYPE_ACERP10: + case ISDN_CTYPE_S0BOX: + case ISDN_CTYPE_FRITZPCI: + case ISDN_CTYPE_HSTSAPHIR: + case ISDN_CTYPE_GAZEL: + case ISDN_CTYPE_HFC_SX: + case ISDN_CTYPE_HFC_SP_PCMCIA: + cards[j].para[0] = irq[i]; + cards[j].para[1] = io[i]; + break; + case ISDN_CTYPE_ISURF: + cards[j].para[0] = irq[i]; + cards[j].para[1] = io[i]; + cards[j].para[2] = mem[i]; + break; + case ISDN_CTYPE_ELSA_PCI: + case ISDN_CTYPE_NETJET_S: + case ISDN_CTYPE_TELESPCI: + case ISDN_CTYPE_W6692: + case ISDN_CTYPE_NETJET_U: + break; + case ISDN_CTYPE_BKM_A4T: + break; + case ISDN_CTYPE_SCT_QUADRO: + if (irq[i]) { + cards[j].para[0] = irq[i]; + } else { + /* QUADRO is a 4 BRI card */ + cards[j++].para[0] = 1; + /* we need to check if further cards can be added */ + if (j < HISAX_MAX_CARDS) { + cards[j].typ = ISDN_CTYPE_SCT_QUADRO; + cards[j].protocol = protocol[i]; + cards[j++].para[0] = 2; + } + if (j < HISAX_MAX_CARDS) { + cards[j].typ = ISDN_CTYPE_SCT_QUADRO; + cards[j].protocol = protocol[i]; + cards[j++].para[0] = 3; + } + if (j < HISAX_MAX_CARDS) { + cards[j].typ = ISDN_CTYPE_SCT_QUADRO; + cards[j].protocol = protocol[i]; + cards[j].para[0] = 4; + } + } + break; + } + j++; + } + if (!nzproto) { + printk(KERN_WARNING + "HiSax: Warning - no protocol specified\n"); + printk(KERN_WARNING "HiSax: using protocol %s\n", + DEFAULT_PROTO_NAME); + } +#endif + if (!HiSax_id) + HiSax_id = HiSaxID; + if (!HiSaxID[0]) + strcpy(HiSaxID, "HiSax"); + for (i = 0; i < HISAX_MAX_CARDS; i++) + if (cards[i].typ > 0) + nrcards++; + printk(KERN_DEBUG "HiSax: Total %d card%s defined\n", + nrcards, (nrcards > 1) ? "s" : ""); + + /* Install only, if at least one card found */ + if (!HiSax_inithardware(NULL)) + return -ENODEV; + return 0; + +out_tei: + TeiFree(); +out_isdnl2: + Isdnl2Free(); +out_isdnl3: + Isdnl3Free(); +out_callc: + CallcFree(); +out: + return retval; +} + +static void __exit HiSax_exit(void) +{ + int cardnr = nrcards - 1; + + while (cardnr >= 0) + HiSax_closecard(cardnr--); + Isdnl1Free(); + TeiFree(); + Isdnl2Free(); + Isdnl3Free(); + CallcFree(); + printk(KERN_INFO "HiSax module removed\n"); +} + +int hisax_init_pcmcia(void *pcm_iob, int *busy_flag, struct IsdnCard *card) +{ + u_char ids[16]; + int ret = -1; + + cards[nrcards] = *card; + if (nrcards) + sprintf(ids, "HiSax%d", nrcards); + else + sprintf(ids, "HiSax"); + if (!checkcard(nrcards, ids, busy_flag, THIS_MODULE, + hisax_cs_setup_card)) + goto error; + + ret = nrcards; + nrcards++; +error: + return ret; +} +EXPORT_SYMBOL(hisax_init_pcmcia); + +EXPORT_SYMBOL(HiSax_closecard); + +#include "hisax_if.h" + +EXPORT_SYMBOL(hisax_register); +EXPORT_SYMBOL(hisax_unregister); + +static void hisax_d_l1l2(struct hisax_if *ifc, int pr, void *arg); +static void hisax_b_l1l2(struct hisax_if *ifc, int pr, void *arg); +static void hisax_d_l2l1(struct PStack *st, int pr, void *arg); +static void hisax_b_l2l1(struct PStack *st, int pr, void *arg); +static int hisax_cardmsg(struct IsdnCardState *cs, int mt, void *arg); +static int hisax_bc_setstack(struct PStack *st, struct BCState *bcs); +static void hisax_bc_close(struct BCState *bcs); +static void hisax_bh(struct work_struct *work); +static void EChannel_proc_rcv(struct hisax_d_if *d_if); + +static int hisax_setup_card_dynamic(struct IsdnCard *card) +{ + return 2; +} + +int hisax_register(struct hisax_d_if *hisax_d_if, struct hisax_b_if *b_if[], + char *name, int protocol) +{ + int i, retval; + char id[20]; + struct IsdnCardState *cs; + + for (i = 0; i < HISAX_MAX_CARDS; i++) { + if (!cards[i].typ) + break; + } + + if (i >= HISAX_MAX_CARDS) + return -EBUSY; + + cards[i].typ = ISDN_CTYPE_DYNAMIC; + cards[i].protocol = protocol; + sprintf(id, "%s%d", name, i); + nrcards++; + retval = checkcard(i, id, NULL, hisax_d_if->owner, + hisax_setup_card_dynamic); + if (retval == 0) { // yuck + cards[i].typ = 0; + nrcards--; + return -EINVAL; + } + cs = cards[i].cs; + hisax_d_if->cs = cs; + cs->hw.hisax_d_if = hisax_d_if; + cs->cardmsg = hisax_cardmsg; + INIT_WORK(&cs->tqueue, hisax_bh); + cs->channel[0].d_st->l2.l2l1 = hisax_d_l2l1; + for (i = 0; i < 2; i++) { + cs->bcs[i].BC_SetStack = hisax_bc_setstack; + cs->bcs[i].BC_Close = hisax_bc_close; + + b_if[i]->ifc.l1l2 = hisax_b_l1l2; + + hisax_d_if->b_if[i] = b_if[i]; + } + hisax_d_if->ifc.l1l2 = hisax_d_l1l2; + skb_queue_head_init(&hisax_d_if->erq); + clear_bit(0, &hisax_d_if->ph_state); + + return 0; +} + +void hisax_unregister(struct hisax_d_if *hisax_d_if) +{ + cards[hisax_d_if->cs->cardnr].typ = 0; + HiSax_closecard(hisax_d_if->cs->cardnr); + skb_queue_purge(&hisax_d_if->erq); +} + +#include "isdnl1.h" + +static void hisax_sched_event(struct IsdnCardState *cs, int event) +{ + test_and_set_bit(event, &cs->event); + schedule_work(&cs->tqueue); +} + +static void hisax_bh(struct work_struct *work) +{ + struct IsdnCardState *cs = + container_of(work, struct IsdnCardState, tqueue); + struct PStack *st; + int pr; + + if (test_and_clear_bit(D_RCVBUFREADY, &cs->event)) + DChannel_proc_rcv(cs); + if (test_and_clear_bit(E_RCVBUFREADY, &cs->event)) + EChannel_proc_rcv(cs->hw.hisax_d_if); + if (test_and_clear_bit(D_L1STATECHANGE, &cs->event)) { + if (test_bit(0, &cs->hw.hisax_d_if->ph_state)) + pr = PH_ACTIVATE | INDICATION; + else + pr = PH_DEACTIVATE | INDICATION; + for (st = cs->stlist; st; st = st->next) + st->l1.l1l2(st, pr, NULL); + + } +} + +static void hisax_b_sched_event(struct BCState *bcs, int event) +{ + test_and_set_bit(event, &bcs->event); + schedule_work(&bcs->tqueue); +} + +static inline void D_L2L1(struct hisax_d_if *d_if, int pr, void *arg) +{ + struct hisax_if *ifc = (struct hisax_if *) d_if; + ifc->l2l1(ifc, pr, arg); +} + +static inline void B_L2L1(struct hisax_b_if *b_if, int pr, void *arg) +{ + struct hisax_if *ifc = (struct hisax_if *) b_if; + ifc->l2l1(ifc, pr, arg); +} + +static void hisax_d_l1l2(struct hisax_if *ifc, int pr, void *arg) +{ + struct hisax_d_if *d_if = (struct hisax_d_if *) ifc; + struct IsdnCardState *cs = d_if->cs; + struct PStack *st; + struct sk_buff *skb; + + switch (pr) { + case PH_ACTIVATE | INDICATION: + set_bit(0, &d_if->ph_state); + hisax_sched_event(cs, D_L1STATECHANGE); + break; + case PH_DEACTIVATE | INDICATION: + clear_bit(0, &d_if->ph_state); + hisax_sched_event(cs, D_L1STATECHANGE); + break; + case PH_DATA | INDICATION: + skb_queue_tail(&cs->rq, arg); + hisax_sched_event(cs, D_RCVBUFREADY); + break; + case PH_DATA | CONFIRM: + skb = skb_dequeue(&cs->sq); + if (skb) { + D_L2L1(d_if, PH_DATA | REQUEST, skb); + break; + } + clear_bit(FLG_L1_DBUSY, &cs->HW_Flags); + for (st = cs->stlist; st; st = st->next) { + if (test_and_clear_bit(FLG_L1_PULL_REQ, &st->l1.Flags)) { + st->l1.l1l2(st, PH_PULL | CONFIRM, NULL); + break; + } + } + break; + case PH_DATA_E | INDICATION: + skb_queue_tail(&d_if->erq, arg); + hisax_sched_event(cs, E_RCVBUFREADY); + break; + default: + printk("pr %#x\n", pr); + break; + } +} + +static void hisax_b_l1l2(struct hisax_if *ifc, int pr, void *arg) +{ + struct hisax_b_if *b_if = (struct hisax_b_if *) ifc; + struct BCState *bcs = b_if->bcs; + struct PStack *st = bcs->st; + struct sk_buff *skb; + + // FIXME use isdnl1? + switch (pr) { + case PH_ACTIVATE | INDICATION: + st->l1.l1l2(st, pr, NULL); + break; + case PH_DEACTIVATE | INDICATION: + st->l1.l1l2(st, pr, NULL); + clear_bit(BC_FLG_BUSY, &bcs->Flag); + skb_queue_purge(&bcs->squeue); + bcs->hw.b_if = NULL; + break; + case PH_DATA | INDICATION: + skb_queue_tail(&bcs->rqueue, arg); + hisax_b_sched_event(bcs, B_RCVBUFREADY); + break; + case PH_DATA | CONFIRM: + bcs->tx_cnt -= (long)arg; + if (test_bit(FLG_LLI_L1WAKEUP, &bcs->st->lli.flag)) { + u_long flags; + spin_lock_irqsave(&bcs->aclock, flags); + bcs->ackcnt += (long)arg; + spin_unlock_irqrestore(&bcs->aclock, flags); + schedule_event(bcs, B_ACKPENDING); + } + skb = skb_dequeue(&bcs->squeue); + if (skb) { + B_L2L1(b_if, PH_DATA | REQUEST, skb); + break; + } + clear_bit(BC_FLG_BUSY, &bcs->Flag); + if (test_and_clear_bit(FLG_L1_PULL_REQ, &st->l1.Flags)) { + st->l1.l1l2(st, PH_PULL | CONFIRM, NULL); + } + break; + default: + printk("hisax_b_l1l2 pr %#x\n", pr); + break; + } +} + +static void hisax_d_l2l1(struct PStack *st, int pr, void *arg) +{ + struct IsdnCardState *cs = st->l1.hardware; + struct hisax_d_if *hisax_d_if = cs->hw.hisax_d_if; + struct sk_buff *skb = arg; + + switch (pr) { + case PH_DATA | REQUEST: + case PH_PULL | INDICATION: + if (cs->debug & DEB_DLOG_HEX) + LogFrame(cs, skb->data, skb->len); + if (cs->debug & DEB_DLOG_VERBOSE) + dlogframe(cs, skb, 0); + Logl2Frame(cs, skb, "PH_DATA_REQ", 0); + // FIXME lock? + if (!test_and_set_bit(FLG_L1_DBUSY, &cs->HW_Flags)) + D_L2L1(hisax_d_if, PH_DATA | REQUEST, skb); + else + skb_queue_tail(&cs->sq, skb); + break; + case PH_PULL | REQUEST: + if (!test_bit(FLG_L1_DBUSY, &cs->HW_Flags)) + st->l1.l1l2(st, PH_PULL | CONFIRM, NULL); + else + set_bit(FLG_L1_PULL_REQ, &st->l1.Flags); + break; + default: + D_L2L1(hisax_d_if, pr, arg); + break; + } +} + +static int hisax_cardmsg(struct IsdnCardState *cs, int mt, void *arg) +{ + return 0; +} + +static void hisax_b_l2l1(struct PStack *st, int pr, void *arg) +{ + struct BCState *bcs = st->l1.bcs; + struct hisax_b_if *b_if = bcs->hw.b_if; + + switch (pr) { + case PH_ACTIVATE | REQUEST: + B_L2L1(b_if, pr, (void *)(unsigned long)st->l1.mode); + break; + case PH_DATA | REQUEST: + case PH_PULL | INDICATION: + // FIXME lock? + if (!test_and_set_bit(BC_FLG_BUSY, &bcs->Flag)) { + B_L2L1(b_if, PH_DATA | REQUEST, arg); + } else { + skb_queue_tail(&bcs->squeue, arg); + } + break; + case PH_PULL | REQUEST: + if (!test_bit(BC_FLG_BUSY, &bcs->Flag)) + st->l1.l1l2(st, PH_PULL | CONFIRM, NULL); + else + set_bit(FLG_L1_PULL_REQ, &st->l1.Flags); + break; + case PH_DEACTIVATE | REQUEST: + test_and_clear_bit(BC_FLG_BUSY, &bcs->Flag); + skb_queue_purge(&bcs->squeue); + /* fall through */ + default: + B_L2L1(b_if, pr, arg); + break; + } +} + +static int hisax_bc_setstack(struct PStack *st, struct BCState *bcs) +{ + struct IsdnCardState *cs = st->l1.hardware; + struct hisax_d_if *hisax_d_if = cs->hw.hisax_d_if; + + bcs->channel = st->l1.bc; + + bcs->hw.b_if = hisax_d_if->b_if[st->l1.bc]; + hisax_d_if->b_if[st->l1.bc]->bcs = bcs; + + st->l1.bcs = bcs; + st->l2.l2l1 = hisax_b_l2l1; + setstack_manager(st); + bcs->st = st; + setstack_l1_B(st); + skb_queue_head_init(&bcs->rqueue); + skb_queue_head_init(&bcs->squeue); + return 0; +} + +static void hisax_bc_close(struct BCState *bcs) +{ + struct hisax_b_if *b_if = bcs->hw.b_if; + + if (b_if) + B_L2L1(b_if, PH_DEACTIVATE | REQUEST, NULL); +} + +static void EChannel_proc_rcv(struct hisax_d_if *d_if) +{ + struct IsdnCardState *cs = d_if->cs; + u_char *ptr; + struct sk_buff *skb; + + while ((skb = skb_dequeue(&d_if->erq)) != NULL) { + if (cs->debug & DEB_DLOG_HEX) { + ptr = cs->dlog; + if ((skb->len) < MAX_DLOG_SPACE / 3 - 10) { + *ptr++ = 'E'; + *ptr++ = 'C'; + *ptr++ = 'H'; + *ptr++ = 'O'; + *ptr++ = ':'; + ptr += QuickHex(ptr, skb->data, skb->len); + ptr--; + *ptr++ = '\n'; + *ptr = 0; + HiSax_putstatus(cs, NULL, cs->dlog); + } else + HiSax_putstatus(cs, "LogEcho: ", + "warning Frame too big (%d)", + skb->len); + } + dev_kfree_skb_any(skb); + } +} + +#ifdef CONFIG_PCI +#include <linux/pci.h> + +static const struct pci_device_id hisax_pci_tbl[] __used = { +#ifdef CONFIG_HISAX_FRITZPCI + {PCI_VDEVICE(AVM, PCI_DEVICE_ID_AVM_A1) }, +#endif +#ifdef CONFIG_HISAX_DIEHLDIVA + {PCI_VDEVICE(EICON, PCI_DEVICE_ID_EICON_DIVA20) }, + {PCI_VDEVICE(EICON, PCI_DEVICE_ID_EICON_DIVA20_U) }, + {PCI_VDEVICE(EICON, PCI_DEVICE_ID_EICON_DIVA201) }, +/*##########################################################################*/ + {PCI_VDEVICE(EICON, PCI_DEVICE_ID_EICON_DIVA202) }, +/*##########################################################################*/ +#endif +#ifdef CONFIG_HISAX_ELSA + {PCI_VDEVICE(ELSA, PCI_DEVICE_ID_ELSA_MICROLINK) }, + {PCI_VDEVICE(ELSA, PCI_DEVICE_ID_ELSA_QS3000) }, +#endif +#ifdef CONFIG_HISAX_GAZEL + {PCI_VDEVICE(PLX, PCI_DEVICE_ID_PLX_R685) }, + {PCI_VDEVICE(PLX, PCI_DEVICE_ID_PLX_R753) }, + {PCI_VDEVICE(PLX, PCI_DEVICE_ID_PLX_DJINN_ITOO) }, + {PCI_VDEVICE(PLX, PCI_DEVICE_ID_PLX_OLITEC) }, +#endif +#ifdef CONFIG_HISAX_SCT_QUADRO + {PCI_VDEVICE(PLX, PCI_DEVICE_ID_PLX_9050) }, +#endif +#ifdef CONFIG_HISAX_NICCY + {PCI_VDEVICE(SATSAGEM, PCI_DEVICE_ID_SATSAGEM_NICCY) }, +#endif +#ifdef CONFIG_HISAX_SEDLBAUER + {PCI_VDEVICE(TIGERJET, PCI_DEVICE_ID_TIGERJET_100) }, +#endif +#if defined(CONFIG_HISAX_NETJET) || defined(CONFIG_HISAX_NETJET_U) + {PCI_VDEVICE(TIGERJET, PCI_DEVICE_ID_TIGERJET_300) }, +#endif +#if defined(CONFIG_HISAX_TELESPCI) || defined(CONFIG_HISAX_SCT_QUADRO) + {PCI_VDEVICE(ZORAN, PCI_DEVICE_ID_ZORAN_36120) }, +#endif +#ifdef CONFIG_HISAX_W6692 + {PCI_VDEVICE(DYNALINK, PCI_DEVICE_ID_DYNALINK_IS64PH) }, + {PCI_VDEVICE(WINBOND2, PCI_DEVICE_ID_WINBOND2_6692) }, +#endif +#ifdef CONFIG_HISAX_HFC_PCI + {PCI_VDEVICE(CCD, PCI_DEVICE_ID_CCD_2BD0) }, + {PCI_VDEVICE(CCD, PCI_DEVICE_ID_CCD_B000) }, + {PCI_VDEVICE(CCD, PCI_DEVICE_ID_CCD_B006) }, + {PCI_VDEVICE(CCD, PCI_DEVICE_ID_CCD_B007) }, + {PCI_VDEVICE(CCD, PCI_DEVICE_ID_CCD_B008) }, + {PCI_VDEVICE(CCD, PCI_DEVICE_ID_CCD_B009) }, + {PCI_VDEVICE(CCD, PCI_DEVICE_ID_CCD_B00A) }, + {PCI_VDEVICE(CCD, PCI_DEVICE_ID_CCD_B00B) }, + {PCI_VDEVICE(CCD, PCI_DEVICE_ID_CCD_B00C) }, + {PCI_VDEVICE(CCD, PCI_DEVICE_ID_CCD_B100) }, + {PCI_VDEVICE(CCD, PCI_DEVICE_ID_CCD_B700) }, + {PCI_VDEVICE(CCD, PCI_DEVICE_ID_CCD_B701) }, + {PCI_VDEVICE(ABOCOM, PCI_DEVICE_ID_ABOCOM_2BD1) }, + {PCI_VDEVICE(ASUSTEK, PCI_DEVICE_ID_ASUSTEK_0675) }, + {PCI_VDEVICE(BERKOM, PCI_DEVICE_ID_BERKOM_T_CONCEPT) }, + {PCI_VDEVICE(BERKOM, PCI_DEVICE_ID_BERKOM_A1T) }, + {PCI_VDEVICE(ANIGMA, PCI_DEVICE_ID_ANIGMA_MC145575) }, + {PCI_VDEVICE(ZOLTRIX, PCI_DEVICE_ID_ZOLTRIX_2BD0) }, + {PCI_VDEVICE(DIGI, PCI_DEVICE_ID_DIGI_DF_M_IOM2_E) }, + {PCI_VDEVICE(DIGI, PCI_DEVICE_ID_DIGI_DF_M_E) }, + {PCI_VDEVICE(DIGI, PCI_DEVICE_ID_DIGI_DF_M_IOM2_A) }, + {PCI_VDEVICE(DIGI, PCI_DEVICE_ID_DIGI_DF_M_A) }, +#endif + { } /* Terminating entry */ +}; + +MODULE_DEVICE_TABLE(pci, hisax_pci_tbl); +#endif /* CONFIG_PCI */ + +module_init(HiSax_init); +module_exit(HiSax_exit); + +EXPORT_SYMBOL(FsmNew); +EXPORT_SYMBOL(FsmFree); +EXPORT_SYMBOL(FsmEvent); +EXPORT_SYMBOL(FsmChangeState); +EXPORT_SYMBOL(FsmInitTimer); +EXPORT_SYMBOL(FsmDelTimer); +EXPORT_SYMBOL(FsmRestartTimer); diff --git a/drivers/isdn/hisax/diva.c b/drivers/isdn/hisax/diva.c new file mode 100644 index 000000000..d23df7a77 --- /dev/null +++ b/drivers/isdn/hisax/diva.c @@ -0,0 +1,1282 @@ +/* $Id: diva.c,v 1.33.2.6 2004/02/11 13:21:33 keil Exp $ + * + * low level stuff for Eicon.Diehl Diva Family ISDN cards + * + * Author Karsten Keil + * Copyright by Karsten Keil <keil@isdn4linux.de> + * + * This software may be used and distributed according to the terms + * of the GNU General Public License, incorporated herein by reference. + * + * For changes and modifications please read + * Documentation/isdn/HiSax.cert + * + * Thanks to Eicon Technology for documents and information + * + */ + +#include <linux/init.h> +#include "hisax.h" +#include "isac.h" +#include "hscx.h" +#include "ipac.h" +#include "ipacx.h" +#include "isdnl1.h" +#include <linux/pci.h> +#include <linux/isapnp.h> + +static const char *Diva_revision = "$Revision: 1.33.2.6 $"; + +#define byteout(addr, val) outb(val, addr) +#define bytein(addr) inb(addr) + +#define DIVA_HSCX_DATA 0 +#define DIVA_HSCX_ADR 4 +#define DIVA_ISA_ISAC_DATA 2 +#define DIVA_ISA_ISAC_ADR 6 +#define DIVA_ISA_CTRL 7 +#define DIVA_IPAC_ADR 0 +#define DIVA_IPAC_DATA 1 + +#define DIVA_PCI_ISAC_DATA 8 +#define DIVA_PCI_ISAC_ADR 0xc +#define DIVA_PCI_CTRL 0x10 + +/* SUB Types */ +#define DIVA_ISA 1 +#define DIVA_PCI 2 +#define DIVA_IPAC_ISA 3 +#define DIVA_IPAC_PCI 4 +#define DIVA_IPACX_PCI 5 + +/* CTRL (Read) */ +#define DIVA_IRQ_STAT 0x01 +#define DIVA_EEPROM_SDA 0x02 + +/* CTRL (Write) */ +#define DIVA_IRQ_REQ 0x01 +#define DIVA_RESET 0x08 +#define DIVA_EEPROM_CLK 0x40 +#define DIVA_PCI_LED_A 0x10 +#define DIVA_PCI_LED_B 0x20 +#define DIVA_ISA_LED_A 0x20 +#define DIVA_ISA_LED_B 0x40 +#define DIVA_IRQ_CLR 0x80 + +/* Siemens PITA */ +#define PITA_MISC_REG 0x1c +#ifdef __BIG_ENDIAN +#define PITA_PARA_SOFTRESET 0x00000001 +#define PITA_SER_SOFTRESET 0x00000002 +#define PITA_PARA_MPX_MODE 0x00000004 +#define PITA_INT0_ENABLE 0x00000200 +#else +#define PITA_PARA_SOFTRESET 0x01000000 +#define PITA_SER_SOFTRESET 0x02000000 +#define PITA_PARA_MPX_MODE 0x04000000 +#define PITA_INT0_ENABLE 0x00020000 +#endif +#define PITA_INT0_STATUS 0x02 + +static inline u_char +readreg(unsigned int ale, unsigned int adr, u_char off) +{ + register u_char ret; + + byteout(ale, off); + ret = bytein(adr); + return (ret); +} + +static inline void +readfifo(unsigned int ale, unsigned int adr, u_char off, u_char *data, int size) +{ + byteout(ale, off); + insb(adr, data, size); +} + + +static inline void +writereg(unsigned int ale, unsigned int adr, u_char off, u_char data) +{ + byteout(ale, off); + byteout(adr, data); +} + +static inline void +writefifo(unsigned int ale, unsigned int adr, u_char off, u_char *data, int size) +{ + byteout(ale, off); + outsb(adr, data, size); +} + +static inline u_char +memreadreg(unsigned long adr, u_char off) +{ + return (*((unsigned char *) + (((unsigned int *)adr) + off))); +} + +static inline void +memwritereg(unsigned long adr, u_char off, u_char data) +{ + register u_char *p; + + p = (unsigned char *)(((unsigned int *)adr) + off); + *p = data; +} + +/* Interface functions */ + +static u_char +ReadISAC(struct IsdnCardState *cs, u_char offset) +{ + return (readreg(cs->hw.diva.isac_adr, cs->hw.diva.isac, offset)); +} + +static void +WriteISAC(struct IsdnCardState *cs, u_char offset, u_char value) +{ + writereg(cs->hw.diva.isac_adr, cs->hw.diva.isac, offset, value); +} + +static void +ReadISACfifo(struct IsdnCardState *cs, u_char *data, int size) +{ + readfifo(cs->hw.diva.isac_adr, cs->hw.diva.isac, 0, data, size); +} + +static void +WriteISACfifo(struct IsdnCardState *cs, u_char *data, int size) +{ + writefifo(cs->hw.diva.isac_adr, cs->hw.diva.isac, 0, data, size); +} + +static u_char +ReadISAC_IPAC(struct IsdnCardState *cs, u_char offset) +{ + return (readreg(cs->hw.diva.isac_adr, cs->hw.diva.isac, offset + 0x80)); +} + +static void +WriteISAC_IPAC(struct IsdnCardState *cs, u_char offset, u_char value) +{ + writereg(cs->hw.diva.isac_adr, cs->hw.diva.isac, offset | 0x80, value); +} + +static void +ReadISACfifo_IPAC(struct IsdnCardState *cs, u_char *data, int size) +{ + readfifo(cs->hw.diva.isac_adr, cs->hw.diva.isac, 0x80, data, size); +} + +static void +WriteISACfifo_IPAC(struct IsdnCardState *cs, u_char *data, int size) +{ + writefifo(cs->hw.diva.isac_adr, cs->hw.diva.isac, 0x80, data, size); +} + +static u_char +ReadHSCX(struct IsdnCardState *cs, int hscx, u_char offset) +{ + return (readreg(cs->hw.diva.hscx_adr, + cs->hw.diva.hscx, offset + (hscx ? 0x40 : 0))); +} + +static void +WriteHSCX(struct IsdnCardState *cs, int hscx, u_char offset, u_char value) +{ + writereg(cs->hw.diva.hscx_adr, + cs->hw.diva.hscx, offset + (hscx ? 0x40 : 0), value); +} + +static u_char +MemReadISAC_IPAC(struct IsdnCardState *cs, u_char offset) +{ + return (memreadreg(cs->hw.diva.cfg_reg, offset + 0x80)); +} + +static void +MemWriteISAC_IPAC(struct IsdnCardState *cs, u_char offset, u_char value) +{ + memwritereg(cs->hw.diva.cfg_reg, offset | 0x80, value); +} + +static void +MemReadISACfifo_IPAC(struct IsdnCardState *cs, u_char *data, int size) +{ + while (size--) + *data++ = memreadreg(cs->hw.diva.cfg_reg, 0x80); +} + +static void +MemWriteISACfifo_IPAC(struct IsdnCardState *cs, u_char *data, int size) +{ + while (size--) + memwritereg(cs->hw.diva.cfg_reg, 0x80, *data++); +} + +static u_char +MemReadHSCX(struct IsdnCardState *cs, int hscx, u_char offset) +{ + return (memreadreg(cs->hw.diva.cfg_reg, offset + (hscx ? 0x40 : 0))); +} + +static void +MemWriteHSCX(struct IsdnCardState *cs, int hscx, u_char offset, u_char value) +{ + memwritereg(cs->hw.diva.cfg_reg, offset + (hscx ? 0x40 : 0), value); +} + +/* IO-Functions for IPACX type cards */ +static u_char +MemReadISAC_IPACX(struct IsdnCardState *cs, u_char offset) +{ + return (memreadreg(cs->hw.diva.cfg_reg, offset)); +} + +static void +MemWriteISAC_IPACX(struct IsdnCardState *cs, u_char offset, u_char value) +{ + memwritereg(cs->hw.diva.cfg_reg, offset, value); +} + +static void +MemReadISACfifo_IPACX(struct IsdnCardState *cs, u_char *data, int size) +{ + while (size--) + *data++ = memreadreg(cs->hw.diva.cfg_reg, 0); +} + +static void +MemWriteISACfifo_IPACX(struct IsdnCardState *cs, u_char *data, int size) +{ + while (size--) + memwritereg(cs->hw.diva.cfg_reg, 0, *data++); +} + +static u_char +MemReadHSCX_IPACX(struct IsdnCardState *cs, int hscx, u_char offset) +{ + return (memreadreg(cs->hw.diva.cfg_reg, offset + + (hscx ? IPACX_OFF_B2 : IPACX_OFF_B1))); +} + +static void +MemWriteHSCX_IPACX(struct IsdnCardState *cs, int hscx, u_char offset, u_char value) +{ + memwritereg(cs->hw.diva.cfg_reg, offset + + (hscx ? IPACX_OFF_B2 : IPACX_OFF_B1), value); +} + +/* + * fast interrupt HSCX stuff goes here + */ + +#define READHSCX(cs, nr, reg) readreg(cs->hw.diva.hscx_adr, \ + cs->hw.diva.hscx, reg + (nr ? 0x40 : 0)) +#define WRITEHSCX(cs, nr, reg, data) writereg(cs->hw.diva.hscx_adr, \ + cs->hw.diva.hscx, reg + (nr ? 0x40 : 0), data) + +#define READHSCXFIFO(cs, nr, ptr, cnt) readfifo(cs->hw.diva.hscx_adr, \ + cs->hw.diva.hscx, (nr ? 0x40 : 0), ptr, cnt) + +#define WRITEHSCXFIFO(cs, nr, ptr, cnt) writefifo(cs->hw.diva.hscx_adr, \ + cs->hw.diva.hscx, (nr ? 0x40 : 0), ptr, cnt) + +#include "hscx_irq.c" + +static irqreturn_t +diva_interrupt(int intno, void *dev_id) +{ + struct IsdnCardState *cs = dev_id; + u_char val, sval; + u_long flags; + int cnt = 5; + + spin_lock_irqsave(&cs->lock, flags); + while (((sval = bytein(cs->hw.diva.ctrl)) & DIVA_IRQ_REQ) && cnt) { + val = readreg(cs->hw.diva.hscx_adr, cs->hw.diva.hscx, HSCX_ISTA + 0x40); + if (val) + hscx_int_main(cs, val); + val = readreg(cs->hw.diva.isac_adr, cs->hw.diva.isac, ISAC_ISTA); + if (val) + isac_interrupt(cs, val); + cnt--; + } + if (!cnt) + printk(KERN_WARNING "Diva: IRQ LOOP\n"); + writereg(cs->hw.diva.hscx_adr, cs->hw.diva.hscx, HSCX_MASK, 0xFF); + writereg(cs->hw.diva.hscx_adr, cs->hw.diva.hscx, HSCX_MASK + 0x40, 0xFF); + writereg(cs->hw.diva.isac_adr, cs->hw.diva.isac, ISAC_MASK, 0xFF); + writereg(cs->hw.diva.isac_adr, cs->hw.diva.isac, ISAC_MASK, 0x0); + writereg(cs->hw.diva.hscx_adr, cs->hw.diva.hscx, HSCX_MASK, 0x0); + writereg(cs->hw.diva.hscx_adr, cs->hw.diva.hscx, HSCX_MASK + 0x40, 0x0); + spin_unlock_irqrestore(&cs->lock, flags); + return IRQ_HANDLED; +} + +static irqreturn_t +diva_irq_ipac_isa(int intno, void *dev_id) +{ + struct IsdnCardState *cs = dev_id; + u_char ista, val; + u_long flags; + int icnt = 5; + + spin_lock_irqsave(&cs->lock, flags); + ista = readreg(cs->hw.diva.isac_adr, cs->hw.diva.isac, IPAC_ISTA); +Start_IPACISA: + if (cs->debug & L1_DEB_IPAC) + debugl1(cs, "IPAC ISTA %02X", ista); + if (ista & 0x0f) { + val = readreg(cs->hw.diva.isac_adr, cs->hw.diva.isac, HSCX_ISTA + 0x40); + if (ista & 0x01) + val |= 0x01; + if (ista & 0x04) + val |= 0x02; + if (ista & 0x08) + val |= 0x04; + if (val) + hscx_int_main(cs, val); + } + if (ista & 0x20) { + val = 0xfe & readreg(cs->hw.diva.isac_adr, cs->hw.diva.isac, ISAC_ISTA + 0x80); + if (val) { + isac_interrupt(cs, val); + } + } + if (ista & 0x10) { + val = 0x01; + isac_interrupt(cs, val); + } + ista = readreg(cs->hw.diva.isac_adr, cs->hw.diva.isac, IPAC_ISTA); + if ((ista & 0x3f) && icnt) { + icnt--; + goto Start_IPACISA; + } + if (!icnt) + printk(KERN_WARNING "DIVA IPAC IRQ LOOP\n"); + writereg(cs->hw.diva.isac_adr, cs->hw.diva.isac, IPAC_MASK, 0xFF); + writereg(cs->hw.diva.isac_adr, cs->hw.diva.isac, IPAC_MASK, 0xC0); + spin_unlock_irqrestore(&cs->lock, flags); + return IRQ_HANDLED; +} + +static inline void +MemwaitforCEC(struct IsdnCardState *cs, int hscx) +{ + int to = 50; + + while ((MemReadHSCX(cs, hscx, HSCX_STAR) & 0x04) && to) { + udelay(1); + to--; + } + if (!to) + printk(KERN_WARNING "HiSax: waitforCEC timeout\n"); +} + + +static inline void +MemwaitforXFW(struct IsdnCardState *cs, int hscx) +{ + int to = 50; + + while (((MemReadHSCX(cs, hscx, HSCX_STAR) & 0x44) != 0x40) && to) { + udelay(1); + to--; + } + if (!to) + printk(KERN_WARNING "HiSax: waitforXFW timeout\n"); +} + +static inline void +MemWriteHSCXCMDR(struct IsdnCardState *cs, int hscx, u_char data) +{ + MemwaitforCEC(cs, hscx); + MemWriteHSCX(cs, hscx, HSCX_CMDR, data); +} + +static void +Memhscx_empty_fifo(struct BCState *bcs, int count) +{ + u_char *ptr; + struct IsdnCardState *cs = bcs->cs; + int cnt; + + if ((cs->debug & L1_DEB_HSCX) && !(cs->debug & L1_DEB_HSCX_FIFO)) + debugl1(cs, "hscx_empty_fifo"); + + if (bcs->hw.hscx.rcvidx + count > HSCX_BUFMAX) { + if (cs->debug & L1_DEB_WARN) + debugl1(cs, "hscx_empty_fifo: incoming packet too large"); + MemWriteHSCXCMDR(cs, bcs->hw.hscx.hscx, 0x80); + bcs->hw.hscx.rcvidx = 0; + return; + } + ptr = bcs->hw.hscx.rcvbuf + bcs->hw.hscx.rcvidx; + cnt = count; + while (cnt--) + *ptr++ = memreadreg(cs->hw.diva.cfg_reg, bcs->hw.hscx.hscx ? 0x40 : 0); + MemWriteHSCXCMDR(cs, bcs->hw.hscx.hscx, 0x80); + ptr = bcs->hw.hscx.rcvbuf + bcs->hw.hscx.rcvidx; + bcs->hw.hscx.rcvidx += count; + if (cs->debug & L1_DEB_HSCX_FIFO) { + char *t = bcs->blog; + + t += sprintf(t, "hscx_empty_fifo %c cnt %d", + bcs->hw.hscx.hscx ? 'B' : 'A', count); + QuickHex(t, ptr, count); + debugl1(cs, "%s", bcs->blog); + } +} + +static void +Memhscx_fill_fifo(struct BCState *bcs) +{ + struct IsdnCardState *cs = bcs->cs; + int more, count, cnt; + int fifo_size = test_bit(HW_IPAC, &cs->HW_Flags) ? 64 : 32; + u_char *ptr, *p; + + if ((cs->debug & L1_DEB_HSCX) && !(cs->debug & L1_DEB_HSCX_FIFO)) + debugl1(cs, "hscx_fill_fifo"); + + if (!bcs->tx_skb) + return; + if (bcs->tx_skb->len <= 0) + return; + + more = (bcs->mode == L1_MODE_TRANS) ? 1 : 0; + if (bcs->tx_skb->len > fifo_size) { + more = !0; + count = fifo_size; + } else + count = bcs->tx_skb->len; + cnt = count; + MemwaitforXFW(cs, bcs->hw.hscx.hscx); + p = ptr = bcs->tx_skb->data; + skb_pull(bcs->tx_skb, count); + bcs->tx_cnt -= count; + bcs->hw.hscx.count += count; + while (cnt--) + memwritereg(cs->hw.diva.cfg_reg, bcs->hw.hscx.hscx ? 0x40 : 0, + *p++); + MemWriteHSCXCMDR(cs, bcs->hw.hscx.hscx, more ? 0x8 : 0xa); + if (cs->debug & L1_DEB_HSCX_FIFO) { + char *t = bcs->blog; + + t += sprintf(t, "hscx_fill_fifo %c cnt %d", + bcs->hw.hscx.hscx ? 'B' : 'A', count); + QuickHex(t, ptr, count); + debugl1(cs, "%s", bcs->blog); + } +} + +static void +Memhscx_interrupt(struct IsdnCardState *cs, u_char val, u_char hscx) +{ + u_char r; + struct BCState *bcs = cs->bcs + hscx; + struct sk_buff *skb; + int fifo_size = test_bit(HW_IPAC, &cs->HW_Flags) ? 64 : 32; + int count; + + if (!test_bit(BC_FLG_INIT, &bcs->Flag)) + return; + + if (val & 0x80) { /* RME */ + r = MemReadHSCX(cs, hscx, HSCX_RSTA); + if ((r & 0xf0) != 0xa0) { + if (!(r & 0x80)) + if (cs->debug & L1_DEB_WARN) + debugl1(cs, "HSCX invalid frame"); + if ((r & 0x40) && bcs->mode) + if (cs->debug & L1_DEB_WARN) + debugl1(cs, "HSCX RDO mode=%d", + bcs->mode); + if (!(r & 0x20)) + if (cs->debug & L1_DEB_WARN) + debugl1(cs, "HSCX CRC error"); + MemWriteHSCXCMDR(cs, hscx, 0x80); + } else { + count = MemReadHSCX(cs, hscx, HSCX_RBCL) & ( + test_bit(HW_IPAC, &cs->HW_Flags) ? 0x3f : 0x1f); + if (count == 0) + count = fifo_size; + Memhscx_empty_fifo(bcs, count); + if ((count = bcs->hw.hscx.rcvidx - 1) > 0) { + if (cs->debug & L1_DEB_HSCX_FIFO) + debugl1(cs, "HX Frame %d", count); + if (!(skb = dev_alloc_skb(count))) + printk(KERN_WARNING "HSCX: receive out of memory\n"); + else { + skb_put_data(skb, bcs->hw.hscx.rcvbuf, + count); + skb_queue_tail(&bcs->rqueue, skb); + } + } + } + bcs->hw.hscx.rcvidx = 0; + schedule_event(bcs, B_RCVBUFREADY); + } + if (val & 0x40) { /* RPF */ + Memhscx_empty_fifo(bcs, fifo_size); + if (bcs->mode == L1_MODE_TRANS) { + /* receive audio data */ + if (!(skb = dev_alloc_skb(fifo_size))) + printk(KERN_WARNING "HiSax: receive out of memory\n"); + else { + skb_put_data(skb, bcs->hw.hscx.rcvbuf, + fifo_size); + skb_queue_tail(&bcs->rqueue, skb); + } + bcs->hw.hscx.rcvidx = 0; + schedule_event(bcs, B_RCVBUFREADY); + } + } + if (val & 0x10) { /* XPR */ + if (bcs->tx_skb) { + if (bcs->tx_skb->len) { + Memhscx_fill_fifo(bcs); + return; + } else { + if (test_bit(FLG_LLI_L1WAKEUP, &bcs->st->lli.flag) && + (PACKET_NOACK != bcs->tx_skb->pkt_type)) { + u_long flags; + spin_lock_irqsave(&bcs->aclock, flags); + bcs->ackcnt += bcs->hw.hscx.count; + spin_unlock_irqrestore(&bcs->aclock, flags); + schedule_event(bcs, B_ACKPENDING); + } + dev_kfree_skb_irq(bcs->tx_skb); + bcs->hw.hscx.count = 0; + bcs->tx_skb = NULL; + } + } + if ((bcs->tx_skb = skb_dequeue(&bcs->squeue))) { + bcs->hw.hscx.count = 0; + test_and_set_bit(BC_FLG_BUSY, &bcs->Flag); + Memhscx_fill_fifo(bcs); + } else { + test_and_clear_bit(BC_FLG_BUSY, &bcs->Flag); + schedule_event(bcs, B_XMTBUFREADY); + } + } +} + +static inline void +Memhscx_int_main(struct IsdnCardState *cs, u_char val) +{ + + u_char exval; + struct BCState *bcs; + + if (val & 0x01) { // EXB + bcs = cs->bcs + 1; + exval = MemReadHSCX(cs, 1, HSCX_EXIR); + if (exval & 0x40) { + if (bcs->mode == 1) + Memhscx_fill_fifo(bcs); + else { + /* Here we lost an TX interrupt, so + * restart transmitting the whole frame. + */ + if (bcs->tx_skb) { + skb_push(bcs->tx_skb, bcs->hw.hscx.count); + bcs->tx_cnt += bcs->hw.hscx.count; + bcs->hw.hscx.count = 0; + } + MemWriteHSCXCMDR(cs, bcs->hw.hscx.hscx, 0x01); + if (cs->debug & L1_DEB_WARN) + debugl1(cs, "HSCX B EXIR %x Lost TX", exval); + } + } else if (cs->debug & L1_DEB_HSCX) + debugl1(cs, "HSCX B EXIR %x", exval); + } + if (val & 0xf8) { + if (cs->debug & L1_DEB_HSCX) + debugl1(cs, "HSCX B interrupt %x", val); + Memhscx_interrupt(cs, val, 1); + } + if (val & 0x02) { // EXA + bcs = cs->bcs; + exval = MemReadHSCX(cs, 0, HSCX_EXIR); + if (exval & 0x40) { + if (bcs->mode == L1_MODE_TRANS) + Memhscx_fill_fifo(bcs); + else { + /* Here we lost an TX interrupt, so + * restart transmitting the whole frame. + */ + if (bcs->tx_skb) { + skb_push(bcs->tx_skb, bcs->hw.hscx.count); + bcs->tx_cnt += bcs->hw.hscx.count; + bcs->hw.hscx.count = 0; + } + MemWriteHSCXCMDR(cs, bcs->hw.hscx.hscx, 0x01); + if (cs->debug & L1_DEB_WARN) + debugl1(cs, "HSCX A EXIR %x Lost TX", exval); + } + } else if (cs->debug & L1_DEB_HSCX) + debugl1(cs, "HSCX A EXIR %x", exval); + } + if (val & 0x04) { // ICA + exval = MemReadHSCX(cs, 0, HSCX_ISTA); + if (cs->debug & L1_DEB_HSCX) + debugl1(cs, "HSCX A interrupt %x", exval); + Memhscx_interrupt(cs, exval, 0); + } +} + +static irqreturn_t +diva_irq_ipac_pci(int intno, void *dev_id) +{ + struct IsdnCardState *cs = dev_id; + u_char ista, val; + int icnt = 5; + u_char *cfg; + u_long flags; + + spin_lock_irqsave(&cs->lock, flags); + cfg = (u_char *) cs->hw.diva.pci_cfg; + val = *cfg; + if (!(val & PITA_INT0_STATUS)) { + spin_unlock_irqrestore(&cs->lock, flags); + return IRQ_NONE; /* other shared IRQ */ + } + *cfg = PITA_INT0_STATUS; /* Reset pending INT0 */ + ista = memreadreg(cs->hw.diva.cfg_reg, IPAC_ISTA); +Start_IPACPCI: + if (cs->debug & L1_DEB_IPAC) + debugl1(cs, "IPAC ISTA %02X", ista); + if (ista & 0x0f) { + val = memreadreg(cs->hw.diva.cfg_reg, HSCX_ISTA + 0x40); + if (ista & 0x01) + val |= 0x01; + if (ista & 0x04) + val |= 0x02; + if (ista & 0x08) + val |= 0x04; + if (val) + Memhscx_int_main(cs, val); + } + if (ista & 0x20) { + val = 0xfe & memreadreg(cs->hw.diva.cfg_reg, ISAC_ISTA + 0x80); + if (val) { + isac_interrupt(cs, val); + } + } + if (ista & 0x10) { + val = 0x01; + isac_interrupt(cs, val); + } + ista = memreadreg(cs->hw.diva.cfg_reg, IPAC_ISTA); + if ((ista & 0x3f) && icnt) { + icnt--; + goto Start_IPACPCI; + } + if (!icnt) + printk(KERN_WARNING "DIVA IPAC PCI IRQ LOOP\n"); + memwritereg(cs->hw.diva.cfg_reg, IPAC_MASK, 0xFF); + memwritereg(cs->hw.diva.cfg_reg, IPAC_MASK, 0xC0); + spin_unlock_irqrestore(&cs->lock, flags); + return IRQ_HANDLED; +} + +static irqreturn_t +diva_irq_ipacx_pci(int intno, void *dev_id) +{ + struct IsdnCardState *cs = dev_id; + u_char val; + u_char *cfg; + u_long flags; + + spin_lock_irqsave(&cs->lock, flags); + cfg = (u_char *) cs->hw.diva.pci_cfg; + val = *cfg; + if (!(val & PITA_INT0_STATUS)) { + spin_unlock_irqrestore(&cs->lock, flags); + return IRQ_NONE; // other shared IRQ + } + interrupt_ipacx(cs); // handler for chip + *cfg = PITA_INT0_STATUS; // Reset PLX interrupt + spin_unlock_irqrestore(&cs->lock, flags); + return IRQ_HANDLED; +} + +static void +release_io_diva(struct IsdnCardState *cs) +{ + int bytecnt; + + if ((cs->subtyp == DIVA_IPAC_PCI) || + (cs->subtyp == DIVA_IPACX_PCI)) { + u_int *cfg = (unsigned int *)cs->hw.diva.pci_cfg; + + *cfg = 0; /* disable INT0/1 */ + *cfg = 2; /* reset pending INT0 */ + if (cs->hw.diva.cfg_reg) + iounmap((void *)cs->hw.diva.cfg_reg); + if (cs->hw.diva.pci_cfg) + iounmap((void *)cs->hw.diva.pci_cfg); + return; + } else if (cs->subtyp != DIVA_IPAC_ISA) { + del_timer(&cs->hw.diva.tl); + if (cs->hw.diva.cfg_reg) + byteout(cs->hw.diva.ctrl, 0); /* LED off, Reset */ + } + if ((cs->subtyp == DIVA_ISA) || (cs->subtyp == DIVA_IPAC_ISA)) + bytecnt = 8; + else + bytecnt = 32; + if (cs->hw.diva.cfg_reg) { + release_region(cs->hw.diva.cfg_reg, bytecnt); + } +} + +static void +iounmap_diva(struct IsdnCardState *cs) +{ + if ((cs->subtyp == DIVA_IPAC_PCI) || (cs->subtyp == DIVA_IPACX_PCI)) { + if (cs->hw.diva.cfg_reg) { + iounmap((void *)cs->hw.diva.cfg_reg); + cs->hw.diva.cfg_reg = 0; + } + if (cs->hw.diva.pci_cfg) { + iounmap((void *)cs->hw.diva.pci_cfg); + cs->hw.diva.pci_cfg = 0; + } + } + + return; +} + +static void +reset_diva(struct IsdnCardState *cs) +{ + if (cs->subtyp == DIVA_IPAC_ISA) { + writereg(cs->hw.diva.isac_adr, cs->hw.diva.isac, IPAC_POTA2, 0x20); + mdelay(10); + writereg(cs->hw.diva.isac_adr, cs->hw.diva.isac, IPAC_POTA2, 0x00); + mdelay(10); + writereg(cs->hw.diva.isac_adr, cs->hw.diva.isac, IPAC_MASK, 0xc0); + } else if (cs->subtyp == DIVA_IPAC_PCI) { + unsigned int *ireg = (unsigned int *)(cs->hw.diva.pci_cfg + + PITA_MISC_REG); + *ireg = PITA_PARA_SOFTRESET | PITA_PARA_MPX_MODE; + mdelay(10); + *ireg = PITA_PARA_MPX_MODE; + mdelay(10); + memwritereg(cs->hw.diva.cfg_reg, IPAC_MASK, 0xc0); + } else if (cs->subtyp == DIVA_IPACX_PCI) { + unsigned int *ireg = (unsigned int *)(cs->hw.diva.pci_cfg + + PITA_MISC_REG); + *ireg = PITA_PARA_SOFTRESET | PITA_PARA_MPX_MODE; + mdelay(10); + *ireg = PITA_PARA_MPX_MODE | PITA_SER_SOFTRESET; + mdelay(10); + MemWriteISAC_IPACX(cs, IPACX_MASK, 0xff); // Interrupts off + } else { /* DIVA 2.0 */ + cs->hw.diva.ctrl_reg = 0; /* Reset On */ + byteout(cs->hw.diva.ctrl, cs->hw.diva.ctrl_reg); + mdelay(10); + cs->hw.diva.ctrl_reg |= DIVA_RESET; /* Reset Off */ + byteout(cs->hw.diva.ctrl, cs->hw.diva.ctrl_reg); + mdelay(10); + if (cs->subtyp == DIVA_ISA) + cs->hw.diva.ctrl_reg |= DIVA_ISA_LED_A; + else { + /* Workaround PCI9060 */ + byteout(cs->hw.diva.pci_cfg + 0x69, 9); + cs->hw.diva.ctrl_reg |= DIVA_PCI_LED_A; + } + byteout(cs->hw.diva.ctrl, cs->hw.diva.ctrl_reg); + } +} + +#define DIVA_ASSIGN 1 + +static void +diva_led_handler(struct timer_list *t) +{ + struct IsdnCardState *cs = from_timer(cs, t, hw.diva.tl); + int blink = 0; + + if ((cs->subtyp == DIVA_IPAC_ISA) || + (cs->subtyp == DIVA_IPAC_PCI) || + (cs->subtyp == DIVA_IPACX_PCI)) + return; + del_timer(&cs->hw.diva.tl); + if (cs->hw.diva.status & DIVA_ASSIGN) + cs->hw.diva.ctrl_reg |= (DIVA_ISA == cs->subtyp) ? + DIVA_ISA_LED_A : DIVA_PCI_LED_A; + else { + cs->hw.diva.ctrl_reg ^= (DIVA_ISA == cs->subtyp) ? + DIVA_ISA_LED_A : DIVA_PCI_LED_A; + blink = 250; + } + if (cs->hw.diva.status & 0xf000) + cs->hw.diva.ctrl_reg |= (DIVA_ISA == cs->subtyp) ? + DIVA_ISA_LED_B : DIVA_PCI_LED_B; + else if (cs->hw.diva.status & 0x0f00) { + cs->hw.diva.ctrl_reg ^= (DIVA_ISA == cs->subtyp) ? + DIVA_ISA_LED_B : DIVA_PCI_LED_B; + blink = 500; + } else + cs->hw.diva.ctrl_reg &= ~((DIVA_ISA == cs->subtyp) ? + DIVA_ISA_LED_B : DIVA_PCI_LED_B); + + byteout(cs->hw.diva.ctrl, cs->hw.diva.ctrl_reg); + if (blink) { + cs->hw.diva.tl.expires = jiffies + ((blink * HZ) / 1000); + add_timer(&cs->hw.diva.tl); + } +} + +static int +Diva_card_msg(struct IsdnCardState *cs, int mt, void *arg) +{ + u_int *ireg; + u_long flags; + + switch (mt) { + case CARD_RESET: + spin_lock_irqsave(&cs->lock, flags); + reset_diva(cs); + spin_unlock_irqrestore(&cs->lock, flags); + return (0); + case CARD_RELEASE: + release_io_diva(cs); + return (0); + case CARD_INIT: + spin_lock_irqsave(&cs->lock, flags); + reset_diva(cs); + if (cs->subtyp == DIVA_IPACX_PCI) { + ireg = (unsigned int *)cs->hw.diva.pci_cfg; + *ireg = PITA_INT0_ENABLE; + init_ipacx(cs, 3); // init chip and enable interrupts + spin_unlock_irqrestore(&cs->lock, flags); + return (0); + } + if (cs->subtyp == DIVA_IPAC_PCI) { + ireg = (unsigned int *)cs->hw.diva.pci_cfg; + *ireg = PITA_INT0_ENABLE; + } + inithscxisac(cs, 3); + spin_unlock_irqrestore(&cs->lock, flags); + return (0); + case CARD_TEST: + return (0); + case (MDL_REMOVE | REQUEST): + cs->hw.diva.status = 0; + break; + case (MDL_ASSIGN | REQUEST): + cs->hw.diva.status |= DIVA_ASSIGN; + break; + case MDL_INFO_SETUP: + if ((long)arg) + cs->hw.diva.status |= 0x0200; + else + cs->hw.diva.status |= 0x0100; + break; + case MDL_INFO_CONN: + if ((long)arg) + cs->hw.diva.status |= 0x2000; + else + cs->hw.diva.status |= 0x1000; + break; + case MDL_INFO_REL: + if ((long)arg) { + cs->hw.diva.status &= ~0x2000; + cs->hw.diva.status &= ~0x0200; + } else { + cs->hw.diva.status &= ~0x1000; + cs->hw.diva.status &= ~0x0100; + } + break; + } + if ((cs->subtyp != DIVA_IPAC_ISA) && + (cs->subtyp != DIVA_IPAC_PCI) && + (cs->subtyp != DIVA_IPACX_PCI)) { + spin_lock_irqsave(&cs->lock, flags); + diva_led_handler(&cs->hw.diva.tl); + spin_unlock_irqrestore(&cs->lock, flags); + } + return (0); +} + +static int setup_diva_common(struct IsdnCardState *cs) +{ + int bytecnt; + u_char val; + + if ((cs->subtyp == DIVA_ISA) || (cs->subtyp == DIVA_IPAC_ISA)) + bytecnt = 8; + else + bytecnt = 32; + + printk(KERN_INFO + "Diva: %s card configured at %#lx IRQ %d\n", + (cs->subtyp == DIVA_PCI) ? "PCI" : + (cs->subtyp == DIVA_ISA) ? "ISA" : + (cs->subtyp == DIVA_IPAC_ISA) ? "IPAC ISA" : + (cs->subtyp == DIVA_IPAC_PCI) ? "IPAC PCI" : "IPACX PCI", + cs->hw.diva.cfg_reg, cs->irq); + if ((cs->subtyp == DIVA_IPAC_PCI) || + (cs->subtyp == DIVA_IPACX_PCI) || + (cs->subtyp == DIVA_PCI)) + printk(KERN_INFO "Diva: %s space at %#lx\n", + (cs->subtyp == DIVA_PCI) ? "PCI" : + (cs->subtyp == DIVA_IPAC_PCI) ? "IPAC PCI" : "IPACX PCI", + cs->hw.diva.pci_cfg); + if ((cs->subtyp != DIVA_IPAC_PCI) && + (cs->subtyp != DIVA_IPACX_PCI)) { + if (!request_region(cs->hw.diva.cfg_reg, bytecnt, "diva isdn")) { + printk(KERN_WARNING + "HiSax: %s config port %lx-%lx already in use\n", + "diva", + cs->hw.diva.cfg_reg, + cs->hw.diva.cfg_reg + bytecnt); + iounmap_diva(cs); + return (0); + } + } + cs->BC_Read_Reg = &ReadHSCX; + cs->BC_Write_Reg = &WriteHSCX; + cs->BC_Send_Data = &hscx_fill_fifo; + cs->cardmsg = &Diva_card_msg; + setup_isac(cs); + if (cs->subtyp == DIVA_IPAC_ISA) { + cs->readisac = &ReadISAC_IPAC; + cs->writeisac = &WriteISAC_IPAC; + cs->readisacfifo = &ReadISACfifo_IPAC; + cs->writeisacfifo = &WriteISACfifo_IPAC; + cs->irq_func = &diva_irq_ipac_isa; + val = readreg(cs->hw.diva.isac_adr, cs->hw.diva.isac, IPAC_ID); + printk(KERN_INFO "Diva: IPAC version %x\n", val); + } else if (cs->subtyp == DIVA_IPAC_PCI) { + cs->readisac = &MemReadISAC_IPAC; + cs->writeisac = &MemWriteISAC_IPAC; + cs->readisacfifo = &MemReadISACfifo_IPAC; + cs->writeisacfifo = &MemWriteISACfifo_IPAC; + cs->BC_Read_Reg = &MemReadHSCX; + cs->BC_Write_Reg = &MemWriteHSCX; + cs->BC_Send_Data = &Memhscx_fill_fifo; + cs->irq_func = &diva_irq_ipac_pci; + val = memreadreg(cs->hw.diva.cfg_reg, IPAC_ID); + printk(KERN_INFO "Diva: IPAC version %x\n", val); + } else if (cs->subtyp == DIVA_IPACX_PCI) { + cs->readisac = &MemReadISAC_IPACX; + cs->writeisac = &MemWriteISAC_IPACX; + cs->readisacfifo = &MemReadISACfifo_IPACX; + cs->writeisacfifo = &MemWriteISACfifo_IPACX; + cs->BC_Read_Reg = &MemReadHSCX_IPACX; + cs->BC_Write_Reg = &MemWriteHSCX_IPACX; + cs->BC_Send_Data = NULL; // function located in ipacx module + cs->irq_func = &diva_irq_ipacx_pci; + printk(KERN_INFO "Diva: IPACX Design Id: %x\n", + MemReadISAC_IPACX(cs, IPACX_ID) & 0x3F); + } else { /* DIVA 2.0 */ + timer_setup(&cs->hw.diva.tl, diva_led_handler, 0); + cs->readisac = &ReadISAC; + cs->writeisac = &WriteISAC; + cs->readisacfifo = &ReadISACfifo; + cs->writeisacfifo = &WriteISACfifo; + cs->irq_func = &diva_interrupt; + ISACVersion(cs, "Diva:"); + if (HscxVersion(cs, "Diva:")) { + printk(KERN_WARNING + "Diva: wrong HSCX versions check IO address\n"); + release_io_diva(cs); + return (0); + } + } + return (1); +} + +#ifdef CONFIG_ISA + +static int setup_diva_isa(struct IsdnCard *card) +{ + struct IsdnCardState *cs = card->cs; + u_char val; + + if (!card->para[1]) + return (-1); /* card not found; continue search */ + + cs->hw.diva.ctrl_reg = 0; + cs->hw.diva.cfg_reg = card->para[1]; + val = readreg(cs->hw.diva.cfg_reg + DIVA_IPAC_ADR, + cs->hw.diva.cfg_reg + DIVA_IPAC_DATA, IPAC_ID); + printk(KERN_INFO "Diva: IPAC version %x\n", val); + if ((val == 1) || (val == 2)) { + cs->subtyp = DIVA_IPAC_ISA; + cs->hw.diva.ctrl = 0; + cs->hw.diva.isac = card->para[1] + DIVA_IPAC_DATA; + cs->hw.diva.hscx = card->para[1] + DIVA_IPAC_DATA; + cs->hw.diva.isac_adr = card->para[1] + DIVA_IPAC_ADR; + cs->hw.diva.hscx_adr = card->para[1] + DIVA_IPAC_ADR; + test_and_set_bit(HW_IPAC, &cs->HW_Flags); + } else { + cs->subtyp = DIVA_ISA; + cs->hw.diva.ctrl = card->para[1] + DIVA_ISA_CTRL; + cs->hw.diva.isac = card->para[1] + DIVA_ISA_ISAC_DATA; + cs->hw.diva.hscx = card->para[1] + DIVA_HSCX_DATA; + cs->hw.diva.isac_adr = card->para[1] + DIVA_ISA_ISAC_ADR; + cs->hw.diva.hscx_adr = card->para[1] + DIVA_HSCX_ADR; + } + cs->irq = card->para[0]; + + return (1); /* card found */ +} + +#else /* if !CONFIG_ISA */ + +static int setup_diva_isa(struct IsdnCard *card) +{ + return (-1); /* card not found; continue search */ +} + +#endif /* CONFIG_ISA */ + +#ifdef __ISAPNP__ +static struct isapnp_device_id diva_ids[] = { + { ISAPNP_VENDOR('G', 'D', 'I'), ISAPNP_FUNCTION(0x51), + ISAPNP_VENDOR('G', 'D', 'I'), ISAPNP_FUNCTION(0x51), + (unsigned long) "Diva picola" }, + { ISAPNP_VENDOR('G', 'D', 'I'), ISAPNP_FUNCTION(0x51), + ISAPNP_VENDOR('E', 'I', 'C'), ISAPNP_FUNCTION(0x51), + (unsigned long) "Diva picola" }, + { ISAPNP_VENDOR('G', 'D', 'I'), ISAPNP_FUNCTION(0x71), + ISAPNP_VENDOR('G', 'D', 'I'), ISAPNP_FUNCTION(0x71), + (unsigned long) "Diva 2.0" }, + { ISAPNP_VENDOR('G', 'D', 'I'), ISAPNP_FUNCTION(0x71), + ISAPNP_VENDOR('E', 'I', 'C'), ISAPNP_FUNCTION(0x71), + (unsigned long) "Diva 2.0" }, + { ISAPNP_VENDOR('G', 'D', 'I'), ISAPNP_FUNCTION(0xA1), + ISAPNP_VENDOR('G', 'D', 'I'), ISAPNP_FUNCTION(0xA1), + (unsigned long) "Diva 2.01" }, + { ISAPNP_VENDOR('G', 'D', 'I'), ISAPNP_FUNCTION(0xA1), + ISAPNP_VENDOR('E', 'I', 'C'), ISAPNP_FUNCTION(0xA1), + (unsigned long) "Diva 2.01" }, + { 0, } +}; + +static struct isapnp_device_id *ipid = &diva_ids[0]; +static struct pnp_card *pnp_c = NULL; + +static int setup_diva_isapnp(struct IsdnCard *card) +{ + struct IsdnCardState *cs = card->cs; + struct pnp_dev *pnp_d; + + if (!isapnp_present()) + return (-1); /* card not found; continue search */ + + while (ipid->card_vendor) { + if ((pnp_c = pnp_find_card(ipid->card_vendor, + ipid->card_device, pnp_c))) { + pnp_d = NULL; + if ((pnp_d = pnp_find_dev(pnp_c, + ipid->vendor, ipid->function, pnp_d))) { + int err; + + printk(KERN_INFO "HiSax: %s detected\n", + (char *)ipid->driver_data); + pnp_disable_dev(pnp_d); + err = pnp_activate_dev(pnp_d); + if (err < 0) { + printk(KERN_WARNING "%s: pnp_activate_dev ret(%d)\n", + __func__, err); + return (0); + } + card->para[1] = pnp_port_start(pnp_d, 0); + card->para[0] = pnp_irq(pnp_d, 0); + if (card->para[0] == -1 || !card->para[1]) { + printk(KERN_ERR "Diva PnP:some resources are missing %ld/%lx\n", + card->para[0], card->para[1]); + pnp_disable_dev(pnp_d); + return (0); + } + cs->hw.diva.cfg_reg = card->para[1]; + cs->irq = card->para[0]; + if (ipid->function == ISAPNP_FUNCTION(0xA1)) { + cs->subtyp = DIVA_IPAC_ISA; + cs->hw.diva.ctrl = 0; + cs->hw.diva.isac = + card->para[1] + DIVA_IPAC_DATA; + cs->hw.diva.hscx = + card->para[1] + DIVA_IPAC_DATA; + cs->hw.diva.isac_adr = + card->para[1] + DIVA_IPAC_ADR; + cs->hw.diva.hscx_adr = + card->para[1] + DIVA_IPAC_ADR; + test_and_set_bit(HW_IPAC, &cs->HW_Flags); + } else { + cs->subtyp = DIVA_ISA; + cs->hw.diva.ctrl = + card->para[1] + DIVA_ISA_CTRL; + cs->hw.diva.isac = + card->para[1] + DIVA_ISA_ISAC_DATA; + cs->hw.diva.hscx = + card->para[1] + DIVA_HSCX_DATA; + cs->hw.diva.isac_adr = + card->para[1] + DIVA_ISA_ISAC_ADR; + cs->hw.diva.hscx_adr = + card->para[1] + DIVA_HSCX_ADR; + } + return (1); /* card found */ + } else { + printk(KERN_ERR "Diva PnP: PnP error card found, no device\n"); + return (0); + } + } + ipid++; + pnp_c = NULL; + } + + return (-1); /* card not found; continue search */ +} + +#else /* if !ISAPNP */ + +static int setup_diva_isapnp(struct IsdnCard *card) +{ + return (-1); /* card not found; continue search */ +} + +#endif /* ISAPNP */ + +#ifdef CONFIG_PCI +static struct pci_dev *dev_diva = NULL; +static struct pci_dev *dev_diva_u = NULL; +static struct pci_dev *dev_diva201 = NULL; +static struct pci_dev *dev_diva202 = NULL; + +static int setup_diva_pci(struct IsdnCard *card) +{ + struct IsdnCardState *cs = card->cs; + + cs->subtyp = 0; + if ((dev_diva = hisax_find_pci_device(PCI_VENDOR_ID_EICON, + PCI_DEVICE_ID_EICON_DIVA20, dev_diva))) { + if (pci_enable_device(dev_diva)) + return (0); + cs->subtyp = DIVA_PCI; + cs->irq = dev_diva->irq; + cs->hw.diva.cfg_reg = pci_resource_start(dev_diva, 2); + } else if ((dev_diva_u = hisax_find_pci_device(PCI_VENDOR_ID_EICON, + PCI_DEVICE_ID_EICON_DIVA20_U, dev_diva_u))) { + if (pci_enable_device(dev_diva_u)) + return (0); + cs->subtyp = DIVA_PCI; + cs->irq = dev_diva_u->irq; + cs->hw.diva.cfg_reg = pci_resource_start(dev_diva_u, 2); + } else if ((dev_diva201 = hisax_find_pci_device(PCI_VENDOR_ID_EICON, + PCI_DEVICE_ID_EICON_DIVA201, dev_diva201))) { + if (pci_enable_device(dev_diva201)) + return (0); + cs->subtyp = DIVA_IPAC_PCI; + cs->irq = dev_diva201->irq; + cs->hw.diva.pci_cfg = + (ulong) ioremap(pci_resource_start(dev_diva201, 0), 4096); + cs->hw.diva.cfg_reg = + (ulong) ioremap(pci_resource_start(dev_diva201, 1), 4096); + } else if ((dev_diva202 = hisax_find_pci_device(PCI_VENDOR_ID_EICON, + PCI_DEVICE_ID_EICON_DIVA202, dev_diva202))) { + if (pci_enable_device(dev_diva202)) + return (0); + cs->subtyp = DIVA_IPACX_PCI; + cs->irq = dev_diva202->irq; + cs->hw.diva.pci_cfg = + (ulong) ioremap(pci_resource_start(dev_diva202, 0), 4096); + cs->hw.diva.cfg_reg = + (ulong) ioremap(pci_resource_start(dev_diva202, 1), 4096); + } else { + return (-1); /* card not found; continue search */ + } + + if (!cs->irq) { + printk(KERN_WARNING "Diva: No IRQ for PCI card found\n"); + iounmap_diva(cs); + return (0); + } + + if (!cs->hw.diva.cfg_reg) { + printk(KERN_WARNING "Diva: No IO-Adr for PCI card found\n"); + iounmap_diva(cs); + return (0); + } + cs->irq_flags |= IRQF_SHARED; + + if ((cs->subtyp == DIVA_IPAC_PCI) || + (cs->subtyp == DIVA_IPACX_PCI)) { + cs->hw.diva.ctrl = 0; + cs->hw.diva.isac = 0; + cs->hw.diva.hscx = 0; + cs->hw.diva.isac_adr = 0; + cs->hw.diva.hscx_adr = 0; + test_and_set_bit(HW_IPAC, &cs->HW_Flags); + } else { + cs->hw.diva.ctrl = cs->hw.diva.cfg_reg + DIVA_PCI_CTRL; + cs->hw.diva.isac = cs->hw.diva.cfg_reg + DIVA_PCI_ISAC_DATA; + cs->hw.diva.hscx = cs->hw.diva.cfg_reg + DIVA_HSCX_DATA; + cs->hw.diva.isac_adr = cs->hw.diva.cfg_reg + DIVA_PCI_ISAC_ADR; + cs->hw.diva.hscx_adr = cs->hw.diva.cfg_reg + DIVA_HSCX_ADR; + } + + return (1); /* card found */ +} + +#else /* if !CONFIG_PCI */ + +static int setup_diva_pci(struct IsdnCard *card) +{ + return (-1); /* card not found; continue search */ +} + +#endif /* CONFIG_PCI */ + +int setup_diva(struct IsdnCard *card) +{ + int rc, have_card = 0; + struct IsdnCardState *cs = card->cs; + char tmp[64]; + + strcpy(tmp, Diva_revision); + printk(KERN_INFO "HiSax: Eicon.Diehl Diva driver Rev. %s\n", HiSax_getrev(tmp)); + if (cs->typ != ISDN_CTYPE_DIEHLDIVA) + return (0); + cs->hw.diva.status = 0; + + rc = setup_diva_isa(card); + if (!rc) + return rc; + if (rc > 0) { + have_card = 1; + goto ready; + } + + rc = setup_diva_isapnp(card); + if (!rc) + return rc; + if (rc > 0) { + have_card = 1; + goto ready; + } + + rc = setup_diva_pci(card); + if (!rc) + return rc; + if (rc > 0) + have_card = 1; + +ready: + if (!have_card) { + printk(KERN_WARNING "Diva: No ISA, ISAPNP or PCI card found\n"); + return (0); + } + + return setup_diva_common(card->cs); +} diff --git a/drivers/isdn/hisax/elsa.c b/drivers/isdn/hisax/elsa.c new file mode 100644 index 000000000..0754c0743 --- /dev/null +++ b/drivers/isdn/hisax/elsa.c @@ -0,0 +1,1245 @@ +/* $Id: elsa.c,v 2.32.2.4 2004/01/24 20:47:21 keil Exp $ + * + * low level stuff for Elsa isdn cards + * + * Author Karsten Keil + * Copyright by Karsten Keil <keil@isdn4linux.de> + * + * This software may be used and distributed according to the terms + * of the GNU General Public License, incorporated herein by reference. + * + * For changes and modifications please read + * Documentation/isdn/HiSax.cert + * + * Thanks to Elsa GmbH for documents and information + * + * Klaus Lichtenwalder (Klaus.Lichtenwalder@WebForum.DE) + * for ELSA PCMCIA support + * + */ + +#include <linux/init.h> +#include <linux/slab.h> +#include "hisax.h" +#include "arcofi.h" +#include "isac.h" +#include "ipac.h" +#include "hscx.h" +#include "isdnl1.h" +#include <linux/pci.h> +#include <linux/isapnp.h> +#include <linux/serial.h> +#include <linux/serial_reg.h> + +static const char *Elsa_revision = "$Revision: 2.32.2.4 $"; +static const char *Elsa_Types[] = +{"None", "PC", "PCC-8", "PCC-16", "PCF", "PCF-Pro", + "PCMCIA", "QS 1000", "QS 3000", "Microlink PCI", "QS 3000 PCI", + "PCMCIA-IPAC" }; + +static const char *ITACVer[] = +{"?0?", "?1?", "?2?", "?3?", "?4?", "V2.2", + "B1", "A1"}; + +#define byteout(addr, val) outb(val, addr) +#define bytein(addr) inb(addr) + +#define ELSA_ISAC 0 +#define ELSA_ISAC_PCM 1 +#define ELSA_ITAC 1 +#define ELSA_HSCX 2 +#define ELSA_ALE 3 +#define ELSA_ALE_PCM 4 +#define ELSA_CONTROL 4 +#define ELSA_CONFIG 5 +#define ELSA_START_TIMER 6 +#define ELSA_TRIG_IRQ 7 + +#define ELSA_PC 1 +#define ELSA_PCC8 2 +#define ELSA_PCC16 3 +#define ELSA_PCF 4 +#define ELSA_PCFPRO 5 +#define ELSA_PCMCIA 6 +#define ELSA_QS1000 7 +#define ELSA_QS3000 8 +#define ELSA_QS1000PCI 9 +#define ELSA_QS3000PCI 10 +#define ELSA_PCMCIA_IPAC 11 + +/* PCI stuff */ +#define ELSA_PCI_IRQ_MASK 0x04 + +/* ITAC Registeradressen (only Microlink PC) */ +#define ITAC_SYS 0x34 +#define ITAC_ISEN 0x48 +#define ITAC_RFIE 0x4A +#define ITAC_XFIE 0x4C +#define ITAC_SCIE 0x4E +#define ITAC_STIE 0x46 + +/*** *** + *** Makros als Befehle fuer die Kartenregister *** + *** (mehrere Befehle werden durch Bit-Oderung kombiniert) *** + *** ***/ + +/* Config-Register (Read) */ +#define ELIRQF_TIMER_RUN 0x02 /* Bit 1 des Config-Reg */ +#define ELIRQF_TIMER_RUN_PCC8 0x01 /* Bit 0 des Config-Reg bei PCC */ +#define ELSA_IRQ_IDX 0x38 /* Bit 3,4,5 des Config-Reg */ +#define ELSA_IRQ_IDX_PCC8 0x30 /* Bit 4,5 des Config-Reg */ +#define ELSA_IRQ_IDX_PC 0x0c /* Bit 2,3 des Config-Reg */ + +/* Control-Register (Write) */ +#define ELSA_LINE_LED 0x02 /* Bit 1 Gelbe LED */ +#define ELSA_STAT_LED 0x08 /* Bit 3 Gruene LED */ +#define ELSA_ISDN_RESET 0x20 /* Bit 5 Reset-Leitung */ +#define ELSA_ENA_TIMER_INT 0x80 /* Bit 7 Freigabe Timer Interrupt */ + +/* ALE-Register (Read) */ +#define ELSA_HW_RELEASE 0x07 /* Bit 0-2 Hardwarerkennung */ +#define ELSA_S0_POWER_BAD 0x08 /* Bit 3 S0-Bus Spannung fehlt */ + +/* Status Flags */ +#define ELIRQF_TIMER_AKTIV 1 +#define ELSA_BAD_PWR 2 +#define ELSA_ASSIGN 4 + +#define RS_ISR_PASS_LIMIT 256 +#define FLG_MODEM_ACTIVE 1 +/* IPAC AUX */ +#define ELSA_IPAC_LINE_LED 0x40 /* Bit 6 Gelbe LED */ +#define ELSA_IPAC_STAT_LED 0x80 /* Bit 7 Gruene LED */ + +#if ARCOFI_USE +static struct arcofi_msg ARCOFI_XOP_F = +{NULL,0,2,{0xa1,0x3f,0,0,0,0,0,0,0,0}}; /* Normal OP */ +static struct arcofi_msg ARCOFI_XOP_1 = +{&ARCOFI_XOP_F,0,2,{0xa1,0x31,0,0,0,0,0,0,0,0}}; /* PWR UP */ +static struct arcofi_msg ARCOFI_SOP_F = +{&ARCOFI_XOP_1,0,10,{0xa1,0x1f,0x00,0x50,0x10,0x00,0x00,0x80,0x02,0x12}}; +static struct arcofi_msg ARCOFI_COP_9 = +{&ARCOFI_SOP_F,0,10,{0xa1,0x29,0x80,0xcb,0xe9,0x88,0x00,0xc8,0xd8,0x80}}; /* RX */ +static struct arcofi_msg ARCOFI_COP_8 = +{&ARCOFI_COP_9,0,10,{0xa1,0x28,0x49,0x31,0x8,0x13,0x6e,0x88,0x2a,0x61}}; /* TX */ +static struct arcofi_msg ARCOFI_COP_7 = +{&ARCOFI_COP_8,0,4,{0xa1,0x27,0x80,0x80,0,0,0,0,0,0}}; /* GZ */ +static struct arcofi_msg ARCOFI_COP_6 = +{&ARCOFI_COP_7,0,6,{0xa1,0x26,0,0,0x82,0x7c,0,0,0,0}}; /* GRL GRH */ +static struct arcofi_msg ARCOFI_COP_5 = +{&ARCOFI_COP_6,0,4,{0xa1,0x25,0xbb,0x4a,0,0,0,0,0,0}}; /* GTX */ +static struct arcofi_msg ARCOFI_VERSION = +{NULL,1,2,{0xa0,0,0,0,0,0,0,0,0,0}}; +static struct arcofi_msg ARCOFI_XOP_0 = +{NULL,0,2,{0xa1,0x30,0,0,0,0,0,0,0,0}}; /* PWR Down */ + +static void set_arcofi(struct IsdnCardState *cs, int bc); + +#include "elsa_ser.c" +#endif /* ARCOFI_USE */ + +static inline u_char +readreg(unsigned int ale, unsigned int adr, u_char off) +{ + register u_char ret; + + byteout(ale, off); + ret = bytein(adr); + return (ret); +} + +static inline void +readfifo(unsigned int ale, unsigned int adr, u_char off, u_char *data, int size) +{ + byteout(ale, off); + insb(adr, data, size); +} + + +static inline void +writereg(unsigned int ale, unsigned int adr, u_char off, u_char data) +{ + byteout(ale, off); + byteout(adr, data); +} + +static inline void +writefifo(unsigned int ale, unsigned int adr, u_char off, u_char *data, int size) +{ + byteout(ale, off); + outsb(adr, data, size); +} + +/* Interface functions */ + +static u_char +ReadISAC(struct IsdnCardState *cs, u_char offset) +{ + return (readreg(cs->hw.elsa.ale, cs->hw.elsa.isac, offset)); +} + +static void +WriteISAC(struct IsdnCardState *cs, u_char offset, u_char value) +{ + writereg(cs->hw.elsa.ale, cs->hw.elsa.isac, offset, value); +} + +static void +ReadISACfifo(struct IsdnCardState *cs, u_char *data, int size) +{ + readfifo(cs->hw.elsa.ale, cs->hw.elsa.isac, 0, data, size); +} + +static void +WriteISACfifo(struct IsdnCardState *cs, u_char *data, int size) +{ + writefifo(cs->hw.elsa.ale, cs->hw.elsa.isac, 0, data, size); +} + +static u_char +ReadISAC_IPAC(struct IsdnCardState *cs, u_char offset) +{ + return (readreg(cs->hw.elsa.ale, cs->hw.elsa.isac, offset + 0x80)); +} + +static void +WriteISAC_IPAC(struct IsdnCardState *cs, u_char offset, u_char value) +{ + writereg(cs->hw.elsa.ale, cs->hw.elsa.isac, offset | 0x80, value); +} + +static void +ReadISACfifo_IPAC(struct IsdnCardState *cs, u_char *data, int size) +{ + readfifo(cs->hw.elsa.ale, cs->hw.elsa.isac, 0x80, data, size); +} + +static void +WriteISACfifo_IPAC(struct IsdnCardState *cs, u_char *data, int size) +{ + writefifo(cs->hw.elsa.ale, cs->hw.elsa.isac, 0x80, data, size); +} + +static u_char +ReadHSCX(struct IsdnCardState *cs, int hscx, u_char offset) +{ + return (readreg(cs->hw.elsa.ale, + cs->hw.elsa.hscx, offset + (hscx ? 0x40 : 0))); +} + +static void +WriteHSCX(struct IsdnCardState *cs, int hscx, u_char offset, u_char value) +{ + writereg(cs->hw.elsa.ale, + cs->hw.elsa.hscx, offset + (hscx ? 0x40 : 0), value); +} + +static inline u_char +readitac(struct IsdnCardState *cs, u_char off) +{ + register u_char ret; + + byteout(cs->hw.elsa.ale, off); + ret = bytein(cs->hw.elsa.itac); + return (ret); +} + +static inline void +writeitac(struct IsdnCardState *cs, u_char off, u_char data) +{ + byteout(cs->hw.elsa.ale, off); + byteout(cs->hw.elsa.itac, data); +} + +static inline int +TimerRun(struct IsdnCardState *cs) +{ + register u_char v; + + v = bytein(cs->hw.elsa.cfg); + if ((cs->subtyp == ELSA_QS1000) || (cs->subtyp == ELSA_QS3000)) + return (0 == (v & ELIRQF_TIMER_RUN)); + else if (cs->subtyp == ELSA_PCC8) + return (v & ELIRQF_TIMER_RUN_PCC8); + return (v & ELIRQF_TIMER_RUN); +} +/* + * fast interrupt HSCX stuff goes here + */ + +#define READHSCX(cs, nr, reg) readreg(cs->hw.elsa.ale, \ + cs->hw.elsa.hscx, reg + (nr ? 0x40 : 0)) +#define WRITEHSCX(cs, nr, reg, data) writereg(cs->hw.elsa.ale, \ + cs->hw.elsa.hscx, reg + (nr ? 0x40 : 0), data) + +#define READHSCXFIFO(cs, nr, ptr, cnt) readfifo(cs->hw.elsa.ale, \ + cs->hw.elsa.hscx, (nr ? 0x40 : 0), ptr, cnt) + +#define WRITEHSCXFIFO(cs, nr, ptr, cnt) writefifo(cs->hw.elsa.ale, \ + cs->hw.elsa.hscx, (nr ? 0x40 : 0), ptr, cnt) + +#include "hscx_irq.c" + +static irqreturn_t +elsa_interrupt(int intno, void *dev_id) +{ + struct IsdnCardState *cs = dev_id; + u_long flags; + u_char val; + int icnt = 5; + + if ((cs->typ == ISDN_CTYPE_ELSA_PCMCIA) && (*cs->busy_flag == 1)) { + /* The card tends to generate interrupts while being removed + causing us to just crash the kernel. bad. */ + printk(KERN_WARNING "Elsa: card not available!\n"); + return IRQ_NONE; + } + spin_lock_irqsave(&cs->lock, flags); +#if ARCOFI_USE + if (cs->hw.elsa.MFlag) { + val = serial_inp(cs, UART_IIR); + if (!(val & UART_IIR_NO_INT)) { + debugl1(cs, "IIR %02x", val); + rs_interrupt_elsa(cs); + } + } +#endif + val = readreg(cs->hw.elsa.ale, cs->hw.elsa.hscx, HSCX_ISTA + 0x40); +Start_HSCX: + if (val) { + hscx_int_main(cs, val); + } + val = readreg(cs->hw.elsa.ale, cs->hw.elsa.isac, ISAC_ISTA); +Start_ISAC: + if (val) { + isac_interrupt(cs, val); + } + val = readreg(cs->hw.elsa.ale, cs->hw.elsa.hscx, HSCX_ISTA + 0x40); + if (val && icnt) { + if (cs->debug & L1_DEB_HSCX) + debugl1(cs, "HSCX IntStat after IntRoutine"); + icnt--; + goto Start_HSCX; + } + val = readreg(cs->hw.elsa.ale, cs->hw.elsa.isac, ISAC_ISTA); + if (val && icnt) { + if (cs->debug & L1_DEB_ISAC) + debugl1(cs, "ISAC IntStat after IntRoutine"); + icnt--; + goto Start_ISAC; + } + if (!icnt) + printk(KERN_WARNING"ELSA IRQ LOOP\n"); + writereg(cs->hw.elsa.ale, cs->hw.elsa.hscx, HSCX_MASK, 0xFF); + writereg(cs->hw.elsa.ale, cs->hw.elsa.hscx, HSCX_MASK + 0x40, 0xFF); + writereg(cs->hw.elsa.ale, cs->hw.elsa.isac, ISAC_MASK, 0xFF); + if (cs->hw.elsa.status & ELIRQF_TIMER_AKTIV) { + if (!TimerRun(cs)) { + /* Timer Restart */ + byteout(cs->hw.elsa.timer, 0); + cs->hw.elsa.counter++; + } + } +#if ARCOFI_USE + if (cs->hw.elsa.MFlag) { + val = serial_inp(cs, UART_MCR); + val ^= 0x8; + serial_outp(cs, UART_MCR, val); + val = serial_inp(cs, UART_MCR); + val ^= 0x8; + serial_outp(cs, UART_MCR, val); + } +#endif + if (cs->hw.elsa.trig) + byteout(cs->hw.elsa.trig, 0x00); + writereg(cs->hw.elsa.ale, cs->hw.elsa.hscx, HSCX_MASK, 0x0); + writereg(cs->hw.elsa.ale, cs->hw.elsa.hscx, HSCX_MASK + 0x40, 0x0); + writereg(cs->hw.elsa.ale, cs->hw.elsa.isac, ISAC_MASK, 0x0); + spin_unlock_irqrestore(&cs->lock, flags); + return IRQ_HANDLED; +} + +static irqreturn_t +elsa_interrupt_ipac(int intno, void *dev_id) +{ + struct IsdnCardState *cs = dev_id; + u_long flags; + u_char ista, val; + int icnt = 5; + + spin_lock_irqsave(&cs->lock, flags); + if (cs->subtyp == ELSA_QS1000PCI || cs->subtyp == ELSA_QS3000PCI) { + val = bytein(cs->hw.elsa.cfg + 0x4c); /* PCI IRQ */ + if (!(val & ELSA_PCI_IRQ_MASK)) { + spin_unlock_irqrestore(&cs->lock, flags); + return IRQ_NONE; + } + } +#if ARCOFI_USE + if (cs->hw.elsa.MFlag) { + val = serial_inp(cs, UART_IIR); + if (!(val & UART_IIR_NO_INT)) { + debugl1(cs, "IIR %02x", val); + rs_interrupt_elsa(cs); + } + } +#endif + ista = readreg(cs->hw.elsa.ale, cs->hw.elsa.isac, IPAC_ISTA); +Start_IPAC: + if (cs->debug & L1_DEB_IPAC) + debugl1(cs, "IPAC ISTA %02X", ista); + if (ista & 0x0f) { + val = readreg(cs->hw.elsa.ale, cs->hw.elsa.hscx, HSCX_ISTA + 0x40); + if (ista & 0x01) + val |= 0x01; + if (ista & 0x04) + val |= 0x02; + if (ista & 0x08) + val |= 0x04; + if (val) + hscx_int_main(cs, val); + } + if (ista & 0x20) { + val = 0xfe & readreg(cs->hw.elsa.ale, cs->hw.elsa.isac, ISAC_ISTA + 0x80); + if (val) { + isac_interrupt(cs, val); + } + } + if (ista & 0x10) { + val = 0x01; + isac_interrupt(cs, val); + } + ista = readreg(cs->hw.elsa.ale, cs->hw.elsa.isac, IPAC_ISTA); + if ((ista & 0x3f) && icnt) { + icnt--; + goto Start_IPAC; + } + if (!icnt) + printk(KERN_WARNING "ELSA IRQ LOOP\n"); + writereg(cs->hw.elsa.ale, cs->hw.elsa.isac, IPAC_MASK, 0xFF); + writereg(cs->hw.elsa.ale, cs->hw.elsa.isac, IPAC_MASK, 0xC0); + spin_unlock_irqrestore(&cs->lock, flags); + return IRQ_HANDLED; +} + +static void +release_io_elsa(struct IsdnCardState *cs) +{ + int bytecnt = 8; + + del_timer(&cs->hw.elsa.tl); +#if ARCOFI_USE + clear_arcofi(cs); +#endif + if (cs->hw.elsa.ctrl) + byteout(cs->hw.elsa.ctrl, 0); /* LEDs Out */ + if (cs->subtyp == ELSA_QS1000PCI) { + byteout(cs->hw.elsa.cfg + 0x4c, 0x01); /* disable IRQ */ + writereg(cs->hw.elsa.ale, cs->hw.elsa.isac, IPAC_ATX, 0xff); + bytecnt = 2; + release_region(cs->hw.elsa.cfg, 0x80); + } + if (cs->subtyp == ELSA_QS3000PCI) { + byteout(cs->hw.elsa.cfg + 0x4c, 0x03); /* disable ELSA PCI IRQ */ + writereg(cs->hw.elsa.ale, cs->hw.elsa.isac, IPAC_ATX, 0xff); + release_region(cs->hw.elsa.cfg, 0x80); + } + if (cs->subtyp == ELSA_PCMCIA_IPAC) { + writereg(cs->hw.elsa.ale, cs->hw.elsa.isac, IPAC_ATX, 0xff); + } + if ((cs->subtyp == ELSA_PCFPRO) || + (cs->subtyp == ELSA_QS3000) || + (cs->subtyp == ELSA_PCF) || + (cs->subtyp == ELSA_QS3000PCI)) { + bytecnt = 16; +#if ARCOFI_USE + release_modem(cs); +#endif + } + if (cs->hw.elsa.base) + release_region(cs->hw.elsa.base, bytecnt); +} + +static void +reset_elsa(struct IsdnCardState *cs) +{ + if (cs->hw.elsa.timer) { + /* Wait 1 Timer */ + byteout(cs->hw.elsa.timer, 0); + while (TimerRun(cs)); + cs->hw.elsa.ctrl_reg |= 0x50; + cs->hw.elsa.ctrl_reg &= ~ELSA_ISDN_RESET; /* Reset On */ + byteout(cs->hw.elsa.ctrl, cs->hw.elsa.ctrl_reg); + /* Wait 1 Timer */ + byteout(cs->hw.elsa.timer, 0); + while (TimerRun(cs)); + cs->hw.elsa.ctrl_reg |= ELSA_ISDN_RESET; /* Reset Off */ + byteout(cs->hw.elsa.ctrl, cs->hw.elsa.ctrl_reg); + /* Wait 1 Timer */ + byteout(cs->hw.elsa.timer, 0); + while (TimerRun(cs)); + if (cs->hw.elsa.trig) + byteout(cs->hw.elsa.trig, 0xff); + } + if ((cs->subtyp == ELSA_QS1000PCI) || (cs->subtyp == ELSA_QS3000PCI) || (cs->subtyp == ELSA_PCMCIA_IPAC)) { + writereg(cs->hw.elsa.ale, cs->hw.elsa.isac, IPAC_POTA2, 0x20); + mdelay(10); + writereg(cs->hw.elsa.ale, cs->hw.elsa.isac, IPAC_POTA2, 0x00); + writereg(cs->hw.elsa.ale, cs->hw.elsa.isac, IPAC_MASK, 0xc0); + mdelay(10); + if (cs->subtyp != ELSA_PCMCIA_IPAC) { + writereg(cs->hw.elsa.ale, cs->hw.elsa.isac, IPAC_ACFG, 0x0); + writereg(cs->hw.elsa.ale, cs->hw.elsa.isac, IPAC_AOE, 0x3c); + } else { + writereg(cs->hw.elsa.ale, cs->hw.elsa.isac, IPAC_PCFG, 0x10); + writereg(cs->hw.elsa.ale, cs->hw.elsa.isac, IPAC_ACFG, 0x4); + writereg(cs->hw.elsa.ale, cs->hw.elsa.isac, IPAC_AOE, 0xf8); + } + writereg(cs->hw.elsa.ale, cs->hw.elsa.isac, IPAC_ATX, 0xff); + if (cs->subtyp == ELSA_QS1000PCI) + byteout(cs->hw.elsa.cfg + 0x4c, 0x41); /* enable ELSA PCI IRQ */ + else if (cs->subtyp == ELSA_QS3000PCI) + byteout(cs->hw.elsa.cfg + 0x4c, 0x43); /* enable ELSA PCI IRQ */ + } +} + +#if ARCOFI_USE + +static void +set_arcofi(struct IsdnCardState *cs, int bc) { + cs->dc.isac.arcofi_bc = bc; + arcofi_fsm(cs, ARCOFI_START, &ARCOFI_COP_5); + wait_event_interruptible(cs->dc.isac.arcofi_wait, + cs->dc.isac.arcofi_state == ARCOFI_NOP); +} + +static int +check_arcofi(struct IsdnCardState *cs) +{ + int arcofi_present = 0; + char tmp[40]; + char *t; + u_char *p; + + if (!cs->dc.isac.mon_tx) + if (!(cs->dc.isac.mon_tx = kmalloc(MAX_MON_FRAME, GFP_ATOMIC))) { + if (cs->debug & L1_DEB_WARN) + debugl1(cs, "ISAC MON TX out of buffers!"); + return (0); + } + cs->dc.isac.arcofi_bc = 0; + arcofi_fsm(cs, ARCOFI_START, &ARCOFI_VERSION); + wait_event_interruptible(cs->dc.isac.arcofi_wait, + cs->dc.isac.arcofi_state == ARCOFI_NOP); + if (!test_and_clear_bit(FLG_ARCOFI_ERROR, &cs->HW_Flags)) { + debugl1(cs, "Arcofi response received %d bytes", cs->dc.isac.mon_rxp); + p = cs->dc.isac.mon_rx; + t = tmp; + t += sprintf(tmp, "Arcofi data"); + QuickHex(t, p, cs->dc.isac.mon_rxp); + debugl1(cs, "%s", tmp); + if ((cs->dc.isac.mon_rxp == 2) && (cs->dc.isac.mon_rx[0] == 0xa0)) { + switch (cs->dc.isac.mon_rx[1]) { + case 0x80: + debugl1(cs, "Arcofi 2160 detected"); + arcofi_present = 1; + break; + case 0x82: + debugl1(cs, "Arcofi 2165 detected"); + arcofi_present = 2; + break; + case 0x84: + debugl1(cs, "Arcofi 2163 detected"); + arcofi_present = 3; + break; + default: + debugl1(cs, "unknown Arcofi response"); + break; + } + } else + debugl1(cs, "undefined Monitor response"); + cs->dc.isac.mon_rxp = 0; + } else if (cs->dc.isac.mon_tx) { + debugl1(cs, "Arcofi not detected"); + } + if (arcofi_present) { + if (cs->subtyp == ELSA_QS1000) { + cs->subtyp = ELSA_QS3000; + printk(KERN_INFO + "Elsa: %s detected modem at 0x%lx\n", + Elsa_Types[cs->subtyp], + cs->hw.elsa.base + 8); + release_region(cs->hw.elsa.base, 8); + if (!request_region(cs->hw.elsa.base, 16, "elsa isdn modem")) { + printk(KERN_WARNING + "HiSax: %s config port %lx-%lx already in use\n", + Elsa_Types[cs->subtyp], + cs->hw.elsa.base + 8, + cs->hw.elsa.base + 16); + } + } else if (cs->subtyp == ELSA_PCC16) { + cs->subtyp = ELSA_PCF; + printk(KERN_INFO + "Elsa: %s detected modem at 0x%lx\n", + Elsa_Types[cs->subtyp], + cs->hw.elsa.base + 8); + release_region(cs->hw.elsa.base, 8); + if (!request_region(cs->hw.elsa.base, 16, "elsa isdn modem")) { + printk(KERN_WARNING + "HiSax: %s config port %lx-%lx already in use\n", + Elsa_Types[cs->subtyp], + cs->hw.elsa.base + 8, + cs->hw.elsa.base + 16); + } + } else + printk(KERN_INFO + "Elsa: %s detected modem at 0x%lx\n", + Elsa_Types[cs->subtyp], + cs->hw.elsa.base + 8); + arcofi_fsm(cs, ARCOFI_START, &ARCOFI_XOP_0); + wait_event_interruptible(cs->dc.isac.arcofi_wait, + cs->dc.isac.arcofi_state == ARCOFI_NOP); + return (1); + } + return (0); +} +#endif /* ARCOFI_USE */ + +static void +elsa_led_handler(struct timer_list *t) +{ + struct IsdnCardState *cs = from_timer(cs, t, hw.elsa.tl); + int blink = 0; + + if (cs->subtyp == ELSA_PCMCIA || cs->subtyp == ELSA_PCMCIA_IPAC) + return; + del_timer(&cs->hw.elsa.tl); + if (cs->hw.elsa.status & ELSA_ASSIGN) + cs->hw.elsa.ctrl_reg |= ELSA_STAT_LED; + else if (cs->hw.elsa.status & ELSA_BAD_PWR) + cs->hw.elsa.ctrl_reg &= ~ELSA_STAT_LED; + else { + cs->hw.elsa.ctrl_reg ^= ELSA_STAT_LED; + blink = 250; + } + if (cs->hw.elsa.status & 0xf000) + cs->hw.elsa.ctrl_reg |= ELSA_LINE_LED; + else if (cs->hw.elsa.status & 0x0f00) { + cs->hw.elsa.ctrl_reg ^= ELSA_LINE_LED; + blink = 500; + } else + cs->hw.elsa.ctrl_reg &= ~ELSA_LINE_LED; + + if ((cs->subtyp == ELSA_QS1000PCI) || + (cs->subtyp == ELSA_QS3000PCI)) { + u_char led = 0xff; + if (cs->hw.elsa.ctrl_reg & ELSA_LINE_LED) + led ^= ELSA_IPAC_LINE_LED; + if (cs->hw.elsa.ctrl_reg & ELSA_STAT_LED) + led ^= ELSA_IPAC_STAT_LED; + writereg(cs->hw.elsa.ale, cs->hw.elsa.isac, IPAC_ATX, led); + } else + byteout(cs->hw.elsa.ctrl, cs->hw.elsa.ctrl_reg); + if (blink) { + cs->hw.elsa.tl.expires = jiffies + ((blink * HZ) / 1000); + add_timer(&cs->hw.elsa.tl); + } +} + +static int +Elsa_card_msg(struct IsdnCardState *cs, int mt, void *arg) +{ + int ret = 0; + u_long flags; + + switch (mt) { + case CARD_RESET: + spin_lock_irqsave(&cs->lock, flags); + reset_elsa(cs); + spin_unlock_irqrestore(&cs->lock, flags); + return (0); + case CARD_RELEASE: + release_io_elsa(cs); + return (0); + case CARD_INIT: + spin_lock_irqsave(&cs->lock, flags); + cs->debug |= L1_DEB_IPAC; + reset_elsa(cs); + inithscxisac(cs, 1); + if ((cs->subtyp == ELSA_QS1000) || + (cs->subtyp == ELSA_QS3000)) + { + byteout(cs->hw.elsa.timer, 0); + } + if (cs->hw.elsa.trig) + byteout(cs->hw.elsa.trig, 0xff); + inithscxisac(cs, 2); + spin_unlock_irqrestore(&cs->lock, flags); + return (0); + case CARD_TEST: + if ((cs->subtyp == ELSA_PCMCIA) || + (cs->subtyp == ELSA_PCMCIA_IPAC) || + (cs->subtyp == ELSA_QS1000PCI)) { + return (0); + } else if (cs->subtyp == ELSA_QS3000PCI) { + ret = 0; + } else { + spin_lock_irqsave(&cs->lock, flags); + cs->hw.elsa.counter = 0; + cs->hw.elsa.ctrl_reg |= ELSA_ENA_TIMER_INT; + cs->hw.elsa.status |= ELIRQF_TIMER_AKTIV; + byteout(cs->hw.elsa.ctrl, cs->hw.elsa.ctrl_reg); + byteout(cs->hw.elsa.timer, 0); + spin_unlock_irqrestore(&cs->lock, flags); + msleep(110); + spin_lock_irqsave(&cs->lock, flags); + cs->hw.elsa.ctrl_reg &= ~ELSA_ENA_TIMER_INT; + byteout(cs->hw.elsa.ctrl, cs->hw.elsa.ctrl_reg); + cs->hw.elsa.status &= ~ELIRQF_TIMER_AKTIV; + spin_unlock_irqrestore(&cs->lock, flags); + printk(KERN_INFO "Elsa: %d timer tics in 110 msek\n", + cs->hw.elsa.counter); + if ((cs->hw.elsa.counter > 10) && + (cs->hw.elsa.counter < 16)) { + printk(KERN_INFO "Elsa: timer and irq OK\n"); + ret = 0; + } else { + printk(KERN_WARNING + "Elsa: timer tic problem (%d/12) maybe an IRQ(%d) conflict\n", + cs->hw.elsa.counter, cs->irq); + ret = 1; + } + } +#if ARCOFI_USE + if (check_arcofi(cs)) { + init_modem(cs); + } +#endif + elsa_led_handler(&cs->hw.elsa.tl); + return (ret); + case (MDL_REMOVE | REQUEST): + cs->hw.elsa.status &= 0; + break; + case (MDL_ASSIGN | REQUEST): + cs->hw.elsa.status |= ELSA_ASSIGN; + break; + case MDL_INFO_SETUP: + if ((long) arg) + cs->hw.elsa.status |= 0x0200; + else + cs->hw.elsa.status |= 0x0100; + break; + case MDL_INFO_CONN: + if ((long) arg) + cs->hw.elsa.status |= 0x2000; + else + cs->hw.elsa.status |= 0x1000; + break; + case MDL_INFO_REL: + if ((long) arg) { + cs->hw.elsa.status &= ~0x2000; + cs->hw.elsa.status &= ~0x0200; + } else { + cs->hw.elsa.status &= ~0x1000; + cs->hw.elsa.status &= ~0x0100; + } + break; +#if ARCOFI_USE + case CARD_AUX_IND: + if (cs->hw.elsa.MFlag) { + int len; + u_char *msg; + + if (!arg) + return (0); + msg = arg; + len = *msg; + msg++; + modem_write_cmd(cs, msg, len); + } + break; +#endif + } + if (cs->typ == ISDN_CTYPE_ELSA) { + int pwr = bytein(cs->hw.elsa.ale); + if (pwr & 0x08) + cs->hw.elsa.status |= ELSA_BAD_PWR; + else + cs->hw.elsa.status &= ~ELSA_BAD_PWR; + } + elsa_led_handler(&cs->hw.elsa.tl); + return (ret); +} + +static unsigned char +probe_elsa_adr(unsigned int adr, int typ) +{ + int i, in1, in2, p16_1 = 0, p16_2 = 0, p8_1 = 0, p8_2 = 0, pc_1 = 0, + pc_2 = 0, pfp_1 = 0, pfp_2 = 0; + + /* In case of the elsa pcmcia card, this region is in use, + reserved for us by the card manager. So we do not check it + here, it would fail. */ + if (typ != ISDN_CTYPE_ELSA_PCMCIA) { + if (request_region(adr, 8, "elsa card")) { + release_region(adr, 8); + } else { + printk(KERN_WARNING + "Elsa: Probing Port 0x%x: already in use\n", adr); + return (0); + } + } + for (i = 0; i < 16; i++) { + in1 = inb(adr + ELSA_CONFIG); /* 'toggelt' bei */ + in2 = inb(adr + ELSA_CONFIG); /* jedem Zugriff */ + p16_1 += 0x04 & in1; + p16_2 += 0x04 & in2; + p8_1 += 0x02 & in1; + p8_2 += 0x02 & in2; + pc_1 += 0x01 & in1; + pc_2 += 0x01 & in2; + pfp_1 += 0x40 & in1; + pfp_2 += 0x40 & in2; + } + printk(KERN_INFO "Elsa: Probing IO 0x%x", adr); + if (65 == ++p16_1 * ++p16_2) { + printk(" PCC-16/PCF found\n"); + return (ELSA_PCC16); + } else if (1025 == ++pfp_1 * ++pfp_2) { + printk(" PCF-Pro found\n"); + return (ELSA_PCFPRO); + } else if (33 == ++p8_1 * ++p8_2) { + printk(" PCC8 found\n"); + return (ELSA_PCC8); + } else if (17 == ++pc_1 * ++pc_2) { + printk(" PC found\n"); + return (ELSA_PC); + } else { + printk(" failed\n"); + return (0); + } +} + +static unsigned int +probe_elsa(struct IsdnCardState *cs) +{ + int i; + unsigned int CARD_portlist[] = + {0x160, 0x170, 0x260, 0x360, 0}; + + for (i = 0; CARD_portlist[i]; i++) { + if ((cs->subtyp = probe_elsa_adr(CARD_portlist[i], cs->typ))) + break; + } + return (CARD_portlist[i]); +} + +static int setup_elsa_isa(struct IsdnCard *card) +{ + struct IsdnCardState *cs = card->cs; + u_char val; + + cs->hw.elsa.base = card->para[0]; + printk(KERN_INFO "Elsa: Microlink IO probing\n"); + if (cs->hw.elsa.base) { + if (!(cs->subtyp = probe_elsa_adr(cs->hw.elsa.base, + cs->typ))) { + printk(KERN_WARNING + "Elsa: no Elsa Microlink at %#lx\n", + cs->hw.elsa.base); + return (0); + } + } else + cs->hw.elsa.base = probe_elsa(cs); + + if (!cs->hw.elsa.base) { + printk(KERN_WARNING + "No Elsa Microlink found\n"); + return (0); + } + + cs->hw.elsa.cfg = cs->hw.elsa.base + ELSA_CONFIG; + cs->hw.elsa.ctrl = cs->hw.elsa.base + ELSA_CONTROL; + cs->hw.elsa.ale = cs->hw.elsa.base + ELSA_ALE; + cs->hw.elsa.isac = cs->hw.elsa.base + ELSA_ISAC; + cs->hw.elsa.itac = cs->hw.elsa.base + ELSA_ITAC; + cs->hw.elsa.hscx = cs->hw.elsa.base + ELSA_HSCX; + cs->hw.elsa.trig = cs->hw.elsa.base + ELSA_TRIG_IRQ; + cs->hw.elsa.timer = cs->hw.elsa.base + ELSA_START_TIMER; + val = bytein(cs->hw.elsa.cfg); + if (cs->subtyp == ELSA_PC) { + const u_char CARD_IrqTab[8] = + {7, 3, 5, 9, 0, 0, 0, 0}; + cs->irq = CARD_IrqTab[(val & ELSA_IRQ_IDX_PC) >> 2]; + } else if (cs->subtyp == ELSA_PCC8) { + const u_char CARD_IrqTab[8] = + {7, 3, 5, 9, 0, 0, 0, 0}; + cs->irq = CARD_IrqTab[(val & ELSA_IRQ_IDX_PCC8) >> 4]; + } else { + const u_char CARD_IrqTab[8] = + {15, 10, 15, 3, 11, 5, 11, 9}; + cs->irq = CARD_IrqTab[(val & ELSA_IRQ_IDX) >> 3]; + } + val = bytein(cs->hw.elsa.ale) & ELSA_HW_RELEASE; + if (val < 3) + val |= 8; + val += 'A' - 3; + if (val == 'B' || val == 'C') + val ^= 1; + if ((cs->subtyp == ELSA_PCFPRO) && (val == 'G')) + val = 'C'; + printk(KERN_INFO + "Elsa: %s found at %#lx Rev.:%c IRQ %d\n", + Elsa_Types[cs->subtyp], + cs->hw.elsa.base, + val, cs->irq); + val = bytein(cs->hw.elsa.ale) & ELSA_S0_POWER_BAD; + if (val) { + printk(KERN_WARNING + "Elsa: Microlink S0 bus power bad\n"); + cs->hw.elsa.status |= ELSA_BAD_PWR; + } + + return (1); +} + +#ifdef __ISAPNP__ +static struct isapnp_device_id elsa_ids[] = { + { ISAPNP_VENDOR('E', 'L', 'S'), ISAPNP_FUNCTION(0x0133), + ISAPNP_VENDOR('E', 'L', 'S'), ISAPNP_FUNCTION(0x0133), + (unsigned long) "Elsa QS1000" }, + { ISAPNP_VENDOR('E', 'L', 'S'), ISAPNP_FUNCTION(0x0134), + ISAPNP_VENDOR('E', 'L', 'S'), ISAPNP_FUNCTION(0x0134), + (unsigned long) "Elsa QS3000" }, + { 0, } +}; + +static struct isapnp_device_id *ipid = &elsa_ids[0]; +static struct pnp_card *pnp_c = NULL; +#endif /* __ISAPNP__ */ + +static int setup_elsa_isapnp(struct IsdnCard *card) +{ + struct IsdnCardState *cs = card->cs; + +#ifdef __ISAPNP__ + if (!card->para[1] && isapnp_present()) { + struct pnp_dev *pnp_d; + while (ipid->card_vendor) { + if ((pnp_c = pnp_find_card(ipid->card_vendor, + ipid->card_device, pnp_c))) { + pnp_d = NULL; + if ((pnp_d = pnp_find_dev(pnp_c, + ipid->vendor, ipid->function, pnp_d))) { + int err; + + printk(KERN_INFO "HiSax: %s detected\n", + (char *)ipid->driver_data); + pnp_disable_dev(pnp_d); + err = pnp_activate_dev(pnp_d); + if (err < 0) { + printk(KERN_WARNING "%s: pnp_activate_dev ret(%d)\n", + __func__, err); + return (0); + } + card->para[1] = pnp_port_start(pnp_d, 0); + card->para[0] = pnp_irq(pnp_d, 0); + + if (card->para[0] == -1 || !card->para[1]) { + printk(KERN_ERR "Elsa PnP:some resources are missing %ld/%lx\n", + card->para[0], card->para[1]); + pnp_disable_dev(pnp_d); + return (0); + } + if (ipid->function == ISAPNP_FUNCTION(0x133)) + cs->subtyp = ELSA_QS1000; + else + cs->subtyp = ELSA_QS3000; + break; + } else { + printk(KERN_ERR "Elsa PnP: PnP error card found, no device\n"); + return (0); + } + } + ipid++; + pnp_c = NULL; + } + if (!ipid->card_vendor) { + printk(KERN_INFO "Elsa PnP: no ISAPnP card found\n"); + return (0); + } + } +#endif /* __ISAPNP__ */ + + if (card->para[1] && card->para[0]) { + cs->hw.elsa.base = card->para[1]; + cs->irq = card->para[0]; + if (!cs->subtyp) + cs->subtyp = ELSA_QS1000; + } else { + printk(KERN_ERR "Elsa PnP: no parameter\n"); + } + cs->hw.elsa.cfg = cs->hw.elsa.base + ELSA_CONFIG; + cs->hw.elsa.ale = cs->hw.elsa.base + ELSA_ALE; + cs->hw.elsa.isac = cs->hw.elsa.base + ELSA_ISAC; + cs->hw.elsa.hscx = cs->hw.elsa.base + ELSA_HSCX; + cs->hw.elsa.trig = cs->hw.elsa.base + ELSA_TRIG_IRQ; + cs->hw.elsa.timer = cs->hw.elsa.base + ELSA_START_TIMER; + cs->hw.elsa.ctrl = cs->hw.elsa.base + ELSA_CONTROL; + printk(KERN_INFO + "Elsa: %s defined at %#lx IRQ %d\n", + Elsa_Types[cs->subtyp], + cs->hw.elsa.base, + cs->irq); + + return (1); +} + +static void setup_elsa_pcmcia(struct IsdnCard *card) +{ + struct IsdnCardState *cs = card->cs; + u_char val; + + cs->hw.elsa.base = card->para[1]; + cs->irq = card->para[0]; + val = readreg(cs->hw.elsa.base + 0, cs->hw.elsa.base + 2, IPAC_ID); + if ((val == 1) || (val == 2)) { /* IPAC version 1.1/1.2 */ + cs->subtyp = ELSA_PCMCIA_IPAC; + cs->hw.elsa.ale = cs->hw.elsa.base + 0; + cs->hw.elsa.isac = cs->hw.elsa.base + 2; + cs->hw.elsa.hscx = cs->hw.elsa.base + 2; + test_and_set_bit(HW_IPAC, &cs->HW_Flags); + } else { + cs->subtyp = ELSA_PCMCIA; + cs->hw.elsa.ale = cs->hw.elsa.base + ELSA_ALE_PCM; + cs->hw.elsa.isac = cs->hw.elsa.base + ELSA_ISAC_PCM; + cs->hw.elsa.hscx = cs->hw.elsa.base + ELSA_HSCX; + } + cs->hw.elsa.timer = 0; + cs->hw.elsa.trig = 0; + cs->hw.elsa.ctrl = 0; + cs->irq_flags |= IRQF_SHARED; + printk(KERN_INFO + "Elsa: %s defined at %#lx IRQ %d\n", + Elsa_Types[cs->subtyp], + cs->hw.elsa.base, + cs->irq); +} + +#ifdef CONFIG_PCI +static struct pci_dev *dev_qs1000 = NULL; +static struct pci_dev *dev_qs3000 = NULL; + +static int setup_elsa_pci(struct IsdnCard *card) +{ + struct IsdnCardState *cs = card->cs; + + cs->subtyp = 0; + if ((dev_qs1000 = hisax_find_pci_device(PCI_VENDOR_ID_ELSA, + PCI_DEVICE_ID_ELSA_MICROLINK, dev_qs1000))) { + if (pci_enable_device(dev_qs1000)) + return (0); + cs->subtyp = ELSA_QS1000PCI; + cs->irq = dev_qs1000->irq; + cs->hw.elsa.cfg = pci_resource_start(dev_qs1000, 1); + cs->hw.elsa.base = pci_resource_start(dev_qs1000, 3); + } else if ((dev_qs3000 = hisax_find_pci_device(PCI_VENDOR_ID_ELSA, + PCI_DEVICE_ID_ELSA_QS3000, dev_qs3000))) { + if (pci_enable_device(dev_qs3000)) + return (0); + cs->subtyp = ELSA_QS3000PCI; + cs->irq = dev_qs3000->irq; + cs->hw.elsa.cfg = pci_resource_start(dev_qs3000, 1); + cs->hw.elsa.base = pci_resource_start(dev_qs3000, 3); + } else { + printk(KERN_WARNING "Elsa: No PCI card found\n"); + return (0); + } + if (!cs->irq) { + printk(KERN_WARNING "Elsa: No IRQ for PCI card found\n"); + return (0); + } + + if (!(cs->hw.elsa.base && cs->hw.elsa.cfg)) { + printk(KERN_WARNING "Elsa: No IO-Adr for PCI card found\n"); + return (0); + } + if ((cs->hw.elsa.cfg & 0xff) || (cs->hw.elsa.base & 0xf)) { + printk(KERN_WARNING "Elsa: You may have a wrong PCI bios\n"); + printk(KERN_WARNING "Elsa: If your system hangs now, read\n"); + printk(KERN_WARNING "Elsa: Documentation/isdn/README.HiSax\n"); + } + cs->hw.elsa.ale = cs->hw.elsa.base; + cs->hw.elsa.isac = cs->hw.elsa.base + 1; + cs->hw.elsa.hscx = cs->hw.elsa.base + 1; + test_and_set_bit(HW_IPAC, &cs->HW_Flags); + cs->hw.elsa.timer = 0; + cs->hw.elsa.trig = 0; + cs->irq_flags |= IRQF_SHARED; + printk(KERN_INFO + "Elsa: %s defined at %#lx/0x%x IRQ %d\n", + Elsa_Types[cs->subtyp], + cs->hw.elsa.base, + cs->hw.elsa.cfg, + cs->irq); + + return (1); +} + +#else + +static int setup_elsa_pci(struct IsdnCard *card) +{ + return (1); +} +#endif /* CONFIG_PCI */ + +static int setup_elsa_common(struct IsdnCard *card) +{ + struct IsdnCardState *cs = card->cs; + u_char val; + int bytecnt; + + switch (cs->subtyp) { + case ELSA_PC: + case ELSA_PCC8: + case ELSA_PCC16: + case ELSA_QS1000: + case ELSA_PCMCIA: + case ELSA_PCMCIA_IPAC: + bytecnt = 8; + break; + case ELSA_PCFPRO: + case ELSA_PCF: + case ELSA_QS3000: + case ELSA_QS3000PCI: + bytecnt = 16; + break; + case ELSA_QS1000PCI: + bytecnt = 2; + break; + default: + printk(KERN_WARNING + "Unknown ELSA subtype %d\n", cs->subtyp); + return (0); + } + /* In case of the elsa pcmcia card, this region is in use, + reserved for us by the card manager. So we do not check it + here, it would fail. */ + if (cs->typ != ISDN_CTYPE_ELSA_PCMCIA && !request_region(cs->hw.elsa.base, bytecnt, "elsa isdn")) { + printk(KERN_WARNING + "HiSax: ELSA config port %#lx-%#lx already in use\n", + cs->hw.elsa.base, + cs->hw.elsa.base + bytecnt); + return (0); + } + if ((cs->subtyp == ELSA_QS1000PCI) || (cs->subtyp == ELSA_QS3000PCI)) { + if (!request_region(cs->hw.elsa.cfg, 0x80, "elsa isdn pci")) { + printk(KERN_WARNING + "HiSax: ELSA pci port %x-%x already in use\n", + cs->hw.elsa.cfg, + cs->hw.elsa.cfg + 0x80); + release_region(cs->hw.elsa.base, bytecnt); + return (0); + } + } +#if ARCOFI_USE + init_arcofi(cs); +#endif + setup_isac(cs); + timer_setup(&cs->hw.elsa.tl, elsa_led_handler, 0); + /* Teste Timer */ + if (cs->hw.elsa.timer) { + byteout(cs->hw.elsa.trig, 0xff); + byteout(cs->hw.elsa.timer, 0); + if (!TimerRun(cs)) { + byteout(cs->hw.elsa.timer, 0); /* 2. Versuch */ + if (!TimerRun(cs)) { + printk(KERN_WARNING + "Elsa: timer do not start\n"); + release_io_elsa(cs); + return (0); + } + } + HZDELAY((HZ / 100) + 1); /* wait >=10 ms */ + if (TimerRun(cs)) { + printk(KERN_WARNING "Elsa: timer do not run down\n"); + release_io_elsa(cs); + return (0); + } + printk(KERN_INFO "Elsa: timer OK; resetting card\n"); + } + cs->BC_Read_Reg = &ReadHSCX; + cs->BC_Write_Reg = &WriteHSCX; + cs->BC_Send_Data = &hscx_fill_fifo; + cs->cardmsg = &Elsa_card_msg; + if ((cs->subtyp == ELSA_QS1000PCI) || (cs->subtyp == ELSA_QS3000PCI) || (cs->subtyp == ELSA_PCMCIA_IPAC)) { + cs->readisac = &ReadISAC_IPAC; + cs->writeisac = &WriteISAC_IPAC; + cs->readisacfifo = &ReadISACfifo_IPAC; + cs->writeisacfifo = &WriteISACfifo_IPAC; + cs->irq_func = &elsa_interrupt_ipac; + val = readreg(cs->hw.elsa.ale, cs->hw.elsa.isac, IPAC_ID); + printk(KERN_INFO "Elsa: IPAC version %x\n", val); + } else { + cs->readisac = &ReadISAC; + cs->writeisac = &WriteISAC; + cs->readisacfifo = &ReadISACfifo; + cs->writeisacfifo = &WriteISACfifo; + cs->irq_func = &elsa_interrupt; + ISACVersion(cs, "Elsa:"); + if (HscxVersion(cs, "Elsa:")) { + printk(KERN_WARNING + "Elsa: wrong HSCX versions check IO address\n"); + release_io_elsa(cs); + return (0); + } + } + if (cs->subtyp == ELSA_PC) { + val = readitac(cs, ITAC_SYS); + printk(KERN_INFO "Elsa: ITAC version %s\n", ITACVer[val & 7]); + writeitac(cs, ITAC_ISEN, 0); + writeitac(cs, ITAC_RFIE, 0); + writeitac(cs, ITAC_XFIE, 0); + writeitac(cs, ITAC_SCIE, 0); + writeitac(cs, ITAC_STIE, 0); + } + return (1); +} + +int setup_elsa(struct IsdnCard *card) +{ + int rc; + struct IsdnCardState *cs = card->cs; + char tmp[64]; + + strcpy(tmp, Elsa_revision); + printk(KERN_INFO "HiSax: Elsa driver Rev. %s\n", HiSax_getrev(tmp)); + cs->hw.elsa.ctrl_reg = 0; + cs->hw.elsa.status = 0; + cs->hw.elsa.MFlag = 0; + cs->subtyp = 0; + + if (cs->typ == ISDN_CTYPE_ELSA) { + rc = setup_elsa_isa(card); + if (!rc) + return (0); + + } else if (cs->typ == ISDN_CTYPE_ELSA_PNP) { + rc = setup_elsa_isapnp(card); + if (!rc) + return (0); + + } else if (cs->typ == ISDN_CTYPE_ELSA_PCMCIA) + setup_elsa_pcmcia(card); + + else if (cs->typ == ISDN_CTYPE_ELSA_PCI) { + rc = setup_elsa_pci(card); + if (!rc) + return (0); + + } else + return (0); + + return setup_elsa_common(card); +} diff --git a/drivers/isdn/hisax/elsa_cs.c b/drivers/isdn/hisax/elsa_cs.c new file mode 100644 index 000000000..40f6fad79 --- /dev/null +++ b/drivers/isdn/hisax/elsa_cs.c @@ -0,0 +1,218 @@ +/*====================================================================== + + An elsa_cs PCMCIA client driver + + This driver is for the Elsa PCM ISDN Cards, i.e. the MicroLink + + + The contents of this file are subject to the Mozilla Public + License Version 1.1 (the "License"); you may not use this file + except in compliance with the License. You may obtain a copy of + the License at http://www.mozilla.org/MPL/ + + Software distributed under the License is distributed on an "AS + IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or + implied. See the License for the specific language governing + rights and limitations under the License. + + The initial developer of the original code is David A. Hinds + <dahinds@users.sourceforge.net>. Portions created by David A. Hinds + are Copyright (C) 1999 David A. Hinds. All Rights Reserved. + + Modifications from dummy_cs.c are Copyright (C) 1999-2001 Klaus + Lichtenwalder <Lichtenwalder@ACM.org>. All Rights Reserved. + + Alternatively, the contents of this file may be used under the + terms of the GNU General Public License version 2 (the "GPL"), in + which case the provisions of the GPL are applicable instead of the + above. If you wish to allow the use of your version of this file + only under the terms of the GPL and not to allow others to use + your version of this file under the MPL, indicate your decision + by deleting the provisions above and replace them with the notice + and other provisions required by the GPL. If you do not delete + the provisions above, a recipient may use your version of this + file under either the MPL or the GPL. + + ======================================================================*/ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/ptrace.h> +#include <linux/slab.h> +#include <linux/string.h> +#include <linux/timer.h> +#include <linux/ioport.h> +#include <asm/io.h> + +#include <pcmcia/cistpl.h> +#include <pcmcia/cisreg.h> +#include <pcmcia/ds.h> +#include "hisax_cfg.h" + +MODULE_DESCRIPTION("ISDN4Linux: PCMCIA client driver for Elsa PCM cards"); +MODULE_AUTHOR("Klaus Lichtenwalder"); +MODULE_LICENSE("Dual MPL/GPL"); + + +/*====================================================================*/ + +/* Parameters that can be set with 'insmod' */ + +static int protocol = 2; /* EURO-ISDN Default */ +module_param(protocol, int, 0); + +static int elsa_cs_config(struct pcmcia_device *link); +static void elsa_cs_release(struct pcmcia_device *link); +static void elsa_cs_detach(struct pcmcia_device *p_dev); + +typedef struct local_info_t { + struct pcmcia_device *p_dev; + int busy; + int cardnr; +} local_info_t; + +static int elsa_cs_probe(struct pcmcia_device *link) +{ + local_info_t *local; + + dev_dbg(&link->dev, "elsa_cs_attach()\n"); + + /* Allocate space for private device-specific data */ + local = kzalloc(sizeof(local_info_t), GFP_KERNEL); + if (!local) return -ENOMEM; + + local->p_dev = link; + link->priv = local; + + local->cardnr = -1; + + return elsa_cs_config(link); +} /* elsa_cs_attach */ + +static void elsa_cs_detach(struct pcmcia_device *link) +{ + local_info_t *info = link->priv; + + dev_dbg(&link->dev, "elsa_cs_detach(0x%p)\n", link); + + info->busy = 1; + elsa_cs_release(link); + + kfree(info); +} /* elsa_cs_detach */ + +static int elsa_cs_configcheck(struct pcmcia_device *p_dev, void *priv_data) +{ + int j; + + p_dev->io_lines = 3; + p_dev->resource[0]->end = 8; + p_dev->resource[0]->flags &= IO_DATA_PATH_WIDTH; + p_dev->resource[0]->flags |= IO_DATA_PATH_WIDTH_AUTO; + + if ((p_dev->resource[0]->end) && p_dev->resource[0]->start) { + printk(KERN_INFO "(elsa_cs: looks like the 96 model)\n"); + if (!pcmcia_request_io(p_dev)) + return 0; + } else { + printk(KERN_INFO "(elsa_cs: looks like the 97 model)\n"); + for (j = 0x2f0; j > 0x100; j -= 0x10) { + p_dev->resource[0]->start = j; + if (!pcmcia_request_io(p_dev)) + return 0; + } + } + return -ENODEV; +} + +static int elsa_cs_config(struct pcmcia_device *link) +{ + int i; + IsdnCard_t icard; + + dev_dbg(&link->dev, "elsa_config(0x%p)\n", link); + + link->config_flags |= CONF_ENABLE_IRQ | CONF_AUTO_SET_IO; + + i = pcmcia_loop_config(link, elsa_cs_configcheck, NULL); + if (i != 0) + goto failed; + + if (!link->irq) + goto failed; + + i = pcmcia_enable_device(link); + if (i != 0) + goto failed; + + icard.para[0] = link->irq; + icard.para[1] = link->resource[0]->start; + icard.protocol = protocol; + icard.typ = ISDN_CTYPE_ELSA_PCMCIA; + + i = hisax_init_pcmcia(link, &(((local_info_t *)link->priv)->busy), &icard); + if (i < 0) { + printk(KERN_ERR "elsa_cs: failed to initialize Elsa " + "PCMCIA %d with %pR\n", i, link->resource[0]); + elsa_cs_release(link); + } else + ((local_info_t *)link->priv)->cardnr = i; + + return 0; +failed: + elsa_cs_release(link); + return -ENODEV; +} /* elsa_cs_config */ + +static void elsa_cs_release(struct pcmcia_device *link) +{ + local_info_t *local = link->priv; + + dev_dbg(&link->dev, "elsa_cs_release(0x%p)\n", link); + + if (local) { + if (local->cardnr >= 0) { + /* no unregister function with hisax */ + HiSax_closecard(local->cardnr); + } + } + + pcmcia_disable_device(link); +} /* elsa_cs_release */ + +static int elsa_suspend(struct pcmcia_device *link) +{ + local_info_t *dev = link->priv; + + dev->busy = 1; + + return 0; +} + +static int elsa_resume(struct pcmcia_device *link) +{ + local_info_t *dev = link->priv; + + dev->busy = 0; + + return 0; +} + +static const struct pcmcia_device_id elsa_ids[] = { + PCMCIA_DEVICE_PROD_ID12("ELSA AG (Aachen, Germany)", "MicroLink ISDN/MC ", 0x983de2c4, 0x333ba257), + PCMCIA_DEVICE_PROD_ID12("ELSA GmbH, Aachen", "MicroLink ISDN/MC ", 0x639e5718, 0x333ba257), + PCMCIA_DEVICE_NULL +}; +MODULE_DEVICE_TABLE(pcmcia, elsa_ids); + +static struct pcmcia_driver elsa_cs_driver = { + .owner = THIS_MODULE, + .name = "elsa_cs", + .probe = elsa_cs_probe, + .remove = elsa_cs_detach, + .id_table = elsa_ids, + .suspend = elsa_suspend, + .resume = elsa_resume, +}; +module_pcmcia_driver(elsa_cs_driver); diff --git a/drivers/isdn/hisax/elsa_ser.c b/drivers/isdn/hisax/elsa_ser.c new file mode 100644 index 000000000..999effd7a --- /dev/null +++ b/drivers/isdn/hisax/elsa_ser.c @@ -0,0 +1,659 @@ +/* $Id: elsa_ser.c,v 2.14.2.3 2004/02/11 13:21:33 keil Exp $ + * + * stuff for the serial modem on ELSA cards + * + * This software may be used and distributed according to the terms + * of the GNU General Public License, incorporated herein by reference. + * + */ + +#include <linux/serial.h> +#include <linux/serial_reg.h> +#include <linux/slab.h> + +#define MAX_MODEM_BUF 256 +#define WAKEUP_CHARS (MAX_MODEM_BUF / 2) +#define RS_ISR_PASS_LIMIT 256 +#define BASE_BAUD (1843200 / 16) + +//#define SERIAL_DEBUG_OPEN 1 +//#define SERIAL_DEBUG_INTR 1 +//#define SERIAL_DEBUG_FLOW 1 +#undef SERIAL_DEBUG_OPEN +#undef SERIAL_DEBUG_INTR +#undef SERIAL_DEBUG_FLOW +#undef SERIAL_DEBUG_REG +//#define SERIAL_DEBUG_REG 1 + +#ifdef SERIAL_DEBUG_REG +static u_char deb[32]; +const char *ModemIn[] = {"RBR", "IER", "IIR", "LCR", "MCR", "LSR", "MSR", "SCR"}; +const char *ModemOut[] = {"THR", "IER", "FCR", "LCR", "MCR", "LSR", "MSR", "SCR"}; +#endif + +static char *MInit_1 = "AT&F&C1E0&D2\r\0"; +static char *MInit_2 = "ATL2M1S64=13\r\0"; +static char *MInit_3 = "AT+FCLASS=0\r\0"; +static char *MInit_4 = "ATV1S2=128X1\r\0"; +static char *MInit_5 = "AT\\V8\\N3\r\0"; +static char *MInit_6 = "ATL0M0&G0%E1\r\0"; +static char *MInit_7 = "AT%L1%M0%C3\r\0"; + +static char *MInit_speed28800 = "AT%G0%B28800\r\0"; + +static char *MInit_dialout = "ATs7=60 x1 d\r\0"; +static char *MInit_dialin = "ATs7=60 x1 a\r\0"; + + +static inline unsigned int serial_in(struct IsdnCardState *cs, int offset) +{ +#ifdef SERIAL_DEBUG_REG + u_int val = inb(cs->hw.elsa.base + 8 + offset); + debugl1(cs, "in %s %02x", ModemIn[offset], val); + return (val); +#else + return inb(cs->hw.elsa.base + 8 + offset); +#endif +} + +static inline unsigned int serial_inp(struct IsdnCardState *cs, int offset) +{ +#ifdef SERIAL_DEBUG_REG +#ifdef ELSA_SERIAL_NOPAUSE_IO + u_int val = inb(cs->hw.elsa.base + 8 + offset); + debugl1(cs, "inp %s %02x", ModemIn[offset], val); +#else + u_int val = inb_p(cs->hw.elsa.base + 8 + offset); + debugl1(cs, "inP %s %02x", ModemIn[offset], val); +#endif + return (val); +#else +#ifdef ELSA_SERIAL_NOPAUSE_IO + return inb(cs->hw.elsa.base + 8 + offset); +#else + return inb_p(cs->hw.elsa.base + 8 + offset); +#endif +#endif +} + +static inline void serial_out(struct IsdnCardState *cs, int offset, int value) +{ +#ifdef SERIAL_DEBUG_REG + debugl1(cs, "out %s %02x", ModemOut[offset], value); +#endif + outb(value, cs->hw.elsa.base + 8 + offset); +} + +static inline void serial_outp(struct IsdnCardState *cs, int offset, + int value) +{ +#ifdef SERIAL_DEBUG_REG +#ifdef ELSA_SERIAL_NOPAUSE_IO + debugl1(cs, "outp %s %02x", ModemOut[offset], value); +#else + debugl1(cs, "outP %s %02x", ModemOut[offset], value); +#endif +#endif +#ifdef ELSA_SERIAL_NOPAUSE_IO + outb(value, cs->hw.elsa.base + 8 + offset); +#else + outb_p(value, cs->hw.elsa.base + 8 + offset); +#endif +} + +/* + * This routine is called to set the UART divisor registers to match + * the specified baud rate for a serial port. + */ +static void change_speed(struct IsdnCardState *cs, int baud) +{ + int quot = 0, baud_base; + unsigned cval, fcr = 0; + + + /* byte size and parity */ + cval = 0x03; + /* Determine divisor based on baud rate */ + baud_base = BASE_BAUD; + quot = baud_base / baud; + /* If the quotient is ever zero, default to 9600 bps */ + if (!quot) + quot = baud_base / 9600; + + /* Set up FIFO's */ + if ((baud_base / quot) < 2400) + fcr = UART_FCR_ENABLE_FIFO | UART_FCR_TRIGGER_1; + else + fcr = UART_FCR_ENABLE_FIFO | UART_FCR_TRIGGER_8; + serial_outp(cs, UART_FCR, fcr); + /* CTS flow control flag and modem status interrupts */ + cs->hw.elsa.IER &= ~UART_IER_MSI; + cs->hw.elsa.IER |= UART_IER_MSI; + serial_outp(cs, UART_IER, cs->hw.elsa.IER); + + debugl1(cs, "modem quot=0x%x", quot); + serial_outp(cs, UART_LCR, cval | UART_LCR_DLAB);/* set DLAB */ + serial_outp(cs, UART_DLL, quot & 0xff); /* LS of divisor */ + serial_outp(cs, UART_DLM, quot >> 8); /* MS of divisor */ + serial_outp(cs, UART_LCR, cval); /* reset DLAB */ + serial_inp(cs, UART_RX); +} + +static int mstartup(struct IsdnCardState *cs) +{ + int retval = 0; + + /* + * Clear the FIFO buffers and disable them + * (they will be reenabled in change_speed()) + */ + serial_outp(cs, UART_FCR, (UART_FCR_CLEAR_RCVR | UART_FCR_CLEAR_XMIT)); + + /* + * At this point there's no way the LSR could still be 0xFF; + * if it is, then bail out, because there's likely no UART + * here. + */ + if (serial_inp(cs, UART_LSR) == 0xff) { + retval = -ENODEV; + goto errout; + } + + /* + * Clear the interrupt registers. + */ + (void) serial_inp(cs, UART_RX); + (void) serial_inp(cs, UART_IIR); + (void) serial_inp(cs, UART_MSR); + + /* + * Now, initialize the UART + */ + serial_outp(cs, UART_LCR, UART_LCR_WLEN8); /* reset DLAB */ + + cs->hw.elsa.MCR = 0; + cs->hw.elsa.MCR = UART_MCR_DTR | UART_MCR_RTS | UART_MCR_OUT2; + serial_outp(cs, UART_MCR, cs->hw.elsa.MCR); + + /* + * Finally, enable interrupts + */ + cs->hw.elsa.IER = UART_IER_MSI | UART_IER_RLSI | UART_IER_RDI; + serial_outp(cs, UART_IER, cs->hw.elsa.IER); /* enable interrupts */ + + /* + * And clear the interrupt registers again for luck. + */ + (void)serial_inp(cs, UART_LSR); + (void)serial_inp(cs, UART_RX); + (void)serial_inp(cs, UART_IIR); + (void)serial_inp(cs, UART_MSR); + + cs->hw.elsa.transcnt = cs->hw.elsa.transp = 0; + cs->hw.elsa.rcvcnt = cs->hw.elsa.rcvp = 0; + + /* + * and set the speed of the serial port + */ + change_speed(cs, BASE_BAUD); + cs->hw.elsa.MFlag = 1; +errout: + return retval; +} + +/* + * This routine will shutdown a serial port; interrupts are disabled, and + * DTR is dropped if the hangup on close termio flag is on. + */ +static void mshutdown(struct IsdnCardState *cs) +{ + +#ifdef SERIAL_DEBUG_OPEN + printk(KERN_DEBUG"Shutting down serial ...."); +#endif + + /* + * clear delta_msr_wait queue to avoid mem leaks: we may free the irq + * here so the queue might never be waken up + */ + + cs->hw.elsa.IER = 0; + serial_outp(cs, UART_IER, 0x00); /* disable all intrs */ + cs->hw.elsa.MCR &= ~UART_MCR_OUT2; + + /* disable break condition */ + serial_outp(cs, UART_LCR, serial_inp(cs, UART_LCR) & ~UART_LCR_SBC); + + cs->hw.elsa.MCR &= ~(UART_MCR_DTR | UART_MCR_RTS); + serial_outp(cs, UART_MCR, cs->hw.elsa.MCR); + + /* disable FIFO's */ + serial_outp(cs, UART_FCR, (UART_FCR_CLEAR_RCVR | UART_FCR_CLEAR_XMIT)); + serial_inp(cs, UART_RX); /* read data port to reset things */ + +#ifdef SERIAL_DEBUG_OPEN + printk(" done\n"); +#endif +} + +static inline int +write_modem(struct BCState *bcs) { + int ret = 0; + struct IsdnCardState *cs = bcs->cs; + int count, len, fp; + + if (!bcs->tx_skb) + return 0; + if (bcs->tx_skb->len <= 0) + return 0; + len = bcs->tx_skb->len; + if (len > MAX_MODEM_BUF - cs->hw.elsa.transcnt) + len = MAX_MODEM_BUF - cs->hw.elsa.transcnt; + fp = cs->hw.elsa.transcnt + cs->hw.elsa.transp; + fp &= (MAX_MODEM_BUF - 1); + count = len; + if (count > MAX_MODEM_BUF - fp) { + count = MAX_MODEM_BUF - fp; + skb_copy_from_linear_data(bcs->tx_skb, + cs->hw.elsa.transbuf + fp, count); + skb_pull(bcs->tx_skb, count); + cs->hw.elsa.transcnt += count; + ret = count; + count = len - count; + fp = 0; + } + skb_copy_from_linear_data(bcs->tx_skb, + cs->hw.elsa.transbuf + fp, count); + skb_pull(bcs->tx_skb, count); + cs->hw.elsa.transcnt += count; + ret += count; + + if (cs->hw.elsa.transcnt && + !(cs->hw.elsa.IER & UART_IER_THRI)) { + cs->hw.elsa.IER |= UART_IER_THRI; + serial_outp(cs, UART_IER, cs->hw.elsa.IER); + } + return (ret); +} + +static inline void +modem_fill(struct BCState *bcs) { + + if (bcs->tx_skb) { + if (bcs->tx_skb->len) { + write_modem(bcs); + return; + } else { + if (test_bit(FLG_LLI_L1WAKEUP, &bcs->st->lli.flag) && + (PACKET_NOACK != bcs->tx_skb->pkt_type)) { + u_long flags; + spin_lock_irqsave(&bcs->aclock, flags); + bcs->ackcnt += bcs->hw.hscx.count; + spin_unlock_irqrestore(&bcs->aclock, flags); + schedule_event(bcs, B_ACKPENDING); + } + dev_kfree_skb_any(bcs->tx_skb); + bcs->tx_skb = NULL; + } + } + if ((bcs->tx_skb = skb_dequeue(&bcs->squeue))) { + bcs->hw.hscx.count = 0; + test_and_set_bit(BC_FLG_BUSY, &bcs->Flag); + write_modem(bcs); + } else { + test_and_clear_bit(BC_FLG_BUSY, &bcs->Flag); + schedule_event(bcs, B_XMTBUFREADY); + } +} + +static inline void receive_chars(struct IsdnCardState *cs, + int *status) +{ + unsigned char ch; + struct sk_buff *skb; + + do { + ch = serial_in(cs, UART_RX); + if (cs->hw.elsa.rcvcnt >= MAX_MODEM_BUF) + break; + cs->hw.elsa.rcvbuf[cs->hw.elsa.rcvcnt++] = ch; +#ifdef SERIAL_DEBUG_INTR + printk("DR%02x:%02x...", ch, *status); +#endif + if (*status & (UART_LSR_BI | UART_LSR_PE | + UART_LSR_FE | UART_LSR_OE)) { + +#ifdef SERIAL_DEBUG_INTR + printk("handling exept...."); +#endif + } + *status = serial_inp(cs, UART_LSR); + } while (*status & UART_LSR_DR); + if (cs->hw.elsa.MFlag == 2) { + if (!(skb = dev_alloc_skb(cs->hw.elsa.rcvcnt))) + printk(KERN_WARNING "ElsaSER: receive out of memory\n"); + else { + skb_put_data(skb, cs->hw.elsa.rcvbuf, + cs->hw.elsa.rcvcnt); + skb_queue_tail(&cs->hw.elsa.bcs->rqueue, skb); + } + schedule_event(cs->hw.elsa.bcs, B_RCVBUFREADY); + } else { + char tmp[128]; + char *t = tmp; + + t += sprintf(t, "modem read cnt %d", cs->hw.elsa.rcvcnt); + QuickHex(t, cs->hw.elsa.rcvbuf, cs->hw.elsa.rcvcnt); + debugl1(cs, "%s", tmp); + } + cs->hw.elsa.rcvcnt = 0; +} + +static inline void transmit_chars(struct IsdnCardState *cs, int *intr_done) +{ + int count; + + debugl1(cs, "transmit_chars: p(%x) cnt(%x)", cs->hw.elsa.transp, + cs->hw.elsa.transcnt); + + if (cs->hw.elsa.transcnt <= 0) { + cs->hw.elsa.IER &= ~UART_IER_THRI; + serial_out(cs, UART_IER, cs->hw.elsa.IER); + return; + } + count = 16; + do { + serial_outp(cs, UART_TX, cs->hw.elsa.transbuf[cs->hw.elsa.transp++]); + if (cs->hw.elsa.transp >= MAX_MODEM_BUF) + cs->hw.elsa.transp = 0; + if (--cs->hw.elsa.transcnt <= 0) + break; + } while (--count > 0); + if ((cs->hw.elsa.transcnt < WAKEUP_CHARS) && (cs->hw.elsa.MFlag == 2)) + modem_fill(cs->hw.elsa.bcs); + +#ifdef SERIAL_DEBUG_INTR + printk("THRE..."); +#endif + if (intr_done) + *intr_done = 0; + if (cs->hw.elsa.transcnt <= 0) { + cs->hw.elsa.IER &= ~UART_IER_THRI; + serial_outp(cs, UART_IER, cs->hw.elsa.IER); + } +} + + +static void rs_interrupt_elsa(struct IsdnCardState *cs) +{ + int status, iir, msr; + int pass_counter = 0; + +#ifdef SERIAL_DEBUG_INTR + printk(KERN_DEBUG "rs_interrupt_single(%d)...", cs->irq); +#endif + + do { + status = serial_inp(cs, UART_LSR); + debugl1(cs, "rs LSR %02x", status); +#ifdef SERIAL_DEBUG_INTR + printk("status = %x...", status); +#endif + if (status & UART_LSR_DR) + receive_chars(cs, &status); + if (status & UART_LSR_THRE) + transmit_chars(cs, NULL); + if (pass_counter++ > RS_ISR_PASS_LIMIT) { + printk("rs_single loop break.\n"); + break; + } + iir = serial_inp(cs, UART_IIR); + debugl1(cs, "rs IIR %02x", iir); + if ((iir & 0xf) == 0) { + msr = serial_inp(cs, UART_MSR); + debugl1(cs, "rs MSR %02x", msr); + } + } while (!(iir & UART_IIR_NO_INT)); +#ifdef SERIAL_DEBUG_INTR + printk("end.\n"); +#endif +} + +extern int open_hscxstate(struct IsdnCardState *cs, struct BCState *bcs); +extern void modehscx(struct BCState *bcs, int mode, int bc); +extern void hscx_l2l1(struct PStack *st, int pr, void *arg); + +static void +close_elsastate(struct BCState *bcs) +{ + modehscx(bcs, 0, bcs->channel); + if (test_and_clear_bit(BC_FLG_INIT, &bcs->Flag)) { + if (bcs->hw.hscx.rcvbuf) { + if (bcs->mode != L1_MODE_MODEM) + kfree(bcs->hw.hscx.rcvbuf); + bcs->hw.hscx.rcvbuf = NULL; + } + skb_queue_purge(&bcs->rqueue); + skb_queue_purge(&bcs->squeue); + if (bcs->tx_skb) { + dev_kfree_skb_any(bcs->tx_skb); + bcs->tx_skb = NULL; + test_and_clear_bit(BC_FLG_BUSY, &bcs->Flag); + } + } +} + +static void +modem_write_cmd(struct IsdnCardState *cs, u_char *buf, int len) { + int count, fp; + u_char *msg = buf; + + if (!len) + return; + if (len > (MAX_MODEM_BUF - cs->hw.elsa.transcnt)) { + return; + } + fp = cs->hw.elsa.transcnt + cs->hw.elsa.transp; + fp &= (MAX_MODEM_BUF - 1); + count = len; + if (count > MAX_MODEM_BUF - fp) { + count = MAX_MODEM_BUF - fp; + memcpy(cs->hw.elsa.transbuf + fp, msg, count); + cs->hw.elsa.transcnt += count; + msg += count; + count = len - count; + fp = 0; + } + memcpy(cs->hw.elsa.transbuf + fp, msg, count); + cs->hw.elsa.transcnt += count; + if (cs->hw.elsa.transcnt && + !(cs->hw.elsa.IER & UART_IER_THRI)) { + cs->hw.elsa.IER |= UART_IER_THRI; + serial_outp(cs, UART_IER, cs->hw.elsa.IER); + } +} + +static void +modem_set_init(struct IsdnCardState *cs) { + int timeout; + +#define RCV_DELAY 20 + modem_write_cmd(cs, MInit_1, strlen(MInit_1)); + timeout = 1000; + while (timeout-- && cs->hw.elsa.transcnt) + udelay(1000); + debugl1(cs, "msi tout=%d", timeout); + mdelay(RCV_DELAY); + modem_write_cmd(cs, MInit_2, strlen(MInit_2)); + timeout = 1000; + while (timeout-- && cs->hw.elsa.transcnt) + udelay(1000); + debugl1(cs, "msi tout=%d", timeout); + mdelay(RCV_DELAY); + modem_write_cmd(cs, MInit_3, strlen(MInit_3)); + timeout = 1000; + while (timeout-- && cs->hw.elsa.transcnt) + udelay(1000); + debugl1(cs, "msi tout=%d", timeout); + mdelay(RCV_DELAY); + modem_write_cmd(cs, MInit_4, strlen(MInit_4)); + timeout = 1000; + while (timeout-- && cs->hw.elsa.transcnt) + udelay(1000); + debugl1(cs, "msi tout=%d", timeout); + mdelay(RCV_DELAY); + modem_write_cmd(cs, MInit_5, strlen(MInit_5)); + timeout = 1000; + while (timeout-- && cs->hw.elsa.transcnt) + udelay(1000); + debugl1(cs, "msi tout=%d", timeout); + mdelay(RCV_DELAY); + modem_write_cmd(cs, MInit_6, strlen(MInit_6)); + timeout = 1000; + while (timeout-- && cs->hw.elsa.transcnt) + udelay(1000); + debugl1(cs, "msi tout=%d", timeout); + mdelay(RCV_DELAY); + modem_write_cmd(cs, MInit_7, strlen(MInit_7)); + timeout = 1000; + while (timeout-- && cs->hw.elsa.transcnt) + udelay(1000); + debugl1(cs, "msi tout=%d", timeout); + mdelay(RCV_DELAY); +} + +static void +modem_set_dial(struct IsdnCardState *cs, int outgoing) { + int timeout; +#define RCV_DELAY 20 + + modem_write_cmd(cs, MInit_speed28800, strlen(MInit_speed28800)); + timeout = 1000; + while (timeout-- && cs->hw.elsa.transcnt) + udelay(1000); + debugl1(cs, "msi tout=%d", timeout); + mdelay(RCV_DELAY); + if (outgoing) + modem_write_cmd(cs, MInit_dialout, strlen(MInit_dialout)); + else + modem_write_cmd(cs, MInit_dialin, strlen(MInit_dialin)); + timeout = 1000; + while (timeout-- && cs->hw.elsa.transcnt) + udelay(1000); + debugl1(cs, "msi tout=%d", timeout); + mdelay(RCV_DELAY); +} + +static void +modem_l2l1(struct PStack *st, int pr, void *arg) +{ + struct BCState *bcs = st->l1.bcs; + struct sk_buff *skb = arg; + u_long flags; + + if (pr == (PH_DATA | REQUEST)) { + spin_lock_irqsave(&bcs->cs->lock, flags); + if (bcs->tx_skb) { + skb_queue_tail(&bcs->squeue, skb); + } else { + bcs->tx_skb = skb; + test_and_set_bit(BC_FLG_BUSY, &bcs->Flag); + bcs->hw.hscx.count = 0; + write_modem(bcs); + } + spin_unlock_irqrestore(&bcs->cs->lock, flags); + } else if (pr == (PH_ACTIVATE | REQUEST)) { + test_and_set_bit(BC_FLG_ACTIV, &bcs->Flag); + st->l1.l1l2(st, PH_ACTIVATE | CONFIRM, NULL); + set_arcofi(bcs->cs, st->l1.bc); + mstartup(bcs->cs); + modem_set_dial(bcs->cs, test_bit(FLG_ORIG, &st->l2.flag)); + bcs->cs->hw.elsa.MFlag = 2; + } else if (pr == (PH_DEACTIVATE | REQUEST)) { + test_and_clear_bit(BC_FLG_ACTIV, &bcs->Flag); + bcs->cs->dc.isac.arcofi_bc = st->l1.bc; + arcofi_fsm(bcs->cs, ARCOFI_START, &ARCOFI_XOP_0); + wait_event_interruptible(bcs->cs->dc.isac.arcofi_wait, + bcs->cs->dc.isac.arcofi_state == ARCOFI_NOP); + bcs->cs->hw.elsa.MFlag = 1; + } else { + printk(KERN_WARNING "ElsaSer: unknown pr %x\n", pr); + } +} + +static int +setstack_elsa(struct PStack *st, struct BCState *bcs) +{ + + bcs->channel = st->l1.bc; + switch (st->l1.mode) { + case L1_MODE_HDLC: + case L1_MODE_TRANS: + if (open_hscxstate(st->l1.hardware, bcs)) + return (-1); + st->l2.l2l1 = hscx_l2l1; + break; + case L1_MODE_MODEM: + bcs->mode = L1_MODE_MODEM; + if (!test_and_set_bit(BC_FLG_INIT, &bcs->Flag)) { + bcs->hw.hscx.rcvbuf = bcs->cs->hw.elsa.rcvbuf; + skb_queue_head_init(&bcs->rqueue); + skb_queue_head_init(&bcs->squeue); + } + bcs->tx_skb = NULL; + test_and_clear_bit(BC_FLG_BUSY, &bcs->Flag); + bcs->event = 0; + bcs->hw.hscx.rcvidx = 0; + bcs->tx_cnt = 0; + bcs->cs->hw.elsa.bcs = bcs; + st->l2.l2l1 = modem_l2l1; + break; + } + st->l1.bcs = bcs; + setstack_manager(st); + bcs->st = st; + setstack_l1_B(st); + return (0); +} + +static void +init_modem(struct IsdnCardState *cs) { + + cs->bcs[0].BC_SetStack = setstack_elsa; + cs->bcs[1].BC_SetStack = setstack_elsa; + cs->bcs[0].BC_Close = close_elsastate; + cs->bcs[1].BC_Close = close_elsastate; + if (!(cs->hw.elsa.rcvbuf = kmalloc(MAX_MODEM_BUF, + GFP_ATOMIC))) { + printk(KERN_WARNING + "Elsa: No modem mem hw.elsa.rcvbuf\n"); + return; + } + if (!(cs->hw.elsa.transbuf = kmalloc(MAX_MODEM_BUF, + GFP_ATOMIC))) { + printk(KERN_WARNING + "Elsa: No modem mem hw.elsa.transbuf\n"); + kfree(cs->hw.elsa.rcvbuf); + cs->hw.elsa.rcvbuf = NULL; + return; + } + if (mstartup(cs)) { + printk(KERN_WARNING "Elsa: problem startup modem\n"); + } + modem_set_init(cs); +} + +static void +release_modem(struct IsdnCardState *cs) { + + cs->hw.elsa.MFlag = 0; + if (cs->hw.elsa.transbuf) { + if (cs->hw.elsa.rcvbuf) { + mshutdown(cs); + kfree(cs->hw.elsa.rcvbuf); + cs->hw.elsa.rcvbuf = NULL; + } + kfree(cs->hw.elsa.transbuf); + cs->hw.elsa.transbuf = NULL; + } +} diff --git a/drivers/isdn/hisax/enternow_pci.c b/drivers/isdn/hisax/enternow_pci.c new file mode 100644 index 000000000..e8d431a83 --- /dev/null +++ b/drivers/isdn/hisax/enternow_pci.c @@ -0,0 +1,420 @@ +/* enternow_pci.c,v 0.99 2001/10/02 + * + * enternow_pci.c Card-specific routines for + * Formula-n enter:now ISDN PCI ab + * Gerdes AG Power ISDN PCI + * Woerltronic SA 16 PCI + * (based on HiSax driver by Karsten Keil) + * + * Author Christoph Ersfeld <info@formula-n.de> + * Formula-n Europe AG (www.formula-n.com) + * previously Gerdes AG + * + * + * This file is (c) under GNU PUBLIC LICENSE + * + * Notes: + * This driver interfaces to netjet.c which performs B-channel + * processing. + * + * Version 0.99 is the first release of this driver and there are + * certainly a few bugs. + * It isn't testet on linux 2.4 yet, so consider this code to be + * beta. + * + * Please don't report me any malfunction without sending + * (compressed) debug-logs. + * It would be nearly impossible to retrace it. + * + * Log D-channel-processing as follows: + * + * 1. Load hisax with card-specific parameters, this example ist for + * Formula-n enter:now ISDN PCI and compatible + * (f.e. Gerdes Power ISDN PCI) + * + * modprobe hisax type=41 protocol=2 id=gerdes + * + * if you chose an other value for id, you need to modify the + * code below, too. + * + * 2. set debug-level + * + * hisaxctrl gerdes 1 0x3ff + * hisaxctrl gerdes 11 0x4f + * cat /dev/isdnctrl >> ~/log & + * + * Please take also a look into /var/log/messages if there is + * anything importand concerning HISAX. + * + * + * Credits: + * Programming the driver for Formula-n enter:now ISDN PCI and + * necessary the driver for the used Amd 7930 D-channel-controller + * was spnsored by Formula-n Europe AG. + * Thanks to Karsten Keil and Petr Novak, who gave me support in + * Hisax-specific questions. + * I want so say special thanks to Carl-Friedrich Braun, who had to + * answer a lot of questions about generally ISDN and about handling + * of the Amd-Chip. + * + */ + + +#include "hisax.h" +#include "isac.h" +#include "isdnl1.h" +#include "amd7930_fn.h" +#include <linux/interrupt.h> +#include <linux/ppp_defs.h> +#include <linux/pci.h> +#include <linux/init.h> +#include "netjet.h" + + + +static const char *enternow_pci_rev = "$Revision: 1.1.4.5 $"; + + +/* for PowerISDN PCI */ +#define TJ_AMD_IRQ 0x20 +#define TJ_LED1 0x40 +#define TJ_LED2 0x80 + + +/* The window to [the] AMD [chip]... + * From address hw.njet.base + TJ_AMD_PORT onwards, the AMD + * maps [consecutive/multiple] 8 bits into the TigerJet I/O space + * -> 0x01 of the AMD at hw.njet.base + 0C4 */ +#define TJ_AMD_PORT 0xC0 + + + +/* *************************** I/O-Interface functions ************************************* */ + + +/* cs->readisac, macro rByteAMD */ +static unsigned char +ReadByteAmd7930(struct IsdnCardState *cs, unsigned char offset) +{ + /* direct register */ + if (offset < 8) + return (inb(cs->hw.njet.isac + 4 * offset)); + + /* indirect register */ + else { + outb(offset, cs->hw.njet.isac + 4 * AMD_CR); + return (inb(cs->hw.njet.isac + 4 * AMD_DR)); + } +} + +/* cs->writeisac, macro wByteAMD */ +static void +WriteByteAmd7930(struct IsdnCardState *cs, unsigned char offset, unsigned char value) +{ + /* direct register */ + if (offset < 8) + outb(value, cs->hw.njet.isac + 4 * offset); + + /* indirect register */ + else { + outb(offset, cs->hw.njet.isac + 4 * AMD_CR); + outb(value, cs->hw.njet.isac + 4 * AMD_DR); + } +} + + +static void +enpci_setIrqMask(struct IsdnCardState *cs, unsigned char val) { + if (!val) + outb(0x00, cs->hw.njet.base + NETJET_IRQMASK1); + else + outb(TJ_AMD_IRQ, cs->hw.njet.base + NETJET_IRQMASK1); +} + + +static unsigned char dummyrr(struct IsdnCardState *cs, int chan, unsigned char off) +{ + return (5); +} + +static void dummywr(struct IsdnCardState *cs, int chan, unsigned char off, unsigned char value) +{ + +} + + +/* ******************************************************************************** */ + + +static void +reset_enpci(struct IsdnCardState *cs) +{ + if (cs->debug & L1_DEB_ISAC) + debugl1(cs, "enter:now PCI: reset"); + + /* Reset on, (also for AMD) */ + cs->hw.njet.ctrl_reg = 0x07; + outb(cs->hw.njet.ctrl_reg, cs->hw.njet.base + NETJET_CTRL); + mdelay(20); + /* Reset off */ + cs->hw.njet.ctrl_reg = 0x30; + outb(cs->hw.njet.ctrl_reg, cs->hw.njet.base + NETJET_CTRL); + /* 20ms delay */ + mdelay(20); + cs->hw.njet.auxd = 0; // LED-status + cs->hw.njet.dmactrl = 0; + outb(~TJ_AMD_IRQ, cs->hw.njet.base + NETJET_AUXCTRL); + outb(TJ_AMD_IRQ, cs->hw.njet.base + NETJET_IRQMASK1); + outb(cs->hw.njet.auxd, cs->hw.njet.auxa); // LED off +} + + +static int +enpci_card_msg(struct IsdnCardState *cs, int mt, void *arg) +{ + u_long flags; + unsigned char *chan; + + if (cs->debug & L1_DEB_ISAC) + debugl1(cs, "enter:now PCI: card_msg: 0x%04X", mt); + + switch (mt) { + case CARD_RESET: + spin_lock_irqsave(&cs->lock, flags); + reset_enpci(cs); + Amd7930_init(cs); + spin_unlock_irqrestore(&cs->lock, flags); + break; + case CARD_RELEASE: + release_io_netjet(cs); + break; + case CARD_INIT: + reset_enpci(cs); + inittiger(cs); + /* irq must be on here */ + Amd7930_init(cs); + break; + case CARD_TEST: + break; + case MDL_ASSIGN: + /* TEI assigned, LED1 on */ + cs->hw.njet.auxd = TJ_AMD_IRQ << 1; + outb(cs->hw.njet.auxd, cs->hw.njet.base + NETJET_AUXDATA); + break; + case MDL_REMOVE: + /* TEI removed, LEDs off */ + cs->hw.njet.auxd = 0; + outb(0x00, cs->hw.njet.base + NETJET_AUXDATA); + break; + case MDL_BC_ASSIGN: + /* activate B-channel */ + chan = (unsigned char *)arg; + + if (cs->debug & L1_DEB_ISAC) + debugl1(cs, "enter:now PCI: assign phys. BC %d in AMD LMR1", *chan); + + cs->dc.amd7930.ph_command(cs, (cs->dc.amd7930.lmr1 | (*chan + 1)), "MDL_BC_ASSIGN"); + /* at least one b-channel in use, LED 2 on */ + cs->hw.njet.auxd |= TJ_AMD_IRQ << 2; + outb(cs->hw.njet.auxd, cs->hw.njet.base + NETJET_AUXDATA); + break; + case MDL_BC_RELEASE: + /* deactivate B-channel */ + chan = (unsigned char *)arg; + + if (cs->debug & L1_DEB_ISAC) + debugl1(cs, "enter:now PCI: release phys. BC %d in Amd LMR1", *chan); + + cs->dc.amd7930.ph_command(cs, (cs->dc.amd7930.lmr1 & ~(*chan + 1)), "MDL_BC_RELEASE"); + /* no b-channel active -> LED2 off */ + if (!(cs->dc.amd7930.lmr1 & 3)) { + cs->hw.njet.auxd &= ~(TJ_AMD_IRQ << 2); + outb(cs->hw.njet.auxd, cs->hw.njet.base + NETJET_AUXDATA); + } + break; + default: + break; + + } + return (0); +} + +static irqreturn_t +enpci_interrupt(int intno, void *dev_id) +{ + struct IsdnCardState *cs = dev_id; + unsigned char s0val, s1val, ir; + u_long flags; + + spin_lock_irqsave(&cs->lock, flags); + s1val = inb(cs->hw.njet.base + NETJET_IRQSTAT1); + + /* AMD threw an interrupt */ + if (!(s1val & TJ_AMD_IRQ)) { + /* read and clear interrupt-register */ + ir = ReadByteAmd7930(cs, 0x00); + Amd7930_interrupt(cs, ir); + s1val = 1; + } else + s1val = 0; + s0val = inb(cs->hw.njet.base + NETJET_IRQSTAT0); + if ((s0val | s1val) == 0) { // shared IRQ + spin_unlock_irqrestore(&cs->lock, flags); + return IRQ_NONE; + } + if (s0val) + outb(s0val, cs->hw.njet.base + NETJET_IRQSTAT0); + + /* DMA-Interrupt: B-channel-stuff */ + /* set bits in sval to indicate which page is free */ + if (inl(cs->hw.njet.base + NETJET_DMA_WRITE_ADR) < + inl(cs->hw.njet.base + NETJET_DMA_WRITE_IRQ)) + /* the 2nd write page is free */ + s0val = 0x08; + else /* the 1st write page is free */ + s0val = 0x04; + if (inl(cs->hw.njet.base + NETJET_DMA_READ_ADR) < + inl(cs->hw.njet.base + NETJET_DMA_READ_IRQ)) + /* the 2nd read page is free */ + s0val = s0val | 0x02; + else /* the 1st read page is free */ + s0val = s0val | 0x01; + if (s0val != cs->hw.njet.last_is0) /* we have a DMA interrupt */ + { + if (test_and_set_bit(FLG_LOCK_ATOMIC, &cs->HW_Flags)) { + spin_unlock_irqrestore(&cs->lock, flags); + return IRQ_HANDLED; + } + cs->hw.njet.irqstat0 = s0val; + if ((cs->hw.njet.irqstat0 & NETJET_IRQM0_READ) != + (cs->hw.njet.last_is0 & NETJET_IRQM0_READ)) + /* we have a read dma int */ + read_tiger(cs); + if ((cs->hw.njet.irqstat0 & NETJET_IRQM0_WRITE) != + (cs->hw.njet.last_is0 & NETJET_IRQM0_WRITE)) + /* we have a write dma int */ + write_tiger(cs); + test_and_clear_bit(FLG_LOCK_ATOMIC, &cs->HW_Flags); + } + spin_unlock_irqrestore(&cs->lock, flags); + return IRQ_HANDLED; +} + +static int en_pci_probe(struct pci_dev *dev_netjet, struct IsdnCardState *cs) +{ + if (pci_enable_device(dev_netjet)) + return (0); + cs->irq = dev_netjet->irq; + if (!cs->irq) { + printk(KERN_WARNING "enter:now PCI: No IRQ for PCI card found\n"); + return (0); + } + cs->hw.njet.base = pci_resource_start(dev_netjet, 0); + if (!cs->hw.njet.base) { + printk(KERN_WARNING "enter:now PCI: No IO-Adr for PCI card found\n"); + return (0); + } + /* checks Sub-Vendor ID because system crashes with Traverse-Card */ + if ((dev_netjet->subsystem_vendor != 0x55) || + (dev_netjet->subsystem_device != 0x02)) { + printk(KERN_WARNING "enter:now: You tried to load this driver with an incompatible TigerJet-card\n"); + printk(KERN_WARNING "Use type=20 for Traverse NetJet PCI Card.\n"); + return (0); + } + + return (1); +} + +static void en_cs_init(struct IsdnCard *card, struct IsdnCardState *cs) +{ + cs->hw.njet.auxa = cs->hw.njet.base + NETJET_AUXDATA; + cs->hw.njet.isac = cs->hw.njet.base + 0xC0; // Fenster zum AMD + + /* Reset an */ + cs->hw.njet.ctrl_reg = 0x07; // geändert von 0xff + outb(cs->hw.njet.ctrl_reg, cs->hw.njet.base + NETJET_CTRL); + /* 20 ms Pause */ + mdelay(20); + + cs->hw.njet.ctrl_reg = 0x30; /* Reset Off and status read clear */ + outb(cs->hw.njet.ctrl_reg, cs->hw.njet.base + NETJET_CTRL); + mdelay(10); + + cs->hw.njet.auxd = 0x00; // war 0xc0 + cs->hw.njet.dmactrl = 0; + + outb(~TJ_AMD_IRQ, cs->hw.njet.base + NETJET_AUXCTRL); + outb(TJ_AMD_IRQ, cs->hw.njet.base + NETJET_IRQMASK1); + outb(cs->hw.njet.auxd, cs->hw.njet.auxa); +} + +static int en_cs_init_rest(struct IsdnCard *card, struct IsdnCardState *cs) +{ + const int bytecnt = 256; + + printk(KERN_INFO + "enter:now PCI: PCI card configured at 0x%lx IRQ %d\n", + cs->hw.njet.base, cs->irq); + if (!request_region(cs->hw.njet.base, bytecnt, "Fn_ISDN")) { + printk(KERN_WARNING + "HiSax: enter:now config port %lx-%lx already in use\n", + cs->hw.njet.base, + cs->hw.njet.base + bytecnt); + return (0); + } + + setup_Amd7930(cs); + cs->hw.njet.last_is0 = 0; + /* macro rByteAMD */ + cs->readisac = &ReadByteAmd7930; + /* macro wByteAMD */ + cs->writeisac = &WriteByteAmd7930; + cs->dc.amd7930.setIrqMask = &enpci_setIrqMask; + + cs->BC_Read_Reg = &dummyrr; + cs->BC_Write_Reg = &dummywr; + cs->BC_Send_Data = &netjet_fill_dma; + cs->cardmsg = &enpci_card_msg; + cs->irq_func = &enpci_interrupt; + cs->irq_flags |= IRQF_SHARED; + + return (1); +} + +static struct pci_dev *dev_netjet = NULL; + +/* called by config.c */ +int setup_enternow_pci(struct IsdnCard *card) +{ + int ret; + struct IsdnCardState *cs = card->cs; + char tmp[64]; + +#ifdef __BIG_ENDIAN +#error "not running on big endian machines now" +#endif + + strcpy(tmp, enternow_pci_rev); + printk(KERN_INFO "HiSax: Formula-n Europe AG enter:now ISDN PCI driver Rev. %s\n", HiSax_getrev(tmp)); + if (cs->typ != ISDN_CTYPE_ENTERNOW) + return (0); + test_and_clear_bit(FLG_LOCK_ATOMIC, &cs->HW_Flags); + + for (;;) + { + if ((dev_netjet = hisax_find_pci_device(PCI_VENDOR_ID_TIGERJET, + PCI_DEVICE_ID_TIGERJET_300, dev_netjet))) { + ret = en_pci_probe(dev_netjet, cs); + if (!ret) + return (0); + } else { + printk(KERN_WARNING "enter:now PCI: No PCI card found\n"); + return (0); + } + + en_cs_init(card, cs); + break; + } + + return en_cs_init_rest(card, cs); +} diff --git a/drivers/isdn/hisax/fsm.c b/drivers/isdn/hisax/fsm.c new file mode 100644 index 000000000..80ba82f77 --- /dev/null +++ b/drivers/isdn/hisax/fsm.c @@ -0,0 +1,161 @@ +/* $Id: fsm.c,v 1.14.6.4 2001/09/23 22:24:47 kai Exp $ + * + * Finite state machine + * + * Author Karsten Keil + * Copyright by Karsten Keil <keil@isdn4linux.de> + * by Kai Germaschewski <kai.germaschewski@gmx.de> + * + * This software may be used and distributed according to the terms + * of the GNU General Public License, incorporated herein by reference. + * + * Thanks to Jan den Ouden + * Fritz Elfert + * + */ + +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/init.h> +#include "hisax.h" + +#define FSM_TIMER_DEBUG 0 + +int +FsmNew(struct Fsm *fsm, struct FsmNode *fnlist, int fncount) +{ + int i; + + fsm->jumpmatrix = + kzalloc(array3_size(sizeof(FSMFNPTR), fsm->state_count, + fsm->event_count), + GFP_KERNEL); + if (!fsm->jumpmatrix) + return -ENOMEM; + + for (i = 0; i < fncount; i++) + if ((fnlist[i].state >= fsm->state_count) || (fnlist[i].event >= fsm->event_count)) { + printk(KERN_ERR "FsmNew Error line %d st(%ld/%ld) ev(%ld/%ld)\n", + i, (long)fnlist[i].state, (long)fsm->state_count, + (long)fnlist[i].event, (long)fsm->event_count); + } else + fsm->jumpmatrix[fsm->state_count * fnlist[i].event + + fnlist[i].state] = (FSMFNPTR)fnlist[i].routine; + return 0; +} + +void +FsmFree(struct Fsm *fsm) +{ + kfree((void *) fsm->jumpmatrix); +} + +int +FsmEvent(struct FsmInst *fi, int event, void *arg) +{ + FSMFNPTR r; + + if ((fi->state >= fi->fsm->state_count) || (event >= fi->fsm->event_count)) { + printk(KERN_ERR "FsmEvent Error st(%ld/%ld) ev(%d/%ld)\n", + (long)fi->state, (long)fi->fsm->state_count, event, (long)fi->fsm->event_count); + return (1); + } + r = fi->fsm->jumpmatrix[fi->fsm->state_count * event + fi->state]; + if (r) { + if (fi->debug) + fi->printdebug(fi, "State %s Event %s", + fi->fsm->strState[fi->state], + fi->fsm->strEvent[event]); + r(fi, event, arg); + return (0); + } else { + if (fi->debug) + fi->printdebug(fi, "State %s Event %s no routine", + fi->fsm->strState[fi->state], + fi->fsm->strEvent[event]); + return (!0); + } +} + +void +FsmChangeState(struct FsmInst *fi, int newstate) +{ + fi->state = newstate; + if (fi->debug) + fi->printdebug(fi, "ChangeState %s", + fi->fsm->strState[newstate]); +} + +static void +FsmExpireTimer(struct timer_list *t) +{ + struct FsmTimer *ft = from_timer(ft, t, tl); +#if FSM_TIMER_DEBUG + if (ft->fi->debug) + ft->fi->printdebug(ft->fi, "FsmExpireTimer %lx", (long) ft); +#endif + FsmEvent(ft->fi, ft->event, ft->arg); +} + +void +FsmInitTimer(struct FsmInst *fi, struct FsmTimer *ft) +{ + ft->fi = fi; +#if FSM_TIMER_DEBUG + if (ft->fi->debug) + ft->fi->printdebug(ft->fi, "FsmInitTimer %lx", (long) ft); +#endif + timer_setup(&ft->tl, FsmExpireTimer, 0); +} + +void +FsmDelTimer(struct FsmTimer *ft, int where) +{ +#if FSM_TIMER_DEBUG + if (ft->fi->debug) + ft->fi->printdebug(ft->fi, "FsmDelTimer %lx %d", (long) ft, where); +#endif + del_timer(&ft->tl); +} + +int +FsmAddTimer(struct FsmTimer *ft, + int millisec, int event, void *arg, int where) +{ + +#if FSM_TIMER_DEBUG + if (ft->fi->debug) + ft->fi->printdebug(ft->fi, "FsmAddTimer %lx %d %d", + (long) ft, millisec, where); +#endif + + if (timer_pending(&ft->tl)) { + printk(KERN_WARNING "FsmAddTimer: timer already active!\n"); + ft->fi->printdebug(ft->fi, "FsmAddTimer already active!"); + return -1; + } + ft->event = event; + ft->arg = arg; + ft->tl.expires = jiffies + (millisec * HZ) / 1000; + add_timer(&ft->tl); + return 0; +} + +void +FsmRestartTimer(struct FsmTimer *ft, + int millisec, int event, void *arg, int where) +{ + +#if FSM_TIMER_DEBUG + if (ft->fi->debug) + ft->fi->printdebug(ft->fi, "FsmRestartTimer %lx %d %d", + (long) ft, millisec, where); +#endif + + if (timer_pending(&ft->tl)) + del_timer(&ft->tl); + ft->event = event; + ft->arg = arg; + ft->tl.expires = jiffies + (millisec * HZ) / 1000; + add_timer(&ft->tl); +} diff --git a/drivers/isdn/hisax/fsm.h b/drivers/isdn/hisax/fsm.h new file mode 100644 index 000000000..8c7385619 --- /dev/null +++ b/drivers/isdn/hisax/fsm.h @@ -0,0 +1,61 @@ +/* $Id: fsm.h,v 1.3.2.2 2001/09/23 22:24:47 kai Exp $ + * + * Finite state machine + * + * Author Karsten Keil + * Copyright by Karsten Keil <keil@isdn4linux.de> + * by Kai Germaschewski <kai.germaschewski@gmx.de> + * + * This software may be used and distributed according to the terms + * of the GNU General Public License, incorporated herein by reference. + * + */ + +#ifndef __FSM_H__ +#define __FSM_H__ + +#include <linux/timer.h> + +struct FsmInst; + +typedef void (*FSMFNPTR)(struct FsmInst *, int, void *); + +struct Fsm { + FSMFNPTR *jumpmatrix; + int state_count, event_count; + char **strEvent, **strState; +}; + +struct FsmInst { + struct Fsm *fsm; + int state; + int debug; + void *userdata; + int userint; + void (*printdebug) (struct FsmInst *, char *, ...); +}; + +struct FsmNode { + int state, event; + void (*routine) (struct FsmInst *, int, void *); +}; + +struct FsmTimer { + struct FsmInst *fi; + struct timer_list tl; + int event; + void *arg; +}; + +int FsmNew(struct Fsm *fsm, struct FsmNode *fnlist, int fncount); +void FsmFree(struct Fsm *fsm); +int FsmEvent(struct FsmInst *fi, int event, void *arg); +void FsmChangeState(struct FsmInst *fi, int newstate); +void FsmInitTimer(struct FsmInst *fi, struct FsmTimer *ft); +int FsmAddTimer(struct FsmTimer *ft, int millisec, int event, + void *arg, int where); +void FsmRestartTimer(struct FsmTimer *ft, int millisec, int event, + void *arg, int where); +void FsmDelTimer(struct FsmTimer *ft, int where); + +#endif diff --git a/drivers/isdn/hisax/gazel.c b/drivers/isdn/hisax/gazel.c new file mode 100644 index 000000000..a6d8af023 --- /dev/null +++ b/drivers/isdn/hisax/gazel.c @@ -0,0 +1,691 @@ +/* $Id: gazel.c,v 2.19.2.4 2004/01/14 16:04:48 keil Exp $ + * + * low level stuff for Gazel isdn cards + * + * Author BeWan Systems + * based on source code from Karsten Keil + * Copyright by BeWan Systems + * + * This software may be used and distributed according to the terms + * of the GNU General Public License, incorporated herein by reference. + * + */ + +#include <linux/init.h> +#include "hisax.h" +#include "isac.h" +#include "hscx.h" +#include "isdnl1.h" +#include "ipac.h" +#include <linux/pci.h> + +static const char *gazel_revision = "$Revision: 2.19.2.4 $"; + +#define R647 1 +#define R685 2 +#define R753 3 +#define R742 4 + +#define PLX_CNTRL 0x50 /* registre de controle PLX */ +#define RESET_GAZEL 0x4 +#define RESET_9050 0x40000000 +#define PLX_INCSR 0x4C /* registre d'IT du 9050 */ +#define INT_ISAC_EN 0x8 /* 1 = enable IT isac */ +#define INT_ISAC 0x20 /* 1 = IT isac en cours */ +#define INT_HSCX_EN 0x1 /* 1 = enable IT hscx */ +#define INT_HSCX 0x4 /* 1 = IT hscx en cours */ +#define INT_PCI_EN 0x40 /* 1 = enable IT PCI */ +#define INT_IPAC_EN 0x3 /* enable IT ipac */ + + +#define byteout(addr, val) outb(val, addr) +#define bytein(addr) inb(addr) + +static inline u_char +readreg(unsigned int adr, u_short off) +{ + return bytein(adr + off); +} + +static inline void +writereg(unsigned int adr, u_short off, u_char data) +{ + byteout(adr + off, data); +} + + +static inline void +read_fifo(unsigned int adr, u_char *data, int size) +{ + insb(adr, data, size); +} + +static void +write_fifo(unsigned int adr, u_char *data, int size) +{ + outsb(adr, data, size); +} + +static inline u_char +readreg_ipac(unsigned int adr, u_short off) +{ + register u_char ret; + + byteout(adr, off); + ret = bytein(adr + 4); + return ret; +} + +static inline void +writereg_ipac(unsigned int adr, u_short off, u_char data) +{ + byteout(adr, off); + byteout(adr + 4, data); +} + + +static inline void +read_fifo_ipac(unsigned int adr, u_short off, u_char *data, int size) +{ + byteout(adr, off); + insb(adr + 4, data, size); +} + +static void +write_fifo_ipac(unsigned int adr, u_short off, u_char *data, int size) +{ + byteout(adr, off); + outsb(adr + 4, data, size); +} + +/* Interface functions */ + +static u_char +ReadISAC(struct IsdnCardState *cs, u_char offset) +{ + u_short off2 = offset; + + switch (cs->subtyp) { + case R647: + off2 = ((off2 << 8 & 0xf000) | (off2 & 0xf)); + /* fall through */ + case R685: + return (readreg(cs->hw.gazel.isac, off2)); + case R753: + case R742: + return (readreg_ipac(cs->hw.gazel.ipac, 0x80 + off2)); + } + return 0; +} + +static void +WriteISAC(struct IsdnCardState *cs, u_char offset, u_char value) +{ + u_short off2 = offset; + + switch (cs->subtyp) { + case R647: + off2 = ((off2 << 8 & 0xf000) | (off2 & 0xf)); + /* fall through */ + case R685: + writereg(cs->hw.gazel.isac, off2, value); + break; + case R753: + case R742: + writereg_ipac(cs->hw.gazel.ipac, 0x80 + off2, value); + break; + } +} + +static void +ReadISACfifo(struct IsdnCardState *cs, u_char *data, int size) +{ + switch (cs->subtyp) { + case R647: + case R685: + read_fifo(cs->hw.gazel.isacfifo, data, size); + break; + case R753: + case R742: + read_fifo_ipac(cs->hw.gazel.ipac, 0x80, data, size); + break; + } +} + +static void +WriteISACfifo(struct IsdnCardState *cs, u_char *data, int size) +{ + switch (cs->subtyp) { + case R647: + case R685: + write_fifo(cs->hw.gazel.isacfifo, data, size); + break; + case R753: + case R742: + write_fifo_ipac(cs->hw.gazel.ipac, 0x80, data, size); + break; + } +} + +static void +ReadHSCXfifo(struct IsdnCardState *cs, int hscx, u_char *data, int size) +{ + switch (cs->subtyp) { + case R647: + case R685: + read_fifo(cs->hw.gazel.hscxfifo[hscx], data, size); + break; + case R753: + case R742: + read_fifo_ipac(cs->hw.gazel.ipac, hscx * 0x40, data, size); + break; + } +} + +static void +WriteHSCXfifo(struct IsdnCardState *cs, int hscx, u_char *data, int size) +{ + switch (cs->subtyp) { + case R647: + case R685: + write_fifo(cs->hw.gazel.hscxfifo[hscx], data, size); + break; + case R753: + case R742: + write_fifo_ipac(cs->hw.gazel.ipac, hscx * 0x40, data, size); + break; + } +} + +static u_char +ReadHSCX(struct IsdnCardState *cs, int hscx, u_char offset) +{ + u_short off2 = offset; + + switch (cs->subtyp) { + case R647: + off2 = ((off2 << 8 & 0xf000) | (off2 & 0xf)); + /* fall through */ + case R685: + return (readreg(cs->hw.gazel.hscx[hscx], off2)); + case R753: + case R742: + return (readreg_ipac(cs->hw.gazel.ipac, hscx * 0x40 + off2)); + } + return 0; +} + +static void +WriteHSCX(struct IsdnCardState *cs, int hscx, u_char offset, u_char value) +{ + u_short off2 = offset; + + switch (cs->subtyp) { + case R647: + off2 = ((off2 << 8 & 0xf000) | (off2 & 0xf)); + /* fall through */ + case R685: + writereg(cs->hw.gazel.hscx[hscx], off2, value); + break; + case R753: + case R742: + writereg_ipac(cs->hw.gazel.ipac, hscx * 0x40 + off2, value); + break; + } +} + +/* + * fast interrupt HSCX stuff goes here + */ + +#define READHSCX(cs, nr, reg) ReadHSCX(cs, nr, reg) +#define WRITEHSCX(cs, nr, reg, data) WriteHSCX(cs, nr, reg, data) +#define READHSCXFIFO(cs, nr, ptr, cnt) ReadHSCXfifo(cs, nr, ptr, cnt) +#define WRITEHSCXFIFO(cs, nr, ptr, cnt) WriteHSCXfifo(cs, nr, ptr, cnt) + +#include "hscx_irq.c" + +static irqreturn_t +gazel_interrupt(int intno, void *dev_id) +{ +#define MAXCOUNT 5 + struct IsdnCardState *cs = dev_id; + u_char valisac, valhscx; + int count = 0; + u_long flags; + + spin_lock_irqsave(&cs->lock, flags); + do { + valhscx = ReadHSCX(cs, 1, HSCX_ISTA); + if (valhscx) + hscx_int_main(cs, valhscx); + valisac = ReadISAC(cs, ISAC_ISTA); + if (valisac) + isac_interrupt(cs, valisac); + count++; + } while ((valhscx || valisac) && (count < MAXCOUNT)); + + WriteHSCX(cs, 0, HSCX_MASK, 0xFF); + WriteHSCX(cs, 1, HSCX_MASK, 0xFF); + WriteISAC(cs, ISAC_MASK, 0xFF); + WriteISAC(cs, ISAC_MASK, 0x0); + WriteHSCX(cs, 0, HSCX_MASK, 0x0); + WriteHSCX(cs, 1, HSCX_MASK, 0x0); + spin_unlock_irqrestore(&cs->lock, flags); + return IRQ_HANDLED; +} + + +static irqreturn_t +gazel_interrupt_ipac(int intno, void *dev_id) +{ + struct IsdnCardState *cs = dev_id; + u_char ista, val; + int count = 0; + u_long flags; + + spin_lock_irqsave(&cs->lock, flags); + ista = ReadISAC(cs, IPAC_ISTA - 0x80); + do { + if (ista & 0x0f) { + val = ReadHSCX(cs, 1, HSCX_ISTA); + if (ista & 0x01) + val |= 0x01; + if (ista & 0x04) + val |= 0x02; + if (ista & 0x08) + val |= 0x04; + if (val) { + hscx_int_main(cs, val); + } + } + if (ista & 0x20) { + val = 0xfe & ReadISAC(cs, ISAC_ISTA); + if (val) { + isac_interrupt(cs, val); + } + } + if (ista & 0x10) { + val = 0x01; + isac_interrupt(cs, val); + } + ista = ReadISAC(cs, IPAC_ISTA - 0x80); + count++; + } + while ((ista & 0x3f) && (count < MAXCOUNT)); + + WriteISAC(cs, IPAC_MASK - 0x80, 0xFF); + WriteISAC(cs, IPAC_MASK - 0x80, 0xC0); + spin_unlock_irqrestore(&cs->lock, flags); + return IRQ_HANDLED; +} + +static void +release_io_gazel(struct IsdnCardState *cs) +{ + unsigned int i; + + switch (cs->subtyp) { + case R647: + for (i = 0x0000; i < 0xC000; i += 0x1000) + release_region(i + cs->hw.gazel.hscx[0], 16); + release_region(0xC000 + cs->hw.gazel.hscx[0], 1); + break; + + case R685: + release_region(cs->hw.gazel.hscx[0], 0x100); + release_region(cs->hw.gazel.cfg_reg, 0x80); + break; + + case R753: + release_region(cs->hw.gazel.ipac, 0x8); + release_region(cs->hw.gazel.cfg_reg, 0x80); + break; + + case R742: + release_region(cs->hw.gazel.ipac, 8); + break; + } +} + +static int +reset_gazel(struct IsdnCardState *cs) +{ + unsigned long plxcntrl, addr = cs->hw.gazel.cfg_reg; + + switch (cs->subtyp) { + case R647: + writereg(addr, 0, 0); + HZDELAY(10); + writereg(addr, 0, 1); + HZDELAY(2); + break; + case R685: + plxcntrl = inl(addr + PLX_CNTRL); + plxcntrl |= (RESET_9050 + RESET_GAZEL); + outl(plxcntrl, addr + PLX_CNTRL); + plxcntrl &= ~(RESET_9050 + RESET_GAZEL); + HZDELAY(4); + outl(plxcntrl, addr + PLX_CNTRL); + HZDELAY(10); + outb(INT_ISAC_EN + INT_HSCX_EN + INT_PCI_EN, addr + PLX_INCSR); + break; + case R753: + plxcntrl = inl(addr + PLX_CNTRL); + plxcntrl |= (RESET_9050 + RESET_GAZEL); + outl(plxcntrl, addr + PLX_CNTRL); + plxcntrl &= ~(RESET_9050 + RESET_GAZEL); + WriteISAC(cs, IPAC_POTA2 - 0x80, 0x20); + HZDELAY(4); + outl(plxcntrl, addr + PLX_CNTRL); + HZDELAY(10); + WriteISAC(cs, IPAC_POTA2 - 0x80, 0x00); + WriteISAC(cs, IPAC_ACFG - 0x80, 0xff); + WriteISAC(cs, IPAC_AOE - 0x80, 0x0); + WriteISAC(cs, IPAC_MASK - 0x80, 0xff); + WriteISAC(cs, IPAC_CONF - 0x80, 0x1); + outb(INT_IPAC_EN + INT_PCI_EN, addr + PLX_INCSR); + WriteISAC(cs, IPAC_MASK - 0x80, 0xc0); + break; + case R742: + WriteISAC(cs, IPAC_POTA2 - 0x80, 0x20); + HZDELAY(4); + WriteISAC(cs, IPAC_POTA2 - 0x80, 0x00); + WriteISAC(cs, IPAC_ACFG - 0x80, 0xff); + WriteISAC(cs, IPAC_AOE - 0x80, 0x0); + WriteISAC(cs, IPAC_MASK - 0x80, 0xff); + WriteISAC(cs, IPAC_CONF - 0x80, 0x1); + WriteISAC(cs, IPAC_MASK - 0x80, 0xc0); + break; + } + return (0); +} + +static int +Gazel_card_msg(struct IsdnCardState *cs, int mt, void *arg) +{ + u_long flags; + + switch (mt) { + case CARD_RESET: + spin_lock_irqsave(&cs->lock, flags); + reset_gazel(cs); + spin_unlock_irqrestore(&cs->lock, flags); + return (0); + case CARD_RELEASE: + release_io_gazel(cs); + return (0); + case CARD_INIT: + spin_lock_irqsave(&cs->lock, flags); + inithscxisac(cs, 1); + if ((cs->subtyp == R647) || (cs->subtyp == R685)) { + int i; + for (i = 0; i < (2 + MAX_WAITING_CALLS); i++) { + cs->bcs[i].hw.hscx.tsaxr0 = 0x1f; + cs->bcs[i].hw.hscx.tsaxr1 = 0x23; + } + } + spin_unlock_irqrestore(&cs->lock, flags); + return (0); + case CARD_TEST: + return (0); + } + return (0); +} + +static int +reserve_regions(struct IsdnCard *card, struct IsdnCardState *cs) +{ + unsigned int i, j, base = 0, adr = 0, len = 0; + + switch (cs->subtyp) { + case R647: + base = cs->hw.gazel.hscx[0]; + if (!request_region(adr = (0xC000 + base), len = 1, "gazel")) + goto error; + for (i = 0x0000; i < 0xC000; i += 0x1000) { + if (!request_region(adr = (i + base), len = 16, "gazel")) + goto error; + } + if (i != 0xC000) { + for (j = 0; j < i; j += 0x1000) + release_region(j + base, 16); + release_region(0xC000 + base, 1); + goto error; + } + break; + + case R685: + if (!request_region(adr = cs->hw.gazel.hscx[0], len = 0x100, "gazel")) + goto error; + if (!request_region(adr = cs->hw.gazel.cfg_reg, len = 0x80, "gazel")) { + release_region(cs->hw.gazel.hscx[0], 0x100); + goto error; + } + break; + + case R753: + if (!request_region(adr = cs->hw.gazel.ipac, len = 0x8, "gazel")) + goto error; + if (!request_region(adr = cs->hw.gazel.cfg_reg, len = 0x80, "gazel")) { + release_region(cs->hw.gazel.ipac, 8); + goto error; + } + break; + + case R742: + if (!request_region(adr = cs->hw.gazel.ipac, len = 0x8, "gazel")) + goto error; + break; + } + + return 0; + +error: + printk(KERN_WARNING "Gazel: io ports 0x%x-0x%x already in use\n", + adr, adr + len); + return 1; +} + +static int setup_gazelisa(struct IsdnCard *card, struct IsdnCardState *cs) +{ + printk(KERN_INFO "Gazel: ISA PnP card automatic recognition\n"); + // we got an irq parameter, assume it is an ISA card + // R742 decodes address even in not started... + // R647 returns FF if not present or not started + // eventually needs improvment + if (readreg_ipac(card->para[1], IPAC_ID) == 1) + cs->subtyp = R742; + else + cs->subtyp = R647; + + setup_isac(cs); + cs->hw.gazel.cfg_reg = card->para[1] + 0xC000; + cs->hw.gazel.ipac = card->para[1]; + cs->hw.gazel.isac = card->para[1] + 0x8000; + cs->hw.gazel.hscx[0] = card->para[1]; + cs->hw.gazel.hscx[1] = card->para[1] + 0x4000; + cs->irq = card->para[0]; + cs->hw.gazel.isacfifo = cs->hw.gazel.isac; + cs->hw.gazel.hscxfifo[0] = cs->hw.gazel.hscx[0]; + cs->hw.gazel.hscxfifo[1] = cs->hw.gazel.hscx[1]; + + switch (cs->subtyp) { + case R647: + printk(KERN_INFO "Gazel: Card ISA R647/R648 found\n"); + cs->dc.isac.adf2 = 0x87; + printk(KERN_INFO + "Gazel: config irq:%d isac:0x%X cfg:0x%X\n", + cs->irq, cs->hw.gazel.isac, cs->hw.gazel.cfg_reg); + printk(KERN_INFO + "Gazel: hscx A:0x%X hscx B:0x%X\n", + cs->hw.gazel.hscx[0], cs->hw.gazel.hscx[1]); + + break; + case R742: + printk(KERN_INFO "Gazel: Card ISA R742 found\n"); + test_and_set_bit(HW_IPAC, &cs->HW_Flags); + printk(KERN_INFO + "Gazel: config irq:%d ipac:0x%X\n", + cs->irq, cs->hw.gazel.ipac); + break; + } + + return (0); +} + +#ifdef CONFIG_PCI +static struct pci_dev *dev_tel = NULL; + +static int setup_gazelpci(struct IsdnCardState *cs) +{ + u_int pci_ioaddr0 = 0, pci_ioaddr1 = 0; + u_char pci_irq = 0, found; + u_int nbseek, seekcard; + + printk(KERN_WARNING "Gazel: PCI card automatic recognition\n"); + + found = 0; + seekcard = PCI_DEVICE_ID_PLX_R685; + for (nbseek = 0; nbseek < 4; nbseek++) { + if ((dev_tel = hisax_find_pci_device(PCI_VENDOR_ID_PLX, + seekcard, dev_tel))) { + if (pci_enable_device(dev_tel)) + return 1; + pci_irq = dev_tel->irq; + pci_ioaddr0 = pci_resource_start(dev_tel, 1); + pci_ioaddr1 = pci_resource_start(dev_tel, 2); + found = 1; + } + if (found) + break; + else { + switch (seekcard) { + case PCI_DEVICE_ID_PLX_R685: + seekcard = PCI_DEVICE_ID_PLX_R753; + break; + case PCI_DEVICE_ID_PLX_R753: + seekcard = PCI_DEVICE_ID_PLX_DJINN_ITOO; + break; + case PCI_DEVICE_ID_PLX_DJINN_ITOO: + seekcard = PCI_DEVICE_ID_PLX_OLITEC; + break; + } + } + } + if (!found) { + printk(KERN_WARNING "Gazel: No PCI card found\n"); + return (1); + } + if (!pci_irq) { + printk(KERN_WARNING "Gazel: No IRQ for PCI card found\n"); + return 1; + } + cs->hw.gazel.pciaddr[0] = pci_ioaddr0; + cs->hw.gazel.pciaddr[1] = pci_ioaddr1; + setup_isac(cs); + pci_ioaddr1 &= 0xfffe; + cs->hw.gazel.cfg_reg = pci_ioaddr0 & 0xfffe; + cs->hw.gazel.ipac = pci_ioaddr1; + cs->hw.gazel.isac = pci_ioaddr1 + 0x80; + cs->hw.gazel.hscx[0] = pci_ioaddr1; + cs->hw.gazel.hscx[1] = pci_ioaddr1 + 0x40; + cs->hw.gazel.isacfifo = cs->hw.gazel.isac; + cs->hw.gazel.hscxfifo[0] = cs->hw.gazel.hscx[0]; + cs->hw.gazel.hscxfifo[1] = cs->hw.gazel.hscx[1]; + cs->irq = pci_irq; + cs->irq_flags |= IRQF_SHARED; + + switch (seekcard) { + case PCI_DEVICE_ID_PLX_R685: + printk(KERN_INFO "Gazel: Card PCI R685 found\n"); + cs->subtyp = R685; + cs->dc.isac.adf2 = 0x87; + printk(KERN_INFO + "Gazel: config irq:%d isac:0x%X cfg:0x%X\n", + cs->irq, cs->hw.gazel.isac, cs->hw.gazel.cfg_reg); + printk(KERN_INFO + "Gazel: hscx A:0x%X hscx B:0x%X\n", + cs->hw.gazel.hscx[0], cs->hw.gazel.hscx[1]); + break; + case PCI_DEVICE_ID_PLX_R753: + case PCI_DEVICE_ID_PLX_DJINN_ITOO: + case PCI_DEVICE_ID_PLX_OLITEC: + printk(KERN_INFO "Gazel: Card PCI R753 found\n"); + cs->subtyp = R753; + test_and_set_bit(HW_IPAC, &cs->HW_Flags); + printk(KERN_INFO + "Gazel: config irq:%d ipac:0x%X cfg:0x%X\n", + cs->irq, cs->hw.gazel.ipac, cs->hw.gazel.cfg_reg); + break; + } + + return (0); +} +#endif /* CONFIG_PCI */ + +int setup_gazel(struct IsdnCard *card) +{ + struct IsdnCardState *cs = card->cs; + char tmp[64]; + u_char val; + + strcpy(tmp, gazel_revision); + printk(KERN_INFO "Gazel: Driver Revision %s\n", HiSax_getrev(tmp)); + + if (cs->typ != ISDN_CTYPE_GAZEL) + return (0); + + if (card->para[0]) { + if (setup_gazelisa(card, cs)) + return (0); + } else { + +#ifdef CONFIG_PCI + if (setup_gazelpci(cs)) + return (0); +#else + printk(KERN_WARNING "Gazel: Card PCI requested and NO_PCI_BIOS, unable to config\n"); + return (0); +#endif /* CONFIG_PCI */ + } + + if (reserve_regions(card, cs)) { + return (0); + } + if (reset_gazel(cs)) { + printk(KERN_WARNING "Gazel: wrong IRQ\n"); + release_io_gazel(cs); + return (0); + } + cs->readisac = &ReadISAC; + cs->writeisac = &WriteISAC; + cs->readisacfifo = &ReadISACfifo; + cs->writeisacfifo = &WriteISACfifo; + cs->BC_Read_Reg = &ReadHSCX; + cs->BC_Write_Reg = &WriteHSCX; + cs->BC_Send_Data = &hscx_fill_fifo; + cs->cardmsg = &Gazel_card_msg; + + switch (cs->subtyp) { + case R647: + case R685: + cs->irq_func = &gazel_interrupt; + ISACVersion(cs, "Gazel:"); + if (HscxVersion(cs, "Gazel:")) { + printk(KERN_WARNING + "Gazel: wrong HSCX versions check IO address\n"); + release_io_gazel(cs); + return (0); + } + break; + case R742: + case R753: + cs->irq_func = &gazel_interrupt_ipac; + val = ReadISAC(cs, IPAC_ID - 0x80); + printk(KERN_INFO "Gazel: IPAC version %x\n", val); + break; + } + + return (1); +} diff --git a/drivers/isdn/hisax/hfc4s8s_l1.c b/drivers/isdn/hisax/hfc4s8s_l1.c new file mode 100644 index 000000000..e9bb8fb67 --- /dev/null +++ b/drivers/isdn/hisax/hfc4s8s_l1.c @@ -0,0 +1,1584 @@ +/*************************************************************************/ +/* $Id: hfc4s8s_l1.c,v 1.10 2005/02/09 16:31:09 martinb1 Exp $ */ +/* HFC-4S/8S low layer interface for Cologne Chip HFC-4S/8S isdn chips */ +/* The low layer (L1) is implemented as a loadable module for usage with */ +/* the HiSax isdn driver for passive cards. */ +/* */ +/* Author: Werner Cornelius */ +/* (C) 2003 Cornelius Consult (werner@cornelius-consult.de) */ +/* */ +/* Driver maintained by Cologne Chip */ +/* - Martin Bachem, support@colognechip.com */ +/* */ +/* This driver only works with chip revisions >= 1, older revision 0 */ +/* engineering samples (only first manufacturer sample cards) will not */ +/* work and are rejected by the driver. */ +/* */ +/* This file distributed under the GNU GPL. */ +/* */ +/* See Version History at the end of this file */ +/* */ +/*************************************************************************/ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/pci.h> +#include <linux/interrupt.h> +#include <linux/delay.h> +#include <linux/slab.h> +#include <linux/timer.h> +#include <linux/skbuff.h> +#include <linux/wait.h> +#include <asm/io.h> +#include "hisax_if.h" +#include "hfc4s8s_l1.h" + +static const char hfc4s8s_rev[] = "Revision: 1.10"; + +/***************************************************************/ +/* adjustable transparent mode fifo threshold */ +/* The value defines the used fifo threshold with the equation */ +/* */ +/* notify number of bytes = 2 * 2 ^ TRANS_FIFO_THRES */ +/* */ +/* The default value is 5 which results in a buffer size of 64 */ +/* and an interrupt rate of 8ms. */ +/* The maximum value is 7 due to fifo size restrictions. */ +/* Values below 3-4 are not recommended due to high interrupt */ +/* load of the processor. For non critical applications the */ +/* value should be raised to 7 to reduce any interrupt overhead*/ +/***************************************************************/ +#define TRANS_FIFO_THRES 5 + +/*************/ +/* constants */ +/*************/ +#define CLOCKMODE_0 0 /* ext. 24.576 MhZ clk freq, int. single clock mode */ +#define CLOCKMODE_1 1 /* ext. 49.576 MhZ clk freq, int. single clock mode */ +#define CHIP_ID_SHIFT 4 +#define HFC_MAX_ST 8 +#define MAX_D_FRAME_SIZE 270 +#define MAX_B_FRAME_SIZE 1536 +#define TRANS_TIMER_MODE (TRANS_FIFO_THRES & 0xf) +#define TRANS_FIFO_BYTES (2 << TRANS_FIFO_THRES) +#define MAX_F_CNT 0x0f + +#define CLKDEL_NT 0x6c +#define CLKDEL_TE 0xf +#define CTRL0_NT 4 +#define CTRL0_TE 0 + +#define L1_TIMER_T4 2 /* minimum in jiffies */ +#define L1_TIMER_T3 (7 * HZ) /* activation timeout */ +#define L1_TIMER_T1 ((120 * HZ) / 1000) /* NT mode deactivation timeout */ + + +/******************/ +/* types and vars */ +/******************/ +static int card_cnt; + +/* private driver_data */ +typedef struct { + int chip_id; + int clock_mode; + int max_st_ports; + char *device_name; +} hfc4s8s_param; + +static const struct pci_device_id hfc4s8s_ids[] = { + {.vendor = PCI_VENDOR_ID_CCD, + .device = PCI_DEVICE_ID_4S, + .subvendor = 0x1397, + .subdevice = 0x08b4, + .driver_data = + (unsigned long) &((hfc4s8s_param) {CHIP_ID_4S, CLOCKMODE_0, 4, + "HFC-4S Evaluation Board"}), + }, + {.vendor = PCI_VENDOR_ID_CCD, + .device = PCI_DEVICE_ID_8S, + .subvendor = 0x1397, + .subdevice = 0x16b8, + .driver_data = + (unsigned long) &((hfc4s8s_param) {CHIP_ID_8S, CLOCKMODE_0, 8, + "HFC-8S Evaluation Board"}), + }, + {.vendor = PCI_VENDOR_ID_CCD, + .device = PCI_DEVICE_ID_4S, + .subvendor = 0x1397, + .subdevice = 0xb520, + .driver_data = + (unsigned long) &((hfc4s8s_param) {CHIP_ID_4S, CLOCKMODE_1, 4, + "IOB4ST"}), + }, + {.vendor = PCI_VENDOR_ID_CCD, + .device = PCI_DEVICE_ID_8S, + .subvendor = 0x1397, + .subdevice = 0xb522, + .driver_data = + (unsigned long) &((hfc4s8s_param) {CHIP_ID_8S, CLOCKMODE_1, 8, + "IOB8ST"}), + }, + {} +}; + +MODULE_DEVICE_TABLE(pci, hfc4s8s_ids); + +MODULE_AUTHOR("Werner Cornelius, werner@cornelius-consult.de"); +MODULE_DESCRIPTION("ISDN layer 1 for Cologne Chip HFC-4S/8S chips"); +MODULE_LICENSE("GPL"); + +/***********/ +/* layer 1 */ +/***********/ +struct hfc4s8s_btype { + spinlock_t lock; + struct hisax_b_if b_if; + struct hfc4s8s_l1 *l1p; + struct sk_buff_head tx_queue; + struct sk_buff *tx_skb; + struct sk_buff *rx_skb; + __u8 *rx_ptr; + int tx_cnt; + int bchan; + int mode; +}; + +struct _hfc4s8s_hw; + +struct hfc4s8s_l1 { + spinlock_t lock; + struct _hfc4s8s_hw *hw; /* pointer to hardware area */ + int l1_state; /* actual l1 state */ + struct timer_list l1_timer; /* layer 1 timer structure */ + int nt_mode; /* set to nt mode */ + int st_num; /* own index */ + int enabled; /* interface is enabled */ + struct sk_buff_head d_tx_queue; /* send queue */ + int tx_cnt; /* bytes to send */ + struct hisax_d_if d_if; /* D-channel interface */ + struct hfc4s8s_btype b_ch[2]; /* B-channel data */ + struct hisax_b_if *b_table[2]; +}; + +/**********************/ +/* hardware structure */ +/**********************/ +typedef struct _hfc4s8s_hw { + spinlock_t lock; + + int cardnum; + int ifnum; + int iobase; + int nt_mode; + u_char *membase; + u_char *hw_membase; + void *pdev; + int max_fifo; + hfc4s8s_param driver_data; + int irq; + int fifo_sched_cnt; + struct work_struct tqueue; + struct hfc4s8s_l1 l1[HFC_MAX_ST]; + char card_name[60]; + struct { + u_char r_irq_ctrl; + u_char r_ctrl0; + volatile u_char r_irq_statech; /* active isdn l1 status */ + u_char r_irqmsk_statchg; /* enabled isdn status ints */ + u_char r_irq_fifo_blx[8]; /* fifo status registers */ + u_char fifo_rx_trans_enables[8]; /* mask for enabled transparent rx fifos */ + u_char fifo_slow_timer_service[8]; /* mask for fifos needing slower timer service */ + volatile u_char r_irq_oview; /* contents of overview register */ + volatile u_char timer_irq; + int timer_usg_cnt; /* number of channels using timer */ + } mr; +} hfc4s8s_hw; + + + +/* inline functions io mapped */ +static inline void +SetRegAddr(hfc4s8s_hw *a, u_char b) +{ + outb(b, (a->iobase) + 4); +} + +static inline u_char +GetRegAddr(hfc4s8s_hw *a) +{ + return (inb((volatile u_int) (a->iobase + 4))); +} + + +static inline void +Write_hfc8(hfc4s8s_hw *a, u_char b, u_char c) +{ + SetRegAddr(a, b); + outb(c, a->iobase); +} + +static inline void +fWrite_hfc8(hfc4s8s_hw *a, u_char c) +{ + outb(c, a->iobase); +} + +static inline void +fWrite_hfc32(hfc4s8s_hw *a, u_long c) +{ + outl(c, a->iobase); +} + +static inline u_char +Read_hfc8(hfc4s8s_hw *a, u_char b) +{ + SetRegAddr(a, b); + return (inb((volatile u_int) a->iobase)); +} + +static inline u_char +fRead_hfc8(hfc4s8s_hw *a) +{ + return (inb((volatile u_int) a->iobase)); +} + + +static inline u_short +Read_hfc16(hfc4s8s_hw *a, u_char b) +{ + SetRegAddr(a, b); + return (inw((volatile u_int) a->iobase)); +} + +static inline u_long +fRead_hfc32(hfc4s8s_hw *a) +{ + return (inl((volatile u_int) a->iobase)); +} + +static inline void +wait_busy(hfc4s8s_hw *a) +{ + SetRegAddr(a, R_STATUS); + while (inb((volatile u_int) a->iobase) & M_BUSY); +} + +#define PCI_ENA_REGIO 0x01 + +/******************************************************/ +/* function to read critical counter registers that */ +/* may be updated by the chip during read */ +/******************************************************/ +static u_char +Read_hfc8_stable(hfc4s8s_hw *hw, int reg) +{ + u_char ref8; + u_char in8; + ref8 = Read_hfc8(hw, reg); + while (((in8 = Read_hfc8(hw, reg)) != ref8)) { + ref8 = in8; + } + return in8; +} + +static int +Read_hfc16_stable(hfc4s8s_hw *hw, int reg) +{ + int ref16; + int in16; + + ref16 = Read_hfc16(hw, reg); + while (((in16 = Read_hfc16(hw, reg)) != ref16)) { + ref16 = in16; + } + return in16; +} + +/*****************************/ +/* D-channel call from HiSax */ +/*****************************/ +static void +dch_l2l1(struct hisax_d_if *iface, int pr, void *arg) +{ + struct hfc4s8s_l1 *l1 = iface->ifc.priv; + struct sk_buff *skb = (struct sk_buff *) arg; + u_long flags; + + switch (pr) { + + case (PH_DATA | REQUEST): + if (!l1->enabled) { + dev_kfree_skb(skb); + break; + } + spin_lock_irqsave(&l1->lock, flags); + skb_queue_tail(&l1->d_tx_queue, skb); + if ((skb_queue_len(&l1->d_tx_queue) == 1) && + (l1->tx_cnt <= 0)) { + l1->hw->mr.r_irq_fifo_blx[l1->st_num] |= + 0x10; + spin_unlock_irqrestore(&l1->lock, flags); + schedule_work(&l1->hw->tqueue); + } else + spin_unlock_irqrestore(&l1->lock, flags); + break; + + case (PH_ACTIVATE | REQUEST): + if (!l1->enabled) + break; + if (!l1->nt_mode) { + if (l1->l1_state < 6) { + spin_lock_irqsave(&l1->lock, + flags); + + Write_hfc8(l1->hw, R_ST_SEL, + l1->st_num); + Write_hfc8(l1->hw, A_ST_WR_STA, + 0x60); + mod_timer(&l1->l1_timer, + jiffies + L1_TIMER_T3); + spin_unlock_irqrestore(&l1->lock, + flags); + } else if (l1->l1_state == 7) + l1->d_if.ifc.l1l2(&l1->d_if.ifc, + PH_ACTIVATE | + INDICATION, + NULL); + } else { + if (l1->l1_state != 3) { + spin_lock_irqsave(&l1->lock, + flags); + Write_hfc8(l1->hw, R_ST_SEL, + l1->st_num); + Write_hfc8(l1->hw, A_ST_WR_STA, + 0x60); + spin_unlock_irqrestore(&l1->lock, + flags); + } else if (l1->l1_state == 3) + l1->d_if.ifc.l1l2(&l1->d_if.ifc, + PH_ACTIVATE | + INDICATION, + NULL); + } + break; + + default: + printk(KERN_INFO + "HFC-4S/8S: Unknown D-chan cmd 0x%x received, ignored\n", + pr); + break; + } + if (!l1->enabled) + l1->d_if.ifc.l1l2(&l1->d_if.ifc, + PH_DEACTIVATE | INDICATION, NULL); +} /* dch_l2l1 */ + +/*****************************/ +/* B-channel call from HiSax */ +/*****************************/ +static void +bch_l2l1(struct hisax_if *ifc, int pr, void *arg) +{ + struct hfc4s8s_btype *bch = ifc->priv; + struct hfc4s8s_l1 *l1 = bch->l1p; + struct sk_buff *skb = (struct sk_buff *) arg; + long mode = (long) arg; + u_long flags; + + switch (pr) { + + case (PH_DATA | REQUEST): + if (!l1->enabled || (bch->mode == L1_MODE_NULL)) { + dev_kfree_skb(skb); + break; + } + spin_lock_irqsave(&l1->lock, flags); + skb_queue_tail(&bch->tx_queue, skb); + if (!bch->tx_skb && (bch->tx_cnt <= 0)) { + l1->hw->mr.r_irq_fifo_blx[l1->st_num] |= + ((bch->bchan == 1) ? 1 : 4); + spin_unlock_irqrestore(&l1->lock, flags); + schedule_work(&l1->hw->tqueue); + } else + spin_unlock_irqrestore(&l1->lock, flags); + break; + + case (PH_ACTIVATE | REQUEST): + case (PH_DEACTIVATE | REQUEST): + if (!l1->enabled) + break; + if (pr == (PH_DEACTIVATE | REQUEST)) + mode = L1_MODE_NULL; + + switch (mode) { + case L1_MODE_HDLC: + spin_lock_irqsave(&l1->lock, + flags); + l1->hw->mr.timer_usg_cnt++; + l1->hw->mr. + fifo_slow_timer_service[l1-> + st_num] + |= + ((bch->bchan == + 1) ? 0x2 : 0x8); + Write_hfc8(l1->hw, R_FIFO, + (l1->st_num * 8 + + ((bch->bchan == + 1) ? 0 : 2))); + wait_busy(l1->hw); + Write_hfc8(l1->hw, A_CON_HDLC, 0xc); /* HDLC mode, flag fill, connect ST */ + Write_hfc8(l1->hw, A_SUBCH_CFG, 0); /* 8 bits */ + Write_hfc8(l1->hw, A_IRQ_MSK, 1); /* enable TX interrupts for hdlc */ + Write_hfc8(l1->hw, A_INC_RES_FIFO, 2); /* reset fifo */ + wait_busy(l1->hw); + + Write_hfc8(l1->hw, R_FIFO, + (l1->st_num * 8 + + ((bch->bchan == + 1) ? 1 : 3))); + wait_busy(l1->hw); + Write_hfc8(l1->hw, A_CON_HDLC, 0xc); /* HDLC mode, flag fill, connect ST */ + Write_hfc8(l1->hw, A_SUBCH_CFG, 0); /* 8 bits */ + Write_hfc8(l1->hw, A_IRQ_MSK, 1); /* enable RX interrupts for hdlc */ + Write_hfc8(l1->hw, A_INC_RES_FIFO, 2); /* reset fifo */ + + Write_hfc8(l1->hw, R_ST_SEL, + l1->st_num); + l1->hw->mr.r_ctrl0 |= + (bch->bchan & 3); + Write_hfc8(l1->hw, A_ST_CTRL0, + l1->hw->mr.r_ctrl0); + bch->mode = L1_MODE_HDLC; + spin_unlock_irqrestore(&l1->lock, + flags); + + bch->b_if.ifc.l1l2(&bch->b_if.ifc, + PH_ACTIVATE | + INDICATION, + NULL); + break; + + case L1_MODE_TRANS: + spin_lock_irqsave(&l1->lock, + flags); + l1->hw->mr. + fifo_rx_trans_enables[l1-> + st_num] + |= + ((bch->bchan == + 1) ? 0x2 : 0x8); + l1->hw->mr.timer_usg_cnt++; + Write_hfc8(l1->hw, R_FIFO, + (l1->st_num * 8 + + ((bch->bchan == + 1) ? 0 : 2))); + wait_busy(l1->hw); + Write_hfc8(l1->hw, A_CON_HDLC, 0xf); /* Transparent mode, 1 fill, connect ST */ + Write_hfc8(l1->hw, A_SUBCH_CFG, 0); /* 8 bits */ + Write_hfc8(l1->hw, A_IRQ_MSK, 0); /* disable TX interrupts */ + Write_hfc8(l1->hw, A_INC_RES_FIFO, 2); /* reset fifo */ + wait_busy(l1->hw); + + Write_hfc8(l1->hw, R_FIFO, + (l1->st_num * 8 + + ((bch->bchan == + 1) ? 1 : 3))); + wait_busy(l1->hw); + Write_hfc8(l1->hw, A_CON_HDLC, 0xf); /* Transparent mode, 1 fill, connect ST */ + Write_hfc8(l1->hw, A_SUBCH_CFG, 0); /* 8 bits */ + Write_hfc8(l1->hw, A_IRQ_MSK, 0); /* disable RX interrupts */ + Write_hfc8(l1->hw, A_INC_RES_FIFO, 2); /* reset fifo */ + + Write_hfc8(l1->hw, R_ST_SEL, + l1->st_num); + l1->hw->mr.r_ctrl0 |= + (bch->bchan & 3); + Write_hfc8(l1->hw, A_ST_CTRL0, + l1->hw->mr.r_ctrl0); + bch->mode = L1_MODE_TRANS; + spin_unlock_irqrestore(&l1->lock, + flags); + + bch->b_if.ifc.l1l2(&bch->b_if.ifc, + PH_ACTIVATE | + INDICATION, + NULL); + break; + + default: + if (bch->mode == L1_MODE_NULL) + break; + spin_lock_irqsave(&l1->lock, + flags); + l1->hw->mr. + fifo_slow_timer_service[l1-> + st_num] + &= + ~((bch->bchan == + 1) ? 0x3 : 0xc); + l1->hw->mr. + fifo_rx_trans_enables[l1-> + st_num] + &= + ~((bch->bchan == + 1) ? 0x3 : 0xc); + l1->hw->mr.timer_usg_cnt--; + Write_hfc8(l1->hw, R_FIFO, + (l1->st_num * 8 + + ((bch->bchan == + 1) ? 0 : 2))); + wait_busy(l1->hw); + Write_hfc8(l1->hw, A_IRQ_MSK, 0); /* disable TX interrupts */ + wait_busy(l1->hw); + Write_hfc8(l1->hw, R_FIFO, + (l1->st_num * 8 + + ((bch->bchan == + 1) ? 1 : 3))); + wait_busy(l1->hw); + Write_hfc8(l1->hw, A_IRQ_MSK, 0); /* disable RX interrupts */ + Write_hfc8(l1->hw, R_ST_SEL, + l1->st_num); + l1->hw->mr.r_ctrl0 &= + ~(bch->bchan & 3); + Write_hfc8(l1->hw, A_ST_CTRL0, + l1->hw->mr.r_ctrl0); + spin_unlock_irqrestore(&l1->lock, + flags); + + bch->mode = L1_MODE_NULL; + bch->b_if.ifc.l1l2(&bch->b_if.ifc, + PH_DEACTIVATE | + INDICATION, + NULL); + if (bch->tx_skb) { + dev_kfree_skb(bch->tx_skb); + bch->tx_skb = NULL; + } + if (bch->rx_skb) { + dev_kfree_skb(bch->rx_skb); + bch->rx_skb = NULL; + } + skb_queue_purge(&bch->tx_queue); + bch->tx_cnt = 0; + bch->rx_ptr = NULL; + break; + } + + /* timer is only used when at least one b channel */ + /* is set up to transparent mode */ + if (l1->hw->mr.timer_usg_cnt) { + Write_hfc8(l1->hw, R_IRQMSK_MISC, + M_TI_IRQMSK); + } else { + Write_hfc8(l1->hw, R_IRQMSK_MISC, 0); + } + + break; + + default: + printk(KERN_INFO + "HFC-4S/8S: Unknown B-chan cmd 0x%x received, ignored\n", + pr); + break; + } + if (!l1->enabled) + bch->b_if.ifc.l1l2(&bch->b_if.ifc, + PH_DEACTIVATE | INDICATION, NULL); +} /* bch_l2l1 */ + +/**************************/ +/* layer 1 timer function */ +/**************************/ +static void +hfc_l1_timer(struct timer_list *t) +{ + struct hfc4s8s_l1 *l1 = from_timer(l1, t, l1_timer); + u_long flags; + + if (!l1->enabled) + return; + + spin_lock_irqsave(&l1->lock, flags); + if (l1->nt_mode) { + l1->l1_state = 1; + Write_hfc8(l1->hw, R_ST_SEL, l1->st_num); + Write_hfc8(l1->hw, A_ST_WR_STA, 0x11); + spin_unlock_irqrestore(&l1->lock, flags); + l1->d_if.ifc.l1l2(&l1->d_if.ifc, + PH_DEACTIVATE | INDICATION, NULL); + spin_lock_irqsave(&l1->lock, flags); + l1->l1_state = 1; + Write_hfc8(l1->hw, A_ST_WR_STA, 0x1); + spin_unlock_irqrestore(&l1->lock, flags); + } else { + /* activation timed out */ + Write_hfc8(l1->hw, R_ST_SEL, l1->st_num); + Write_hfc8(l1->hw, A_ST_WR_STA, 0x13); + spin_unlock_irqrestore(&l1->lock, flags); + l1->d_if.ifc.l1l2(&l1->d_if.ifc, + PH_DEACTIVATE | INDICATION, NULL); + spin_lock_irqsave(&l1->lock, flags); + Write_hfc8(l1->hw, R_ST_SEL, l1->st_num); + Write_hfc8(l1->hw, A_ST_WR_STA, 0x3); + spin_unlock_irqrestore(&l1->lock, flags); + } +} /* hfc_l1_timer */ + +/****************************************/ +/* a complete D-frame has been received */ +/****************************************/ +static void +rx_d_frame(struct hfc4s8s_l1 *l1p, int ech) +{ + int z1, z2; + u_char f1, f2, df; + struct sk_buff *skb; + u_char *cp; + + + if (!l1p->enabled) + return; + do { + /* E/D RX fifo */ + Write_hfc8(l1p->hw, R_FIFO, + (l1p->st_num * 8 + ((ech) ? 7 : 5))); + wait_busy(l1p->hw); + + f1 = Read_hfc8_stable(l1p->hw, A_F1); + f2 = Read_hfc8(l1p->hw, A_F2); + + if (f1 < f2) + df = MAX_F_CNT + 1 + f1 - f2; + else + df = f1 - f2; + + if (!df) + return; /* no complete frame in fifo */ + + z1 = Read_hfc16_stable(l1p->hw, A_Z1); + z2 = Read_hfc16(l1p->hw, A_Z2); + + z1 = z1 - z2 + 1; + if (z1 < 0) + z1 += 384; + + if (!(skb = dev_alloc_skb(MAX_D_FRAME_SIZE))) { + printk(KERN_INFO + "HFC-4S/8S: Could not allocate D/E " + "channel receive buffer"); + Write_hfc8(l1p->hw, A_INC_RES_FIFO, 2); + wait_busy(l1p->hw); + return; + } + + if (((z1 < 4) || (z1 > MAX_D_FRAME_SIZE))) { + if (skb) + dev_kfree_skb(skb); + /* remove errornous D frame */ + if (df == 1) { + /* reset fifo */ + Write_hfc8(l1p->hw, A_INC_RES_FIFO, 2); + wait_busy(l1p->hw); + return; + } else { + /* read errornous D frame */ + SetRegAddr(l1p->hw, A_FIFO_DATA0); + + while (z1 >= 4) { + fRead_hfc32(l1p->hw); + z1 -= 4; + } + + while (z1--) + fRead_hfc8(l1p->hw); + + Write_hfc8(l1p->hw, A_INC_RES_FIFO, 1); + wait_busy(l1p->hw); + return; + } + } + + cp = skb->data; + + SetRegAddr(l1p->hw, A_FIFO_DATA0); + + while (z1 >= 4) { + *((unsigned long *) cp) = fRead_hfc32(l1p->hw); + cp += 4; + z1 -= 4; + } + + while (z1--) + *cp++ = fRead_hfc8(l1p->hw); + + Write_hfc8(l1p->hw, A_INC_RES_FIFO, 1); /* increment f counter */ + wait_busy(l1p->hw); + + if (*(--cp)) { + dev_kfree_skb(skb); + } else { + skb->len = (cp - skb->data) - 2; + if (ech) + l1p->d_if.ifc.l1l2(&l1p->d_if.ifc, + PH_DATA_E | INDICATION, + skb); + else + l1p->d_if.ifc.l1l2(&l1p->d_if.ifc, + PH_DATA | INDICATION, + skb); + } + } while (1); +} /* rx_d_frame */ + +/*************************************************************/ +/* a B-frame has been received (perhaps not fully completed) */ +/*************************************************************/ +static void +rx_b_frame(struct hfc4s8s_btype *bch) +{ + int z1, z2, hdlc_complete; + u_char f1, f2; + struct hfc4s8s_l1 *l1 = bch->l1p; + struct sk_buff *skb; + + if (!l1->enabled || (bch->mode == L1_MODE_NULL)) + return; + + do { + /* RX Fifo */ + Write_hfc8(l1->hw, R_FIFO, + (l1->st_num * 8 + ((bch->bchan == 1) ? 1 : 3))); + wait_busy(l1->hw); + + if (bch->mode == L1_MODE_HDLC) { + f1 = Read_hfc8_stable(l1->hw, A_F1); + f2 = Read_hfc8(l1->hw, A_F2); + hdlc_complete = ((f1 ^ f2) & MAX_F_CNT); + } else + hdlc_complete = 0; + z1 = Read_hfc16_stable(l1->hw, A_Z1); + z2 = Read_hfc16(l1->hw, A_Z2); + z1 = (z1 - z2); + if (hdlc_complete) + z1++; + if (z1 < 0) + z1 += 384; + + if (!z1) + break; + + if (!(skb = bch->rx_skb)) { + if (! + (skb = + dev_alloc_skb((bch->mode == + L1_MODE_TRANS) ? z1 + : (MAX_B_FRAME_SIZE + 3)))) { + printk(KERN_ERR + "HFC-4S/8S: Could not allocate B " + "channel receive buffer"); + return; + } + bch->rx_ptr = skb->data; + bch->rx_skb = skb; + } + + skb->len = (bch->rx_ptr - skb->data) + z1; + + /* HDLC length check */ + if ((bch->mode == L1_MODE_HDLC) && + ((hdlc_complete && (skb->len < 4)) || + (skb->len > (MAX_B_FRAME_SIZE + 3)))) { + + skb->len = 0; + bch->rx_ptr = skb->data; + Write_hfc8(l1->hw, A_INC_RES_FIFO, 2); /* reset fifo */ + wait_busy(l1->hw); + return; + } + SetRegAddr(l1->hw, A_FIFO_DATA0); + + while (z1 >= 4) { + *((unsigned long *) bch->rx_ptr) = + fRead_hfc32(l1->hw); + bch->rx_ptr += 4; + z1 -= 4; + } + + while (z1--) + *(bch->rx_ptr++) = fRead_hfc8(l1->hw); + + if (hdlc_complete) { + /* increment f counter */ + Write_hfc8(l1->hw, A_INC_RES_FIFO, 1); + wait_busy(l1->hw); + + /* hdlc crc check */ + bch->rx_ptr--; + if (*bch->rx_ptr) { + skb->len = 0; + bch->rx_ptr = skb->data; + continue; + } + skb->len -= 3; + } + if (hdlc_complete || (bch->mode == L1_MODE_TRANS)) { + bch->rx_skb = NULL; + bch->rx_ptr = NULL; + bch->b_if.ifc.l1l2(&bch->b_if.ifc, + PH_DATA | INDICATION, skb); + } + + } while (1); +} /* rx_b_frame */ + +/********************************************/ +/* a D-frame has been/should be transmitted */ +/********************************************/ +static void +tx_d_frame(struct hfc4s8s_l1 *l1p) +{ + struct sk_buff *skb; + u_char f1, f2; + u_char *cp; + long cnt; + + if (l1p->l1_state != 7) + return; + + /* TX fifo */ + Write_hfc8(l1p->hw, R_FIFO, (l1p->st_num * 8 + 4)); + wait_busy(l1p->hw); + + f1 = Read_hfc8(l1p->hw, A_F1); + f2 = Read_hfc8_stable(l1p->hw, A_F2); + + if ((f1 ^ f2) & MAX_F_CNT) + return; /* fifo is still filled */ + + if (l1p->tx_cnt > 0) { + cnt = l1p->tx_cnt; + l1p->tx_cnt = 0; + l1p->d_if.ifc.l1l2(&l1p->d_if.ifc, PH_DATA | CONFIRM, + (void *) cnt); + } + + if ((skb = skb_dequeue(&l1p->d_tx_queue))) { + cp = skb->data; + cnt = skb->len; + SetRegAddr(l1p->hw, A_FIFO_DATA0); + + while (cnt >= 4) { + SetRegAddr(l1p->hw, A_FIFO_DATA0); + fWrite_hfc32(l1p->hw, *(unsigned long *) cp); + cp += 4; + cnt -= 4; + } + + while (cnt--) + fWrite_hfc8(l1p->hw, *cp++); + + l1p->tx_cnt = skb->truesize; + Write_hfc8(l1p->hw, A_INC_RES_FIFO, 1); /* increment f counter */ + wait_busy(l1p->hw); + + dev_kfree_skb(skb); + } +} /* tx_d_frame */ + +/******************************************************/ +/* a B-frame may be transmitted (or is not completed) */ +/******************************************************/ +static void +tx_b_frame(struct hfc4s8s_btype *bch) +{ + struct sk_buff *skb; + struct hfc4s8s_l1 *l1 = bch->l1p; + u_char *cp; + int cnt, max, hdlc_num; + long ack_len = 0; + + if (!l1->enabled || (bch->mode == L1_MODE_NULL)) + return; + + /* TX fifo */ + Write_hfc8(l1->hw, R_FIFO, + (l1->st_num * 8 + ((bch->bchan == 1) ? 0 : 2))); + wait_busy(l1->hw); + do { + + if (bch->mode == L1_MODE_HDLC) { + hdlc_num = Read_hfc8(l1->hw, A_F1) & MAX_F_CNT; + hdlc_num -= + (Read_hfc8_stable(l1->hw, A_F2) & MAX_F_CNT); + if (hdlc_num < 0) + hdlc_num += 16; + if (hdlc_num >= 15) + break; /* fifo still filled up with hdlc frames */ + } else + hdlc_num = 0; + + if (!(skb = bch->tx_skb)) { + if (!(skb = skb_dequeue(&bch->tx_queue))) { + l1->hw->mr.fifo_slow_timer_service[l1-> + st_num] + &= ~((bch->bchan == 1) ? 1 : 4); + break; /* list empty */ + } + bch->tx_skb = skb; + bch->tx_cnt = 0; + } + + if (!hdlc_num) + l1->hw->mr.fifo_slow_timer_service[l1->st_num] |= + ((bch->bchan == 1) ? 1 : 4); + else + l1->hw->mr.fifo_slow_timer_service[l1->st_num] &= + ~((bch->bchan == 1) ? 1 : 4); + + max = Read_hfc16_stable(l1->hw, A_Z2); + max -= Read_hfc16(l1->hw, A_Z1); + if (max <= 0) + max += 384; + max--; + + if (max < 16) + break; /* don't write to small amounts of bytes */ + + cnt = skb->len - bch->tx_cnt; + if (cnt > max) + cnt = max; + cp = skb->data + bch->tx_cnt; + bch->tx_cnt += cnt; + + SetRegAddr(l1->hw, A_FIFO_DATA0); + while (cnt >= 4) { + fWrite_hfc32(l1->hw, *(unsigned long *) cp); + cp += 4; + cnt -= 4; + } + + while (cnt--) + fWrite_hfc8(l1->hw, *cp++); + + if (bch->tx_cnt >= skb->len) { + if (bch->mode == L1_MODE_HDLC) { + /* increment f counter */ + Write_hfc8(l1->hw, A_INC_RES_FIFO, 1); + } + ack_len += skb->truesize; + bch->tx_skb = NULL; + bch->tx_cnt = 0; + dev_kfree_skb(skb); + } else + /* Re-Select */ + Write_hfc8(l1->hw, R_FIFO, + (l1->st_num * 8 + + ((bch->bchan == 1) ? 0 : 2))); + wait_busy(l1->hw); + } while (1); + + if (ack_len) + bch->b_if.ifc.l1l2((struct hisax_if *) &bch->b_if, + PH_DATA | CONFIRM, (void *) ack_len); +} /* tx_b_frame */ + +/*************************************/ +/* bottom half handler for interrupt */ +/*************************************/ +static void +hfc4s8s_bh(struct work_struct *work) +{ + hfc4s8s_hw *hw = container_of(work, hfc4s8s_hw, tqueue); + u_char b; + struct hfc4s8s_l1 *l1p; + volatile u_char *fifo_stat; + int idx; + + /* handle layer 1 state changes */ + b = 1; + l1p = hw->l1; + while (b) { + if ((b & hw->mr.r_irq_statech)) { + /* reset l1 event */ + hw->mr.r_irq_statech &= ~b; + if (l1p->enabled) { + if (l1p->nt_mode) { + u_char oldstate = l1p->l1_state; + + Write_hfc8(l1p->hw, R_ST_SEL, + l1p->st_num); + l1p->l1_state = + Read_hfc8(l1p->hw, + A_ST_RD_STA) & 0xf; + + if ((oldstate == 3) + && (l1p->l1_state != 3)) + l1p->d_if.ifc.l1l2(&l1p-> + d_if. + ifc, + PH_DEACTIVATE + | + INDICATION, + NULL); + + if (l1p->l1_state != 2) { + del_timer(&l1p->l1_timer); + if (l1p->l1_state == 3) { + l1p->d_if.ifc. + l1l2(&l1p-> + d_if.ifc, + PH_ACTIVATE + | + INDICATION, + NULL); + } + } else { + /* allow transition */ + Write_hfc8(hw, A_ST_WR_STA, + M_SET_G2_G3); + mod_timer(&l1p->l1_timer, + jiffies + + L1_TIMER_T1); + } + printk(KERN_INFO + "HFC-4S/8S: NT ch %d l1 state %d -> %d\n", + l1p->st_num, oldstate, + l1p->l1_state); + } else { + u_char oldstate = l1p->l1_state; + + Write_hfc8(l1p->hw, R_ST_SEL, + l1p->st_num); + l1p->l1_state = + Read_hfc8(l1p->hw, + A_ST_RD_STA) & 0xf; + + if (((l1p->l1_state == 3) && + ((oldstate == 7) || + (oldstate == 8))) || + ((timer_pending + (&l1p->l1_timer)) + && (l1p->l1_state == 8))) { + mod_timer(&l1p->l1_timer, + L1_TIMER_T4 + + jiffies); + } else { + if (l1p->l1_state == 7) { + del_timer(&l1p-> + l1_timer); + l1p->d_if.ifc. + l1l2(&l1p-> + d_if.ifc, + PH_ACTIVATE + | + INDICATION, + NULL); + tx_d_frame(l1p); + } + if (l1p->l1_state == 3) { + if (oldstate != 3) + l1p->d_if. + ifc. + l1l2 + (&l1p-> + d_if. + ifc, + PH_DEACTIVATE + | + INDICATION, + NULL); + } + } + printk(KERN_INFO + "HFC-4S/8S: TE %d ch %d l1 state %d -> %d\n", + l1p->hw->cardnum, + l1p->st_num, oldstate, + l1p->l1_state); + } + } + } + b <<= 1; + l1p++; + } + + /* now handle the fifos */ + idx = 0; + fifo_stat = hw->mr.r_irq_fifo_blx; + l1p = hw->l1; + while (idx < hw->driver_data.max_st_ports) { + + if (hw->mr.timer_irq) { + *fifo_stat |= hw->mr.fifo_rx_trans_enables[idx]; + if (hw->fifo_sched_cnt <= 0) { + *fifo_stat |= + hw->mr.fifo_slow_timer_service[l1p-> + st_num]; + } + } + /* ignore fifo 6 (TX E fifo) */ + *fifo_stat &= 0xff - 0x40; + + while (*fifo_stat) { + + if (!l1p->nt_mode) { + /* RX Fifo has data to read */ + if ((*fifo_stat & 0x20)) { + *fifo_stat &= ~0x20; + rx_d_frame(l1p, 0); + } + /* E Fifo has data to read */ + if ((*fifo_stat & 0x80)) { + *fifo_stat &= ~0x80; + rx_d_frame(l1p, 1); + } + /* TX Fifo completed send */ + if ((*fifo_stat & 0x10)) { + *fifo_stat &= ~0x10; + tx_d_frame(l1p); + } + } + /* B1 RX Fifo has data to read */ + if ((*fifo_stat & 0x2)) { + *fifo_stat &= ~0x2; + rx_b_frame(l1p->b_ch); + } + /* B1 TX Fifo has send completed */ + if ((*fifo_stat & 0x1)) { + *fifo_stat &= ~0x1; + tx_b_frame(l1p->b_ch); + } + /* B2 RX Fifo has data to read */ + if ((*fifo_stat & 0x8)) { + *fifo_stat &= ~0x8; + rx_b_frame(l1p->b_ch + 1); + } + /* B2 TX Fifo has send completed */ + if ((*fifo_stat & 0x4)) { + *fifo_stat &= ~0x4; + tx_b_frame(l1p->b_ch + 1); + } + } + fifo_stat++; + l1p++; + idx++; + } + + if (hw->fifo_sched_cnt <= 0) + hw->fifo_sched_cnt += (1 << (7 - TRANS_TIMER_MODE)); + hw->mr.timer_irq = 0; /* clear requested timer irq */ +} /* hfc4s8s_bh */ + +/*********************/ +/* interrupt handler */ +/*********************/ +static irqreturn_t +hfc4s8s_interrupt(int intno, void *dev_id) +{ + hfc4s8s_hw *hw = dev_id; + u_char b, ovr; + volatile u_char *ovp; + int idx; + u_char old_ioreg; + + if (!hw || !(hw->mr.r_irq_ctrl & M_GLOB_IRQ_EN)) + return IRQ_NONE; + + /* read current selected regsister */ + old_ioreg = GetRegAddr(hw); + + /* Layer 1 State change */ + hw->mr.r_irq_statech |= + (Read_hfc8(hw, R_SCI) & hw->mr.r_irqmsk_statchg); + if (! + (b = (Read_hfc8(hw, R_STATUS) & (M_MISC_IRQSTA | M_FR_IRQSTA))) + && !hw->mr.r_irq_statech) { + SetRegAddr(hw, old_ioreg); + return IRQ_NONE; + } + + /* timer event */ + if (Read_hfc8(hw, R_IRQ_MISC) & M_TI_IRQ) { + hw->mr.timer_irq = 1; + hw->fifo_sched_cnt--; + } + + /* FIFO event */ + if ((ovr = Read_hfc8(hw, R_IRQ_OVIEW))) { + hw->mr.r_irq_oview |= ovr; + idx = R_IRQ_FIFO_BL0; + ovp = hw->mr.r_irq_fifo_blx; + while (ovr) { + if ((ovr & 1)) { + *ovp |= Read_hfc8(hw, idx); + } + ovp++; + idx++; + ovr >>= 1; + } + } + + /* queue the request to allow other cards to interrupt */ + schedule_work(&hw->tqueue); + + SetRegAddr(hw, old_ioreg); + return IRQ_HANDLED; +} /* hfc4s8s_interrupt */ + +/***********************************************************************/ +/* reset the complete chip, don't release the chips irq but disable it */ +/***********************************************************************/ +static void +chipreset(hfc4s8s_hw *hw) +{ + u_long flags; + + spin_lock_irqsave(&hw->lock, flags); + Write_hfc8(hw, R_CTRL, 0); /* use internal RAM */ + Write_hfc8(hw, R_RAM_MISC, 0); /* 32k*8 RAM */ + Write_hfc8(hw, R_FIFO_MD, 0); /* fifo mode 386 byte/fifo simple mode */ + Write_hfc8(hw, R_CIRM, M_SRES); /* reset chip */ + hw->mr.r_irq_ctrl = 0; /* interrupt is inactive */ + spin_unlock_irqrestore(&hw->lock, flags); + + udelay(3); + Write_hfc8(hw, R_CIRM, 0); /* disable reset */ + wait_busy(hw); + + Write_hfc8(hw, R_PCM_MD0, M_PCM_MD); /* master mode */ + Write_hfc8(hw, R_RAM_MISC, M_FZ_MD); /* transmit fifo option */ + if (hw->driver_data.clock_mode == 1) + Write_hfc8(hw, R_BRG_PCM_CFG, M_PCM_CLK); /* PCM clk / 2 */ + Write_hfc8(hw, R_TI_WD, TRANS_TIMER_MODE); /* timer interval */ + + memset(&hw->mr, 0, sizeof(hw->mr)); +} /* chipreset */ + +/********************************************/ +/* disable/enable hardware in nt or te mode */ +/********************************************/ +static void +hfc_hardware_enable(hfc4s8s_hw *hw, int enable, int nt_mode) +{ + u_long flags; + char if_name[40]; + int i; + + if (enable) { + /* save system vars */ + hw->nt_mode = nt_mode; + + /* enable fifo and state irqs, but not global irq enable */ + hw->mr.r_irq_ctrl = M_FIFO_IRQ; + Write_hfc8(hw, R_IRQ_CTRL, hw->mr.r_irq_ctrl); + hw->mr.r_irqmsk_statchg = 0; + Write_hfc8(hw, R_SCI_MSK, hw->mr.r_irqmsk_statchg); + Write_hfc8(hw, R_PWM_MD, 0x80); + Write_hfc8(hw, R_PWM1, 26); + if (!nt_mode) + Write_hfc8(hw, R_ST_SYNC, M_AUTO_SYNC); + + /* enable the line interfaces and fifos */ + for (i = 0; i < hw->driver_data.max_st_ports; i++) { + hw->mr.r_irqmsk_statchg |= (1 << i); + Write_hfc8(hw, R_SCI_MSK, hw->mr.r_irqmsk_statchg); + Write_hfc8(hw, R_ST_SEL, i); + Write_hfc8(hw, A_ST_CLK_DLY, + ((nt_mode) ? CLKDEL_NT : CLKDEL_TE)); + hw->mr.r_ctrl0 = ((nt_mode) ? CTRL0_NT : CTRL0_TE); + Write_hfc8(hw, A_ST_CTRL0, hw->mr.r_ctrl0); + Write_hfc8(hw, A_ST_CTRL2, 3); + Write_hfc8(hw, A_ST_WR_STA, 0); /* enable state machine */ + + hw->l1[i].enabled = 1; + hw->l1[i].nt_mode = nt_mode; + + if (!nt_mode) { + /* setup E-fifo */ + Write_hfc8(hw, R_FIFO, i * 8 + 7); /* E fifo */ + wait_busy(hw); + Write_hfc8(hw, A_CON_HDLC, 0x11); /* HDLC mode, 1 fill, connect ST */ + Write_hfc8(hw, A_SUBCH_CFG, 2); /* only 2 bits */ + Write_hfc8(hw, A_IRQ_MSK, 1); /* enable interrupt */ + Write_hfc8(hw, A_INC_RES_FIFO, 2); /* reset fifo */ + wait_busy(hw); + + /* setup D RX-fifo */ + Write_hfc8(hw, R_FIFO, i * 8 + 5); /* RX fifo */ + wait_busy(hw); + Write_hfc8(hw, A_CON_HDLC, 0x11); /* HDLC mode, 1 fill, connect ST */ + Write_hfc8(hw, A_SUBCH_CFG, 2); /* only 2 bits */ + Write_hfc8(hw, A_IRQ_MSK, 1); /* enable interrupt */ + Write_hfc8(hw, A_INC_RES_FIFO, 2); /* reset fifo */ + wait_busy(hw); + + /* setup D TX-fifo */ + Write_hfc8(hw, R_FIFO, i * 8 + 4); /* TX fifo */ + wait_busy(hw); + Write_hfc8(hw, A_CON_HDLC, 0x11); /* HDLC mode, 1 fill, connect ST */ + Write_hfc8(hw, A_SUBCH_CFG, 2); /* only 2 bits */ + Write_hfc8(hw, A_IRQ_MSK, 1); /* enable interrupt */ + Write_hfc8(hw, A_INC_RES_FIFO, 2); /* reset fifo */ + wait_busy(hw); + } + + sprintf(if_name, "hfc4s8s_%d%d_", hw->cardnum, i); + + if (hisax_register + (&hw->l1[i].d_if, hw->l1[i].b_table, if_name, + ((nt_mode) ? 3 : 2))) { + + hw->l1[i].enabled = 0; + hw->mr.r_irqmsk_statchg &= ~(1 << i); + Write_hfc8(hw, R_SCI_MSK, + hw->mr.r_irqmsk_statchg); + printk(KERN_INFO + "HFC-4S/8S: Unable to register S/T device %s, break\n", + if_name); + break; + } + } + spin_lock_irqsave(&hw->lock, flags); + hw->mr.r_irq_ctrl |= M_GLOB_IRQ_EN; + Write_hfc8(hw, R_IRQ_CTRL, hw->mr.r_irq_ctrl); + spin_unlock_irqrestore(&hw->lock, flags); + } else { + /* disable hardware */ + spin_lock_irqsave(&hw->lock, flags); + hw->mr.r_irq_ctrl &= ~M_GLOB_IRQ_EN; + Write_hfc8(hw, R_IRQ_CTRL, hw->mr.r_irq_ctrl); + spin_unlock_irqrestore(&hw->lock, flags); + + for (i = hw->driver_data.max_st_ports - 1; i >= 0; i--) { + hw->l1[i].enabled = 0; + hisax_unregister(&hw->l1[i].d_if); + del_timer(&hw->l1[i].l1_timer); + skb_queue_purge(&hw->l1[i].d_tx_queue); + skb_queue_purge(&hw->l1[i].b_ch[0].tx_queue); + skb_queue_purge(&hw->l1[i].b_ch[1].tx_queue); + } + chipreset(hw); + } +} /* hfc_hardware_enable */ + +/******************************************/ +/* disable memory mapped ports / io ports */ +/******************************************/ +static void +release_pci_ports(hfc4s8s_hw *hw) +{ + pci_write_config_word(hw->pdev, PCI_COMMAND, 0); + if (hw->iobase) + release_region(hw->iobase, 8); +} + +/*****************************************/ +/* enable memory mapped ports / io ports */ +/*****************************************/ +static void +enable_pci_ports(hfc4s8s_hw *hw) +{ + pci_write_config_word(hw->pdev, PCI_COMMAND, PCI_ENA_REGIO); +} + +/*************************************/ +/* initialise the HFC-4s/8s hardware */ +/* return 0 on success. */ +/*************************************/ +static int +setup_instance(hfc4s8s_hw *hw) +{ + int err = -EIO; + int i; + + for (i = 0; i < HFC_MAX_ST; i++) { + struct hfc4s8s_l1 *l1p; + + l1p = hw->l1 + i; + spin_lock_init(&l1p->lock); + l1p->hw = hw; + timer_setup(&l1p->l1_timer, hfc_l1_timer, 0); + l1p->st_num = i; + skb_queue_head_init(&l1p->d_tx_queue); + l1p->d_if.ifc.priv = hw->l1 + i; + l1p->d_if.ifc.l2l1 = (void *) dch_l2l1; + + spin_lock_init(&l1p->b_ch[0].lock); + l1p->b_ch[0].b_if.ifc.l2l1 = (void *) bch_l2l1; + l1p->b_ch[0].b_if.ifc.priv = (void *) &l1p->b_ch[0]; + l1p->b_ch[0].l1p = hw->l1 + i; + l1p->b_ch[0].bchan = 1; + l1p->b_table[0] = &l1p->b_ch[0].b_if; + skb_queue_head_init(&l1p->b_ch[0].tx_queue); + + spin_lock_init(&l1p->b_ch[1].lock); + l1p->b_ch[1].b_if.ifc.l2l1 = (void *) bch_l2l1; + l1p->b_ch[1].b_if.ifc.priv = (void *) &l1p->b_ch[1]; + l1p->b_ch[1].l1p = hw->l1 + i; + l1p->b_ch[1].bchan = 2; + l1p->b_table[1] = &l1p->b_ch[1].b_if; + skb_queue_head_init(&l1p->b_ch[1].tx_queue); + } + + enable_pci_ports(hw); + chipreset(hw); + + i = Read_hfc8(hw, R_CHIP_ID) >> CHIP_ID_SHIFT; + if (i != hw->driver_data.chip_id) { + printk(KERN_INFO + "HFC-4S/8S: invalid chip id 0x%x instead of 0x%x, card ignored\n", + i, hw->driver_data.chip_id); + goto out; + } + + i = Read_hfc8(hw, R_CHIP_RV) & 0xf; + if (!i) { + printk(KERN_INFO + "HFC-4S/8S: chip revision 0 not supported, card ignored\n"); + goto out; + } + + INIT_WORK(&hw->tqueue, hfc4s8s_bh); + + if (request_irq + (hw->irq, hfc4s8s_interrupt, IRQF_SHARED, hw->card_name, hw)) { + printk(KERN_INFO + "HFC-4S/8S: unable to alloc irq %d, card ignored\n", + hw->irq); + goto out; + } + printk(KERN_INFO + "HFC-4S/8S: found PCI card at iobase 0x%x, irq %d\n", + hw->iobase, hw->irq); + + hfc_hardware_enable(hw, 1, 0); + + return (0); + +out: + hw->irq = 0; + release_pci_ports(hw); + kfree(hw); + return (err); +} + +/*****************************************/ +/* PCI hotplug interface: probe new card */ +/*****************************************/ +static int +hfc4s8s_probe(struct pci_dev *pdev, const struct pci_device_id *ent) +{ + int err = -ENOMEM; + hfc4s8s_param *driver_data = (hfc4s8s_param *) ent->driver_data; + hfc4s8s_hw *hw; + + if (!(hw = kzalloc(sizeof(hfc4s8s_hw), GFP_ATOMIC))) { + printk(KERN_ERR "No kmem for HFC-4S/8S card\n"); + return (err); + } + + hw->pdev = pdev; + err = pci_enable_device(pdev); + + if (err) + goto out; + + hw->cardnum = card_cnt; + sprintf(hw->card_name, "hfc4s8s_%d", hw->cardnum); + printk(KERN_INFO "HFC-4S/8S: found adapter %s (%s) at %s\n", + driver_data->device_name, hw->card_name, pci_name(pdev)); + + spin_lock_init(&hw->lock); + + hw->driver_data = *driver_data; + hw->irq = pdev->irq; + hw->iobase = pci_resource_start(pdev, 0); + + if (!request_region(hw->iobase, 8, hw->card_name)) { + printk(KERN_INFO + "HFC-4S/8S: failed to request address space at 0x%04x\n", + hw->iobase); + err = -EBUSY; + goto out; + } + + pci_set_drvdata(pdev, hw); + err = setup_instance(hw); + if (!err) + card_cnt++; + return (err); + +out: + kfree(hw); + return (err); +} + +/**************************************/ +/* PCI hotplug interface: remove card */ +/**************************************/ +static void +hfc4s8s_remove(struct pci_dev *pdev) +{ + hfc4s8s_hw *hw = pci_get_drvdata(pdev); + + printk(KERN_INFO "HFC-4S/8S: removing card %d\n", hw->cardnum); + hfc_hardware_enable(hw, 0, 0); + + if (hw->irq) + free_irq(hw->irq, hw); + hw->irq = 0; + release_pci_ports(hw); + + card_cnt--; + pci_disable_device(pdev); + kfree(hw); + return; +} + +static struct pci_driver hfc4s8s_driver = { + .name = "hfc4s8s_l1", + .probe = hfc4s8s_probe, + .remove = hfc4s8s_remove, + .id_table = hfc4s8s_ids, +}; + +/**********************/ +/* driver Module init */ +/**********************/ +static int __init +hfc4s8s_module_init(void) +{ + int err; + + printk(KERN_INFO + "HFC-4S/8S: Layer 1 driver module for HFC-4S/8S isdn chips, %s\n", + hfc4s8s_rev); + printk(KERN_INFO + "HFC-4S/8S: (C) 2003 Cornelius Consult, www.cornelius-consult.de\n"); + + card_cnt = 0; + + err = pci_register_driver(&hfc4s8s_driver); + if (err < 0) { + goto out; + } + printk(KERN_INFO "HFC-4S/8S: found %d cards\n", card_cnt); + + return 0; +out: + return (err); +} /* hfc4s8s_init_hw */ + +/*************************************/ +/* driver module exit : */ +/* release the HFC-4s/8s hardware */ +/*************************************/ +static void __exit +hfc4s8s_module_exit(void) +{ + pci_unregister_driver(&hfc4s8s_driver); + printk(KERN_INFO "HFC-4S/8S: module removed\n"); +} /* hfc4s8s_release_hw */ + +module_init(hfc4s8s_module_init); +module_exit(hfc4s8s_module_exit); diff --git a/drivers/isdn/hisax/hfc4s8s_l1.h b/drivers/isdn/hisax/hfc4s8s_l1.h new file mode 100644 index 000000000..4665b9d5d --- /dev/null +++ b/drivers/isdn/hisax/hfc4s8s_l1.h @@ -0,0 +1,89 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/***************************************************************/ +/* $Id: hfc4s8s_l1.h,v 1.1 2005/02/02 17:28:55 martinb1 Exp $ */ +/* */ +/* This file is a minimal required extraction of hfc48scu.h */ +/* (Genero 3.2, HFC XML 1.7a for HFC-E1, HFC-4S and HFC-8S) */ +/* */ +/* To get this complete register description contact */ +/* Cologne Chip AG : */ +/* Internet: http://www.colognechip.com/ */ +/* E-Mail: info@colognechip.com */ +/***************************************************************/ + +#ifndef _HFC4S8S_L1_H_ +#define _HFC4S8S_L1_H_ + + +/* + * include Genero generated HFC-4S/8S header file hfc48scu.h + * for complete register description. This will define _HFC48SCU_H_ + * to prevent redefinitions + */ + +// #include "hfc48scu.h" + +#ifndef _HFC48SCU_H_ +#define _HFC48SCU_H_ + +#ifndef PCI_VENDOR_ID_CCD +#define PCI_VENDOR_ID_CCD 0x1397 +#endif + +#define CHIP_ID_4S 0x0C +#define CHIP_ID_8S 0x08 +#define PCI_DEVICE_ID_4S 0x08B4 +#define PCI_DEVICE_ID_8S 0x16B8 + +#define R_IRQ_MISC 0x11 +#define M_TI_IRQ 0x02 +#define A_ST_RD_STA 0x30 +#define A_ST_WR_STA 0x30 +#define M_SET_G2_G3 0x80 +#define A_ST_CTRL0 0x31 +#define A_ST_CTRL2 0x33 +#define A_ST_CLK_DLY 0x37 +#define A_Z1 0x04 +#define A_Z2 0x06 +#define R_CIRM 0x00 +#define M_SRES 0x08 +#define R_CTRL 0x01 +#define R_BRG_PCM_CFG 0x02 +#define M_PCM_CLK 0x20 +#define R_RAM_MISC 0x0C +#define M_FZ_MD 0x80 +#define R_FIFO_MD 0x0D +#define A_INC_RES_FIFO 0x0E +#define R_FIFO 0x0F +#define A_F1 0x0C +#define A_F2 0x0D +#define R_IRQ_OVIEW 0x10 +#define R_CHIP_ID 0x16 +#define R_STATUS 0x1C +#define M_BUSY 0x01 +#define M_MISC_IRQSTA 0x40 +#define M_FR_IRQSTA 0x80 +#define R_CHIP_RV 0x1F +#define R_IRQ_CTRL 0x13 +#define M_FIFO_IRQ 0x01 +#define M_GLOB_IRQ_EN 0x08 +#define R_PCM_MD0 0x14 +#define M_PCM_MD 0x01 +#define A_FIFO_DATA0 0x80 +#define R_TI_WD 0x1A +#define R_PWM1 0x39 +#define R_PWM_MD 0x46 +#define R_IRQ_FIFO_BL0 0xC8 +#define A_CON_HDLC 0xFA +#define A_SUBCH_CFG 0xFB +#define A_IRQ_MSK 0xFF +#define R_SCI_MSK 0x12 +#define R_ST_SEL 0x16 +#define R_ST_SYNC 0x17 +#define M_AUTO_SYNC 0x08 +#define R_SCI 0x12 +#define R_IRQMSK_MISC 0x11 +#define M_TI_IRQMSK 0x02 + +#endif /* _HFC4S8S_L1_H_ */ +#endif /* _HFC48SCU_H_ */ diff --git a/drivers/isdn/hisax/hfc_2bds0.c b/drivers/isdn/hisax/hfc_2bds0.c new file mode 100644 index 000000000..3715fa034 --- /dev/null +++ b/drivers/isdn/hisax/hfc_2bds0.c @@ -0,0 +1,1078 @@ +/* $Id: hfc_2bds0.c,v 1.18.2.6 2004/02/11 13:21:33 keil Exp $ + * + * specific routines for CCD's HFC 2BDS0 + * + * Author Karsten Keil + * Copyright by Karsten Keil <keil@isdn4linux.de> + * + * This software may be used and distributed according to the terms + * of the GNU General Public License, incorporated herein by reference. + * + */ + +#include <linux/init.h> +#include <linux/sched.h> +#include <linux/slab.h> +#include "hisax.h" +#include "hfc_2bds0.h" +#include "isdnl1.h" +#include <linux/interrupt.h> +/* + #define KDEBUG_DEF + #include "kdebug.h" +*/ + +#define byteout(addr, val) outb(val, addr) +#define bytein(addr) inb(addr) + +static void +dummyf(struct IsdnCardState *cs, u_char *data, int size) +{ + printk(KERN_WARNING "HiSax: hfcd dummy fifo called\n"); +} + +static inline u_char +ReadReg(struct IsdnCardState *cs, int data, u_char reg) +{ + register u_char ret; + + if (data) { + if (cs->hw.hfcD.cip != reg) { + cs->hw.hfcD.cip = reg; + byteout(cs->hw.hfcD.addr | 1, reg); + } + ret = bytein(cs->hw.hfcD.addr); +#ifdef HFC_REG_DEBUG + if (cs->debug & L1_DEB_HSCX_FIFO && (data != 2)) + debugl1(cs, "t3c RD %02x %02x", reg, ret); +#endif + } else + ret = bytein(cs->hw.hfcD.addr | 1); + return (ret); +} + +static inline void +WriteReg(struct IsdnCardState *cs, int data, u_char reg, u_char value) +{ + if (cs->hw.hfcD.cip != reg) { + cs->hw.hfcD.cip = reg; + byteout(cs->hw.hfcD.addr | 1, reg); + } + if (data) + byteout(cs->hw.hfcD.addr, value); +#ifdef HFC_REG_DEBUG + if (cs->debug & L1_DEB_HSCX_FIFO && (data != HFCD_DATA_NODEB)) + debugl1(cs, "t3c W%c %02x %02x", data ? 'D' : 'C', reg, value); +#endif +} + +/* Interface functions */ + +static u_char +readreghfcd(struct IsdnCardState *cs, u_char offset) +{ + return (ReadReg(cs, HFCD_DATA, offset)); +} + +static void +writereghfcd(struct IsdnCardState *cs, u_char offset, u_char value) +{ + WriteReg(cs, HFCD_DATA, offset, value); +} + +static inline int +WaitForBusy(struct IsdnCardState *cs) +{ + int to = 130; + + while (!(ReadReg(cs, HFCD_DATA, HFCD_STAT) & HFCD_BUSY) && to) { + udelay(1); + to--; + } + if (!to) + printk(KERN_WARNING "HiSax: WaitForBusy timeout\n"); + return (to); +} + +static inline int +WaitNoBusy(struct IsdnCardState *cs) +{ + int to = 130; + + while ((ReadReg(cs, HFCD_STATUS, HFCD_STATUS) & HFCD_BUSY) && to) { + udelay(1); + to--; + } + if (!to) + printk(KERN_WARNING "HiSax: WaitNoBusy timeout\n"); + return (to); +} + +static int +SelFiFo(struct IsdnCardState *cs, u_char FiFo) +{ + u_char cip; + + if (cs->hw.hfcD.fifo == FiFo) + return (1); + switch (FiFo) { + case 0: cip = HFCB_FIFO | HFCB_Z1 | HFCB_SEND | HFCB_B1; + break; + case 1: cip = HFCB_FIFO | HFCB_Z1 | HFCB_REC | HFCB_B1; + break; + case 2: cip = HFCB_FIFO | HFCB_Z1 | HFCB_SEND | HFCB_B2; + break; + case 3: cip = HFCB_FIFO | HFCB_Z1 | HFCB_REC | HFCB_B2; + break; + case 4: cip = HFCD_FIFO | HFCD_Z1 | HFCD_SEND; + break; + case 5: cip = HFCD_FIFO | HFCD_Z1 | HFCD_REC; + break; + default: + debugl1(cs, "SelFiFo Error"); + return (0); + } + cs->hw.hfcD.fifo = FiFo; + WaitNoBusy(cs); + cs->BC_Write_Reg(cs, HFCD_DATA, cip, 0); + WaitForBusy(cs); + return (2); +} + +static int +GetFreeFifoBytes_B(struct BCState *bcs) +{ + int s; + + if (bcs->hw.hfc.f1 == bcs->hw.hfc.f2) + return (bcs->cs->hw.hfcD.bfifosize); + s = bcs->hw.hfc.send[bcs->hw.hfc.f1] - bcs->hw.hfc.send[bcs->hw.hfc.f2]; + if (s <= 0) + s += bcs->cs->hw.hfcD.bfifosize; + s = bcs->cs->hw.hfcD.bfifosize - s; + return (s); +} + +static int +GetFreeFifoBytes_D(struct IsdnCardState *cs) +{ + int s; + + if (cs->hw.hfcD.f1 == cs->hw.hfcD.f2) + return (cs->hw.hfcD.dfifosize); + s = cs->hw.hfcD.send[cs->hw.hfcD.f1] - cs->hw.hfcD.send[cs->hw.hfcD.f2]; + if (s <= 0) + s += cs->hw.hfcD.dfifosize; + s = cs->hw.hfcD.dfifosize - s; + return (s); +} + +static int +ReadZReg(struct IsdnCardState *cs, u_char reg) +{ + int val; + + WaitNoBusy(cs); + val = 256 * ReadReg(cs, HFCD_DATA, reg | HFCB_Z_HIGH); + WaitNoBusy(cs); + val += ReadReg(cs, HFCD_DATA, reg | HFCB_Z_LOW); + return (val); +} + +static struct sk_buff +*hfc_empty_fifo(struct BCState *bcs, int count) +{ + u_char *ptr; + struct sk_buff *skb; + struct IsdnCardState *cs = bcs->cs; + int idx; + int chksum; + u_char stat, cip; + + if ((cs->debug & L1_DEB_HSCX) && !(cs->debug & L1_DEB_HSCX_FIFO)) + debugl1(cs, "hfc_empty_fifo"); + idx = 0; + if (count > HSCX_BUFMAX + 3) { + if (cs->debug & L1_DEB_WARN) + debugl1(cs, "hfc_empty_fifo: incoming packet too large"); + cip = HFCB_FIFO | HFCB_FIFO_OUT | HFCB_REC | HFCB_CHANNEL(bcs->channel); + while (idx++ < count) { + WaitNoBusy(cs); + ReadReg(cs, HFCD_DATA_NODEB, cip); + } + skb = NULL; + } else if (count < 4) { + if (cs->debug & L1_DEB_WARN) + debugl1(cs, "hfc_empty_fifo: incoming packet too small"); + cip = HFCB_FIFO | HFCB_FIFO_OUT | HFCB_REC | HFCB_CHANNEL(bcs->channel); +#ifdef ERROR_STATISTIC + bcs->err_inv++; +#endif + while ((idx++ < count) && WaitNoBusy(cs)) + ReadReg(cs, HFCD_DATA_NODEB, cip); + skb = NULL; + } else if (!(skb = dev_alloc_skb(count - 3))) + printk(KERN_WARNING "HFC: receive out of memory\n"); + else { + ptr = skb_put(skb, count - 3); + idx = 0; + cip = HFCB_FIFO | HFCB_FIFO_OUT | HFCB_REC | HFCB_CHANNEL(bcs->channel); + while (idx < (count - 3)) { + if (!WaitNoBusy(cs)) + break; + *ptr = ReadReg(cs, HFCD_DATA_NODEB, cip); + ptr++; + idx++; + } + if (idx != count - 3) { + debugl1(cs, "RFIFO BUSY error"); + printk(KERN_WARNING "HFC FIFO channel %d BUSY Error\n", bcs->channel); + dev_kfree_skb_irq(skb); + skb = NULL; + } else { + WaitNoBusy(cs); + chksum = (ReadReg(cs, HFCD_DATA, cip) << 8); + WaitNoBusy(cs); + chksum += ReadReg(cs, HFCD_DATA, cip); + WaitNoBusy(cs); + stat = ReadReg(cs, HFCD_DATA, cip); + if (cs->debug & L1_DEB_HSCX) + debugl1(cs, "hfc_empty_fifo %d chksum %x stat %x", + bcs->channel, chksum, stat); + if (stat) { + debugl1(cs, "FIFO CRC error"); + dev_kfree_skb_irq(skb); + skb = NULL; +#ifdef ERROR_STATISTIC + bcs->err_crc++; +#endif + } + } + } + WaitForBusy(cs); + WaitNoBusy(cs); + stat = ReadReg(cs, HFCD_DATA, HFCB_FIFO | HFCB_F2_INC | + HFCB_REC | HFCB_CHANNEL(bcs->channel)); + WaitForBusy(cs); + return (skb); +} + +static void +hfc_fill_fifo(struct BCState *bcs) +{ + struct IsdnCardState *cs = bcs->cs; + int idx, fcnt; + int count; + u_char cip; + + if (!bcs->tx_skb) + return; + if (bcs->tx_skb->len <= 0) + return; + SelFiFo(cs, HFCB_SEND | HFCB_CHANNEL(bcs->channel)); + cip = HFCB_FIFO | HFCB_F1 | HFCB_SEND | HFCB_CHANNEL(bcs->channel); + WaitNoBusy(cs); + bcs->hw.hfc.f1 = ReadReg(cs, HFCD_DATA, cip); + WaitNoBusy(cs); + cip = HFCB_FIFO | HFCB_F2 | HFCB_SEND | HFCB_CHANNEL(bcs->channel); + WaitNoBusy(cs); + bcs->hw.hfc.f2 = ReadReg(cs, HFCD_DATA, cip); + bcs->hw.hfc.send[bcs->hw.hfc.f1] = ReadZReg(cs, HFCB_FIFO | HFCB_Z1 | HFCB_SEND | HFCB_CHANNEL(bcs->channel)); + if (cs->debug & L1_DEB_HSCX) + debugl1(cs, "hfc_fill_fifo %d f1(%d) f2(%d) z1(%x)", + bcs->channel, bcs->hw.hfc.f1, bcs->hw.hfc.f2, + bcs->hw.hfc.send[bcs->hw.hfc.f1]); + fcnt = bcs->hw.hfc.f1 - bcs->hw.hfc.f2; + if (fcnt < 0) + fcnt += 32; + if (fcnt > 30) { + if (cs->debug & L1_DEB_HSCX) + debugl1(cs, "hfc_fill_fifo more as 30 frames"); + return; + } + count = GetFreeFifoBytes_B(bcs); + if (cs->debug & L1_DEB_HSCX) + debugl1(cs, "hfc_fill_fifo %d count(%u/%d),%lx", + bcs->channel, bcs->tx_skb->len, + count, current->state); + if (count < bcs->tx_skb->len) { + if (cs->debug & L1_DEB_HSCX) + debugl1(cs, "hfc_fill_fifo no fifo mem"); + return; + } + cip = HFCB_FIFO | HFCB_FIFO_IN | HFCB_SEND | HFCB_CHANNEL(bcs->channel); + idx = 0; + WaitForBusy(cs); + WaitNoBusy(cs); + WriteReg(cs, HFCD_DATA_NODEB, cip, bcs->tx_skb->data[idx++]); + while (idx < bcs->tx_skb->len) { + if (!WaitNoBusy(cs)) + break; + WriteReg(cs, HFCD_DATA_NODEB, cip, bcs->tx_skb->data[idx]); + idx++; + } + if (idx != bcs->tx_skb->len) { + debugl1(cs, "FIFO Send BUSY error"); + printk(KERN_WARNING "HFC S FIFO channel %d BUSY Error\n", bcs->channel); + } else { + bcs->tx_cnt -= bcs->tx_skb->len; + if (test_bit(FLG_LLI_L1WAKEUP, &bcs->st->lli.flag) && + (PACKET_NOACK != bcs->tx_skb->pkt_type)) { + u_long flags; + spin_lock_irqsave(&bcs->aclock, flags); + bcs->ackcnt += bcs->tx_skb->len; + spin_unlock_irqrestore(&bcs->aclock, flags); + schedule_event(bcs, B_ACKPENDING); + } + dev_kfree_skb_any(bcs->tx_skb); + bcs->tx_skb = NULL; + } + WaitForBusy(cs); + WaitNoBusy(cs); + ReadReg(cs, HFCD_DATA, HFCB_FIFO | HFCB_F1_INC | HFCB_SEND | HFCB_CHANNEL(bcs->channel)); + WaitForBusy(cs); + test_and_clear_bit(BC_FLG_BUSY, &bcs->Flag); + return; +} + +static void +hfc_send_data(struct BCState *bcs) +{ + struct IsdnCardState *cs = bcs->cs; + + if (!test_and_set_bit(FLG_LOCK_ATOMIC, &cs->HW_Flags)) { + hfc_fill_fifo(bcs); + test_and_clear_bit(FLG_LOCK_ATOMIC, &cs->HW_Flags); + } else + debugl1(cs, "send_data %d blocked", bcs->channel); +} + +static void +main_rec_2bds0(struct BCState *bcs) +{ + struct IsdnCardState *cs = bcs->cs; + int z1, z2, rcnt; + u_char f1, f2, cip; + int receive, count = 5; + struct sk_buff *skb; + +Begin: + count--; + if (test_and_set_bit(FLG_LOCK_ATOMIC, &cs->HW_Flags)) { + debugl1(cs, "rec_data %d blocked", bcs->channel); + return; + } + SelFiFo(cs, HFCB_REC | HFCB_CHANNEL(bcs->channel)); + cip = HFCB_FIFO | HFCB_F1 | HFCB_REC | HFCB_CHANNEL(bcs->channel); + WaitNoBusy(cs); + f1 = ReadReg(cs, HFCD_DATA, cip); + cip = HFCB_FIFO | HFCB_F2 | HFCB_REC | HFCB_CHANNEL(bcs->channel); + WaitNoBusy(cs); + f2 = ReadReg(cs, HFCD_DATA, cip); + if (f1 != f2) { + if (cs->debug & L1_DEB_HSCX) + debugl1(cs, "hfc rec %d f1(%d) f2(%d)", + bcs->channel, f1, f2); + z1 = ReadZReg(cs, HFCB_FIFO | HFCB_Z1 | HFCB_REC | HFCB_CHANNEL(bcs->channel)); + z2 = ReadZReg(cs, HFCB_FIFO | HFCB_Z2 | HFCB_REC | HFCB_CHANNEL(bcs->channel)); + rcnt = z1 - z2; + if (rcnt < 0) + rcnt += cs->hw.hfcD.bfifosize; + rcnt++; + if (cs->debug & L1_DEB_HSCX) + debugl1(cs, "hfc rec %d z1(%x) z2(%x) cnt(%d)", + bcs->channel, z1, z2, rcnt); + if ((skb = hfc_empty_fifo(bcs, rcnt))) { + skb_queue_tail(&bcs->rqueue, skb); + schedule_event(bcs, B_RCVBUFREADY); + } + rcnt = f1 - f2; + if (rcnt < 0) + rcnt += 32; + if (rcnt > 1) + receive = 1; + else + receive = 0; + } else + receive = 0; + test_and_clear_bit(FLG_LOCK_ATOMIC, &cs->HW_Flags); + if (count && receive) + goto Begin; + return; +} + +static void +mode_2bs0(struct BCState *bcs, int mode, int bc) +{ + struct IsdnCardState *cs = bcs->cs; + + if (cs->debug & L1_DEB_HSCX) + debugl1(cs, "HFCD bchannel mode %d bchan %d/%d", + mode, bc, bcs->channel); + bcs->mode = mode; + bcs->channel = bc; + switch (mode) { + case (L1_MODE_NULL): + if (bc) { + cs->hw.hfcD.conn |= 0x18; + cs->hw.hfcD.sctrl &= ~SCTRL_B2_ENA; + } else { + cs->hw.hfcD.conn |= 0x3; + cs->hw.hfcD.sctrl &= ~SCTRL_B1_ENA; + } + break; + case (L1_MODE_TRANS): + if (bc) { + cs->hw.hfcD.ctmt |= 2; + cs->hw.hfcD.conn &= ~0x18; + cs->hw.hfcD.sctrl |= SCTRL_B2_ENA; + } else { + cs->hw.hfcD.ctmt |= 1; + cs->hw.hfcD.conn &= ~0x3; + cs->hw.hfcD.sctrl |= SCTRL_B1_ENA; + } + break; + case (L1_MODE_HDLC): + if (bc) { + cs->hw.hfcD.ctmt &= ~2; + cs->hw.hfcD.conn &= ~0x18; + cs->hw.hfcD.sctrl |= SCTRL_B2_ENA; + } else { + cs->hw.hfcD.ctmt &= ~1; + cs->hw.hfcD.conn &= ~0x3; + cs->hw.hfcD.sctrl |= SCTRL_B1_ENA; + } + break; + } + WriteReg(cs, HFCD_DATA, HFCD_SCTRL, cs->hw.hfcD.sctrl); + WriteReg(cs, HFCD_DATA, HFCD_CTMT, cs->hw.hfcD.ctmt); + WriteReg(cs, HFCD_DATA, HFCD_CONN, cs->hw.hfcD.conn); +} + +static void +hfc_l2l1(struct PStack *st, int pr, void *arg) +{ + struct BCState *bcs = st->l1.bcs; + struct sk_buff *skb = arg; + u_long flags; + + switch (pr) { + case (PH_DATA | REQUEST): + spin_lock_irqsave(&bcs->cs->lock, flags); + if (bcs->tx_skb) { + skb_queue_tail(&bcs->squeue, skb); + } else { + bcs->tx_skb = skb; +// test_and_set_bit(BC_FLG_BUSY, &bcs->Flag); + bcs->cs->BC_Send_Data(bcs); + } + spin_unlock_irqrestore(&bcs->cs->lock, flags); + break; + case (PH_PULL | INDICATION): + spin_lock_irqsave(&bcs->cs->lock, flags); + if (bcs->tx_skb) { + printk(KERN_WARNING "hfc_l2l1: this shouldn't happen\n"); + } else { +// test_and_set_bit(BC_FLG_BUSY, &bcs->Flag); + bcs->tx_skb = skb; + bcs->cs->BC_Send_Data(bcs); + } + spin_unlock_irqrestore(&bcs->cs->lock, flags); + break; + case (PH_PULL | REQUEST): + if (!bcs->tx_skb) { + test_and_clear_bit(FLG_L1_PULL_REQ, &st->l1.Flags); + st->l1.l1l2(st, PH_PULL | CONFIRM, NULL); + } else + test_and_set_bit(FLG_L1_PULL_REQ, &st->l1.Flags); + break; + case (PH_ACTIVATE | REQUEST): + spin_lock_irqsave(&bcs->cs->lock, flags); + test_and_set_bit(BC_FLG_ACTIV, &bcs->Flag); + mode_2bs0(bcs, st->l1.mode, st->l1.bc); + spin_unlock_irqrestore(&bcs->cs->lock, flags); + l1_msg_b(st, pr, arg); + break; + case (PH_DEACTIVATE | REQUEST): + l1_msg_b(st, pr, arg); + break; + case (PH_DEACTIVATE | CONFIRM): + spin_lock_irqsave(&bcs->cs->lock, flags); + test_and_clear_bit(BC_FLG_ACTIV, &bcs->Flag); + test_and_clear_bit(BC_FLG_BUSY, &bcs->Flag); + mode_2bs0(bcs, 0, st->l1.bc); + spin_unlock_irqrestore(&bcs->cs->lock, flags); + st->l1.l1l2(st, PH_DEACTIVATE | CONFIRM, NULL); + break; + } +} + +static void +close_2bs0(struct BCState *bcs) +{ + mode_2bs0(bcs, 0, bcs->channel); + if (test_and_clear_bit(BC_FLG_INIT, &bcs->Flag)) { + skb_queue_purge(&bcs->rqueue); + skb_queue_purge(&bcs->squeue); + if (bcs->tx_skb) { + dev_kfree_skb_any(bcs->tx_skb); + bcs->tx_skb = NULL; + test_and_clear_bit(BC_FLG_BUSY, &bcs->Flag); + } + } +} + +static int +open_hfcstate(struct IsdnCardState *cs, struct BCState *bcs) +{ + if (!test_and_set_bit(BC_FLG_INIT, &bcs->Flag)) { + skb_queue_head_init(&bcs->rqueue); + skb_queue_head_init(&bcs->squeue); + } + bcs->tx_skb = NULL; + test_and_clear_bit(BC_FLG_BUSY, &bcs->Flag); + bcs->event = 0; + bcs->tx_cnt = 0; + return (0); +} + +static int +setstack_2b(struct PStack *st, struct BCState *bcs) +{ + bcs->channel = st->l1.bc; + if (open_hfcstate(st->l1.hardware, bcs)) + return (-1); + st->l1.bcs = bcs; + st->l2.l2l1 = hfc_l2l1; + setstack_manager(st); + bcs->st = st; + setstack_l1_B(st); + return (0); +} + +static void +hfcd_bh(struct work_struct *work) +{ + struct IsdnCardState *cs = + container_of(work, struct IsdnCardState, tqueue); + + if (test_and_clear_bit(D_L1STATECHANGE, &cs->event)) { + switch (cs->dc.hfcd.ph_state) { + case (0): + l1_msg(cs, HW_RESET | INDICATION, NULL); + break; + case (3): + l1_msg(cs, HW_DEACTIVATE | INDICATION, NULL); + break; + case (8): + l1_msg(cs, HW_RSYNC | INDICATION, NULL); + break; + case (6): + l1_msg(cs, HW_INFO2 | INDICATION, NULL); + break; + case (7): + l1_msg(cs, HW_INFO4_P8 | INDICATION, NULL); + break; + default: + break; + } + } + if (test_and_clear_bit(D_RCVBUFREADY, &cs->event)) + DChannel_proc_rcv(cs); + if (test_and_clear_bit(D_XMTBUFREADY, &cs->event)) + DChannel_proc_xmt(cs); +} + +static +int receive_dmsg(struct IsdnCardState *cs) +{ + struct sk_buff *skb; + int idx; + int rcnt, z1, z2; + u_char stat, cip, f1, f2; + int chksum; + int count = 5; + u_char *ptr; + + if (test_and_set_bit(FLG_LOCK_ATOMIC, &cs->HW_Flags)) { + debugl1(cs, "rec_dmsg blocked"); + return (1); + } + SelFiFo(cs, 4 | HFCD_REC); + cip = HFCD_FIFO | HFCD_F1 | HFCD_REC; + WaitNoBusy(cs); + f1 = cs->readisac(cs, cip) & 0xf; + cip = HFCD_FIFO | HFCD_F2 | HFCD_REC; + WaitNoBusy(cs); + f2 = cs->readisac(cs, cip) & 0xf; + while ((f1 != f2) && count--) { + z1 = ReadZReg(cs, HFCD_FIFO | HFCD_Z1 | HFCD_REC); + z2 = ReadZReg(cs, HFCD_FIFO | HFCD_Z2 | HFCD_REC); + rcnt = z1 - z2; + if (rcnt < 0) + rcnt += cs->hw.hfcD.dfifosize; + rcnt++; + if (cs->debug & L1_DEB_ISAC) + debugl1(cs, "hfcd recd f1(%d) f2(%d) z1(%x) z2(%x) cnt(%d)", + f1, f2, z1, z2, rcnt); + idx = 0; + cip = HFCD_FIFO | HFCD_FIFO_OUT | HFCD_REC; + if (rcnt > MAX_DFRAME_LEN + 3) { + if (cs->debug & L1_DEB_WARN) + debugl1(cs, "empty_fifo d: incoming packet too large"); + while (idx < rcnt) { + if (!(WaitNoBusy(cs))) + break; + ReadReg(cs, HFCD_DATA_NODEB, cip); + idx++; + } + } else if (rcnt < 4) { + if (cs->debug & L1_DEB_WARN) + debugl1(cs, "empty_fifo d: incoming packet too small"); + while ((idx++ < rcnt) && WaitNoBusy(cs)) + ReadReg(cs, HFCD_DATA_NODEB, cip); + } else if ((skb = dev_alloc_skb(rcnt - 3))) { + ptr = skb_put(skb, rcnt - 3); + while (idx < (rcnt - 3)) { + if (!(WaitNoBusy(cs))) + break; + *ptr = ReadReg(cs, HFCD_DATA_NODEB, cip); + idx++; + ptr++; + } + if (idx != (rcnt - 3)) { + debugl1(cs, "RFIFO D BUSY error"); + printk(KERN_WARNING "HFC DFIFO channel BUSY Error\n"); + dev_kfree_skb_irq(skb); + skb = NULL; +#ifdef ERROR_STATISTIC + cs->err_rx++; +#endif + } else { + WaitNoBusy(cs); + chksum = (ReadReg(cs, HFCD_DATA, cip) << 8); + WaitNoBusy(cs); + chksum += ReadReg(cs, HFCD_DATA, cip); + WaitNoBusy(cs); + stat = ReadReg(cs, HFCD_DATA, cip); + if (cs->debug & L1_DEB_ISAC) + debugl1(cs, "empty_dfifo chksum %x stat %x", + chksum, stat); + if (stat) { + debugl1(cs, "FIFO CRC error"); + dev_kfree_skb_irq(skb); + skb = NULL; +#ifdef ERROR_STATISTIC + cs->err_crc++; +#endif + } else { + skb_queue_tail(&cs->rq, skb); + schedule_event(cs, D_RCVBUFREADY); + } + } + } else + printk(KERN_WARNING "HFC: D receive out of memory\n"); + WaitForBusy(cs); + cip = HFCD_FIFO | HFCD_F2_INC | HFCD_REC; + WaitNoBusy(cs); + stat = ReadReg(cs, HFCD_DATA, cip); + WaitForBusy(cs); + cip = HFCD_FIFO | HFCD_F2 | HFCD_REC; + WaitNoBusy(cs); + f2 = cs->readisac(cs, cip) & 0xf; + } + test_and_clear_bit(FLG_LOCK_ATOMIC, &cs->HW_Flags); + return (1); +} + +static void +hfc_fill_dfifo(struct IsdnCardState *cs) +{ + int idx, fcnt; + int count; + u_char cip; + + if (!cs->tx_skb) + return; + if (cs->tx_skb->len <= 0) + return; + + SelFiFo(cs, 4 | HFCD_SEND); + cip = HFCD_FIFO | HFCD_F1 | HFCD_SEND; + WaitNoBusy(cs); + cs->hw.hfcD.f1 = ReadReg(cs, HFCD_DATA, cip) & 0xf; + WaitNoBusy(cs); + cip = HFCD_FIFO | HFCD_F2 | HFCD_SEND; + cs->hw.hfcD.f2 = ReadReg(cs, HFCD_DATA, cip) & 0xf; + cs->hw.hfcD.send[cs->hw.hfcD.f1] = ReadZReg(cs, HFCD_FIFO | HFCD_Z1 | HFCD_SEND); + if (cs->debug & L1_DEB_ISAC) + debugl1(cs, "hfc_fill_Dfifo f1(%d) f2(%d) z1(%x)", + cs->hw.hfcD.f1, cs->hw.hfcD.f2, + cs->hw.hfcD.send[cs->hw.hfcD.f1]); + fcnt = cs->hw.hfcD.f1 - cs->hw.hfcD.f2; + if (fcnt < 0) + fcnt += 16; + if (fcnt > 14) { + if (cs->debug & L1_DEB_HSCX) + debugl1(cs, "hfc_fill_Dfifo more as 14 frames"); + return; + } + count = GetFreeFifoBytes_D(cs); + if (cs->debug & L1_DEB_ISAC) + debugl1(cs, "hfc_fill_Dfifo count(%u/%d)", + cs->tx_skb->len, count); + if (count < cs->tx_skb->len) { + if (cs->debug & L1_DEB_ISAC) + debugl1(cs, "hfc_fill_Dfifo no fifo mem"); + return; + } + cip = HFCD_FIFO | HFCD_FIFO_IN | HFCD_SEND; + idx = 0; + WaitForBusy(cs); + WaitNoBusy(cs); + WriteReg(cs, HFCD_DATA_NODEB, cip, cs->tx_skb->data[idx++]); + while (idx < cs->tx_skb->len) { + if (!(WaitNoBusy(cs))) + break; + WriteReg(cs, HFCD_DATA_NODEB, cip, cs->tx_skb->data[idx]); + idx++; + } + if (idx != cs->tx_skb->len) { + debugl1(cs, "DFIFO Send BUSY error"); + printk(KERN_WARNING "HFC S DFIFO channel BUSY Error\n"); + } + WaitForBusy(cs); + WaitNoBusy(cs); + ReadReg(cs, HFCD_DATA, HFCD_FIFO | HFCD_F1_INC | HFCD_SEND); + dev_kfree_skb_any(cs->tx_skb); + cs->tx_skb = NULL; + WaitForBusy(cs); + return; +} + +static +struct BCState *Sel_BCS(struct IsdnCardState *cs, int channel) +{ + if (cs->bcs[0].mode && (cs->bcs[0].channel == channel)) + return (&cs->bcs[0]); + else if (cs->bcs[1].mode && (cs->bcs[1].channel == channel)) + return (&cs->bcs[1]); + else + return (NULL); +} + +void +hfc2bds0_interrupt(struct IsdnCardState *cs, u_char val) +{ + u_char exval; + struct BCState *bcs; + int count = 15; + + if (cs->debug & L1_DEB_ISAC) + debugl1(cs, "HFCD irq %x %s", val, + test_bit(FLG_LOCK_ATOMIC, &cs->HW_Flags) ? + "locked" : "unlocked"); + val &= cs->hw.hfcD.int_m1; + if (val & 0x40) { /* TE state machine irq */ + exval = cs->readisac(cs, HFCD_STATES) & 0xf; + if (cs->debug & L1_DEB_ISAC) + debugl1(cs, "ph_state chg %d->%d", cs->dc.hfcd.ph_state, + exval); + cs->dc.hfcd.ph_state = exval; + schedule_event(cs, D_L1STATECHANGE); + val &= ~0x40; + } + while (val) { + if (test_bit(FLG_LOCK_ATOMIC, &cs->HW_Flags)) { + cs->hw.hfcD.int_s1 |= val; + return; + } + if (cs->hw.hfcD.int_s1 & 0x18) { + exval = val; + val = cs->hw.hfcD.int_s1; + cs->hw.hfcD.int_s1 = exval; + } + if (val & 0x08) { + if (!(bcs = Sel_BCS(cs, 0))) { + if (cs->debug) + debugl1(cs, "hfcd spurious 0x08 IRQ"); + } else + main_rec_2bds0(bcs); + } + if (val & 0x10) { + if (!(bcs = Sel_BCS(cs, 1))) { + if (cs->debug) + debugl1(cs, "hfcd spurious 0x10 IRQ"); + } else + main_rec_2bds0(bcs); + } + if (val & 0x01) { + if (!(bcs = Sel_BCS(cs, 0))) { + if (cs->debug) + debugl1(cs, "hfcd spurious 0x01 IRQ"); + } else { + if (bcs->tx_skb) { + if (!test_and_set_bit(FLG_LOCK_ATOMIC, &cs->HW_Flags)) { + hfc_fill_fifo(bcs); + test_and_clear_bit(FLG_LOCK_ATOMIC, &cs->HW_Flags); + } else + debugl1(cs, "fill_data %d blocked", bcs->channel); + } else { + if ((bcs->tx_skb = skb_dequeue(&bcs->squeue))) { + if (!test_and_set_bit(FLG_LOCK_ATOMIC, &cs->HW_Flags)) { + hfc_fill_fifo(bcs); + test_and_clear_bit(FLG_LOCK_ATOMIC, &cs->HW_Flags); + } else + debugl1(cs, "fill_data %d blocked", bcs->channel); + } else { + schedule_event(bcs, B_XMTBUFREADY); + } + } + } + } + if (val & 0x02) { + if (!(bcs = Sel_BCS(cs, 1))) { + if (cs->debug) + debugl1(cs, "hfcd spurious 0x02 IRQ"); + } else { + if (bcs->tx_skb) { + if (!test_and_set_bit(FLG_LOCK_ATOMIC, &cs->HW_Flags)) { + hfc_fill_fifo(bcs); + test_and_clear_bit(FLG_LOCK_ATOMIC, &cs->HW_Flags); + } else + debugl1(cs, "fill_data %d blocked", bcs->channel); + } else { + if ((bcs->tx_skb = skb_dequeue(&bcs->squeue))) { + if (!test_and_set_bit(FLG_LOCK_ATOMIC, &cs->HW_Flags)) { + hfc_fill_fifo(bcs); + test_and_clear_bit(FLG_LOCK_ATOMIC, &cs->HW_Flags); + } else + debugl1(cs, "fill_data %d blocked", bcs->channel); + } else { + schedule_event(bcs, B_XMTBUFREADY); + } + } + } + } + if (val & 0x20) { /* receive dframe */ + receive_dmsg(cs); + } + if (val & 0x04) { /* dframe transmitted */ + if (test_and_clear_bit(FLG_DBUSY_TIMER, &cs->HW_Flags)) + del_timer(&cs->dbusytimer); + if (test_and_clear_bit(FLG_L1_DBUSY, &cs->HW_Flags)) + schedule_event(cs, D_CLEARBUSY); + if (cs->tx_skb) { + if (cs->tx_skb->len) { + if (!test_and_set_bit(FLG_LOCK_ATOMIC, &cs->HW_Flags)) { + hfc_fill_dfifo(cs); + test_and_clear_bit(FLG_LOCK_ATOMIC, &cs->HW_Flags); + } else { + debugl1(cs, "hfc_fill_dfifo irq blocked"); + } + goto afterXPR; + } else { + dev_kfree_skb_irq(cs->tx_skb); + cs->tx_cnt = 0; + cs->tx_skb = NULL; + } + } + if ((cs->tx_skb = skb_dequeue(&cs->sq))) { + cs->tx_cnt = 0; + if (!test_and_set_bit(FLG_LOCK_ATOMIC, &cs->HW_Flags)) { + hfc_fill_dfifo(cs); + test_and_clear_bit(FLG_LOCK_ATOMIC, &cs->HW_Flags); + } else { + debugl1(cs, "hfc_fill_dfifo irq blocked"); + } + } else + schedule_event(cs, D_XMTBUFREADY); + } + afterXPR: + if (cs->hw.hfcD.int_s1 && count--) { + val = cs->hw.hfcD.int_s1; + cs->hw.hfcD.int_s1 = 0; + if (cs->debug & L1_DEB_ISAC) + debugl1(cs, "HFCD irq %x loop %d", val, 15-count); + } else + val = 0; + } +} + +static void +HFCD_l1hw(struct PStack *st, int pr, void *arg) +{ + struct IsdnCardState *cs = (struct IsdnCardState *) st->l1.hardware; + struct sk_buff *skb = arg; + u_long flags; + + switch (pr) { + case (PH_DATA | REQUEST): + if (cs->debug & DEB_DLOG_HEX) + LogFrame(cs, skb->data, skb->len); + if (cs->debug & DEB_DLOG_VERBOSE) + dlogframe(cs, skb, 0); + spin_lock_irqsave(&cs->lock, flags); + if (cs->tx_skb) { + skb_queue_tail(&cs->sq, skb); +#ifdef L2FRAME_DEBUG /* psa */ + if (cs->debug & L1_DEB_LAPD) + Logl2Frame(cs, skb, "PH_DATA Queued", 0); +#endif + } else { + cs->tx_skb = skb; + cs->tx_cnt = 0; +#ifdef L2FRAME_DEBUG /* psa */ + if (cs->debug & L1_DEB_LAPD) + Logl2Frame(cs, skb, "PH_DATA", 0); +#endif + if (!test_and_set_bit(FLG_LOCK_ATOMIC, &cs->HW_Flags)) { + hfc_fill_dfifo(cs); + test_and_clear_bit(FLG_LOCK_ATOMIC, &cs->HW_Flags); + } else + debugl1(cs, "hfc_fill_dfifo blocked"); + + } + spin_unlock_irqrestore(&cs->lock, flags); + break; + case (PH_PULL | INDICATION): + spin_lock_irqsave(&cs->lock, flags); + if (cs->tx_skb) { + if (cs->debug & L1_DEB_WARN) + debugl1(cs, " l2l1 tx_skb exist this shouldn't happen"); + skb_queue_tail(&cs->sq, skb); + spin_unlock_irqrestore(&cs->lock, flags); + break; + } + if (cs->debug & DEB_DLOG_HEX) + LogFrame(cs, skb->data, skb->len); + if (cs->debug & DEB_DLOG_VERBOSE) + dlogframe(cs, skb, 0); + cs->tx_skb = skb; + cs->tx_cnt = 0; +#ifdef L2FRAME_DEBUG /* psa */ + if (cs->debug & L1_DEB_LAPD) + Logl2Frame(cs, skb, "PH_DATA_PULLED", 0); +#endif + if (!test_and_set_bit(FLG_LOCK_ATOMIC, &cs->HW_Flags)) { + hfc_fill_dfifo(cs); + test_and_clear_bit(FLG_LOCK_ATOMIC, &cs->HW_Flags); + } else + debugl1(cs, "hfc_fill_dfifo blocked"); + spin_unlock_irqrestore(&cs->lock, flags); + break; + case (PH_PULL | REQUEST): +#ifdef L2FRAME_DEBUG /* psa */ + if (cs->debug & L1_DEB_LAPD) + debugl1(cs, "-> PH_REQUEST_PULL"); +#endif + if (!cs->tx_skb) { + test_and_clear_bit(FLG_L1_PULL_REQ, &st->l1.Flags); + st->l1.l1l2(st, PH_PULL | CONFIRM, NULL); + } else + test_and_set_bit(FLG_L1_PULL_REQ, &st->l1.Flags); + break; + case (HW_RESET | REQUEST): + spin_lock_irqsave(&cs->lock, flags); + cs->writeisac(cs, HFCD_STATES, HFCD_LOAD_STATE | 3); /* HFC ST 3 */ + udelay(6); + cs->writeisac(cs, HFCD_STATES, 3); /* HFC ST 2 */ + cs->hw.hfcD.mst_m |= HFCD_MASTER; + cs->writeisac(cs, HFCD_MST_MODE, cs->hw.hfcD.mst_m); + cs->writeisac(cs, HFCD_STATES, HFCD_ACTIVATE | HFCD_DO_ACTION); + spin_unlock_irqrestore(&cs->lock, flags); + l1_msg(cs, HW_POWERUP | CONFIRM, NULL); + break; + case (HW_ENABLE | REQUEST): + spin_lock_irqsave(&cs->lock, flags); + cs->writeisac(cs, HFCD_STATES, HFCD_ACTIVATE | HFCD_DO_ACTION); + spin_unlock_irqrestore(&cs->lock, flags); + break; + case (HW_DEACTIVATE | REQUEST): + spin_lock_irqsave(&cs->lock, flags); + cs->hw.hfcD.mst_m &= ~HFCD_MASTER; + cs->writeisac(cs, HFCD_MST_MODE, cs->hw.hfcD.mst_m); + spin_unlock_irqrestore(&cs->lock, flags); + break; + case (HW_INFO3 | REQUEST): + spin_lock_irqsave(&cs->lock, flags); + cs->hw.hfcD.mst_m |= HFCD_MASTER; + cs->writeisac(cs, HFCD_MST_MODE, cs->hw.hfcD.mst_m); + spin_unlock_irqrestore(&cs->lock, flags); + break; + default: + if (cs->debug & L1_DEB_WARN) + debugl1(cs, "hfcd_l1hw unknown pr %4x", pr); + break; + } +} + +static void +setstack_hfcd(struct PStack *st, struct IsdnCardState *cs) +{ + st->l1.l1hw = HFCD_l1hw; +} + +static void +hfc_dbusy_timer(struct timer_list *t) +{ +} + +static unsigned int +*init_send_hfcd(int cnt) +{ + int i; + unsigned *send; + + if (!(send = kmalloc_array(cnt, sizeof(unsigned int), GFP_ATOMIC))) { + printk(KERN_WARNING + "HiSax: No memory for hfcd.send\n"); + return (NULL); + } + for (i = 0; i < cnt; i++) + send[i] = 0x1fff; + return (send); +} + +void +init2bds0(struct IsdnCardState *cs) +{ + cs->setstack_d = setstack_hfcd; + if (!cs->hw.hfcD.send) + cs->hw.hfcD.send = init_send_hfcd(16); + if (!cs->bcs[0].hw.hfc.send) + cs->bcs[0].hw.hfc.send = init_send_hfcd(32); + if (!cs->bcs[1].hw.hfc.send) + cs->bcs[1].hw.hfc.send = init_send_hfcd(32); + cs->BC_Send_Data = &hfc_send_data; + cs->bcs[0].BC_SetStack = setstack_2b; + cs->bcs[1].BC_SetStack = setstack_2b; + cs->bcs[0].BC_Close = close_2bs0; + cs->bcs[1].BC_Close = close_2bs0; + mode_2bs0(cs->bcs, 0, 0); + mode_2bs0(cs->bcs + 1, 0, 1); +} + +void +release2bds0(struct IsdnCardState *cs) +{ + kfree(cs->bcs[0].hw.hfc.send); + cs->bcs[0].hw.hfc.send = NULL; + kfree(cs->bcs[1].hw.hfc.send); + cs->bcs[1].hw.hfc.send = NULL; + kfree(cs->hw.hfcD.send); + cs->hw.hfcD.send = NULL; +} + +void +set_cs_func(struct IsdnCardState *cs) +{ + cs->readisac = &readreghfcd; + cs->writeisac = &writereghfcd; + cs->readisacfifo = &dummyf; + cs->writeisacfifo = &dummyf; + cs->BC_Read_Reg = &ReadReg; + cs->BC_Write_Reg = &WriteReg; + timer_setup(&cs->dbusytimer, hfc_dbusy_timer, 0); + INIT_WORK(&cs->tqueue, hfcd_bh); +} diff --git a/drivers/isdn/hisax/hfc_2bds0.h b/drivers/isdn/hisax/hfc_2bds0.h new file mode 100644 index 000000000..8c7582a3c --- /dev/null +++ b/drivers/isdn/hisax/hfc_2bds0.h @@ -0,0 +1,128 @@ +/* $Id: hfc_2bds0.h,v 1.6.2.2 2004/01/12 22:52:26 keil Exp $ + * + * specific defines for CCD's HFC 2BDS0 + * + * Author Karsten Keil + * Copyright by Karsten Keil <keil@isdn4linux.de> + * + * This software may be used and distributed according to the terms + * of the GNU General Public License, incorporated herein by reference. + * + */ + +#define HFCD_CIRM 0x18 +#define HFCD_CTMT 0x19 +#define HFCD_INT_M1 0x1A +#define HFCD_INT_M2 0x1B +#define HFCD_INT_S1 0x1E +#define HFCD_STAT 0x1C +#define HFCD_STAT_DISB 0x1D +#define HFCD_STATES 0x30 +#define HFCD_SCTRL 0x31 +#define HFCD_TEST 0x32 +#define HFCD_SQ 0x34 +#define HFCD_CLKDEL 0x37 +#define HFCD_MST_MODE 0x2E +#define HFCD_CONN 0x2F + +#define HFCD_FIFO 0x80 +#define HFCD_Z1 0x10 +#define HFCD_Z2 0x18 +#define HFCD_Z_LOW 0x00 +#define HFCD_Z_HIGH 0x04 +#define HFCD_F1_INC 0x12 +#define HFCD_FIFO_IN 0x16 +#define HFCD_F1 0x1a +#define HFCD_F2 0x1e +#define HFCD_F2_INC 0x22 +#define HFCD_FIFO_OUT 0x26 +#define HFCD_REC 0x01 +#define HFCD_SEND 0x00 + +#define HFCB_FIFO 0x80 +#define HFCB_Z1 0x00 +#define HFCB_Z2 0x08 +#define HFCB_Z_LOW 0x00 +#define HFCB_Z_HIGH 0x04 +#define HFCB_F1_INC 0x28 +#define HFCB_FIFO_IN 0x2c +#define HFCB_F1 0x30 +#define HFCB_F2 0x34 +#define HFCB_F2_INC 0x38 +#define HFCB_FIFO_OUT 0x3c +#define HFCB_REC 0x01 +#define HFCB_SEND 0x00 +#define HFCB_B1 0x00 +#define HFCB_B2 0x02 +#define HFCB_CHANNEL(ch) (ch ? HFCB_B2 : HFCB_B1) + +#define HFCD_STATUS 0 +#define HFCD_DATA 1 +#define HFCD_DATA_NODEB 2 + +/* Status (READ) */ +#define HFCD_BUSY 0x01 +#define HFCD_BUSY_NBUSY 0x04 +#define HFCD_TIMER_ELAP 0x10 +#define HFCD_STATINT 0x20 +#define HFCD_FRAMEINT 0x40 +#define HFCD_ANYINT 0x80 + +/* CTMT (Write) */ +#define HFCD_CLTIMER 0x80 +#define HFCD_TIM25 0x00 +#define HFCD_TIM50 0x08 +#define HFCD_TIM400 0x10 +#define HFCD_TIM800 0x18 +#define HFCD_AUTO_TIMER 0x20 +#define HFCD_TRANSB2 0x02 +#define HFCD_TRANSB1 0x01 + +/* CIRM (Write) */ +#define HFCD_RESET 0x08 +#define HFCD_MEM8K 0x10 +#define HFCD_INTA 0x01 +#define HFCD_INTB 0x02 +#define HFCD_INTC 0x03 +#define HFCD_INTD 0x04 +#define HFCD_INTE 0x05 +#define HFCD_INTF 0x06 + +/* INT_M1;INT_S1 */ +#define HFCD_INTS_B1TRANS 0x01 +#define HFCD_INTS_B2TRANS 0x02 +#define HFCD_INTS_DTRANS 0x04 +#define HFCD_INTS_B1REC 0x08 +#define HFCD_INTS_B2REC 0x10 +#define HFCD_INTS_DREC 0x20 +#define HFCD_INTS_L1STATE 0x40 +#define HFCD_INTS_TIMER 0x80 + +/* INT_M2 */ +#define HFCD_IRQ_ENABLE 0x08 + +/* STATES */ +#define HFCD_LOAD_STATE 0x10 +#define HFCD_ACTIVATE 0x20 +#define HFCD_DO_ACTION 0x40 + +/* HFCD_MST_MODE */ +#define HFCD_MASTER 0x01 + +/* HFCD_SCTRL */ +#define SCTRL_B1_ENA 0x01 +#define SCTRL_B2_ENA 0x02 +#define SCTRL_LOW_PRIO 0x08 +#define SCTRL_SQ_ENA 0x10 +#define SCTRL_TEST 0x20 +#define SCTRL_NONE_CAP 0x40 +#define SCTRL_PWR_DOWN 0x80 + +/* HFCD_TEST */ +#define HFCD_AUTO_AWAKE 0x01 + +extern void main_irq_2bds0(struct BCState *bcs); +extern void init2bds0(struct IsdnCardState *cs); +extern void release2bds0(struct IsdnCardState *cs); +extern void hfc2bds0_interrupt(struct IsdnCardState *cs, u_char val); +extern void set_cs_func(struct IsdnCardState *cs); diff --git a/drivers/isdn/hisax/hfc_2bs0.c b/drivers/isdn/hisax/hfc_2bs0.c new file mode 100644 index 000000000..34d599928 --- /dev/null +++ b/drivers/isdn/hisax/hfc_2bs0.c @@ -0,0 +1,591 @@ +/* $Id: hfc_2bs0.c,v 1.20.2.6 2004/02/11 13:21:33 keil Exp $ + * + * specific routines for CCD's HFC 2BS0 + * + * Author Karsten Keil + * Copyright by Karsten Keil <keil@isdn4linux.de> + * + * This software may be used and distributed according to the terms + * of the GNU General Public License, incorporated herein by reference. + * + */ + +#include <linux/init.h> +#include "hisax.h" +#include "hfc_2bs0.h" +#include "isac.h" +#include "isdnl1.h" +#include <linux/interrupt.h> +#include <linux/slab.h> + +static inline int +WaitForBusy(struct IsdnCardState *cs) +{ + int to = 130; + u_char val; + + while (!(cs->BC_Read_Reg(cs, HFC_STATUS, 0) & HFC_BUSY) && to) { + val = cs->BC_Read_Reg(cs, HFC_DATA, HFC_CIP | HFC_F2 | + (cs->hw.hfc.cip & 3)); + udelay(1); + to--; + } + if (!to) { + printk(KERN_WARNING "HiSax: %s timeout\n", __func__); + return (0); + } else + return (to); +} + +static inline int +WaitNoBusy(struct IsdnCardState *cs) +{ + int to = 125; + + while ((cs->BC_Read_Reg(cs, HFC_STATUS, 0) & HFC_BUSY) && to) { + udelay(1); + to--; + } + if (!to) { + printk(KERN_WARNING "HiSax: waitforBusy timeout\n"); + return (0); + } else + return (to); +} + +static int +GetFreeFifoBytes(struct BCState *bcs) +{ + int s; + + if (bcs->hw.hfc.f1 == bcs->hw.hfc.f2) + return (bcs->cs->hw.hfc.fifosize); + s = bcs->hw.hfc.send[bcs->hw.hfc.f1] - bcs->hw.hfc.send[bcs->hw.hfc.f2]; + if (s <= 0) + s += bcs->cs->hw.hfc.fifosize; + s = bcs->cs->hw.hfc.fifosize - s; + return (s); +} + +static int +ReadZReg(struct BCState *bcs, u_char reg) +{ + int val; + + WaitNoBusy(bcs->cs); + val = 256 * bcs->cs->BC_Read_Reg(bcs->cs, HFC_DATA, reg | HFC_CIP | HFC_Z_HIGH); + WaitNoBusy(bcs->cs); + val += bcs->cs->BC_Read_Reg(bcs->cs, HFC_DATA, reg | HFC_CIP | HFC_Z_LOW); + return (val); +} + +static void +hfc_clear_fifo(struct BCState *bcs) +{ + struct IsdnCardState *cs = bcs->cs; + int idx, cnt; + int rcnt, z1, z2; + u_char cip, f1, f2; + + if ((cs->debug & L1_DEB_HSCX) && !(cs->debug & L1_DEB_HSCX_FIFO)) + debugl1(cs, "hfc_clear_fifo"); + cip = HFC_CIP | HFC_F1 | HFC_REC | HFC_CHANNEL(bcs->channel); + if ((cip & 0xc3) != (cs->hw.hfc.cip & 0xc3)) { + cs->BC_Write_Reg(cs, HFC_STATUS, cip, cip); + WaitForBusy(cs); + } + WaitNoBusy(cs); + f1 = cs->BC_Read_Reg(cs, HFC_DATA, cip); + cip = HFC_CIP | HFC_F2 | HFC_REC | HFC_CHANNEL(bcs->channel); + WaitNoBusy(cs); + f2 = cs->BC_Read_Reg(cs, HFC_DATA, cip); + z1 = ReadZReg(bcs, HFC_Z1 | HFC_REC | HFC_CHANNEL(bcs->channel)); + z2 = ReadZReg(bcs, HFC_Z2 | HFC_REC | HFC_CHANNEL(bcs->channel)); + cnt = 32; + while (((f1 != f2) || (z1 != z2)) && cnt--) { + if (cs->debug & L1_DEB_HSCX) + debugl1(cs, "hfc clear %d f1(%d) f2(%d)", + bcs->channel, f1, f2); + rcnt = z1 - z2; + if (rcnt < 0) + rcnt += cs->hw.hfc.fifosize; + if (rcnt) + rcnt++; + if (cs->debug & L1_DEB_HSCX) + debugl1(cs, "hfc clear %d z1(%x) z2(%x) cnt(%d)", + bcs->channel, z1, z2, rcnt); + cip = HFC_CIP | HFC_FIFO_OUT | HFC_REC | HFC_CHANNEL(bcs->channel); + idx = 0; + while ((idx < rcnt) && WaitNoBusy(cs)) { + cs->BC_Read_Reg(cs, HFC_DATA_NODEB, cip); + idx++; + } + if (f1 != f2) { + WaitNoBusy(cs); + cs->BC_Read_Reg(cs, HFC_DATA, HFC_CIP | HFC_F2_INC | HFC_REC | + HFC_CHANNEL(bcs->channel)); + WaitForBusy(cs); + } + cip = HFC_CIP | HFC_F1 | HFC_REC | HFC_CHANNEL(bcs->channel); + WaitNoBusy(cs); + f1 = cs->BC_Read_Reg(cs, HFC_DATA, cip); + cip = HFC_CIP | HFC_F2 | HFC_REC | HFC_CHANNEL(bcs->channel); + WaitNoBusy(cs); + f2 = cs->BC_Read_Reg(cs, HFC_DATA, cip); + z1 = ReadZReg(bcs, HFC_Z1 | HFC_REC | HFC_CHANNEL(bcs->channel)); + z2 = ReadZReg(bcs, HFC_Z2 | HFC_REC | HFC_CHANNEL(bcs->channel)); + } + return; +} + + +static struct sk_buff +* +hfc_empty_fifo(struct BCState *bcs, int count) +{ + u_char *ptr; + struct sk_buff *skb; + struct IsdnCardState *cs = bcs->cs; + int idx; + int chksum; + u_char stat, cip; + + if ((cs->debug & L1_DEB_HSCX) && !(cs->debug & L1_DEB_HSCX_FIFO)) + debugl1(cs, "hfc_empty_fifo"); + idx = 0; + if (count > HSCX_BUFMAX + 3) { + if (cs->debug & L1_DEB_WARN) + debugl1(cs, "hfc_empty_fifo: incoming packet too large"); + cip = HFC_CIP | HFC_FIFO_OUT | HFC_REC | HFC_CHANNEL(bcs->channel); + while ((idx++ < count) && WaitNoBusy(cs)) + cs->BC_Read_Reg(cs, HFC_DATA_NODEB, cip); + WaitNoBusy(cs); + stat = cs->BC_Read_Reg(cs, HFC_DATA, HFC_CIP | HFC_F2_INC | HFC_REC | + HFC_CHANNEL(bcs->channel)); + WaitForBusy(cs); + return (NULL); + } + if ((count < 4) && (bcs->mode != L1_MODE_TRANS)) { + if (cs->debug & L1_DEB_WARN) + debugl1(cs, "hfc_empty_fifo: incoming packet too small"); + cip = HFC_CIP | HFC_FIFO_OUT | HFC_REC | HFC_CHANNEL(bcs->channel); + while ((idx++ < count) && WaitNoBusy(cs)) + cs->BC_Read_Reg(cs, HFC_DATA_NODEB, cip); + WaitNoBusy(cs); + stat = cs->BC_Read_Reg(cs, HFC_DATA, HFC_CIP | HFC_F2_INC | HFC_REC | + HFC_CHANNEL(bcs->channel)); + WaitForBusy(cs); +#ifdef ERROR_STATISTIC + bcs->err_inv++; +#endif + return (NULL); + } + if (bcs->mode == L1_MODE_TRANS) + count -= 1; + else + count -= 3; + if (!(skb = dev_alloc_skb(count))) + printk(KERN_WARNING "HFC: receive out of memory\n"); + else { + ptr = skb_put(skb, count); + idx = 0; + cip = HFC_CIP | HFC_FIFO_OUT | HFC_REC | HFC_CHANNEL(bcs->channel); + while ((idx < count) && WaitNoBusy(cs)) { + *ptr++ = cs->BC_Read_Reg(cs, HFC_DATA_NODEB, cip); + idx++; + } + if (idx != count) { + debugl1(cs, "RFIFO BUSY error"); + printk(KERN_WARNING "HFC FIFO channel %d BUSY Error\n", bcs->channel); + dev_kfree_skb_any(skb); + if (bcs->mode != L1_MODE_TRANS) { + WaitNoBusy(cs); + stat = cs->BC_Read_Reg(cs, HFC_DATA, HFC_CIP | HFC_F2_INC | HFC_REC | + HFC_CHANNEL(bcs->channel)); + WaitForBusy(cs); + } + return (NULL); + } + if (bcs->mode != L1_MODE_TRANS) { + WaitNoBusy(cs); + chksum = (cs->BC_Read_Reg(cs, HFC_DATA, cip) << 8); + WaitNoBusy(cs); + chksum += cs->BC_Read_Reg(cs, HFC_DATA, cip); + WaitNoBusy(cs); + stat = cs->BC_Read_Reg(cs, HFC_DATA, cip); + if (cs->debug & L1_DEB_HSCX) + debugl1(cs, "hfc_empty_fifo %d chksum %x stat %x", + bcs->channel, chksum, stat); + if (stat) { + debugl1(cs, "FIFO CRC error"); + dev_kfree_skb_any(skb); + skb = NULL; +#ifdef ERROR_STATISTIC + bcs->err_crc++; +#endif + } + WaitNoBusy(cs); + stat = cs->BC_Read_Reg(cs, HFC_DATA, HFC_CIP | HFC_F2_INC | HFC_REC | + HFC_CHANNEL(bcs->channel)); + WaitForBusy(cs); + } + } + return (skb); +} + +static void +hfc_fill_fifo(struct BCState *bcs) +{ + struct IsdnCardState *cs = bcs->cs; + int idx, fcnt; + int count; + int z1, z2; + u_char cip; + + if (!bcs->tx_skb) + return; + if (bcs->tx_skb->len <= 0) + return; + + cip = HFC_CIP | HFC_F1 | HFC_SEND | HFC_CHANNEL(bcs->channel); + if ((cip & 0xc3) != (cs->hw.hfc.cip & 0xc3)) { + cs->BC_Write_Reg(cs, HFC_STATUS, cip, cip); + WaitForBusy(cs); + } + WaitNoBusy(cs); + if (bcs->mode != L1_MODE_TRANS) { + bcs->hw.hfc.f1 = cs->BC_Read_Reg(cs, HFC_DATA, cip); + cip = HFC_CIP | HFC_F2 | HFC_SEND | HFC_CHANNEL(bcs->channel); + WaitNoBusy(cs); + bcs->hw.hfc.f2 = cs->BC_Read_Reg(cs, HFC_DATA, cip); + bcs->hw.hfc.send[bcs->hw.hfc.f1] = ReadZReg(bcs, HFC_Z1 | HFC_SEND | HFC_CHANNEL(bcs->channel)); + if (cs->debug & L1_DEB_HSCX) + debugl1(cs, "hfc_fill_fifo %d f1(%d) f2(%d) z1(%x)", + bcs->channel, bcs->hw.hfc.f1, bcs->hw.hfc.f2, + bcs->hw.hfc.send[bcs->hw.hfc.f1]); + fcnt = bcs->hw.hfc.f1 - bcs->hw.hfc.f2; + if (fcnt < 0) + fcnt += 32; + if (fcnt > 30) { + if (cs->debug & L1_DEB_HSCX) + debugl1(cs, "hfc_fill_fifo more as 30 frames"); + return; + } + count = GetFreeFifoBytes(bcs); + } + else { + WaitForBusy(cs); + z1 = ReadZReg(bcs, HFC_Z1 | HFC_REC | HFC_CHANNEL(bcs->channel)); + z2 = ReadZReg(bcs, HFC_Z2 | HFC_REC | HFC_CHANNEL(bcs->channel)); + count = z1 - z2; + if (count < 0) + count += cs->hw.hfc.fifosize; + } /* L1_MODE_TRANS */ + if (cs->debug & L1_DEB_HSCX) + debugl1(cs, "hfc_fill_fifo %d count(%u/%d)", + bcs->channel, bcs->tx_skb->len, + count); + if (count < bcs->tx_skb->len) { + if (cs->debug & L1_DEB_HSCX) + debugl1(cs, "hfc_fill_fifo no fifo mem"); + return; + } + cip = HFC_CIP | HFC_FIFO_IN | HFC_SEND | HFC_CHANNEL(bcs->channel); + idx = 0; + while ((idx < bcs->tx_skb->len) && WaitNoBusy(cs)) + cs->BC_Write_Reg(cs, HFC_DATA_NODEB, cip, bcs->tx_skb->data[idx++]); + if (idx != bcs->tx_skb->len) { + debugl1(cs, "FIFO Send BUSY error"); + printk(KERN_WARNING "HFC S FIFO channel %d BUSY Error\n", bcs->channel); + } else { + count = bcs->tx_skb->len; + bcs->tx_cnt -= count; + if (PACKET_NOACK == bcs->tx_skb->pkt_type) + count = -1; + dev_kfree_skb_any(bcs->tx_skb); + bcs->tx_skb = NULL; + if (bcs->mode != L1_MODE_TRANS) { + WaitForBusy(cs); + WaitNoBusy(cs); + cs->BC_Read_Reg(cs, HFC_DATA, HFC_CIP | HFC_F1_INC | HFC_SEND | HFC_CHANNEL(bcs->channel)); + } + if (test_bit(FLG_LLI_L1WAKEUP, &bcs->st->lli.flag) && + (count >= 0)) { + u_long flags; + spin_lock_irqsave(&bcs->aclock, flags); + bcs->ackcnt += count; + spin_unlock_irqrestore(&bcs->aclock, flags); + schedule_event(bcs, B_ACKPENDING); + } + test_and_clear_bit(BC_FLG_BUSY, &bcs->Flag); + } + return; +} + +void +main_irq_hfc(struct BCState *bcs) +{ + struct IsdnCardState *cs = bcs->cs; + int z1, z2, rcnt; + u_char f1, f2, cip; + int receive, transmit, count = 5; + struct sk_buff *skb; + +Begin: + count--; + cip = HFC_CIP | HFC_F1 | HFC_REC | HFC_CHANNEL(bcs->channel); + if ((cip & 0xc3) != (cs->hw.hfc.cip & 0xc3)) { + cs->BC_Write_Reg(cs, HFC_STATUS, cip, cip); + WaitForBusy(cs); + } + WaitNoBusy(cs); + receive = 0; + if (bcs->mode == L1_MODE_HDLC) { + f1 = cs->BC_Read_Reg(cs, HFC_DATA, cip); + cip = HFC_CIP | HFC_F2 | HFC_REC | HFC_CHANNEL(bcs->channel); + WaitNoBusy(cs); + f2 = cs->BC_Read_Reg(cs, HFC_DATA, cip); + if (f1 != f2) { + if (cs->debug & L1_DEB_HSCX) + debugl1(cs, "hfc rec %d f1(%d) f2(%d)", + bcs->channel, f1, f2); + receive = 1; + } + } + if (receive || (bcs->mode == L1_MODE_TRANS)) { + WaitForBusy(cs); + z1 = ReadZReg(bcs, HFC_Z1 | HFC_REC | HFC_CHANNEL(bcs->channel)); + z2 = ReadZReg(bcs, HFC_Z2 | HFC_REC | HFC_CHANNEL(bcs->channel)); + rcnt = z1 - z2; + if (rcnt < 0) + rcnt += cs->hw.hfc.fifosize; + if ((bcs->mode == L1_MODE_HDLC) || (rcnt)) { + rcnt++; + if (cs->debug & L1_DEB_HSCX) + debugl1(cs, "hfc rec %d z1(%x) z2(%x) cnt(%d)", + bcs->channel, z1, z2, rcnt); + /* sti(); */ + if ((skb = hfc_empty_fifo(bcs, rcnt))) { + skb_queue_tail(&bcs->rqueue, skb); + schedule_event(bcs, B_RCVBUFREADY); + } + } + receive = 1; + } + if (bcs->tx_skb) { + transmit = 1; + test_and_set_bit(BC_FLG_BUSY, &bcs->Flag); + hfc_fill_fifo(bcs); + if (test_bit(BC_FLG_BUSY, &bcs->Flag)) + transmit = 0; + } else { + if ((bcs->tx_skb = skb_dequeue(&bcs->squeue))) { + transmit = 1; + test_and_set_bit(BC_FLG_BUSY, &bcs->Flag); + hfc_fill_fifo(bcs); + if (test_bit(BC_FLG_BUSY, &bcs->Flag)) + transmit = 0; + } else { + transmit = 0; + schedule_event(bcs, B_XMTBUFREADY); + } + } + if ((receive || transmit) && count) + goto Begin; + return; +} + +static void +mode_hfc(struct BCState *bcs, int mode, int bc) +{ + struct IsdnCardState *cs = bcs->cs; + + if (cs->debug & L1_DEB_HSCX) + debugl1(cs, "HFC 2BS0 mode %d bchan %d/%d", + mode, bc, bcs->channel); + bcs->mode = mode; + bcs->channel = bc; + + switch (mode) { + case (L1_MODE_NULL): + if (bc) { + cs->hw.hfc.ctmt &= ~1; + cs->hw.hfc.isac_spcr &= ~0x03; + } + else { + cs->hw.hfc.ctmt &= ~2; + cs->hw.hfc.isac_spcr &= ~0x0c; + } + break; + case (L1_MODE_TRANS): + cs->hw.hfc.ctmt &= ~(1 << bc); /* set HDLC mode */ + cs->BC_Write_Reg(cs, HFC_STATUS, cs->hw.hfc.ctmt, cs->hw.hfc.ctmt); + hfc_clear_fifo(bcs); /* complete fifo clear */ + if (bc) { + cs->hw.hfc.ctmt |= 1; + cs->hw.hfc.isac_spcr &= ~0x03; + cs->hw.hfc.isac_spcr |= 0x02; + } else { + cs->hw.hfc.ctmt |= 2; + cs->hw.hfc.isac_spcr &= ~0x0c; + cs->hw.hfc.isac_spcr |= 0x08; + } + break; + case (L1_MODE_HDLC): + if (bc) { + cs->hw.hfc.ctmt &= ~1; + cs->hw.hfc.isac_spcr &= ~0x03; + cs->hw.hfc.isac_spcr |= 0x02; + } else { + cs->hw.hfc.ctmt &= ~2; + cs->hw.hfc.isac_spcr &= ~0x0c; + cs->hw.hfc.isac_spcr |= 0x08; + } + break; + } + cs->BC_Write_Reg(cs, HFC_STATUS, cs->hw.hfc.ctmt, cs->hw.hfc.ctmt); + cs->writeisac(cs, ISAC_SPCR, cs->hw.hfc.isac_spcr); + if (mode == L1_MODE_HDLC) + hfc_clear_fifo(bcs); +} + +static void +hfc_l2l1(struct PStack *st, int pr, void *arg) +{ + struct BCState *bcs = st->l1.bcs; + struct sk_buff *skb = arg; + u_long flags; + + switch (pr) { + case (PH_DATA | REQUEST): + spin_lock_irqsave(&bcs->cs->lock, flags); + if (bcs->tx_skb) { + skb_queue_tail(&bcs->squeue, skb); + } else { + bcs->tx_skb = skb; + test_and_set_bit(BC_FLG_BUSY, &bcs->Flag); + bcs->cs->BC_Send_Data(bcs); + } + spin_unlock_irqrestore(&bcs->cs->lock, flags); + break; + case (PH_PULL | INDICATION): + spin_lock_irqsave(&bcs->cs->lock, flags); + if (bcs->tx_skb) { + printk(KERN_WARNING "hfc_l2l1: this shouldn't happen\n"); + } else { + test_and_set_bit(BC_FLG_BUSY, &bcs->Flag); + bcs->tx_skb = skb; + bcs->cs->BC_Send_Data(bcs); + } + spin_unlock_irqrestore(&bcs->cs->lock, flags); + break; + case (PH_PULL | REQUEST): + if (!bcs->tx_skb) { + test_and_clear_bit(FLG_L1_PULL_REQ, &st->l1.Flags); + st->l1.l1l2(st, PH_PULL | CONFIRM, NULL); + } else + test_and_set_bit(FLG_L1_PULL_REQ, &st->l1.Flags); + break; + case (PH_ACTIVATE | REQUEST): + spin_lock_irqsave(&bcs->cs->lock, flags); + test_and_set_bit(BC_FLG_ACTIV, &bcs->Flag); + mode_hfc(bcs, st->l1.mode, st->l1.bc); + spin_unlock_irqrestore(&bcs->cs->lock, flags); + l1_msg_b(st, pr, arg); + break; + case (PH_DEACTIVATE | REQUEST): + l1_msg_b(st, pr, arg); + break; + case (PH_DEACTIVATE | CONFIRM): + spin_lock_irqsave(&bcs->cs->lock, flags); + test_and_clear_bit(BC_FLG_ACTIV, &bcs->Flag); + test_and_clear_bit(BC_FLG_BUSY, &bcs->Flag); + mode_hfc(bcs, 0, st->l1.bc); + spin_unlock_irqrestore(&bcs->cs->lock, flags); + st->l1.l1l2(st, PH_DEACTIVATE | CONFIRM, NULL); + break; + } +} + + +static void +close_hfcstate(struct BCState *bcs) +{ + mode_hfc(bcs, 0, bcs->channel); + if (test_bit(BC_FLG_INIT, &bcs->Flag)) { + skb_queue_purge(&bcs->rqueue); + skb_queue_purge(&bcs->squeue); + if (bcs->tx_skb) { + dev_kfree_skb_any(bcs->tx_skb); + bcs->tx_skb = NULL; + test_and_clear_bit(BC_FLG_BUSY, &bcs->Flag); + } + } + test_and_clear_bit(BC_FLG_INIT, &bcs->Flag); +} + +static int +open_hfcstate(struct IsdnCardState *cs, struct BCState *bcs) +{ + if (!test_and_set_bit(BC_FLG_INIT, &bcs->Flag)) { + skb_queue_head_init(&bcs->rqueue); + skb_queue_head_init(&bcs->squeue); + } + bcs->tx_skb = NULL; + test_and_clear_bit(BC_FLG_BUSY, &bcs->Flag); + bcs->event = 0; + bcs->tx_cnt = 0; + return (0); +} + +static int +setstack_hfc(struct PStack *st, struct BCState *bcs) +{ + bcs->channel = st->l1.bc; + if (open_hfcstate(st->l1.hardware, bcs)) + return (-1); + st->l1.bcs = bcs; + st->l2.l2l1 = hfc_l2l1; + setstack_manager(st); + bcs->st = st; + setstack_l1_B(st); + return (0); +} + +static void +init_send(struct BCState *bcs) +{ + int i; + + bcs->hw.hfc.send = kmalloc_array(32, sizeof(unsigned int), GFP_ATOMIC); + if (!bcs->hw.hfc.send) { + printk(KERN_WARNING + "HiSax: No memory for hfc.send\n"); + return; + } + for (i = 0; i < 32; i++) + bcs->hw.hfc.send[i] = 0x1fff; +} + +void +inithfc(struct IsdnCardState *cs) +{ + init_send(&cs->bcs[0]); + init_send(&cs->bcs[1]); + cs->BC_Send_Data = &hfc_fill_fifo; + cs->bcs[0].BC_SetStack = setstack_hfc; + cs->bcs[1].BC_SetStack = setstack_hfc; + cs->bcs[0].BC_Close = close_hfcstate; + cs->bcs[1].BC_Close = close_hfcstate; + mode_hfc(cs->bcs, 0, 0); + mode_hfc(cs->bcs + 1, 0, 0); +} + +void +releasehfc(struct IsdnCardState *cs) +{ + kfree(cs->bcs[0].hw.hfc.send); + cs->bcs[0].hw.hfc.send = NULL; + kfree(cs->bcs[1].hw.hfc.send); + cs->bcs[1].hw.hfc.send = NULL; +} diff --git a/drivers/isdn/hisax/hfc_2bs0.h b/drivers/isdn/hisax/hfc_2bs0.h new file mode 100644 index 000000000..151009636 --- /dev/null +++ b/drivers/isdn/hisax/hfc_2bs0.h @@ -0,0 +1,60 @@ +/* $Id: hfc_2bs0.h,v 1.5.2.2 2004/01/12 22:52:26 keil Exp $ + * + * specific defines for CCD's HFC 2BS0 + * + * Author Karsten Keil + * Copyright by Karsten Keil <keil@isdn4linux.de> + * + * This software may be used and distributed according to the terms + * of the GNU General Public License, incorporated herein by reference. + * + */ + +#define HFC_CTMT 0xe0 +#define HFC_CIRM 0xc0 +#define HFC_CIP 0x80 +#define HFC_Z1 0x00 +#define HFC_Z2 0x08 +#define HFC_Z_LOW 0x00 +#define HFC_Z_HIGH 0x04 +#define HFC_F1_INC 0x28 +#define HFC_FIFO_IN 0x2c +#define HFC_F1 0x30 +#define HFC_F2 0x34 +#define HFC_F2_INC 0x38 +#define HFC_FIFO_OUT 0x3c +#define HFC_B1 0x00 +#define HFC_B2 0x02 +#define HFC_REC 0x01 +#define HFC_SEND 0x00 +#define HFC_CHANNEL(ch) (ch ? HFC_B2 : HFC_B1) + +#define HFC_STATUS 0 +#define HFC_DATA 1 +#define HFC_DATA_NODEB 2 + +/* Status (READ) */ +#define HFC_BUSY 0x01 +#define HFC_TIMINT 0x02 +#define HFC_EXTINT 0x04 + +/* CTMT (Write) */ +#define HFC_CLTIMER 0x10 +#define HFC_TIM50MS 0x08 +#define HFC_TIMIRQE 0x04 +#define HFC_TRANSB2 0x02 +#define HFC_TRANSB1 0x01 + +/* CIRM (Write) */ +#define HFC_RESET 0x08 +#define HFC_MEM8K 0x10 +#define HFC_INTA 0x01 +#define HFC_INTB 0x02 +#define HFC_INTC 0x03 +#define HFC_INTD 0x04 +#define HFC_INTE 0x05 +#define HFC_INTF 0x06 + +extern void main_irq_hfc(struct BCState *bcs); +extern void inithfc(struct IsdnCardState *cs); +extern void releasehfc(struct IsdnCardState *cs); diff --git a/drivers/isdn/hisax/hfc_pci.c b/drivers/isdn/hisax/hfc_pci.c new file mode 100644 index 000000000..64a63711f --- /dev/null +++ b/drivers/isdn/hisax/hfc_pci.c @@ -0,0 +1,1757 @@ +/* $Id: hfc_pci.c,v 1.48.2.4 2004/02/11 13:21:33 keil Exp $ + * + * low level driver for CCD's hfc-pci based cards + * + * Author Werner Cornelius + * based on existing driver for CCD hfc ISA cards + * Copyright by Werner Cornelius <werner@isdn4linux.de> + * by Karsten Keil <keil@isdn4linux.de> + * + * This software may be used and distributed according to the terms + * of the GNU General Public License, incorporated herein by reference. + * + * For changes and modifications please read + * Documentation/isdn/HiSax.cert + * + */ + +#include <linux/init.h> +#include "hisax.h" +#include "hfc_pci.h" +#include "isdnl1.h" +#include <linux/pci.h> +#include <linux/sched.h> +#include <linux/interrupt.h> + +static const char *hfcpci_revision = "$Revision: 1.48.2.4 $"; + +/* table entry in the PCI devices list */ +typedef struct { + int vendor_id; + int device_id; + char *vendor_name; + char *card_name; +} PCI_ENTRY; + +#define NT_T1_COUNT 20 /* number of 3.125ms interrupts for G2 timeout */ +#define CLKDEL_TE 0x0e /* CLKDEL in TE mode */ +#define CLKDEL_NT 0x6c /* CLKDEL in NT mode */ + +static const PCI_ENTRY id_list[] = +{ + {PCI_VENDOR_ID_CCD, PCI_DEVICE_ID_CCD_2BD0, "CCD/Billion/Asuscom", "2BD0"}, + {PCI_VENDOR_ID_CCD, PCI_DEVICE_ID_CCD_B000, "Billion", "B000"}, + {PCI_VENDOR_ID_CCD, PCI_DEVICE_ID_CCD_B006, "Billion", "B006"}, + {PCI_VENDOR_ID_CCD, PCI_DEVICE_ID_CCD_B007, "Billion", "B007"}, + {PCI_VENDOR_ID_CCD, PCI_DEVICE_ID_CCD_B008, "Billion", "B008"}, + {PCI_VENDOR_ID_CCD, PCI_DEVICE_ID_CCD_B009, "Billion", "B009"}, + {PCI_VENDOR_ID_CCD, PCI_DEVICE_ID_CCD_B00A, "Billion", "B00A"}, + {PCI_VENDOR_ID_CCD, PCI_DEVICE_ID_CCD_B00B, "Billion", "B00B"}, + {PCI_VENDOR_ID_CCD, PCI_DEVICE_ID_CCD_B00C, "Billion", "B00C"}, + {PCI_VENDOR_ID_CCD, PCI_DEVICE_ID_CCD_B100, "Seyeon", "B100"}, + {PCI_VENDOR_ID_CCD, PCI_DEVICE_ID_CCD_B700, "Primux II S0", "B700"}, + {PCI_VENDOR_ID_CCD, PCI_DEVICE_ID_CCD_B701, "Primux II S0 NT", "B701"}, + {PCI_VENDOR_ID_ABOCOM, PCI_DEVICE_ID_ABOCOM_2BD1, "Abocom/Magitek", "2BD1"}, + {PCI_VENDOR_ID_ASUSTEK, PCI_DEVICE_ID_ASUSTEK_0675, "Asuscom/Askey", "675"}, + {PCI_VENDOR_ID_BERKOM, PCI_DEVICE_ID_BERKOM_T_CONCEPT, "German telekom", "T-Concept"}, + {PCI_VENDOR_ID_BERKOM, PCI_DEVICE_ID_BERKOM_A1T, "German telekom", "A1T"}, + {PCI_VENDOR_ID_ANIGMA, PCI_DEVICE_ID_ANIGMA_MC145575, "Motorola MC145575", "MC145575"}, + {PCI_VENDOR_ID_ZOLTRIX, PCI_DEVICE_ID_ZOLTRIX_2BD0, "Zoltrix", "2BD0"}, + {PCI_VENDOR_ID_DIGI, PCI_DEVICE_ID_DIGI_DF_M_IOM2_E, "Digi International", "Digi DataFire Micro V IOM2 (Europe)"}, + {PCI_VENDOR_ID_DIGI, PCI_DEVICE_ID_DIGI_DF_M_E, "Digi International", "Digi DataFire Micro V (Europe)"}, + {PCI_VENDOR_ID_DIGI, PCI_DEVICE_ID_DIGI_DF_M_IOM2_A, "Digi International", "Digi DataFire Micro V IOM2 (North America)"}, + {PCI_VENDOR_ID_DIGI, PCI_DEVICE_ID_DIGI_DF_M_A, "Digi International", "Digi DataFire Micro V (North America)"}, + {PCI_VENDOR_ID_SITECOM, PCI_DEVICE_ID_SITECOM_DC105V2, "Sitecom Europe", "DC-105 ISDN PCI"}, + {0, 0, NULL, NULL}, +}; + + +/******************************************/ +/* free hardware resources used by driver */ +/******************************************/ +static void +release_io_hfcpci(struct IsdnCardState *cs) +{ + printk(KERN_INFO "HiSax: release hfcpci at %p\n", + cs->hw.hfcpci.pci_io); + cs->hw.hfcpci.int_m2 = 0; /* interrupt output off ! */ + Write_hfc(cs, HFCPCI_INT_M2, cs->hw.hfcpci.int_m2); + Write_hfc(cs, HFCPCI_CIRM, HFCPCI_RESET); /* Reset On */ + mdelay(10); + Write_hfc(cs, HFCPCI_CIRM, 0); /* Reset Off */ + mdelay(10); + Write_hfc(cs, HFCPCI_INT_M2, cs->hw.hfcpci.int_m2); + pci_write_config_word(cs->hw.hfcpci.dev, PCI_COMMAND, 0); /* disable memory mapped ports + busmaster */ + del_timer(&cs->hw.hfcpci.timer); + pci_free_consistent(cs->hw.hfcpci.dev, 0x8000, + cs->hw.hfcpci.fifos, cs->hw.hfcpci.dma); + cs->hw.hfcpci.fifos = NULL; + iounmap((void *)cs->hw.hfcpci.pci_io); +} + +/********************************************************************************/ +/* function called to reset the HFC PCI chip. A complete software reset of chip */ +/* and fifos is done. */ +/********************************************************************************/ +static void +reset_hfcpci(struct IsdnCardState *cs) +{ + pci_write_config_word(cs->hw.hfcpci.dev, PCI_COMMAND, PCI_ENA_MEMIO); /* enable memory mapped ports, disable busmaster */ + cs->hw.hfcpci.int_m2 = 0; /* interrupt output off ! */ + Write_hfc(cs, HFCPCI_INT_M2, cs->hw.hfcpci.int_m2); + + printk(KERN_INFO "HFC_PCI: resetting card\n"); + pci_write_config_word(cs->hw.hfcpci.dev, PCI_COMMAND, PCI_ENA_MEMIO + PCI_ENA_MASTER); /* enable memory ports + busmaster */ + Write_hfc(cs, HFCPCI_CIRM, HFCPCI_RESET); /* Reset On */ + mdelay(10); + Write_hfc(cs, HFCPCI_CIRM, 0); /* Reset Off */ + mdelay(10); + if (Read_hfc(cs, HFCPCI_STATUS) & 2) + printk(KERN_WARNING "HFC-PCI init bit busy\n"); + + cs->hw.hfcpci.fifo_en = 0x30; /* only D fifos enabled */ + Write_hfc(cs, HFCPCI_FIFO_EN, cs->hw.hfcpci.fifo_en); + + cs->hw.hfcpci.trm = 0 + HFCPCI_BTRANS_THRESMASK; /* no echo connect , threshold */ + Write_hfc(cs, HFCPCI_TRM, cs->hw.hfcpci.trm); + + Write_hfc(cs, HFCPCI_CLKDEL, CLKDEL_TE); /* ST-Bit delay for TE-Mode */ + cs->hw.hfcpci.sctrl_e = HFCPCI_AUTO_AWAKE; + Write_hfc(cs, HFCPCI_SCTRL_E, cs->hw.hfcpci.sctrl_e); /* S/T Auto awake */ + cs->hw.hfcpci.bswapped = 0; /* no exchange */ + cs->hw.hfcpci.nt_mode = 0; /* we are in TE mode */ + cs->hw.hfcpci.ctmt = HFCPCI_TIM3_125 | HFCPCI_AUTO_TIMER; + Write_hfc(cs, HFCPCI_CTMT, cs->hw.hfcpci.ctmt); + + cs->hw.hfcpci.int_m1 = HFCPCI_INTS_DTRANS | HFCPCI_INTS_DREC | + HFCPCI_INTS_L1STATE | HFCPCI_INTS_TIMER; + Write_hfc(cs, HFCPCI_INT_M1, cs->hw.hfcpci.int_m1); + + /* Clear already pending ints */ + if (Read_hfc(cs, HFCPCI_INT_S1)); + + Write_hfc(cs, HFCPCI_STATES, HFCPCI_LOAD_STATE | 2); /* HFC ST 2 */ + udelay(10); + Write_hfc(cs, HFCPCI_STATES, 2); /* HFC ST 2 */ + cs->hw.hfcpci.mst_m = HFCPCI_MASTER; /* HFC Master Mode */ + + Write_hfc(cs, HFCPCI_MST_MODE, cs->hw.hfcpci.mst_m); + cs->hw.hfcpci.sctrl = 0x40; /* set tx_lo mode, error in datasheet ! */ + Write_hfc(cs, HFCPCI_SCTRL, cs->hw.hfcpci.sctrl); + cs->hw.hfcpci.sctrl_r = 0; + Write_hfc(cs, HFCPCI_SCTRL_R, cs->hw.hfcpci.sctrl_r); + + /* Init GCI/IOM2 in master mode */ + /* Slots 0 and 1 are set for B-chan 1 and 2 */ + /* D- and monitor/CI channel are not enabled */ + /* STIO1 is used as output for data, B1+B2 from ST->IOM+HFC */ + /* STIO2 is used as data input, B1+B2 from IOM->ST */ + /* ST B-channel send disabled -> continuous 1s */ + /* The IOM slots are always enabled */ + cs->hw.hfcpci.conn = 0x36; /* set data flow directions */ + Write_hfc(cs, HFCPCI_CONNECT, cs->hw.hfcpci.conn); + Write_hfc(cs, HFCPCI_B1_SSL, 0x80); /* B1-Slot 0 STIO1 out enabled */ + Write_hfc(cs, HFCPCI_B2_SSL, 0x81); /* B2-Slot 1 STIO1 out enabled */ + Write_hfc(cs, HFCPCI_B1_RSL, 0x80); /* B1-Slot 0 STIO2 in enabled */ + Write_hfc(cs, HFCPCI_B2_RSL, 0x81); /* B2-Slot 1 STIO2 in enabled */ + + /* Finally enable IRQ output */ + cs->hw.hfcpci.int_m2 = HFCPCI_IRQ_ENABLE; + Write_hfc(cs, HFCPCI_INT_M2, cs->hw.hfcpci.int_m2); + if (Read_hfc(cs, HFCPCI_INT_S1)); +} + +/***************************************************/ +/* Timer function called when kernel timer expires */ +/***************************************************/ +static void +hfcpci_Timer(struct timer_list *t) +{ + struct IsdnCardState *cs = from_timer(cs, t, hw.hfcpci.timer); + cs->hw.hfcpci.timer.expires = jiffies + 75; + /* WD RESET */ +/* WriteReg(cs, HFCD_DATA, HFCD_CTMT, cs->hw.hfcpci.ctmt | 0x80); + add_timer(&cs->hw.hfcpci.timer); +*/ +} + + +/*********************************/ +/* schedule a new D-channel task */ +/*********************************/ +static void +sched_event_D_pci(struct IsdnCardState *cs, int event) +{ + test_and_set_bit(event, &cs->event); + schedule_work(&cs->tqueue); +} + +/*********************************/ +/* schedule a new b_channel task */ +/*********************************/ +static void +hfcpci_sched_event(struct BCState *bcs, int event) +{ + test_and_set_bit(event, &bcs->event); + schedule_work(&bcs->tqueue); +} + +/************************************************/ +/* select a b-channel entry matching and active */ +/************************************************/ +static +struct BCState * +Sel_BCS(struct IsdnCardState *cs, int channel) +{ + if (cs->bcs[0].mode && (cs->bcs[0].channel == channel)) + return (&cs->bcs[0]); + else if (cs->bcs[1].mode && (cs->bcs[1].channel == channel)) + return (&cs->bcs[1]); + else + return (NULL); +} + +/***************************************/ +/* clear the desired B-channel rx fifo */ +/***************************************/ +static void hfcpci_clear_fifo_rx(struct IsdnCardState *cs, int fifo) +{ u_char fifo_state; + bzfifo_type *bzr; + + if (fifo) { + bzr = &((fifo_area *) (cs->hw.hfcpci.fifos))->b_chans.rxbz_b2; + fifo_state = cs->hw.hfcpci.fifo_en & HFCPCI_FIFOEN_B2RX; + } else { + bzr = &((fifo_area *) (cs->hw.hfcpci.fifos))->b_chans.rxbz_b1; + fifo_state = cs->hw.hfcpci.fifo_en & HFCPCI_FIFOEN_B1RX; + } + if (fifo_state) + cs->hw.hfcpci.fifo_en ^= fifo_state; + Write_hfc(cs, HFCPCI_FIFO_EN, cs->hw.hfcpci.fifo_en); + cs->hw.hfcpci.last_bfifo_cnt[fifo] = 0; + bzr->za[MAX_B_FRAMES].z1 = B_FIFO_SIZE + B_SUB_VAL - 1; + bzr->za[MAX_B_FRAMES].z2 = bzr->za[MAX_B_FRAMES].z1; + bzr->f1 = MAX_B_FRAMES; + bzr->f2 = bzr->f1; /* init F pointers to remain constant */ + if (fifo_state) + cs->hw.hfcpci.fifo_en |= fifo_state; + Write_hfc(cs, HFCPCI_FIFO_EN, cs->hw.hfcpci.fifo_en); +} + +/***************************************/ +/* clear the desired B-channel tx fifo */ +/***************************************/ +static void hfcpci_clear_fifo_tx(struct IsdnCardState *cs, int fifo) +{ u_char fifo_state; + bzfifo_type *bzt; + + if (fifo) { + bzt = &((fifo_area *) (cs->hw.hfcpci.fifos))->b_chans.txbz_b2; + fifo_state = cs->hw.hfcpci.fifo_en & HFCPCI_FIFOEN_B2TX; + } else { + bzt = &((fifo_area *) (cs->hw.hfcpci.fifos))->b_chans.txbz_b1; + fifo_state = cs->hw.hfcpci.fifo_en & HFCPCI_FIFOEN_B1TX; + } + if (fifo_state) + cs->hw.hfcpci.fifo_en ^= fifo_state; + Write_hfc(cs, HFCPCI_FIFO_EN, cs->hw.hfcpci.fifo_en); + bzt->za[MAX_B_FRAMES].z1 = B_FIFO_SIZE + B_SUB_VAL - 1; + bzt->za[MAX_B_FRAMES].z2 = bzt->za[MAX_B_FRAMES].z1; + bzt->f1 = MAX_B_FRAMES; + bzt->f2 = bzt->f1; /* init F pointers to remain constant */ + if (fifo_state) + cs->hw.hfcpci.fifo_en |= fifo_state; + Write_hfc(cs, HFCPCI_FIFO_EN, cs->hw.hfcpci.fifo_en); +} + +/*********************************************/ +/* read a complete B-frame out of the buffer */ +/*********************************************/ +static struct sk_buff +* +hfcpci_empty_fifo(struct BCState *bcs, bzfifo_type *bz, u_char *bdata, int count) +{ + u_char *ptr, *ptr1, new_f2; + struct sk_buff *skb; + struct IsdnCardState *cs = bcs->cs; + int total, maxlen, new_z2; + z_type *zp; + + if ((cs->debug & L1_DEB_HSCX) && !(cs->debug & L1_DEB_HSCX_FIFO)) + debugl1(cs, "hfcpci_empty_fifo"); + zp = &bz->za[bz->f2]; /* point to Z-Regs */ + new_z2 = zp->z2 + count; /* new position in fifo */ + if (new_z2 >= (B_FIFO_SIZE + B_SUB_VAL)) + new_z2 -= B_FIFO_SIZE; /* buffer wrap */ + new_f2 = (bz->f2 + 1) & MAX_B_FRAMES; + if ((count > HSCX_BUFMAX + 3) || (count < 4) || + (*(bdata + (zp->z1 - B_SUB_VAL)))) { + if (cs->debug & L1_DEB_WARN) + debugl1(cs, "hfcpci_empty_fifo: incoming packet invalid length %d or crc", count); +#ifdef ERROR_STATISTIC + bcs->err_inv++; +#endif + bz->za[new_f2].z2 = new_z2; + bz->f2 = new_f2; /* next buffer */ + skb = NULL; + } else if (!(skb = dev_alloc_skb(count - 3))) + printk(KERN_WARNING "HFCPCI: receive out of memory\n"); + else { + total = count; + count -= 3; + ptr = skb_put(skb, count); + + if (zp->z2 + count <= B_FIFO_SIZE + B_SUB_VAL) + maxlen = count; /* complete transfer */ + else + maxlen = B_FIFO_SIZE + B_SUB_VAL - zp->z2; /* maximum */ + + ptr1 = bdata + (zp->z2 - B_SUB_VAL); /* start of data */ + memcpy(ptr, ptr1, maxlen); /* copy data */ + count -= maxlen; + + if (count) { /* rest remaining */ + ptr += maxlen; + ptr1 = bdata; /* start of buffer */ + memcpy(ptr, ptr1, count); /* rest */ + } + bz->za[new_f2].z2 = new_z2; + bz->f2 = new_f2; /* next buffer */ + + } + return (skb); +} + +/*******************************/ +/* D-channel receive procedure */ +/*******************************/ +static +int +receive_dmsg(struct IsdnCardState *cs) +{ + struct sk_buff *skb; + int maxlen; + int rcnt, total; + int count = 5; + u_char *ptr, *ptr1; + dfifo_type *df; + z_type *zp; + + df = &((fifo_area *) (cs->hw.hfcpci.fifos))->d_chan.d_rx; + if (test_and_set_bit(FLG_LOCK_ATOMIC, &cs->HW_Flags)) { + debugl1(cs, "rec_dmsg blocked"); + return (1); + } + while (((df->f1 & D_FREG_MASK) != (df->f2 & D_FREG_MASK)) && count--) { + zp = &df->za[df->f2 & D_FREG_MASK]; + rcnt = zp->z1 - zp->z2; + if (rcnt < 0) + rcnt += D_FIFO_SIZE; + rcnt++; + if (cs->debug & L1_DEB_ISAC) + debugl1(cs, "hfcpci recd f1(%d) f2(%d) z1(%x) z2(%x) cnt(%d)", + df->f1, df->f2, zp->z1, zp->z2, rcnt); + + if ((rcnt > MAX_DFRAME_LEN + 3) || (rcnt < 4) || + (df->data[zp->z1])) { + if (cs->debug & L1_DEB_WARN) + debugl1(cs, "empty_fifo hfcpci packet inv. len %d or crc %d", rcnt, df->data[zp->z1]); +#ifdef ERROR_STATISTIC + cs->err_rx++; +#endif + df->f2 = ((df->f2 + 1) & MAX_D_FRAMES) | (MAX_D_FRAMES + 1); /* next buffer */ + df->za[df->f2 & D_FREG_MASK].z2 = (zp->z2 + rcnt) & (D_FIFO_SIZE - 1); + } else if ((skb = dev_alloc_skb(rcnt - 3))) { + total = rcnt; + rcnt -= 3; + ptr = skb_put(skb, rcnt); + + if (zp->z2 + rcnt <= D_FIFO_SIZE) + maxlen = rcnt; /* complete transfer */ + else + maxlen = D_FIFO_SIZE - zp->z2; /* maximum */ + + ptr1 = df->data + zp->z2; /* start of data */ + memcpy(ptr, ptr1, maxlen); /* copy data */ + rcnt -= maxlen; + + if (rcnt) { /* rest remaining */ + ptr += maxlen; + ptr1 = df->data; /* start of buffer */ + memcpy(ptr, ptr1, rcnt); /* rest */ + } + df->f2 = ((df->f2 + 1) & MAX_D_FRAMES) | (MAX_D_FRAMES + 1); /* next buffer */ + df->za[df->f2 & D_FREG_MASK].z2 = (zp->z2 + total) & (D_FIFO_SIZE - 1); + + skb_queue_tail(&cs->rq, skb); + sched_event_D_pci(cs, D_RCVBUFREADY); + } else + printk(KERN_WARNING "HFC-PCI: D receive out of memory\n"); + } + test_and_clear_bit(FLG_LOCK_ATOMIC, &cs->HW_Flags); + return (1); +} + +/*******************************************************************************/ +/* check for transparent receive data and read max one threshold size if avail */ +/*******************************************************************************/ +static int +hfcpci_empty_fifo_trans(struct BCState *bcs, bzfifo_type *bz, u_char *bdata) +{ + unsigned short *z1r, *z2r; + int new_z2, fcnt, maxlen; + struct sk_buff *skb; + u_char *ptr, *ptr1; + + z1r = &bz->za[MAX_B_FRAMES].z1; /* pointer to z reg */ + z2r = z1r + 1; + + if (!(fcnt = *z1r - *z2r)) + return (0); /* no data avail */ + + if (fcnt <= 0) + fcnt += B_FIFO_SIZE; /* bytes actually buffered */ + if (fcnt > HFCPCI_BTRANS_THRESHOLD) + fcnt = HFCPCI_BTRANS_THRESHOLD; /* limit size */ + + new_z2 = *z2r + fcnt; /* new position in fifo */ + if (new_z2 >= (B_FIFO_SIZE + B_SUB_VAL)) + new_z2 -= B_FIFO_SIZE; /* buffer wrap */ + + if (!(skb = dev_alloc_skb(fcnt))) + printk(KERN_WARNING "HFCPCI: receive out of memory\n"); + else { + ptr = skb_put(skb, fcnt); + if (*z2r + fcnt <= B_FIFO_SIZE + B_SUB_VAL) + maxlen = fcnt; /* complete transfer */ + else + maxlen = B_FIFO_SIZE + B_SUB_VAL - *z2r; /* maximum */ + + ptr1 = bdata + (*z2r - B_SUB_VAL); /* start of data */ + memcpy(ptr, ptr1, maxlen); /* copy data */ + fcnt -= maxlen; + + if (fcnt) { /* rest remaining */ + ptr += maxlen; + ptr1 = bdata; /* start of buffer */ + memcpy(ptr, ptr1, fcnt); /* rest */ + } + skb_queue_tail(&bcs->rqueue, skb); + hfcpci_sched_event(bcs, B_RCVBUFREADY); + } + + *z2r = new_z2; /* new position */ + return (1); +} /* hfcpci_empty_fifo_trans */ + +/**********************************/ +/* B-channel main receive routine */ +/**********************************/ +static void +main_rec_hfcpci(struct BCState *bcs) +{ + struct IsdnCardState *cs = bcs->cs; + int rcnt, real_fifo; + int receive, count = 5; + struct sk_buff *skb; + bzfifo_type *bz; + u_char *bdata; + z_type *zp; + + + if ((bcs->channel) && (!cs->hw.hfcpci.bswapped)) { + bz = &((fifo_area *) (cs->hw.hfcpci.fifos))->b_chans.rxbz_b2; + bdata = ((fifo_area *) (cs->hw.hfcpci.fifos))->b_chans.rxdat_b2; + real_fifo = 1; + } else { + bz = &((fifo_area *) (cs->hw.hfcpci.fifos))->b_chans.rxbz_b1; + bdata = ((fifo_area *) (cs->hw.hfcpci.fifos))->b_chans.rxdat_b1; + real_fifo = 0; + } +Begin: + count--; + if (test_and_set_bit(FLG_LOCK_ATOMIC, &cs->HW_Flags)) { + debugl1(cs, "rec_data %d blocked", bcs->channel); + return; + } + if (bz->f1 != bz->f2) { + if (cs->debug & L1_DEB_HSCX) + debugl1(cs, "hfcpci rec %d f1(%d) f2(%d)", + bcs->channel, bz->f1, bz->f2); + zp = &bz->za[bz->f2]; + + rcnt = zp->z1 - zp->z2; + if (rcnt < 0) + rcnt += B_FIFO_SIZE; + rcnt++; + if (cs->debug & L1_DEB_HSCX) + debugl1(cs, "hfcpci rec %d z1(%x) z2(%x) cnt(%d)", + bcs->channel, zp->z1, zp->z2, rcnt); + if ((skb = hfcpci_empty_fifo(bcs, bz, bdata, rcnt))) { + skb_queue_tail(&bcs->rqueue, skb); + hfcpci_sched_event(bcs, B_RCVBUFREADY); + } + rcnt = bz->f1 - bz->f2; + if (rcnt < 0) + rcnt += MAX_B_FRAMES + 1; + if (cs->hw.hfcpci.last_bfifo_cnt[real_fifo] > rcnt + 1) { + rcnt = 0; + hfcpci_clear_fifo_rx(cs, real_fifo); + } + cs->hw.hfcpci.last_bfifo_cnt[real_fifo] = rcnt; + if (rcnt > 1) + receive = 1; + else + receive = 0; + } else if (bcs->mode == L1_MODE_TRANS) + receive = hfcpci_empty_fifo_trans(bcs, bz, bdata); + else + receive = 0; + test_and_clear_bit(FLG_LOCK_ATOMIC, &cs->HW_Flags); + if (count && receive) + goto Begin; +} + +/**************************/ +/* D-channel send routine */ +/**************************/ +static void +hfcpci_fill_dfifo(struct IsdnCardState *cs) +{ + int fcnt; + int count, new_z1, maxlen; + dfifo_type *df; + u_char *src, *dst, new_f1; + + if (!cs->tx_skb) + return; + if (cs->tx_skb->len <= 0) + return; + + df = &((fifo_area *) (cs->hw.hfcpci.fifos))->d_chan.d_tx; + + if (cs->debug & L1_DEB_ISAC) + debugl1(cs, "hfcpci_fill_Dfifo f1(%d) f2(%d) z1(f1)(%x)", + df->f1, df->f2, + df->za[df->f1 & D_FREG_MASK].z1); + fcnt = df->f1 - df->f2; /* frame count actually buffered */ + if (fcnt < 0) + fcnt += (MAX_D_FRAMES + 1); /* if wrap around */ + if (fcnt > (MAX_D_FRAMES - 1)) { + if (cs->debug & L1_DEB_ISAC) + debugl1(cs, "hfcpci_fill_Dfifo more as 14 frames"); +#ifdef ERROR_STATISTIC + cs->err_tx++; +#endif + return; + } + /* now determine free bytes in FIFO buffer */ + count = df->za[df->f2 & D_FREG_MASK].z2 - df->za[df->f1 & D_FREG_MASK].z1 - 1; + if (count <= 0) + count += D_FIFO_SIZE; /* count now contains available bytes */ + + if (cs->debug & L1_DEB_ISAC) + debugl1(cs, "hfcpci_fill_Dfifo count(%u/%d)", + cs->tx_skb->len, count); + if (count < cs->tx_skb->len) { + if (cs->debug & L1_DEB_ISAC) + debugl1(cs, "hfcpci_fill_Dfifo no fifo mem"); + return; + } + count = cs->tx_skb->len; /* get frame len */ + new_z1 = (df->za[df->f1 & D_FREG_MASK].z1 + count) & (D_FIFO_SIZE - 1); + new_f1 = ((df->f1 + 1) & D_FREG_MASK) | (D_FREG_MASK + 1); + src = cs->tx_skb->data; /* source pointer */ + dst = df->data + df->za[df->f1 & D_FREG_MASK].z1; + maxlen = D_FIFO_SIZE - df->za[df->f1 & D_FREG_MASK].z1; /* end fifo */ + if (maxlen > count) + maxlen = count; /* limit size */ + memcpy(dst, src, maxlen); /* first copy */ + + count -= maxlen; /* remaining bytes */ + if (count) { + dst = df->data; /* start of buffer */ + src += maxlen; /* new position */ + memcpy(dst, src, count); + } + df->za[new_f1 & D_FREG_MASK].z1 = new_z1; /* for next buffer */ + df->za[df->f1 & D_FREG_MASK].z1 = new_z1; /* new pos actual buffer */ + df->f1 = new_f1; /* next frame */ + + dev_kfree_skb_any(cs->tx_skb); + cs->tx_skb = NULL; +} + +/**************************/ +/* B-channel send routine */ +/**************************/ +static void +hfcpci_fill_fifo(struct BCState *bcs) +{ + struct IsdnCardState *cs = bcs->cs; + int maxlen, fcnt; + int count, new_z1; + bzfifo_type *bz; + u_char *bdata; + u_char new_f1, *src, *dst; + unsigned short *z1t, *z2t; + + if (!bcs->tx_skb) + return; + if (bcs->tx_skb->len <= 0) + return; + + if ((bcs->channel) && (!cs->hw.hfcpci.bswapped)) { + bz = &((fifo_area *) (cs->hw.hfcpci.fifos))->b_chans.txbz_b2; + bdata = ((fifo_area *) (cs->hw.hfcpci.fifos))->b_chans.txdat_b2; + } else { + bz = &((fifo_area *) (cs->hw.hfcpci.fifos))->b_chans.txbz_b1; + bdata = ((fifo_area *) (cs->hw.hfcpci.fifos))->b_chans.txdat_b1; + } + + if (bcs->mode == L1_MODE_TRANS) { + z1t = &bz->za[MAX_B_FRAMES].z1; + z2t = z1t + 1; + if (cs->debug & L1_DEB_HSCX) + debugl1(cs, "hfcpci_fill_fifo_trans %d z1(%x) z2(%x)", + bcs->channel, *z1t, *z2t); + fcnt = *z2t - *z1t; + if (fcnt <= 0) + fcnt += B_FIFO_SIZE; /* fcnt contains available bytes in fifo */ + fcnt = B_FIFO_SIZE - fcnt; /* remaining bytes to send */ + + while ((fcnt < 2 * HFCPCI_BTRANS_THRESHOLD) && (bcs->tx_skb)) { + if (bcs->tx_skb->len < B_FIFO_SIZE - fcnt) { + /* data is suitable for fifo */ + count = bcs->tx_skb->len; + + new_z1 = *z1t + count; /* new buffer Position */ + if (new_z1 >= (B_FIFO_SIZE + B_SUB_VAL)) + new_z1 -= B_FIFO_SIZE; /* buffer wrap */ + src = bcs->tx_skb->data; /* source pointer */ + dst = bdata + (*z1t - B_SUB_VAL); + maxlen = (B_FIFO_SIZE + B_SUB_VAL) - *z1t; /* end of fifo */ + if (maxlen > count) + maxlen = count; /* limit size */ + memcpy(dst, src, maxlen); /* first copy */ + + count -= maxlen; /* remaining bytes */ + if (count) { + dst = bdata; /* start of buffer */ + src += maxlen; /* new position */ + memcpy(dst, src, count); + } + bcs->tx_cnt -= bcs->tx_skb->len; + fcnt += bcs->tx_skb->len; + *z1t = new_z1; /* now send data */ + } else if (cs->debug & L1_DEB_HSCX) + debugl1(cs, "hfcpci_fill_fifo_trans %d frame length %d discarded", + bcs->channel, bcs->tx_skb->len); + + if (test_bit(FLG_LLI_L1WAKEUP, &bcs->st->lli.flag) && + (PACKET_NOACK != bcs->tx_skb->pkt_type)) { + u_long flags; + spin_lock_irqsave(&bcs->aclock, flags); + bcs->ackcnt += bcs->tx_skb->len; + spin_unlock_irqrestore(&bcs->aclock, flags); + schedule_event(bcs, B_ACKPENDING); + } + + dev_kfree_skb_any(bcs->tx_skb); + bcs->tx_skb = skb_dequeue(&bcs->squeue); /* fetch next data */ + } + test_and_clear_bit(BC_FLG_BUSY, &bcs->Flag); + return; + } + if (cs->debug & L1_DEB_HSCX) + debugl1(cs, "hfcpci_fill_fifo_hdlc %d f1(%d) f2(%d) z1(f1)(%x)", + bcs->channel, bz->f1, bz->f2, + bz->za[bz->f1].z1); + + fcnt = bz->f1 - bz->f2; /* frame count actually buffered */ + if (fcnt < 0) + fcnt += (MAX_B_FRAMES + 1); /* if wrap around */ + if (fcnt > (MAX_B_FRAMES - 1)) { + if (cs->debug & L1_DEB_HSCX) + debugl1(cs, "hfcpci_fill_Bfifo more as 14 frames"); + return; + } + /* now determine free bytes in FIFO buffer */ + count = bz->za[bz->f2].z2 - bz->za[bz->f1].z1 - 1; + if (count <= 0) + count += B_FIFO_SIZE; /* count now contains available bytes */ + + if (cs->debug & L1_DEB_HSCX) + debugl1(cs, "hfcpci_fill_fifo %d count(%u/%d),%lx", + bcs->channel, bcs->tx_skb->len, + count, current->state); + + if (count < bcs->tx_skb->len) { + if (cs->debug & L1_DEB_HSCX) + debugl1(cs, "hfcpci_fill_fifo no fifo mem"); + return; + } + count = bcs->tx_skb->len; /* get frame len */ + new_z1 = bz->za[bz->f1].z1 + count; /* new buffer Position */ + if (new_z1 >= (B_FIFO_SIZE + B_SUB_VAL)) + new_z1 -= B_FIFO_SIZE; /* buffer wrap */ + + new_f1 = ((bz->f1 + 1) & MAX_B_FRAMES); + src = bcs->tx_skb->data; /* source pointer */ + dst = bdata + (bz->za[bz->f1].z1 - B_SUB_VAL); + maxlen = (B_FIFO_SIZE + B_SUB_VAL) - bz->za[bz->f1].z1; /* end fifo */ + if (maxlen > count) + maxlen = count; /* limit size */ + memcpy(dst, src, maxlen); /* first copy */ + + count -= maxlen; /* remaining bytes */ + if (count) { + dst = bdata; /* start of buffer */ + src += maxlen; /* new position */ + memcpy(dst, src, count); + } + bcs->tx_cnt -= bcs->tx_skb->len; + if (test_bit(FLG_LLI_L1WAKEUP, &bcs->st->lli.flag) && + (PACKET_NOACK != bcs->tx_skb->pkt_type)) { + u_long flags; + spin_lock_irqsave(&bcs->aclock, flags); + bcs->ackcnt += bcs->tx_skb->len; + spin_unlock_irqrestore(&bcs->aclock, flags); + schedule_event(bcs, B_ACKPENDING); + } + + bz->za[new_f1].z1 = new_z1; /* for next buffer */ + bz->f1 = new_f1; /* next frame */ + + dev_kfree_skb_any(bcs->tx_skb); + bcs->tx_skb = NULL; + test_and_clear_bit(BC_FLG_BUSY, &bcs->Flag); +} + +/**********************************************/ +/* D-channel l1 state call for leased NT-mode */ +/**********************************************/ +static void +dch_nt_l2l1(struct PStack *st, int pr, void *arg) +{ + struct IsdnCardState *cs = (struct IsdnCardState *) st->l1.hardware; + + switch (pr) { + case (PH_DATA | REQUEST): + case (PH_PULL | REQUEST): + case (PH_PULL | INDICATION): + st->l1.l1hw(st, pr, arg); + break; + case (PH_ACTIVATE | REQUEST): + st->l1.l1l2(st, PH_ACTIVATE | CONFIRM, NULL); + break; + case (PH_TESTLOOP | REQUEST): + if (1 & (long) arg) + debugl1(cs, "PH_TEST_LOOP B1"); + if (2 & (long) arg) + debugl1(cs, "PH_TEST_LOOP B2"); + if (!(3 & (long) arg)) + debugl1(cs, "PH_TEST_LOOP DISABLED"); + st->l1.l1hw(st, HW_TESTLOOP | REQUEST, arg); + break; + default: + if (cs->debug) + debugl1(cs, "dch_nt_l2l1 msg %04X unhandled", pr); + break; + } +} + + + +/***********************/ +/* set/reset echo mode */ +/***********************/ +static int +hfcpci_auxcmd(struct IsdnCardState *cs, isdn_ctrl *ic) +{ + u_long flags; + int i = *(unsigned int *) ic->parm.num; + + if ((ic->arg == 98) && + (!(cs->hw.hfcpci.int_m1 & (HFCPCI_INTS_B2TRANS + HFCPCI_INTS_B2REC + HFCPCI_INTS_B1TRANS + HFCPCI_INTS_B1REC)))) { + spin_lock_irqsave(&cs->lock, flags); + Write_hfc(cs, HFCPCI_CLKDEL, CLKDEL_NT); /* ST-Bit delay for NT-Mode */ + Write_hfc(cs, HFCPCI_STATES, HFCPCI_LOAD_STATE | 0); /* HFC ST G0 */ + udelay(10); + cs->hw.hfcpci.sctrl |= SCTRL_MODE_NT; + Write_hfc(cs, HFCPCI_SCTRL, cs->hw.hfcpci.sctrl); /* set NT-mode */ + udelay(10); + Write_hfc(cs, HFCPCI_STATES, HFCPCI_LOAD_STATE | 1); /* HFC ST G1 */ + udelay(10); + Write_hfc(cs, HFCPCI_STATES, 1 | HFCPCI_ACTIVATE | HFCPCI_DO_ACTION); + cs->dc.hfcpci.ph_state = 1; + cs->hw.hfcpci.nt_mode = 1; + cs->hw.hfcpci.nt_timer = 0; + cs->stlist->l2.l2l1 = dch_nt_l2l1; + spin_unlock_irqrestore(&cs->lock, flags); + debugl1(cs, "NT mode activated"); + return (0); + } + if ((cs->chanlimit > 1) || (cs->hw.hfcpci.bswapped) || + (cs->hw.hfcpci.nt_mode) || (ic->arg != 12)) + return (-EINVAL); + + spin_lock_irqsave(&cs->lock, flags); + if (i) { + cs->logecho = 1; + cs->hw.hfcpci.trm |= 0x20; /* enable echo chan */ + cs->hw.hfcpci.int_m1 |= HFCPCI_INTS_B2REC; + cs->hw.hfcpci.fifo_en |= HFCPCI_FIFOEN_B2RX; + } else { + cs->logecho = 0; + cs->hw.hfcpci.trm &= ~0x20; /* disable echo chan */ + cs->hw.hfcpci.int_m1 &= ~HFCPCI_INTS_B2REC; + cs->hw.hfcpci.fifo_en &= ~HFCPCI_FIFOEN_B2RX; + } + cs->hw.hfcpci.sctrl_r &= ~SCTRL_B2_ENA; + cs->hw.hfcpci.sctrl &= ~SCTRL_B2_ENA; + cs->hw.hfcpci.conn |= 0x10; /* B2-IOM -> B2-ST */ + cs->hw.hfcpci.ctmt &= ~2; + Write_hfc(cs, HFCPCI_CTMT, cs->hw.hfcpci.ctmt); + Write_hfc(cs, HFCPCI_SCTRL_R, cs->hw.hfcpci.sctrl_r); + Write_hfc(cs, HFCPCI_SCTRL, cs->hw.hfcpci.sctrl); + Write_hfc(cs, HFCPCI_CONNECT, cs->hw.hfcpci.conn); + Write_hfc(cs, HFCPCI_TRM, cs->hw.hfcpci.trm); + Write_hfc(cs, HFCPCI_FIFO_EN, cs->hw.hfcpci.fifo_en); + Write_hfc(cs, HFCPCI_INT_M1, cs->hw.hfcpci.int_m1); + spin_unlock_irqrestore(&cs->lock, flags); + return (0); +} /* hfcpci_auxcmd */ + +/*****************************/ +/* E-channel receive routine */ +/*****************************/ +static void +receive_emsg(struct IsdnCardState *cs) +{ + int rcnt; + int receive, count = 5; + bzfifo_type *bz; + u_char *bdata; + z_type *zp; + u_char *ptr, *ptr1, new_f2; + int total, maxlen, new_z2; + u_char e_buffer[256]; + + bz = &((fifo_area *) (cs->hw.hfcpci.fifos))->b_chans.rxbz_b2; + bdata = ((fifo_area *) (cs->hw.hfcpci.fifos))->b_chans.rxdat_b2; +Begin: + count--; + if (test_and_set_bit(FLG_LOCK_ATOMIC, &cs->HW_Flags)) { + debugl1(cs, "echo_rec_data blocked"); + return; + } + if (bz->f1 != bz->f2) { + if (cs->debug & L1_DEB_ISAC) + debugl1(cs, "hfcpci e_rec f1(%d) f2(%d)", + bz->f1, bz->f2); + zp = &bz->za[bz->f2]; + + rcnt = zp->z1 - zp->z2; + if (rcnt < 0) + rcnt += B_FIFO_SIZE; + rcnt++; + if (cs->debug & L1_DEB_ISAC) + debugl1(cs, "hfcpci e_rec z1(%x) z2(%x) cnt(%d)", + zp->z1, zp->z2, rcnt); + new_z2 = zp->z2 + rcnt; /* new position in fifo */ + if (new_z2 >= (B_FIFO_SIZE + B_SUB_VAL)) + new_z2 -= B_FIFO_SIZE; /* buffer wrap */ + new_f2 = (bz->f2 + 1) & MAX_B_FRAMES; + if ((rcnt > 256 + 3) || (count < 4) || + (*(bdata + (zp->z1 - B_SUB_VAL)))) { + if (cs->debug & L1_DEB_WARN) + debugl1(cs, "hfcpci_empty_echan: incoming packet invalid length %d or crc", rcnt); + bz->za[new_f2].z2 = new_z2; + bz->f2 = new_f2; /* next buffer */ + } else { + total = rcnt; + rcnt -= 3; + ptr = e_buffer; + + if (zp->z2 <= B_FIFO_SIZE + B_SUB_VAL) + maxlen = rcnt; /* complete transfer */ + else + maxlen = B_FIFO_SIZE + B_SUB_VAL - zp->z2; /* maximum */ + + ptr1 = bdata + (zp->z2 - B_SUB_VAL); /* start of data */ + memcpy(ptr, ptr1, maxlen); /* copy data */ + rcnt -= maxlen; + + if (rcnt) { /* rest remaining */ + ptr += maxlen; + ptr1 = bdata; /* start of buffer */ + memcpy(ptr, ptr1, rcnt); /* rest */ + } + bz->za[new_f2].z2 = new_z2; + bz->f2 = new_f2; /* next buffer */ + if (cs->debug & DEB_DLOG_HEX) { + ptr = cs->dlog; + if ((total - 3) < MAX_DLOG_SPACE / 3 - 10) { + *ptr++ = 'E'; + *ptr++ = 'C'; + *ptr++ = 'H'; + *ptr++ = 'O'; + *ptr++ = ':'; + ptr += QuickHex(ptr, e_buffer, total - 3); + ptr--; + *ptr++ = '\n'; + *ptr = 0; + HiSax_putstatus(cs, NULL, cs->dlog); + } else + HiSax_putstatus(cs, "LogEcho: ", "warning Frame too big (%d)", total - 3); + } + } + + rcnt = bz->f1 - bz->f2; + if (rcnt < 0) + rcnt += MAX_B_FRAMES + 1; + if (rcnt > 1) + receive = 1; + else + receive = 0; + } else + receive = 0; + test_and_clear_bit(FLG_LOCK_ATOMIC, &cs->HW_Flags); + if (count && receive) + goto Begin; +} /* receive_emsg */ + +/*********************/ +/* Interrupt handler */ +/*********************/ +static irqreturn_t +hfcpci_interrupt(int intno, void *dev_id) +{ + u_long flags; + struct IsdnCardState *cs = dev_id; + u_char exval; + struct BCState *bcs; + int count = 15; + u_char val, stat; + + if (!(cs->hw.hfcpci.int_m2 & 0x08)) { + debugl1(cs, "HFC-PCI: int_m2 %x not initialised", cs->hw.hfcpci.int_m2); + return IRQ_NONE; /* not initialised */ + } + spin_lock_irqsave(&cs->lock, flags); + if (HFCPCI_ANYINT & (stat = Read_hfc(cs, HFCPCI_STATUS))) { + val = Read_hfc(cs, HFCPCI_INT_S1); + if (cs->debug & L1_DEB_ISAC) + debugl1(cs, "HFC-PCI: stat(%02x) s1(%02x)", stat, val); + } else { + spin_unlock_irqrestore(&cs->lock, flags); + return IRQ_NONE; + } + if (cs->debug & L1_DEB_ISAC) + debugl1(cs, "HFC-PCI irq %x %s", val, + test_bit(FLG_LOCK_ATOMIC, &cs->HW_Flags) ? + "locked" : "unlocked"); + val &= cs->hw.hfcpci.int_m1; + if (val & 0x40) { /* state machine irq */ + exval = Read_hfc(cs, HFCPCI_STATES) & 0xf; + if (cs->debug & L1_DEB_ISAC) + debugl1(cs, "ph_state chg %d->%d", cs->dc.hfcpci.ph_state, + exval); + cs->dc.hfcpci.ph_state = exval; + sched_event_D_pci(cs, D_L1STATECHANGE); + val &= ~0x40; + } + if (val & 0x80) { /* timer irq */ + if (cs->hw.hfcpci.nt_mode) { + if ((--cs->hw.hfcpci.nt_timer) < 0) + sched_event_D_pci(cs, D_L1STATECHANGE); + } + val &= ~0x80; + Write_hfc(cs, HFCPCI_CTMT, cs->hw.hfcpci.ctmt | HFCPCI_CLTIMER); + } + while (val) { + if (test_bit(FLG_LOCK_ATOMIC, &cs->HW_Flags)) { + cs->hw.hfcpci.int_s1 |= val; + spin_unlock_irqrestore(&cs->lock, flags); + return IRQ_HANDLED; + } + if (cs->hw.hfcpci.int_s1 & 0x18) { + exval = val; + val = cs->hw.hfcpci.int_s1; + cs->hw.hfcpci.int_s1 = exval; + } + if (val & 0x08) { + if (!(bcs = Sel_BCS(cs, cs->hw.hfcpci.bswapped ? 1 : 0))) { + if (cs->debug) + debugl1(cs, "hfcpci spurious 0x08 IRQ"); + } else + main_rec_hfcpci(bcs); + } + if (val & 0x10) { + if (cs->logecho) + receive_emsg(cs); + else if (!(bcs = Sel_BCS(cs, 1))) { + if (cs->debug) + debugl1(cs, "hfcpci spurious 0x10 IRQ"); + } else + main_rec_hfcpci(bcs); + } + if (val & 0x01) { + if (!(bcs = Sel_BCS(cs, cs->hw.hfcpci.bswapped ? 1 : 0))) { + if (cs->debug) + debugl1(cs, "hfcpci spurious 0x01 IRQ"); + } else { + if (bcs->tx_skb) { + if (!test_and_set_bit(FLG_LOCK_ATOMIC, &cs->HW_Flags)) { + hfcpci_fill_fifo(bcs); + test_and_clear_bit(FLG_LOCK_ATOMIC, &cs->HW_Flags); + } else + debugl1(cs, "fill_data %d blocked", bcs->channel); + } else { + if ((bcs->tx_skb = skb_dequeue(&bcs->squeue))) { + if (!test_and_set_bit(FLG_LOCK_ATOMIC, &cs->HW_Flags)) { + hfcpci_fill_fifo(bcs); + test_and_clear_bit(FLG_LOCK_ATOMIC, &cs->HW_Flags); + } else + debugl1(cs, "fill_data %d blocked", bcs->channel); + } else { + hfcpci_sched_event(bcs, B_XMTBUFREADY); + } + } + } + } + if (val & 0x02) { + if (!(bcs = Sel_BCS(cs, 1))) { + if (cs->debug) + debugl1(cs, "hfcpci spurious 0x02 IRQ"); + } else { + if (bcs->tx_skb) { + if (!test_and_set_bit(FLG_LOCK_ATOMIC, &cs->HW_Flags)) { + hfcpci_fill_fifo(bcs); + test_and_clear_bit(FLG_LOCK_ATOMIC, &cs->HW_Flags); + } else + debugl1(cs, "fill_data %d blocked", bcs->channel); + } else { + if ((bcs->tx_skb = skb_dequeue(&bcs->squeue))) { + if (!test_and_set_bit(FLG_LOCK_ATOMIC, &cs->HW_Flags)) { + hfcpci_fill_fifo(bcs); + test_and_clear_bit(FLG_LOCK_ATOMIC, &cs->HW_Flags); + } else + debugl1(cs, "fill_data %d blocked", bcs->channel); + } else { + hfcpci_sched_event(bcs, B_XMTBUFREADY); + } + } + } + } + if (val & 0x20) { /* receive dframe */ + receive_dmsg(cs); + } + if (val & 0x04) { /* dframe transmitted */ + if (test_and_clear_bit(FLG_DBUSY_TIMER, &cs->HW_Flags)) + del_timer(&cs->dbusytimer); + if (test_and_clear_bit(FLG_L1_DBUSY, &cs->HW_Flags)) + sched_event_D_pci(cs, D_CLEARBUSY); + if (cs->tx_skb) { + if (cs->tx_skb->len) { + if (!test_and_set_bit(FLG_LOCK_ATOMIC, &cs->HW_Flags)) { + hfcpci_fill_dfifo(cs); + test_and_clear_bit(FLG_LOCK_ATOMIC, &cs->HW_Flags); + } else { + debugl1(cs, "hfcpci_fill_dfifo irq blocked"); + } + goto afterXPR; + } else { + dev_kfree_skb_irq(cs->tx_skb); + cs->tx_cnt = 0; + cs->tx_skb = NULL; + } + } + if ((cs->tx_skb = skb_dequeue(&cs->sq))) { + cs->tx_cnt = 0; + if (!test_and_set_bit(FLG_LOCK_ATOMIC, &cs->HW_Flags)) { + hfcpci_fill_dfifo(cs); + test_and_clear_bit(FLG_LOCK_ATOMIC, &cs->HW_Flags); + } else { + debugl1(cs, "hfcpci_fill_dfifo irq blocked"); + } + } else + sched_event_D_pci(cs, D_XMTBUFREADY); + } + afterXPR: + if (cs->hw.hfcpci.int_s1 && count--) { + val = cs->hw.hfcpci.int_s1; + cs->hw.hfcpci.int_s1 = 0; + if (cs->debug & L1_DEB_ISAC) + debugl1(cs, "HFC-PCI irq %x loop %d", val, 15 - count); + } else + val = 0; + } + spin_unlock_irqrestore(&cs->lock, flags); + return IRQ_HANDLED; +} + +/********************************************************************/ +/* timer callback for D-chan busy resolution. Currently no function */ +/********************************************************************/ +static void +hfcpci_dbusy_timer(struct timer_list *t) +{ +} + +/*************************************/ +/* Layer 1 D-channel hardware access */ +/*************************************/ +static void +HFCPCI_l1hw(struct PStack *st, int pr, void *arg) +{ + u_long flags; + struct IsdnCardState *cs = (struct IsdnCardState *) st->l1.hardware; + struct sk_buff *skb = arg; + + switch (pr) { + case (PH_DATA | REQUEST): + if (cs->debug & DEB_DLOG_HEX) + LogFrame(cs, skb->data, skb->len); + if (cs->debug & DEB_DLOG_VERBOSE) + dlogframe(cs, skb, 0); + spin_lock_irqsave(&cs->lock, flags); + if (cs->tx_skb) { + skb_queue_tail(&cs->sq, skb); +#ifdef L2FRAME_DEBUG /* psa */ + if (cs->debug & L1_DEB_LAPD) + Logl2Frame(cs, skb, "PH_DATA Queued", 0); +#endif + } else { + cs->tx_skb = skb; + cs->tx_cnt = 0; +#ifdef L2FRAME_DEBUG /* psa */ + if (cs->debug & L1_DEB_LAPD) + Logl2Frame(cs, skb, "PH_DATA", 0); +#endif + if (!test_and_set_bit(FLG_LOCK_ATOMIC, &cs->HW_Flags)) { + hfcpci_fill_dfifo(cs); + test_and_clear_bit(FLG_LOCK_ATOMIC, &cs->HW_Flags); + } else + debugl1(cs, "hfcpci_fill_dfifo blocked"); + + } + spin_unlock_irqrestore(&cs->lock, flags); + break; + case (PH_PULL | INDICATION): + spin_lock_irqsave(&cs->lock, flags); + if (cs->tx_skb) { + if (cs->debug & L1_DEB_WARN) + debugl1(cs, " l2l1 tx_skb exist this shouldn't happen"); + skb_queue_tail(&cs->sq, skb); + spin_unlock_irqrestore(&cs->lock, flags); + break; + } + if (cs->debug & DEB_DLOG_HEX) + LogFrame(cs, skb->data, skb->len); + if (cs->debug & DEB_DLOG_VERBOSE) + dlogframe(cs, skb, 0); + cs->tx_skb = skb; + cs->tx_cnt = 0; +#ifdef L2FRAME_DEBUG /* psa */ + if (cs->debug & L1_DEB_LAPD) + Logl2Frame(cs, skb, "PH_DATA_PULLED", 0); +#endif + if (!test_and_set_bit(FLG_LOCK_ATOMIC, &cs->HW_Flags)) { + hfcpci_fill_dfifo(cs); + test_and_clear_bit(FLG_LOCK_ATOMIC, &cs->HW_Flags); + } else + debugl1(cs, "hfcpci_fill_dfifo blocked"); + spin_unlock_irqrestore(&cs->lock, flags); + break; + case (PH_PULL | REQUEST): +#ifdef L2FRAME_DEBUG /* psa */ + if (cs->debug & L1_DEB_LAPD) + debugl1(cs, "-> PH_REQUEST_PULL"); +#endif + spin_lock_irqsave(&cs->lock, flags); + if (!cs->tx_skb) { + test_and_clear_bit(FLG_L1_PULL_REQ, &st->l1.Flags); + st->l1.l1l2(st, PH_PULL | CONFIRM, NULL); + } else + test_and_set_bit(FLG_L1_PULL_REQ, &st->l1.Flags); + spin_unlock_irqrestore(&cs->lock, flags); + break; + case (HW_RESET | REQUEST): + spin_lock_irqsave(&cs->lock, flags); + Write_hfc(cs, HFCPCI_STATES, HFCPCI_LOAD_STATE | 3); /* HFC ST 3 */ + udelay(6); + Write_hfc(cs, HFCPCI_STATES, 3); /* HFC ST 2 */ + cs->hw.hfcpci.mst_m |= HFCPCI_MASTER; + Write_hfc(cs, HFCPCI_MST_MODE, cs->hw.hfcpci.mst_m); + Write_hfc(cs, HFCPCI_STATES, HFCPCI_ACTIVATE | HFCPCI_DO_ACTION); + spin_unlock_irqrestore(&cs->lock, flags); + l1_msg(cs, HW_POWERUP | CONFIRM, NULL); + break; + case (HW_ENABLE | REQUEST): + spin_lock_irqsave(&cs->lock, flags); + Write_hfc(cs, HFCPCI_STATES, HFCPCI_DO_ACTION); + spin_unlock_irqrestore(&cs->lock, flags); + break; + case (HW_DEACTIVATE | REQUEST): + spin_lock_irqsave(&cs->lock, flags); + cs->hw.hfcpci.mst_m &= ~HFCPCI_MASTER; + Write_hfc(cs, HFCPCI_MST_MODE, cs->hw.hfcpci.mst_m); + spin_unlock_irqrestore(&cs->lock, flags); + break; + case (HW_INFO3 | REQUEST): + spin_lock_irqsave(&cs->lock, flags); + cs->hw.hfcpci.mst_m |= HFCPCI_MASTER; + Write_hfc(cs, HFCPCI_MST_MODE, cs->hw.hfcpci.mst_m); + spin_unlock_irqrestore(&cs->lock, flags); + break; + case (HW_TESTLOOP | REQUEST): + spin_lock_irqsave(&cs->lock, flags); + switch ((long) arg) { + case (1): + Write_hfc(cs, HFCPCI_B1_SSL, 0x80); /* tx slot */ + Write_hfc(cs, HFCPCI_B1_RSL, 0x80); /* rx slot */ + cs->hw.hfcpci.conn = (cs->hw.hfcpci.conn & ~7) | 1; + Write_hfc(cs, HFCPCI_CONNECT, cs->hw.hfcpci.conn); + break; + + case (2): + Write_hfc(cs, HFCPCI_B2_SSL, 0x81); /* tx slot */ + Write_hfc(cs, HFCPCI_B2_RSL, 0x81); /* rx slot */ + cs->hw.hfcpci.conn = (cs->hw.hfcpci.conn & ~0x38) | 0x08; + Write_hfc(cs, HFCPCI_CONNECT, cs->hw.hfcpci.conn); + break; + + default: + spin_unlock_irqrestore(&cs->lock, flags); + if (cs->debug & L1_DEB_WARN) + debugl1(cs, "hfcpci_l1hw loop invalid %4lx", (long) arg); + return; + } + cs->hw.hfcpci.trm |= 0x80; /* enable IOM-loop */ + Write_hfc(cs, HFCPCI_TRM, cs->hw.hfcpci.trm); + spin_unlock_irqrestore(&cs->lock, flags); + break; + default: + if (cs->debug & L1_DEB_WARN) + debugl1(cs, "hfcpci_l1hw unknown pr %4x", pr); + break; + } +} + +/***********************************************/ +/* called during init setting l1 stack pointer */ +/***********************************************/ +static void +setstack_hfcpci(struct PStack *st, struct IsdnCardState *cs) +{ + st->l1.l1hw = HFCPCI_l1hw; +} + +/**************************************/ +/* send B-channel data if not blocked */ +/**************************************/ +static void +hfcpci_send_data(struct BCState *bcs) +{ + struct IsdnCardState *cs = bcs->cs; + + if (!test_and_set_bit(FLG_LOCK_ATOMIC, &cs->HW_Flags)) { + hfcpci_fill_fifo(bcs); + test_and_clear_bit(FLG_LOCK_ATOMIC, &cs->HW_Flags); + } else + debugl1(cs, "send_data %d blocked", bcs->channel); +} + +/***************************************************************/ +/* activate/deactivate hardware for selected channels and mode */ +/***************************************************************/ +static void +mode_hfcpci(struct BCState *bcs, int mode, int bc) +{ + struct IsdnCardState *cs = bcs->cs; + int fifo2; + + if (cs->debug & L1_DEB_HSCX) + debugl1(cs, "HFCPCI bchannel mode %d bchan %d/%d", + mode, bc, bcs->channel); + bcs->mode = mode; + bcs->channel = bc; + fifo2 = bc; + if (cs->chanlimit > 1) { + cs->hw.hfcpci.bswapped = 0; /* B1 and B2 normal mode */ + cs->hw.hfcpci.sctrl_e &= ~0x80; + } else { + if (bc) { + if (mode != L1_MODE_NULL) { + cs->hw.hfcpci.bswapped = 1; /* B1 and B2 exchanged */ + cs->hw.hfcpci.sctrl_e |= 0x80; + } else { + cs->hw.hfcpci.bswapped = 0; /* B1 and B2 normal mode */ + cs->hw.hfcpci.sctrl_e &= ~0x80; + } + fifo2 = 0; + } else { + cs->hw.hfcpci.bswapped = 0; /* B1 and B2 normal mode */ + cs->hw.hfcpci.sctrl_e &= ~0x80; + } + } + switch (mode) { + case (L1_MODE_NULL): + if (bc) { + cs->hw.hfcpci.sctrl &= ~SCTRL_B2_ENA; + cs->hw.hfcpci.sctrl_r &= ~SCTRL_B2_ENA; + } else { + cs->hw.hfcpci.sctrl &= ~SCTRL_B1_ENA; + cs->hw.hfcpci.sctrl_r &= ~SCTRL_B1_ENA; + } + if (fifo2) { + cs->hw.hfcpci.fifo_en &= ~HFCPCI_FIFOEN_B2; + cs->hw.hfcpci.int_m1 &= ~(HFCPCI_INTS_B2TRANS + HFCPCI_INTS_B2REC); + } else { + cs->hw.hfcpci.fifo_en &= ~HFCPCI_FIFOEN_B1; + cs->hw.hfcpci.int_m1 &= ~(HFCPCI_INTS_B1TRANS + HFCPCI_INTS_B1REC); + } + break; + case (L1_MODE_TRANS): + hfcpci_clear_fifo_rx(cs, fifo2); + hfcpci_clear_fifo_tx(cs, fifo2); + if (bc) { + cs->hw.hfcpci.sctrl |= SCTRL_B2_ENA; + cs->hw.hfcpci.sctrl_r |= SCTRL_B2_ENA; + } else { + cs->hw.hfcpci.sctrl |= SCTRL_B1_ENA; + cs->hw.hfcpci.sctrl_r |= SCTRL_B1_ENA; + } + if (fifo2) { + cs->hw.hfcpci.fifo_en |= HFCPCI_FIFOEN_B2; + cs->hw.hfcpci.int_m1 |= (HFCPCI_INTS_B2TRANS + HFCPCI_INTS_B2REC); + cs->hw.hfcpci.ctmt |= 2; + cs->hw.hfcpci.conn &= ~0x18; + } else { + cs->hw.hfcpci.fifo_en |= HFCPCI_FIFOEN_B1; + cs->hw.hfcpci.int_m1 |= (HFCPCI_INTS_B1TRANS + HFCPCI_INTS_B1REC); + cs->hw.hfcpci.ctmt |= 1; + cs->hw.hfcpci.conn &= ~0x03; + } + break; + case (L1_MODE_HDLC): + hfcpci_clear_fifo_rx(cs, fifo2); + hfcpci_clear_fifo_tx(cs, fifo2); + if (bc) { + cs->hw.hfcpci.sctrl |= SCTRL_B2_ENA; + cs->hw.hfcpci.sctrl_r |= SCTRL_B2_ENA; + } else { + cs->hw.hfcpci.sctrl |= SCTRL_B1_ENA; + cs->hw.hfcpci.sctrl_r |= SCTRL_B1_ENA; + } + if (fifo2) { + cs->hw.hfcpci.last_bfifo_cnt[1] = 0; + cs->hw.hfcpci.fifo_en |= HFCPCI_FIFOEN_B2; + cs->hw.hfcpci.int_m1 |= (HFCPCI_INTS_B2TRANS + HFCPCI_INTS_B2REC); + cs->hw.hfcpci.ctmt &= ~2; + cs->hw.hfcpci.conn &= ~0x18; + } else { + cs->hw.hfcpci.last_bfifo_cnt[0] = 0; + cs->hw.hfcpci.fifo_en |= HFCPCI_FIFOEN_B1; + cs->hw.hfcpci.int_m1 |= (HFCPCI_INTS_B1TRANS + HFCPCI_INTS_B1REC); + cs->hw.hfcpci.ctmt &= ~1; + cs->hw.hfcpci.conn &= ~0x03; + } + break; + case (L1_MODE_EXTRN): + if (bc) { + cs->hw.hfcpci.conn |= 0x10; + cs->hw.hfcpci.sctrl |= SCTRL_B2_ENA; + cs->hw.hfcpci.sctrl_r |= SCTRL_B2_ENA; + cs->hw.hfcpci.fifo_en &= ~HFCPCI_FIFOEN_B2; + cs->hw.hfcpci.int_m1 &= ~(HFCPCI_INTS_B2TRANS + HFCPCI_INTS_B2REC); + } else { + cs->hw.hfcpci.conn |= 0x02; + cs->hw.hfcpci.sctrl |= SCTRL_B1_ENA; + cs->hw.hfcpci.sctrl_r |= SCTRL_B1_ENA; + cs->hw.hfcpci.fifo_en &= ~HFCPCI_FIFOEN_B1; + cs->hw.hfcpci.int_m1 &= ~(HFCPCI_INTS_B1TRANS + HFCPCI_INTS_B1REC); + } + break; + } + Write_hfc(cs, HFCPCI_SCTRL_E, cs->hw.hfcpci.sctrl_e); + Write_hfc(cs, HFCPCI_INT_M1, cs->hw.hfcpci.int_m1); + Write_hfc(cs, HFCPCI_FIFO_EN, cs->hw.hfcpci.fifo_en); + Write_hfc(cs, HFCPCI_SCTRL, cs->hw.hfcpci.sctrl); + Write_hfc(cs, HFCPCI_SCTRL_R, cs->hw.hfcpci.sctrl_r); + Write_hfc(cs, HFCPCI_CTMT, cs->hw.hfcpci.ctmt); + Write_hfc(cs, HFCPCI_CONNECT, cs->hw.hfcpci.conn); +} + +/******************************/ +/* Layer2 -> Layer 1 Transfer */ +/******************************/ +static void +hfcpci_l2l1(struct PStack *st, int pr, void *arg) +{ + struct BCState *bcs = st->l1.bcs; + u_long flags; + struct sk_buff *skb = arg; + + switch (pr) { + case (PH_DATA | REQUEST): + spin_lock_irqsave(&bcs->cs->lock, flags); + if (bcs->tx_skb) { + skb_queue_tail(&bcs->squeue, skb); + } else { + bcs->tx_skb = skb; +// test_and_set_bit(BC_FLG_BUSY, &bcs->Flag); + bcs->cs->BC_Send_Data(bcs); + } + spin_unlock_irqrestore(&bcs->cs->lock, flags); + break; + case (PH_PULL | INDICATION): + spin_lock_irqsave(&bcs->cs->lock, flags); + if (bcs->tx_skb) { + spin_unlock_irqrestore(&bcs->cs->lock, flags); + printk(KERN_WARNING "hfc_l2l1: this shouldn't happen\n"); + break; + } +// test_and_set_bit(BC_FLG_BUSY, &bcs->Flag); + bcs->tx_skb = skb; + bcs->cs->BC_Send_Data(bcs); + spin_unlock_irqrestore(&bcs->cs->lock, flags); + break; + case (PH_PULL | REQUEST): + if (!bcs->tx_skb) { + test_and_clear_bit(FLG_L1_PULL_REQ, &st->l1.Flags); + st->l1.l1l2(st, PH_PULL | CONFIRM, NULL); + } else + test_and_set_bit(FLG_L1_PULL_REQ, &st->l1.Flags); + break; + case (PH_ACTIVATE | REQUEST): + spin_lock_irqsave(&bcs->cs->lock, flags); + test_and_set_bit(BC_FLG_ACTIV, &bcs->Flag); + mode_hfcpci(bcs, st->l1.mode, st->l1.bc); + spin_unlock_irqrestore(&bcs->cs->lock, flags); + l1_msg_b(st, pr, arg); + break; + case (PH_DEACTIVATE | REQUEST): + l1_msg_b(st, pr, arg); + break; + case (PH_DEACTIVATE | CONFIRM): + spin_lock_irqsave(&bcs->cs->lock, flags); + test_and_clear_bit(BC_FLG_ACTIV, &bcs->Flag); + test_and_clear_bit(BC_FLG_BUSY, &bcs->Flag); + mode_hfcpci(bcs, 0, st->l1.bc); + spin_unlock_irqrestore(&bcs->cs->lock, flags); + st->l1.l1l2(st, PH_DEACTIVATE | CONFIRM, NULL); + break; + } +} + +/******************************************/ +/* deactivate B-channel access and queues */ +/******************************************/ +static void +close_hfcpci(struct BCState *bcs) +{ + mode_hfcpci(bcs, 0, bcs->channel); + if (test_and_clear_bit(BC_FLG_INIT, &bcs->Flag)) { + skb_queue_purge(&bcs->rqueue); + skb_queue_purge(&bcs->squeue); + if (bcs->tx_skb) { + dev_kfree_skb_any(bcs->tx_skb); + bcs->tx_skb = NULL; + test_and_clear_bit(BC_FLG_BUSY, &bcs->Flag); + } + } +} + +/*************************************/ +/* init B-channel queues and control */ +/*************************************/ +static int +open_hfcpcistate(struct IsdnCardState *cs, struct BCState *bcs) +{ + if (!test_and_set_bit(BC_FLG_INIT, &bcs->Flag)) { + skb_queue_head_init(&bcs->rqueue); + skb_queue_head_init(&bcs->squeue); + } + bcs->tx_skb = NULL; + test_and_clear_bit(BC_FLG_BUSY, &bcs->Flag); + bcs->event = 0; + bcs->tx_cnt = 0; + return (0); +} + +/*********************************/ +/* inits the stack for B-channel */ +/*********************************/ +static int +setstack_2b(struct PStack *st, struct BCState *bcs) +{ + bcs->channel = st->l1.bc; + if (open_hfcpcistate(st->l1.hardware, bcs)) + return (-1); + st->l1.bcs = bcs; + st->l2.l2l1 = hfcpci_l2l1; + setstack_manager(st); + bcs->st = st; + setstack_l1_B(st); + return (0); +} + +/***************************/ +/* handle L1 state changes */ +/***************************/ +static void +hfcpci_bh(struct work_struct *work) +{ + struct IsdnCardState *cs = + container_of(work, struct IsdnCardState, tqueue); + u_long flags; +// struct PStack *stptr; + + if (test_and_clear_bit(D_L1STATECHANGE, &cs->event)) { + if (!cs->hw.hfcpci.nt_mode) + switch (cs->dc.hfcpci.ph_state) { + case (0): + l1_msg(cs, HW_RESET | INDICATION, NULL); + break; + case (3): + l1_msg(cs, HW_DEACTIVATE | INDICATION, NULL); + break; + case (8): + l1_msg(cs, HW_RSYNC | INDICATION, NULL); + break; + case (6): + l1_msg(cs, HW_INFO2 | INDICATION, NULL); + break; + case (7): + l1_msg(cs, HW_INFO4_P8 | INDICATION, NULL); + break; + default: + break; + } else { + spin_lock_irqsave(&cs->lock, flags); + switch (cs->dc.hfcpci.ph_state) { + case (2): + if (cs->hw.hfcpci.nt_timer < 0) { + cs->hw.hfcpci.nt_timer = 0; + cs->hw.hfcpci.int_m1 &= ~HFCPCI_INTS_TIMER; + Write_hfc(cs, HFCPCI_INT_M1, cs->hw.hfcpci.int_m1); + /* Clear already pending ints */ + if (Read_hfc(cs, HFCPCI_INT_S1)); + Write_hfc(cs, HFCPCI_STATES, 4 | HFCPCI_LOAD_STATE); + udelay(10); + Write_hfc(cs, HFCPCI_STATES, 4); + cs->dc.hfcpci.ph_state = 4; + } else { + cs->hw.hfcpci.int_m1 |= HFCPCI_INTS_TIMER; + Write_hfc(cs, HFCPCI_INT_M1, cs->hw.hfcpci.int_m1); + cs->hw.hfcpci.ctmt &= ~HFCPCI_AUTO_TIMER; + cs->hw.hfcpci.ctmt |= HFCPCI_TIM3_125; + Write_hfc(cs, HFCPCI_CTMT, cs->hw.hfcpci.ctmt | HFCPCI_CLTIMER); + Write_hfc(cs, HFCPCI_CTMT, cs->hw.hfcpci.ctmt | HFCPCI_CLTIMER); + cs->hw.hfcpci.nt_timer = NT_T1_COUNT; + Write_hfc(cs, HFCPCI_STATES, 2 | HFCPCI_NT_G2_G3); /* allow G2 -> G3 transition */ + } + break; + case (1): + case (3): + case (4): + cs->hw.hfcpci.nt_timer = 0; + cs->hw.hfcpci.int_m1 &= ~HFCPCI_INTS_TIMER; + Write_hfc(cs, HFCPCI_INT_M1, cs->hw.hfcpci.int_m1); + break; + default: + break; + } + spin_unlock_irqrestore(&cs->lock, flags); + } + } + if (test_and_clear_bit(D_RCVBUFREADY, &cs->event)) + DChannel_proc_rcv(cs); + if (test_and_clear_bit(D_XMTBUFREADY, &cs->event)) + DChannel_proc_xmt(cs); +} + + +/********************************/ +/* called for card init message */ +/********************************/ +static void +inithfcpci(struct IsdnCardState *cs) +{ + cs->bcs[0].BC_SetStack = setstack_2b; + cs->bcs[1].BC_SetStack = setstack_2b; + cs->bcs[0].BC_Close = close_hfcpci; + cs->bcs[1].BC_Close = close_hfcpci; + timer_setup(&cs->dbusytimer, hfcpci_dbusy_timer, 0); + mode_hfcpci(cs->bcs, 0, 0); + mode_hfcpci(cs->bcs + 1, 0, 1); +} + + + +/*******************************************/ +/* handle card messages from control layer */ +/*******************************************/ +static int +hfcpci_card_msg(struct IsdnCardState *cs, int mt, void *arg) +{ + u_long flags; + + if (cs->debug & L1_DEB_ISAC) + debugl1(cs, "HFCPCI: card_msg %x", mt); + switch (mt) { + case CARD_RESET: + spin_lock_irqsave(&cs->lock, flags); + reset_hfcpci(cs); + spin_unlock_irqrestore(&cs->lock, flags); + return (0); + case CARD_RELEASE: + release_io_hfcpci(cs); + return (0); + case CARD_INIT: + spin_lock_irqsave(&cs->lock, flags); + inithfcpci(cs); + reset_hfcpci(cs); + spin_unlock_irqrestore(&cs->lock, flags); + msleep(80); /* Timeout 80ms */ + /* now switch timer interrupt off */ + spin_lock_irqsave(&cs->lock, flags); + cs->hw.hfcpci.int_m1 &= ~HFCPCI_INTS_TIMER; + Write_hfc(cs, HFCPCI_INT_M1, cs->hw.hfcpci.int_m1); + /* reinit mode reg */ + Write_hfc(cs, HFCPCI_MST_MODE, cs->hw.hfcpci.mst_m); + spin_unlock_irqrestore(&cs->lock, flags); + return (0); + case CARD_TEST: + return (0); + } + return (0); +} + + +/* this variable is used as card index when more than one cards are present */ +static struct pci_dev *dev_hfcpci = NULL; + +int +setup_hfcpci(struct IsdnCard *card) +{ + u_long flags; + struct IsdnCardState *cs = card->cs; + char tmp[64]; + int i; + struct pci_dev *tmp_hfcpci = NULL; + + strcpy(tmp, hfcpci_revision); + printk(KERN_INFO "HiSax: HFC-PCI driver Rev. %s\n", HiSax_getrev(tmp)); + + cs->hw.hfcpci.int_s1 = 0; + cs->dc.hfcpci.ph_state = 0; + cs->hw.hfcpci.fifo = 255; + if (cs->typ != ISDN_CTYPE_HFC_PCI) + return (0); + + i = 0; + while (id_list[i].vendor_id) { + tmp_hfcpci = hisax_find_pci_device(id_list[i].vendor_id, + id_list[i].device_id, + dev_hfcpci); + i++; + if (tmp_hfcpci) { + dma_addr_t dma_mask = DMA_BIT_MASK(32) & ~0x7fffUL; + if (pci_enable_device(tmp_hfcpci)) + continue; + if (pci_set_dma_mask(tmp_hfcpci, dma_mask)) { + printk(KERN_WARNING + "HiSax hfc_pci: No suitable DMA available.\n"); + continue; + } + if (pci_set_consistent_dma_mask(tmp_hfcpci, dma_mask)) { + printk(KERN_WARNING + "HiSax hfc_pci: No suitable consistent DMA available.\n"); + continue; + } + pci_set_master(tmp_hfcpci); + if ((card->para[0]) && (card->para[0] != (tmp_hfcpci->resource[0].start & PCI_BASE_ADDRESS_IO_MASK))) + continue; + else + break; + } + } + + if (!tmp_hfcpci) { + printk(KERN_WARNING "HFC-PCI: No PCI card found\n"); + return (0); + } + + i--; + dev_hfcpci = tmp_hfcpci; /* old device */ + cs->hw.hfcpci.dev = dev_hfcpci; + cs->irq = dev_hfcpci->irq; + if (!cs->irq) { + printk(KERN_WARNING "HFC-PCI: No IRQ for PCI card found\n"); + return (0); + } + cs->hw.hfcpci.pci_io = (char *)(unsigned long)dev_hfcpci->resource[1].start; + printk(KERN_INFO "HiSax: HFC-PCI card manufacturer: %s card name: %s\n", id_list[i].vendor_name, id_list[i].card_name); + + if (!cs->hw.hfcpci.pci_io) { + printk(KERN_WARNING "HFC-PCI: No IO-Mem for PCI card found\n"); + return (0); + } + + /* Allocate memory for FIFOS */ + cs->hw.hfcpci.fifos = pci_alloc_consistent(cs->hw.hfcpci.dev, + 0x8000, &cs->hw.hfcpci.dma); + if (!cs->hw.hfcpci.fifos) { + printk(KERN_WARNING "HFC-PCI: Error allocating FIFO memory!\n"); + return 0; + } + if (cs->hw.hfcpci.dma & 0x7fff) { + printk(KERN_WARNING + "HFC-PCI: Error DMA memory not on 32K boundary (%lx)\n", + (u_long)cs->hw.hfcpci.dma); + pci_free_consistent(cs->hw.hfcpci.dev, 0x8000, + cs->hw.hfcpci.fifos, cs->hw.hfcpci.dma); + return 0; + } + pci_write_config_dword(cs->hw.hfcpci.dev, 0x80, (u32)cs->hw.hfcpci.dma); + cs->hw.hfcpci.pci_io = ioremap((ulong) cs->hw.hfcpci.pci_io, 256); + printk(KERN_INFO + "HFC-PCI: defined at mem %p fifo %p(%lx) IRQ %d HZ %d\n", + cs->hw.hfcpci.pci_io, + cs->hw.hfcpci.fifos, + (u_long)cs->hw.hfcpci.dma, + cs->irq, HZ); + + spin_lock_irqsave(&cs->lock, flags); + + pci_write_config_word(cs->hw.hfcpci.dev, PCI_COMMAND, PCI_ENA_MEMIO); /* enable memory mapped ports, disable busmaster */ + cs->hw.hfcpci.int_m2 = 0; /* disable alle interrupts */ + cs->hw.hfcpci.int_m1 = 0; + Write_hfc(cs, HFCPCI_INT_M1, cs->hw.hfcpci.int_m1); + Write_hfc(cs, HFCPCI_INT_M2, cs->hw.hfcpci.int_m2); + /* At this point the needed PCI config is done */ + /* fifos are still not enabled */ + + INIT_WORK(&cs->tqueue, hfcpci_bh); + cs->setstack_d = setstack_hfcpci; + cs->BC_Send_Data = &hfcpci_send_data; + cs->readisac = NULL; + cs->writeisac = NULL; + cs->readisacfifo = NULL; + cs->writeisacfifo = NULL; + cs->BC_Read_Reg = NULL; + cs->BC_Write_Reg = NULL; + cs->irq_func = &hfcpci_interrupt; + cs->irq_flags |= IRQF_SHARED; + timer_setup(&cs->hw.hfcpci.timer, hfcpci_Timer, 0); + cs->cardmsg = &hfcpci_card_msg; + cs->auxcmd = &hfcpci_auxcmd; + + spin_unlock_irqrestore(&cs->lock, flags); + + return (1); +} diff --git a/drivers/isdn/hisax/hfc_pci.h b/drivers/isdn/hisax/hfc_pci.h new file mode 100644 index 000000000..4e58700a3 --- /dev/null +++ b/drivers/isdn/hisax/hfc_pci.h @@ -0,0 +1,235 @@ +/* $Id: hfc_pci.h,v 1.10.2.2 2004/01/12 22:52:26 keil Exp $ + * + * specific defines for CCD's HFC 2BDS0 PCI chips + * + * Author Werner Cornelius + * Copyright by Werner Cornelius <werner@isdn4linux.de> + * + * This software may be used and distributed according to the terms + * of the GNU General Public License, incorporated herein by reference. + * + */ + +/*********************************************/ +/* thresholds for transparent B-channel mode */ +/* change mask and threshold simultaneously */ +/*********************************************/ +#define HFCPCI_BTRANS_THRESHOLD 128 +#define HFCPCI_BTRANS_THRESMASK 0x00 + + + +/* defines for PCI config */ + +#define PCI_ENA_MEMIO 0x02 +#define PCI_ENA_MASTER 0x04 + + +/* GCI/IOM bus monitor registers */ + +#define HCFPCI_C_I 0x08 +#define HFCPCI_TRxR 0x0C +#define HFCPCI_MON1_D 0x28 +#define HFCPCI_MON2_D 0x2C + + +/* GCI/IOM bus timeslot registers */ + +#define HFCPCI_B1_SSL 0x80 +#define HFCPCI_B2_SSL 0x84 +#define HFCPCI_AUX1_SSL 0x88 +#define HFCPCI_AUX2_SSL 0x8C +#define HFCPCI_B1_RSL 0x90 +#define HFCPCI_B2_RSL 0x94 +#define HFCPCI_AUX1_RSL 0x98 +#define HFCPCI_AUX2_RSL 0x9C + +/* GCI/IOM bus data registers */ + +#define HFCPCI_B1_D 0xA0 +#define HFCPCI_B2_D 0xA4 +#define HFCPCI_AUX1_D 0xA8 +#define HFCPCI_AUX2_D 0xAC + +/* GCI/IOM bus configuration registers */ + +#define HFCPCI_MST_EMOD 0xB4 +#define HFCPCI_MST_MODE 0xB8 +#define HFCPCI_CONNECT 0xBC + + +/* Interrupt and status registers */ + +#define HFCPCI_FIFO_EN 0x44 +#define HFCPCI_TRM 0x48 +#define HFCPCI_B_MODE 0x4C +#define HFCPCI_CHIP_ID 0x58 +#define HFCPCI_CIRM 0x60 +#define HFCPCI_CTMT 0x64 +#define HFCPCI_INT_M1 0x68 +#define HFCPCI_INT_M2 0x6C +#define HFCPCI_INT_S1 0x78 +#define HFCPCI_INT_S2 0x7C +#define HFCPCI_STATUS 0x70 + +/* S/T section registers */ + +#define HFCPCI_STATES 0xC0 +#define HFCPCI_SCTRL 0xC4 +#define HFCPCI_SCTRL_E 0xC8 +#define HFCPCI_SCTRL_R 0xCC +#define HFCPCI_SQ 0xD0 +#define HFCPCI_CLKDEL 0xDC +#define HFCPCI_B1_REC 0xF0 +#define HFCPCI_B1_SEND 0xF0 +#define HFCPCI_B2_REC 0xF4 +#define HFCPCI_B2_SEND 0xF4 +#define HFCPCI_D_REC 0xF8 +#define HFCPCI_D_SEND 0xF8 +#define HFCPCI_E_REC 0xFC + + +/* bits in status register (READ) */ +#define HFCPCI_PCI_PROC 0x02 +#define HFCPCI_NBUSY 0x04 +#define HFCPCI_TIMER_ELAP 0x10 +#define HFCPCI_STATINT 0x20 +#define HFCPCI_FRAMEINT 0x40 +#define HFCPCI_ANYINT 0x80 + +/* bits in CTMT (Write) */ +#define HFCPCI_CLTIMER 0x80 +#define HFCPCI_TIM3_125 0x04 +#define HFCPCI_TIM25 0x10 +#define HFCPCI_TIM50 0x14 +#define HFCPCI_TIM400 0x18 +#define HFCPCI_TIM800 0x1C +#define HFCPCI_AUTO_TIMER 0x20 +#define HFCPCI_TRANSB2 0x02 +#define HFCPCI_TRANSB1 0x01 + +/* bits in CIRM (Write) */ +#define HFCPCI_AUX_MSK 0x07 +#define HFCPCI_RESET 0x08 +#define HFCPCI_B1_REV 0x40 +#define HFCPCI_B2_REV 0x80 + +/* bits in INT_M1 and INT_S1 */ +#define HFCPCI_INTS_B1TRANS 0x01 +#define HFCPCI_INTS_B2TRANS 0x02 +#define HFCPCI_INTS_DTRANS 0x04 +#define HFCPCI_INTS_B1REC 0x08 +#define HFCPCI_INTS_B2REC 0x10 +#define HFCPCI_INTS_DREC 0x20 +#define HFCPCI_INTS_L1STATE 0x40 +#define HFCPCI_INTS_TIMER 0x80 + +/* bits in INT_M2 */ +#define HFCPCI_PROC_TRANS 0x01 +#define HFCPCI_GCI_I_CHG 0x02 +#define HFCPCI_GCI_MON_REC 0x04 +#define HFCPCI_IRQ_ENABLE 0x08 +#define HFCPCI_PMESEL 0x80 + +/* bits in STATES */ +#define HFCPCI_STATE_MSK 0x0F +#define HFCPCI_LOAD_STATE 0x10 +#define HFCPCI_ACTIVATE 0x20 +#define HFCPCI_DO_ACTION 0x40 +#define HFCPCI_NT_G2_G3 0x80 + +/* bits in HFCD_MST_MODE */ +#define HFCPCI_MASTER 0x01 +#define HFCPCI_SLAVE 0x00 +/* remaining bits are for codecs control */ + +/* bits in HFCD_SCTRL */ +#define SCTRL_B1_ENA 0x01 +#define SCTRL_B2_ENA 0x02 +#define SCTRL_MODE_TE 0x00 +#define SCTRL_MODE_NT 0x04 +#define SCTRL_LOW_PRIO 0x08 +#define SCTRL_SQ_ENA 0x10 +#define SCTRL_TEST 0x20 +#define SCTRL_NONE_CAP 0x40 +#define SCTRL_PWR_DOWN 0x80 + +/* bits in SCTRL_E */ +#define HFCPCI_AUTO_AWAKE 0x01 +#define HFCPCI_DBIT_1 0x04 +#define HFCPCI_IGNORE_COL 0x08 +#define HFCPCI_CHG_B1_B2 0x80 + +/****************************/ +/* bits in FIFO_EN register */ +/****************************/ +#define HFCPCI_FIFOEN_B1 0x03 +#define HFCPCI_FIFOEN_B2 0x0C +#define HFCPCI_FIFOEN_DTX 0x10 +#define HFCPCI_FIFOEN_B1TX 0x01 +#define HFCPCI_FIFOEN_B1RX 0x02 +#define HFCPCI_FIFOEN_B2TX 0x04 +#define HFCPCI_FIFOEN_B2RX 0x08 + + +/***********************************/ +/* definitions of fifo memory area */ +/***********************************/ +#define MAX_D_FRAMES 15 +#define MAX_B_FRAMES 31 +#define B_SUB_VAL 0x200 +#define B_FIFO_SIZE (0x2000 - B_SUB_VAL) +#define D_FIFO_SIZE 512 +#define D_FREG_MASK 0xF + +typedef struct { + unsigned short z1; /* Z1 pointer 16 Bit */ + unsigned short z2; /* Z2 pointer 16 Bit */ +} z_type; + +typedef struct { + u_char data[D_FIFO_SIZE]; /* FIFO data space */ + u_char fill1[0x20A0 - D_FIFO_SIZE]; /* reserved, do not use */ + u_char f1, f2; /* f pointers */ + u_char fill2[0x20C0 - 0x20A2]; /* reserved, do not use */ + z_type za[MAX_D_FRAMES + 1]; /* mask index with D_FREG_MASK for access */ + u_char fill3[0x4000 - 0x2100]; /* align 16K */ +} dfifo_type; + +typedef struct { + z_type za[MAX_B_FRAMES + 1]; /* only range 0x0..0x1F allowed */ + u_char f1, f2; /* f pointers */ + u_char fill[0x2100 - 0x2082]; /* alignment */ +} bzfifo_type; + + +typedef union { + struct { + dfifo_type d_tx; /* D-send channel */ + dfifo_type d_rx; /* D-receive channel */ + } d_chan; + struct { + u_char fill1[0x200]; + u_char txdat_b1[B_FIFO_SIZE]; + bzfifo_type txbz_b1; + + bzfifo_type txbz_b2; + u_char txdat_b2[B_FIFO_SIZE]; + + u_char fill2[D_FIFO_SIZE]; + + u_char rxdat_b1[B_FIFO_SIZE]; + bzfifo_type rxbz_b1; + + bzfifo_type rxbz_b2; + u_char rxdat_b2[B_FIFO_SIZE]; + } b_chans; + u_char fill[32768]; +} fifo_area; + + +#define Write_hfc(a, b, c) (*(((u_char *)a->hw.hfcpci.pci_io) + b) = c) +#define Read_hfc(a, b) (*(((u_char *)a->hw.hfcpci.pci_io) + b)) + +extern void main_irq_hcpci(struct BCState *bcs); +extern void releasehfcpci(struct IsdnCardState *cs); diff --git a/drivers/isdn/hisax/hfc_sx.c b/drivers/isdn/hisax/hfc_sx.c new file mode 100644 index 000000000..4d3b4b2f2 --- /dev/null +++ b/drivers/isdn/hisax/hfc_sx.c @@ -0,0 +1,1517 @@ +/* $Id: hfc_sx.c,v 1.12.2.5 2004/02/11 13:21:33 keil Exp $ + * + * level driver for Cologne Chip Designs hfc-s+/sp based cards + * + * Author Werner Cornelius + * based on existing driver for CCD HFC PCI cards + * Copyright by Werner Cornelius <werner@isdn4linux.de> + * + * This software may be used and distributed according to the terms + * of the GNU General Public License, incorporated herein by reference. + * + */ + +#include <linux/init.h> +#include "hisax.h" +#include "hfc_sx.h" +#include "isdnl1.h" +#include <linux/interrupt.h> +#include <linux/isapnp.h> +#include <linux/slab.h> + +static const char *hfcsx_revision = "$Revision: 1.12.2.5 $"; + +/***************************************/ +/* IRQ-table for CCDs demo board */ +/* IRQs 6,5,10,11,12,15 are supported */ +/***************************************/ + +/* Teles 16.3c Vendor Id TAG2620, Version 1.0, Vendor version 2.1 + * + * Thanks to Uwe Wisniewski + * + * ISA-SLOT Signal PIN + * B25 IRQ3 92 IRQ_G + * B23 IRQ5 94 IRQ_A + * B4 IRQ2/9 95 IRQ_B + * D3 IRQ10 96 IRQ_C + * D4 IRQ11 97 IRQ_D + * D5 IRQ12 98 IRQ_E + * D6 IRQ15 99 IRQ_F + */ + +#undef CCD_DEMO_BOARD +#ifdef CCD_DEMO_BOARD +static u_char ccd_sp_irqtab[16] = { + 0, 0, 0, 0, 0, 2, 1, 0, 0, 0, 3, 4, 5, 0, 0, 6 +}; +#else /* Teles 16.3c */ +static u_char ccd_sp_irqtab[16] = { + 0, 0, 0, 7, 0, 1, 0, 0, 0, 2, 3, 4, 5, 0, 0, 6 +}; +#endif +#define NT_T1_COUNT 20 /* number of 3.125ms interrupts for G2 timeout */ + +#define byteout(addr, val) outb(val, addr) +#define bytein(addr) inb(addr) + +/******************************/ +/* In/Out access to registers */ +/******************************/ +static inline void +Write_hfc(struct IsdnCardState *cs, u_char regnum, u_char val) +{ + byteout(cs->hw.hfcsx.base + 1, regnum); + byteout(cs->hw.hfcsx.base, val); +} + +static inline u_char +Read_hfc(struct IsdnCardState *cs, u_char regnum) +{ + u_char ret; + + byteout(cs->hw.hfcsx.base + 1, regnum); + ret = bytein(cs->hw.hfcsx.base); + return (ret); +} + + +/**************************************************/ +/* select a fifo and remember which one for reuse */ +/**************************************************/ +static void +fifo_select(struct IsdnCardState *cs, u_char fifo) +{ + if (fifo == cs->hw.hfcsx.last_fifo) + return; /* still valid */ + + byteout(cs->hw.hfcsx.base + 1, HFCSX_FIF_SEL); + byteout(cs->hw.hfcsx.base, fifo); + while (bytein(cs->hw.hfcsx.base + 1) & 1); /* wait for busy */ + udelay(4); + byteout(cs->hw.hfcsx.base, fifo); + while (bytein(cs->hw.hfcsx.base + 1) & 1); /* wait for busy */ +} + +/******************************************/ +/* reset the specified fifo to defaults. */ +/* If its a send fifo init needed markers */ +/******************************************/ +static void +reset_fifo(struct IsdnCardState *cs, u_char fifo) +{ + fifo_select(cs, fifo); /* first select the fifo */ + byteout(cs->hw.hfcsx.base + 1, HFCSX_CIRM); + byteout(cs->hw.hfcsx.base, cs->hw.hfcsx.cirm | 0x80); /* reset cmd */ + udelay(1); + while (bytein(cs->hw.hfcsx.base + 1) & 1); /* wait for busy */ +} + + +/*************************************************************/ +/* write_fifo writes the skb contents to the desired fifo */ +/* if no space is available or an error occurs 0 is returned */ +/* the skb is not released in any way. */ +/*************************************************************/ +static int +write_fifo(struct IsdnCardState *cs, struct sk_buff *skb, u_char fifo, int trans_max) +{ + unsigned short *msp; + int fifo_size, count, z1, z2; + u_char f_msk, f1, f2, *src; + + if (skb->len <= 0) return (0); + if (fifo & 1) return (0); /* no write fifo */ + + fifo_select(cs, fifo); + if (fifo & 4) { + fifo_size = D_FIFO_SIZE; /* D-channel */ + f_msk = MAX_D_FRAMES; + if (trans_max) return (0); /* only HDLC */ + } + else { + fifo_size = cs->hw.hfcsx.b_fifo_size; /* B-channel */ + f_msk = MAX_B_FRAMES; + } + + z1 = Read_hfc(cs, HFCSX_FIF_Z1H); + z1 = ((z1 << 8) | Read_hfc(cs, HFCSX_FIF_Z1L)); + + /* Check for transparent mode */ + if (trans_max) { + z2 = Read_hfc(cs, HFCSX_FIF_Z2H); + z2 = ((z2 << 8) | Read_hfc(cs, HFCSX_FIF_Z2L)); + count = z2 - z1; + if (count <= 0) + count += fifo_size; /* free bytes */ + if (count < skb->len + 1) return (0); /* no room */ + count = fifo_size - count; /* bytes still not send */ + if (count > 2 * trans_max) return (0); /* delay to long */ + count = skb->len; + src = skb->data; + while (count--) + Write_hfc(cs, HFCSX_FIF_DWR, *src++); + return (1); /* success */ + } + + msp = ((struct hfcsx_extra *)(cs->hw.hfcsx.extra))->marker; + msp += (((fifo >> 1) & 3) * (MAX_B_FRAMES + 1)); + f1 = Read_hfc(cs, HFCSX_FIF_F1) & f_msk; + f2 = Read_hfc(cs, HFCSX_FIF_F2) & f_msk; + + count = f1 - f2; /* frame count actually buffered */ + if (count < 0) + count += (f_msk + 1); /* if wrap around */ + if (count > f_msk - 1) { + if (cs->debug & L1_DEB_ISAC_FIFO) + debugl1(cs, "hfcsx_write_fifo %d more as %d frames", fifo, f_msk - 1); + return (0); + } + + *(msp + f1) = z1; /* remember marker */ + + if (cs->debug & L1_DEB_ISAC_FIFO) + debugl1(cs, "hfcsx_write_fifo %d f1(%x) f2(%x) z1(f1)(%x)", + fifo, f1, f2, z1); + /* now determine free bytes in FIFO buffer */ + count = *(msp + f2) - z1; + if (count <= 0) + count += fifo_size; /* count now contains available bytes */ + + if (cs->debug & L1_DEB_ISAC_FIFO) + debugl1(cs, "hfcsx_write_fifo %d count(%u/%d)", + fifo, skb->len, count); + if (count < skb->len) { + if (cs->debug & L1_DEB_ISAC_FIFO) + debugl1(cs, "hfcsx_write_fifo %d no fifo mem", fifo); + return (0); + } + + count = skb->len; /* get frame len */ + src = skb->data; /* source pointer */ + while (count--) + Write_hfc(cs, HFCSX_FIF_DWR, *src++); + + Read_hfc(cs, HFCSX_FIF_INCF1); /* increment F1 */ + udelay(1); + while (bytein(cs->hw.hfcsx.base + 1) & 1); /* wait for busy */ + return (1); +} + +/***************************************************************/ +/* read_fifo reads data to an skb from the desired fifo */ +/* if no data is available or an error occurs NULL is returned */ +/* the skb is not released in any way. */ +/***************************************************************/ +static struct sk_buff * +read_fifo(struct IsdnCardState *cs, u_char fifo, int trans_max) +{ int fifo_size, count, z1, z2; + u_char f_msk, f1, f2, *dst; + struct sk_buff *skb; + + if (!(fifo & 1)) return (NULL); /* no read fifo */ + fifo_select(cs, fifo); + if (fifo & 4) { + fifo_size = D_FIFO_SIZE; /* D-channel */ + f_msk = MAX_D_FRAMES; + if (trans_max) return (NULL); /* only hdlc */ + } + else { + fifo_size = cs->hw.hfcsx.b_fifo_size; /* B-channel */ + f_msk = MAX_B_FRAMES; + } + + /* transparent mode */ + if (trans_max) { + z1 = Read_hfc(cs, HFCSX_FIF_Z1H); + z1 = ((z1 << 8) | Read_hfc(cs, HFCSX_FIF_Z1L)); + z2 = Read_hfc(cs, HFCSX_FIF_Z2H); + z2 = ((z2 << 8) | Read_hfc(cs, HFCSX_FIF_Z2L)); + /* now determine bytes in actual FIFO buffer */ + count = z1 - z2; + if (count <= 0) + count += fifo_size; /* count now contains buffered bytes */ + count++; + if (count > trans_max) + count = trans_max; /* limit length */ + skb = dev_alloc_skb(count); + if (skb) { + dst = skb_put(skb, count); + while (count--) + *dst++ = Read_hfc(cs, HFCSX_FIF_DRD); + return skb; + } else + return NULL; /* no memory */ + } + + do { + f1 = Read_hfc(cs, HFCSX_FIF_F1) & f_msk; + f2 = Read_hfc(cs, HFCSX_FIF_F2) & f_msk; + + if (f1 == f2) return (NULL); /* no frame available */ + + z1 = Read_hfc(cs, HFCSX_FIF_Z1H); + z1 = ((z1 << 8) | Read_hfc(cs, HFCSX_FIF_Z1L)); + z2 = Read_hfc(cs, HFCSX_FIF_Z2H); + z2 = ((z2 << 8) | Read_hfc(cs, HFCSX_FIF_Z2L)); + + if (cs->debug & L1_DEB_ISAC_FIFO) + debugl1(cs, "hfcsx_read_fifo %d f1(%x) f2(%x) z1(f2)(%x) z2(f2)(%x)", + fifo, f1, f2, z1, z2); + /* now determine bytes in actual FIFO buffer */ + count = z1 - z2; + if (count <= 0) + count += fifo_size; /* count now contains buffered bytes */ + count++; + + if (cs->debug & L1_DEB_ISAC_FIFO) + debugl1(cs, "hfcsx_read_fifo %d count %u)", + fifo, count); + + if ((count > fifo_size) || (count < 4)) { + if (cs->debug & L1_DEB_WARN) + debugl1(cs, "hfcsx_read_fifo %d packet inv. len %d ", fifo , count); + while (count) { + count--; /* empty fifo */ + Read_hfc(cs, HFCSX_FIF_DRD); + } + skb = NULL; + } else + if ((skb = dev_alloc_skb(count - 3))) { + count -= 3; + dst = skb_put(skb, count); + + while (count--) + *dst++ = Read_hfc(cs, HFCSX_FIF_DRD); + + Read_hfc(cs, HFCSX_FIF_DRD); /* CRC 1 */ + Read_hfc(cs, HFCSX_FIF_DRD); /* CRC 2 */ + if (Read_hfc(cs, HFCSX_FIF_DRD)) { + dev_kfree_skb_irq(skb); + if (cs->debug & L1_DEB_ISAC_FIFO) + debugl1(cs, "hfcsx_read_fifo %d crc error", fifo); + skb = NULL; + } + } else { + printk(KERN_WARNING "HFC-SX: receive out of memory\n"); + return (NULL); + } + + Read_hfc(cs, HFCSX_FIF_INCF2); /* increment F2 */ + udelay(1); + while (bytein(cs->hw.hfcsx.base + 1) & 1); /* wait for busy */ + udelay(1); + } while (!skb); /* retry in case of crc error */ + return (skb); +} + +/******************************************/ +/* free hardware resources used by driver */ +/******************************************/ +static void +release_io_hfcsx(struct IsdnCardState *cs) +{ + cs->hw.hfcsx.int_m2 = 0; /* interrupt output off ! */ + Write_hfc(cs, HFCSX_INT_M2, cs->hw.hfcsx.int_m2); + Write_hfc(cs, HFCSX_CIRM, HFCSX_RESET); /* Reset On */ + msleep(30); /* Timeout 30ms */ + Write_hfc(cs, HFCSX_CIRM, 0); /* Reset Off */ + del_timer(&cs->hw.hfcsx.timer); + release_region(cs->hw.hfcsx.base, 2); /* release IO-Block */ + kfree(cs->hw.hfcsx.extra); + cs->hw.hfcsx.extra = NULL; +} + +/**********************************************************/ +/* set_fifo_size determines the size of the RAM and FIFOs */ +/* returning 0 -> need to reset the chip again. */ +/**********************************************************/ +static int set_fifo_size(struct IsdnCardState *cs) +{ + + if (cs->hw.hfcsx.b_fifo_size) return (1); /* already determined */ + + if ((cs->hw.hfcsx.chip >> 4) == 9) { + cs->hw.hfcsx.b_fifo_size = B_FIFO_SIZE_32K; + return (1); + } + + cs->hw.hfcsx.b_fifo_size = B_FIFO_SIZE_8K; + cs->hw.hfcsx.cirm |= 0x10; /* only 8K of ram */ + return (0); + +} + +/********************************************************************************/ +/* function called to reset the HFC SX chip. A complete software reset of chip */ +/* and fifos is done. */ +/********************************************************************************/ +static void +reset_hfcsx(struct IsdnCardState *cs) +{ + cs->hw.hfcsx.int_m2 = 0; /* interrupt output off ! */ + Write_hfc(cs, HFCSX_INT_M2, cs->hw.hfcsx.int_m2); + + printk(KERN_INFO "HFC_SX: resetting card\n"); + while (1) { + Write_hfc(cs, HFCSX_CIRM, HFCSX_RESET | cs->hw.hfcsx.cirm); /* Reset */ + mdelay(30); + Write_hfc(cs, HFCSX_CIRM, cs->hw.hfcsx.cirm); /* Reset Off */ + mdelay(20); + if (Read_hfc(cs, HFCSX_STATUS) & 2) + printk(KERN_WARNING "HFC-SX init bit busy\n"); + cs->hw.hfcsx.last_fifo = 0xff; /* invalidate */ + if (!set_fifo_size(cs)) continue; + break; + } + + cs->hw.hfcsx.trm = 0 + HFCSX_BTRANS_THRESMASK; /* no echo connect , threshold */ + Write_hfc(cs, HFCSX_TRM, cs->hw.hfcsx.trm); + + Write_hfc(cs, HFCSX_CLKDEL, 0x0e); /* ST-Bit delay for TE-Mode */ + cs->hw.hfcsx.sctrl_e = HFCSX_AUTO_AWAKE; + Write_hfc(cs, HFCSX_SCTRL_E, cs->hw.hfcsx.sctrl_e); /* S/T Auto awake */ + cs->hw.hfcsx.bswapped = 0; /* no exchange */ + cs->hw.hfcsx.nt_mode = 0; /* we are in TE mode */ + cs->hw.hfcsx.ctmt = HFCSX_TIM3_125 | HFCSX_AUTO_TIMER; + Write_hfc(cs, HFCSX_CTMT, cs->hw.hfcsx.ctmt); + + cs->hw.hfcsx.int_m1 = HFCSX_INTS_DTRANS | HFCSX_INTS_DREC | + HFCSX_INTS_L1STATE | HFCSX_INTS_TIMER; + Write_hfc(cs, HFCSX_INT_M1, cs->hw.hfcsx.int_m1); + + /* Clear already pending ints */ + if (Read_hfc(cs, HFCSX_INT_S1)); + + Write_hfc(cs, HFCSX_STATES, HFCSX_LOAD_STATE | 2); /* HFC ST 2 */ + udelay(10); + Write_hfc(cs, HFCSX_STATES, 2); /* HFC ST 2 */ + cs->hw.hfcsx.mst_m = HFCSX_MASTER; /* HFC Master Mode */ + + Write_hfc(cs, HFCSX_MST_MODE, cs->hw.hfcsx.mst_m); + cs->hw.hfcsx.sctrl = 0x40; /* set tx_lo mode, error in datasheet ! */ + Write_hfc(cs, HFCSX_SCTRL, cs->hw.hfcsx.sctrl); + cs->hw.hfcsx.sctrl_r = 0; + Write_hfc(cs, HFCSX_SCTRL_R, cs->hw.hfcsx.sctrl_r); + + /* Init GCI/IOM2 in master mode */ + /* Slots 0 and 1 are set for B-chan 1 and 2 */ + /* D- and monitor/CI channel are not enabled */ + /* STIO1 is used as output for data, B1+B2 from ST->IOM+HFC */ + /* STIO2 is used as data input, B1+B2 from IOM->ST */ + /* ST B-channel send disabled -> continuous 1s */ + /* The IOM slots are always enabled */ + cs->hw.hfcsx.conn = 0x36; /* set data flow directions */ + Write_hfc(cs, HFCSX_CONNECT, cs->hw.hfcsx.conn); + Write_hfc(cs, HFCSX_B1_SSL, 0x80); /* B1-Slot 0 STIO1 out enabled */ + Write_hfc(cs, HFCSX_B2_SSL, 0x81); /* B2-Slot 1 STIO1 out enabled */ + Write_hfc(cs, HFCSX_B1_RSL, 0x80); /* B1-Slot 0 STIO2 in enabled */ + Write_hfc(cs, HFCSX_B2_RSL, 0x81); /* B2-Slot 1 STIO2 in enabled */ + + /* Finally enable IRQ output */ + cs->hw.hfcsx.int_m2 = HFCSX_IRQ_ENABLE; + Write_hfc(cs, HFCSX_INT_M2, cs->hw.hfcsx.int_m2); + if (Read_hfc(cs, HFCSX_INT_S2)); +} + +/***************************************************/ +/* Timer function called when kernel timer expires */ +/***************************************************/ +static void +hfcsx_Timer(struct timer_list *t) +{ + struct IsdnCardState *cs = from_timer(cs, t, hw.hfcsx.timer); + cs->hw.hfcsx.timer.expires = jiffies + 75; + /* WD RESET */ +/* WriteReg(cs, HFCD_DATA, HFCD_CTMT, cs->hw.hfcsx.ctmt | 0x80); + add_timer(&cs->hw.hfcsx.timer); +*/ +} + +/************************************************/ +/* select a b-channel entry matching and active */ +/************************************************/ +static +struct BCState * +Sel_BCS(struct IsdnCardState *cs, int channel) +{ + if (cs->bcs[0].mode && (cs->bcs[0].channel == channel)) + return (&cs->bcs[0]); + else if (cs->bcs[1].mode && (cs->bcs[1].channel == channel)) + return (&cs->bcs[1]); + else + return (NULL); +} + +/*******************************/ +/* D-channel receive procedure */ +/*******************************/ +static +int +receive_dmsg(struct IsdnCardState *cs) +{ + struct sk_buff *skb; + int count = 5; + + if (test_and_set_bit(FLG_LOCK_ATOMIC, &cs->HW_Flags)) { + debugl1(cs, "rec_dmsg blocked"); + return (1); + } + + do { + skb = read_fifo(cs, HFCSX_SEL_D_RX, 0); + if (skb) { + skb_queue_tail(&cs->rq, skb); + schedule_event(cs, D_RCVBUFREADY); + } + } while (--count && skb); + + test_and_clear_bit(FLG_LOCK_ATOMIC, &cs->HW_Flags); + return (1); +} + +/**********************************/ +/* B-channel main receive routine */ +/**********************************/ +static void +main_rec_hfcsx(struct BCState *bcs) +{ + struct IsdnCardState *cs = bcs->cs; + int count = 5; + struct sk_buff *skb; + +Begin: + count--; + if (test_and_set_bit(FLG_LOCK_ATOMIC, &cs->HW_Flags)) { + debugl1(cs, "rec_data %d blocked", bcs->channel); + return; + } + skb = read_fifo(cs, ((bcs->channel) && (!cs->hw.hfcsx.bswapped)) ? + HFCSX_SEL_B2_RX : HFCSX_SEL_B1_RX, + (bcs->mode == L1_MODE_TRANS) ? + HFCSX_BTRANS_THRESHOLD : 0); + + if (skb) { + skb_queue_tail(&bcs->rqueue, skb); + schedule_event(bcs, B_RCVBUFREADY); + } + + test_and_clear_bit(FLG_LOCK_ATOMIC, &cs->HW_Flags); + if (count && skb) + goto Begin; + return; +} + +/**************************/ +/* D-channel send routine */ +/**************************/ +static void +hfcsx_fill_dfifo(struct IsdnCardState *cs) +{ + if (!cs->tx_skb) + return; + if (cs->tx_skb->len <= 0) + return; + + if (write_fifo(cs, cs->tx_skb, HFCSX_SEL_D_TX, 0)) { + dev_kfree_skb_any(cs->tx_skb); + cs->tx_skb = NULL; + } + return; +} + +/**************************/ +/* B-channel send routine */ +/**************************/ +static void +hfcsx_fill_fifo(struct BCState *bcs) +{ + struct IsdnCardState *cs = bcs->cs; + + if (!bcs->tx_skb) + return; + if (bcs->tx_skb->len <= 0) + return; + + if (write_fifo(cs, bcs->tx_skb, + ((bcs->channel) && (!cs->hw.hfcsx.bswapped)) ? + HFCSX_SEL_B2_TX : HFCSX_SEL_B1_TX, + (bcs->mode == L1_MODE_TRANS) ? + HFCSX_BTRANS_THRESHOLD : 0)) { + + bcs->tx_cnt -= bcs->tx_skb->len; + if (test_bit(FLG_LLI_L1WAKEUP, &bcs->st->lli.flag) && + (PACKET_NOACK != bcs->tx_skb->pkt_type)) { + u_long flags; + spin_lock_irqsave(&bcs->aclock, flags); + bcs->ackcnt += bcs->tx_skb->len; + spin_unlock_irqrestore(&bcs->aclock, flags); + schedule_event(bcs, B_ACKPENDING); + } + dev_kfree_skb_any(bcs->tx_skb); + bcs->tx_skb = NULL; + test_and_clear_bit(BC_FLG_BUSY, &bcs->Flag); + } +} + +/**********************************************/ +/* D-channel l1 state call for leased NT-mode */ +/**********************************************/ +static void +dch_nt_l2l1(struct PStack *st, int pr, void *arg) +{ + struct IsdnCardState *cs = (struct IsdnCardState *) st->l1.hardware; + + switch (pr) { + case (PH_DATA | REQUEST): + case (PH_PULL | REQUEST): + case (PH_PULL | INDICATION): + st->l1.l1hw(st, pr, arg); + break; + case (PH_ACTIVATE | REQUEST): + st->l1.l1l2(st, PH_ACTIVATE | CONFIRM, NULL); + break; + case (PH_TESTLOOP | REQUEST): + if (1 & (long) arg) + debugl1(cs, "PH_TEST_LOOP B1"); + if (2 & (long) arg) + debugl1(cs, "PH_TEST_LOOP B2"); + if (!(3 & (long) arg)) + debugl1(cs, "PH_TEST_LOOP DISABLED"); + st->l1.l1hw(st, HW_TESTLOOP | REQUEST, arg); + break; + default: + if (cs->debug) + debugl1(cs, "dch_nt_l2l1 msg %04X unhandled", pr); + break; + } +} + + + +/***********************/ +/* set/reset echo mode */ +/***********************/ +static int +hfcsx_auxcmd(struct IsdnCardState *cs, isdn_ctrl *ic) +{ + unsigned long flags; + int i = *(unsigned int *) ic->parm.num; + + if ((ic->arg == 98) && + (!(cs->hw.hfcsx.int_m1 & (HFCSX_INTS_B2TRANS + HFCSX_INTS_B2REC + HFCSX_INTS_B1TRANS + HFCSX_INTS_B1REC)))) { + spin_lock_irqsave(&cs->lock, flags); + Write_hfc(cs, HFCSX_STATES, HFCSX_LOAD_STATE | 0); /* HFC ST G0 */ + udelay(10); + cs->hw.hfcsx.sctrl |= SCTRL_MODE_NT; + Write_hfc(cs, HFCSX_SCTRL, cs->hw.hfcsx.sctrl); /* set NT-mode */ + udelay(10); + Write_hfc(cs, HFCSX_STATES, HFCSX_LOAD_STATE | 1); /* HFC ST G1 */ + udelay(10); + Write_hfc(cs, HFCSX_STATES, 1 | HFCSX_ACTIVATE | HFCSX_DO_ACTION); + cs->dc.hfcsx.ph_state = 1; + cs->hw.hfcsx.nt_mode = 1; + cs->hw.hfcsx.nt_timer = 0; + spin_unlock_irqrestore(&cs->lock, flags); + cs->stlist->l2.l2l1 = dch_nt_l2l1; + debugl1(cs, "NT mode activated"); + return (0); + } + if ((cs->chanlimit > 1) || (cs->hw.hfcsx.bswapped) || + (cs->hw.hfcsx.nt_mode) || (ic->arg != 12)) + return (-EINVAL); + + if (i) { + cs->logecho = 1; + cs->hw.hfcsx.trm |= 0x20; /* enable echo chan */ + cs->hw.hfcsx.int_m1 |= HFCSX_INTS_B2REC; + /* reset Channel !!!!! */ + } else { + cs->logecho = 0; + cs->hw.hfcsx.trm &= ~0x20; /* disable echo chan */ + cs->hw.hfcsx.int_m1 &= ~HFCSX_INTS_B2REC; + } + cs->hw.hfcsx.sctrl_r &= ~SCTRL_B2_ENA; + cs->hw.hfcsx.sctrl &= ~SCTRL_B2_ENA; + cs->hw.hfcsx.conn |= 0x10; /* B2-IOM -> B2-ST */ + cs->hw.hfcsx.ctmt &= ~2; + spin_lock_irqsave(&cs->lock, flags); + Write_hfc(cs, HFCSX_CTMT, cs->hw.hfcsx.ctmt); + Write_hfc(cs, HFCSX_SCTRL_R, cs->hw.hfcsx.sctrl_r); + Write_hfc(cs, HFCSX_SCTRL, cs->hw.hfcsx.sctrl); + Write_hfc(cs, HFCSX_CONNECT, cs->hw.hfcsx.conn); + Write_hfc(cs, HFCSX_TRM, cs->hw.hfcsx.trm); + Write_hfc(cs, HFCSX_INT_M1, cs->hw.hfcsx.int_m1); + spin_unlock_irqrestore(&cs->lock, flags); + return (0); +} /* hfcsx_auxcmd */ + +/*****************************/ +/* E-channel receive routine */ +/*****************************/ +static void +receive_emsg(struct IsdnCardState *cs) +{ + int count = 5; + u_char *ptr; + struct sk_buff *skb; + + if (test_and_set_bit(FLG_LOCK_ATOMIC, &cs->HW_Flags)) { + debugl1(cs, "echo_rec_data blocked"); + return; + } + do { + skb = read_fifo(cs, HFCSX_SEL_B2_RX, 0); + if (skb) { + if (cs->debug & DEB_DLOG_HEX) { + ptr = cs->dlog; + if ((skb->len) < MAX_DLOG_SPACE / 3 - 10) { + *ptr++ = 'E'; + *ptr++ = 'C'; + *ptr++ = 'H'; + *ptr++ = 'O'; + *ptr++ = ':'; + ptr += QuickHex(ptr, skb->data, skb->len); + ptr--; + *ptr++ = '\n'; + *ptr = 0; + HiSax_putstatus(cs, NULL, cs->dlog); + } else + HiSax_putstatus(cs, "LogEcho: ", "warning Frame too big (%d)", skb->len); + } + dev_kfree_skb_any(skb); + } + } while (--count && skb); + + test_and_clear_bit(FLG_LOCK_ATOMIC, &cs->HW_Flags); + return; +} /* receive_emsg */ + + +/*********************/ +/* Interrupt handler */ +/*********************/ +static irqreturn_t +hfcsx_interrupt(int intno, void *dev_id) +{ + struct IsdnCardState *cs = dev_id; + u_char exval; + struct BCState *bcs; + int count = 15; + u_long flags; + u_char val, stat; + + if (!(cs->hw.hfcsx.int_m2 & 0x08)) + return IRQ_NONE; /* not initialised */ + + spin_lock_irqsave(&cs->lock, flags); + if (HFCSX_ANYINT & (stat = Read_hfc(cs, HFCSX_STATUS))) { + val = Read_hfc(cs, HFCSX_INT_S1); + if (cs->debug & L1_DEB_ISAC) + debugl1(cs, "HFC-SX: stat(%02x) s1(%02x)", stat, val); + } else { + spin_unlock_irqrestore(&cs->lock, flags); + return IRQ_NONE; + } + if (cs->debug & L1_DEB_ISAC) + debugl1(cs, "HFC-SX irq %x %s", val, + test_bit(FLG_LOCK_ATOMIC, &cs->HW_Flags) ? + "locked" : "unlocked"); + val &= cs->hw.hfcsx.int_m1; + if (val & 0x40) { /* state machine irq */ + exval = Read_hfc(cs, HFCSX_STATES) & 0xf; + if (cs->debug & L1_DEB_ISAC) + debugl1(cs, "ph_state chg %d->%d", cs->dc.hfcsx.ph_state, + exval); + cs->dc.hfcsx.ph_state = exval; + schedule_event(cs, D_L1STATECHANGE); + val &= ~0x40; + } + if (val & 0x80) { /* timer irq */ + if (cs->hw.hfcsx.nt_mode) { + if ((--cs->hw.hfcsx.nt_timer) < 0) + schedule_event(cs, D_L1STATECHANGE); + } + val &= ~0x80; + Write_hfc(cs, HFCSX_CTMT, cs->hw.hfcsx.ctmt | HFCSX_CLTIMER); + } + while (val) { + if (test_bit(FLG_LOCK_ATOMIC, &cs->HW_Flags)) { + cs->hw.hfcsx.int_s1 |= val; + spin_unlock_irqrestore(&cs->lock, flags); + return IRQ_HANDLED; + } + if (cs->hw.hfcsx.int_s1 & 0x18) { + exval = val; + val = cs->hw.hfcsx.int_s1; + cs->hw.hfcsx.int_s1 = exval; + } + if (val & 0x08) { + if (!(bcs = Sel_BCS(cs, cs->hw.hfcsx.bswapped ? 1 : 0))) { + if (cs->debug) + debugl1(cs, "hfcsx spurious 0x08 IRQ"); + } else + main_rec_hfcsx(bcs); + } + if (val & 0x10) { + if (cs->logecho) + receive_emsg(cs); + else if (!(bcs = Sel_BCS(cs, 1))) { + if (cs->debug) + debugl1(cs, "hfcsx spurious 0x10 IRQ"); + } else + main_rec_hfcsx(bcs); + } + if (val & 0x01) { + if (!(bcs = Sel_BCS(cs, cs->hw.hfcsx.bswapped ? 1 : 0))) { + if (cs->debug) + debugl1(cs, "hfcsx spurious 0x01 IRQ"); + } else { + if (bcs->tx_skb) { + if (!test_and_set_bit(FLG_LOCK_ATOMIC, &cs->HW_Flags)) { + hfcsx_fill_fifo(bcs); + test_and_clear_bit(FLG_LOCK_ATOMIC, &cs->HW_Flags); + } else + debugl1(cs, "fill_data %d blocked", bcs->channel); + } else { + if ((bcs->tx_skb = skb_dequeue(&bcs->squeue))) { + if (!test_and_set_bit(FLG_LOCK_ATOMIC, &cs->HW_Flags)) { + hfcsx_fill_fifo(bcs); + test_and_clear_bit(FLG_LOCK_ATOMIC, &cs->HW_Flags); + } else + debugl1(cs, "fill_data %d blocked", bcs->channel); + } else { + schedule_event(bcs, B_XMTBUFREADY); + } + } + } + } + if (val & 0x02) { + if (!(bcs = Sel_BCS(cs, 1))) { + if (cs->debug) + debugl1(cs, "hfcsx spurious 0x02 IRQ"); + } else { + if (bcs->tx_skb) { + if (!test_and_set_bit(FLG_LOCK_ATOMIC, &cs->HW_Flags)) { + hfcsx_fill_fifo(bcs); + test_and_clear_bit(FLG_LOCK_ATOMIC, &cs->HW_Flags); + } else + debugl1(cs, "fill_data %d blocked", bcs->channel); + } else { + if ((bcs->tx_skb = skb_dequeue(&bcs->squeue))) { + if (!test_and_set_bit(FLG_LOCK_ATOMIC, &cs->HW_Flags)) { + hfcsx_fill_fifo(bcs); + test_and_clear_bit(FLG_LOCK_ATOMIC, &cs->HW_Flags); + } else + debugl1(cs, "fill_data %d blocked", bcs->channel); + } else { + schedule_event(bcs, B_XMTBUFREADY); + } + } + } + } + if (val & 0x20) { /* receive dframe */ + receive_dmsg(cs); + } + if (val & 0x04) { /* dframe transmitted */ + if (test_and_clear_bit(FLG_DBUSY_TIMER, &cs->HW_Flags)) + del_timer(&cs->dbusytimer); + if (test_and_clear_bit(FLG_L1_DBUSY, &cs->HW_Flags)) + schedule_event(cs, D_CLEARBUSY); + if (cs->tx_skb) { + if (cs->tx_skb->len) { + if (!test_and_set_bit(FLG_LOCK_ATOMIC, &cs->HW_Flags)) { + hfcsx_fill_dfifo(cs); + test_and_clear_bit(FLG_LOCK_ATOMIC, &cs->HW_Flags); + } else { + debugl1(cs, "hfcsx_fill_dfifo irq blocked"); + } + goto afterXPR; + } else { + dev_kfree_skb_irq(cs->tx_skb); + cs->tx_cnt = 0; + cs->tx_skb = NULL; + } + } + if ((cs->tx_skb = skb_dequeue(&cs->sq))) { + cs->tx_cnt = 0; + if (!test_and_set_bit(FLG_LOCK_ATOMIC, &cs->HW_Flags)) { + hfcsx_fill_dfifo(cs); + test_and_clear_bit(FLG_LOCK_ATOMIC, &cs->HW_Flags); + } else { + debugl1(cs, "hfcsx_fill_dfifo irq blocked"); + } + } else + schedule_event(cs, D_XMTBUFREADY); + } + afterXPR: + if (cs->hw.hfcsx.int_s1 && count--) { + val = cs->hw.hfcsx.int_s1; + cs->hw.hfcsx.int_s1 = 0; + if (cs->debug & L1_DEB_ISAC) + debugl1(cs, "HFC-SX irq %x loop %d", val, 15 - count); + } else + val = 0; + } + spin_unlock_irqrestore(&cs->lock, flags); + return IRQ_HANDLED; +} + +/********************************************************************/ +/* timer callback for D-chan busy resolution. Currently no function */ +/********************************************************************/ +static void +hfcsx_dbusy_timer(struct timer_list *t) +{ +} + +/*************************************/ +/* Layer 1 D-channel hardware access */ +/*************************************/ +static void +HFCSX_l1hw(struct PStack *st, int pr, void *arg) +{ + struct IsdnCardState *cs = (struct IsdnCardState *) st->l1.hardware; + struct sk_buff *skb = arg; + u_long flags; + + switch (pr) { + case (PH_DATA | REQUEST): + if (cs->debug & DEB_DLOG_HEX) + LogFrame(cs, skb->data, skb->len); + if (cs->debug & DEB_DLOG_VERBOSE) + dlogframe(cs, skb, 0); + spin_lock_irqsave(&cs->lock, flags); + if (cs->tx_skb) { + skb_queue_tail(&cs->sq, skb); +#ifdef L2FRAME_DEBUG /* psa */ + if (cs->debug & L1_DEB_LAPD) + Logl2Frame(cs, skb, "PH_DATA Queued", 0); +#endif + } else { + cs->tx_skb = skb; + cs->tx_cnt = 0; +#ifdef L2FRAME_DEBUG /* psa */ + if (cs->debug & L1_DEB_LAPD) + Logl2Frame(cs, skb, "PH_DATA", 0); +#endif + if (!test_and_set_bit(FLG_LOCK_ATOMIC, &cs->HW_Flags)) { + hfcsx_fill_dfifo(cs); + test_and_clear_bit(FLG_LOCK_ATOMIC, &cs->HW_Flags); + } else + debugl1(cs, "hfcsx_fill_dfifo blocked"); + + } + spin_unlock_irqrestore(&cs->lock, flags); + break; + case (PH_PULL | INDICATION): + spin_lock_irqsave(&cs->lock, flags); + if (cs->tx_skb) { + if (cs->debug & L1_DEB_WARN) + debugl1(cs, " l2l1 tx_skb exist this shouldn't happen"); + skb_queue_tail(&cs->sq, skb); + spin_unlock_irqrestore(&cs->lock, flags); + break; + } + if (cs->debug & DEB_DLOG_HEX) + LogFrame(cs, skb->data, skb->len); + if (cs->debug & DEB_DLOG_VERBOSE) + dlogframe(cs, skb, 0); + cs->tx_skb = skb; + cs->tx_cnt = 0; +#ifdef L2FRAME_DEBUG /* psa */ + if (cs->debug & L1_DEB_LAPD) + Logl2Frame(cs, skb, "PH_DATA_PULLED", 0); +#endif + if (!test_and_set_bit(FLG_LOCK_ATOMIC, &cs->HW_Flags)) { + hfcsx_fill_dfifo(cs); + test_and_clear_bit(FLG_LOCK_ATOMIC, &cs->HW_Flags); + } else + debugl1(cs, "hfcsx_fill_dfifo blocked"); + spin_unlock_irqrestore(&cs->lock, flags); + break; + case (PH_PULL | REQUEST): +#ifdef L2FRAME_DEBUG /* psa */ + if (cs->debug & L1_DEB_LAPD) + debugl1(cs, "-> PH_REQUEST_PULL"); +#endif + if (!cs->tx_skb) { + test_and_clear_bit(FLG_L1_PULL_REQ, &st->l1.Flags); + st->l1.l1l2(st, PH_PULL | CONFIRM, NULL); + } else + test_and_set_bit(FLG_L1_PULL_REQ, &st->l1.Flags); + break; + case (HW_RESET | REQUEST): + spin_lock_irqsave(&cs->lock, flags); + Write_hfc(cs, HFCSX_STATES, HFCSX_LOAD_STATE | 3); /* HFC ST 3 */ + udelay(6); + Write_hfc(cs, HFCSX_STATES, 3); /* HFC ST 2 */ + cs->hw.hfcsx.mst_m |= HFCSX_MASTER; + Write_hfc(cs, HFCSX_MST_MODE, cs->hw.hfcsx.mst_m); + Write_hfc(cs, HFCSX_STATES, HFCSX_ACTIVATE | HFCSX_DO_ACTION); + spin_unlock_irqrestore(&cs->lock, flags); + l1_msg(cs, HW_POWERUP | CONFIRM, NULL); + break; + case (HW_ENABLE | REQUEST): + spin_lock_irqsave(&cs->lock, flags); + Write_hfc(cs, HFCSX_STATES, HFCSX_ACTIVATE | HFCSX_DO_ACTION); + spin_unlock_irqrestore(&cs->lock, flags); + break; + case (HW_DEACTIVATE | REQUEST): + spin_lock_irqsave(&cs->lock, flags); + cs->hw.hfcsx.mst_m &= ~HFCSX_MASTER; + Write_hfc(cs, HFCSX_MST_MODE, cs->hw.hfcsx.mst_m); + spin_unlock_irqrestore(&cs->lock, flags); + break; + case (HW_INFO3 | REQUEST): + spin_lock_irqsave(&cs->lock, flags); + cs->hw.hfcsx.mst_m |= HFCSX_MASTER; + Write_hfc(cs, HFCSX_MST_MODE, cs->hw.hfcsx.mst_m); + spin_unlock_irqrestore(&cs->lock, flags); + break; + case (HW_TESTLOOP | REQUEST): + spin_lock_irqsave(&cs->lock, flags); + switch ((long) arg) { + case (1): + Write_hfc(cs, HFCSX_B1_SSL, 0x80); /* tx slot */ + Write_hfc(cs, HFCSX_B1_RSL, 0x80); /* rx slot */ + cs->hw.hfcsx.conn = (cs->hw.hfcsx.conn & ~7) | 1; + Write_hfc(cs, HFCSX_CONNECT, cs->hw.hfcsx.conn); + break; + case (2): + Write_hfc(cs, HFCSX_B2_SSL, 0x81); /* tx slot */ + Write_hfc(cs, HFCSX_B2_RSL, 0x81); /* rx slot */ + cs->hw.hfcsx.conn = (cs->hw.hfcsx.conn & ~0x38) | 0x08; + Write_hfc(cs, HFCSX_CONNECT, cs->hw.hfcsx.conn); + break; + default: + spin_unlock_irqrestore(&cs->lock, flags); + if (cs->debug & L1_DEB_WARN) + debugl1(cs, "hfcsx_l1hw loop invalid %4lx", (unsigned long)arg); + return; + } + cs->hw.hfcsx.trm |= 0x80; /* enable IOM-loop */ + Write_hfc(cs, HFCSX_TRM, cs->hw.hfcsx.trm); + spin_unlock_irqrestore(&cs->lock, flags); + break; + default: + if (cs->debug & L1_DEB_WARN) + debugl1(cs, "hfcsx_l1hw unknown pr %4x", pr); + break; + } +} + +/***********************************************/ +/* called during init setting l1 stack pointer */ +/***********************************************/ +static void +setstack_hfcsx(struct PStack *st, struct IsdnCardState *cs) +{ + st->l1.l1hw = HFCSX_l1hw; +} + +/**************************************/ +/* send B-channel data if not blocked */ +/**************************************/ +static void +hfcsx_send_data(struct BCState *bcs) +{ + struct IsdnCardState *cs = bcs->cs; + + if (!test_and_set_bit(FLG_LOCK_ATOMIC, &cs->HW_Flags)) { + hfcsx_fill_fifo(bcs); + test_and_clear_bit(FLG_LOCK_ATOMIC, &cs->HW_Flags); + } else + debugl1(cs, "send_data %d blocked", bcs->channel); +} + +/***************************************************************/ +/* activate/deactivate hardware for selected channels and mode */ +/***************************************************************/ +static void +mode_hfcsx(struct BCState *bcs, int mode, int bc) +{ + struct IsdnCardState *cs = bcs->cs; + int fifo2; + + if (cs->debug & L1_DEB_HSCX) + debugl1(cs, "HFCSX bchannel mode %d bchan %d/%d", + mode, bc, bcs->channel); + bcs->mode = mode; + bcs->channel = bc; + fifo2 = bc; + if (cs->chanlimit > 1) { + cs->hw.hfcsx.bswapped = 0; /* B1 and B2 normal mode */ + cs->hw.hfcsx.sctrl_e &= ~0x80; + } else { + if (bc) { + if (mode != L1_MODE_NULL) { + cs->hw.hfcsx.bswapped = 1; /* B1 and B2 exchanged */ + cs->hw.hfcsx.sctrl_e |= 0x80; + } else { + cs->hw.hfcsx.bswapped = 0; /* B1 and B2 normal mode */ + cs->hw.hfcsx.sctrl_e &= ~0x80; + } + fifo2 = 0; + } else { + cs->hw.hfcsx.bswapped = 0; /* B1 and B2 normal mode */ + cs->hw.hfcsx.sctrl_e &= ~0x80; + } + } + switch (mode) { + case (L1_MODE_NULL): + if (bc) { + cs->hw.hfcsx.sctrl &= ~SCTRL_B2_ENA; + cs->hw.hfcsx.sctrl_r &= ~SCTRL_B2_ENA; + } else { + cs->hw.hfcsx.sctrl &= ~SCTRL_B1_ENA; + cs->hw.hfcsx.sctrl_r &= ~SCTRL_B1_ENA; + } + if (fifo2) { + cs->hw.hfcsx.int_m1 &= ~(HFCSX_INTS_B2TRANS + HFCSX_INTS_B2REC); + } else { + cs->hw.hfcsx.int_m1 &= ~(HFCSX_INTS_B1TRANS + HFCSX_INTS_B1REC); + } + break; + case (L1_MODE_TRANS): + if (bc) { + cs->hw.hfcsx.sctrl |= SCTRL_B2_ENA; + cs->hw.hfcsx.sctrl_r |= SCTRL_B2_ENA; + } else { + cs->hw.hfcsx.sctrl |= SCTRL_B1_ENA; + cs->hw.hfcsx.sctrl_r |= SCTRL_B1_ENA; + } + if (fifo2) { + cs->hw.hfcsx.int_m1 |= (HFCSX_INTS_B2TRANS + HFCSX_INTS_B2REC); + cs->hw.hfcsx.ctmt |= 2; + cs->hw.hfcsx.conn &= ~0x18; + } else { + cs->hw.hfcsx.int_m1 |= (HFCSX_INTS_B1TRANS + HFCSX_INTS_B1REC); + cs->hw.hfcsx.ctmt |= 1; + cs->hw.hfcsx.conn &= ~0x03; + } + break; + case (L1_MODE_HDLC): + if (bc) { + cs->hw.hfcsx.sctrl |= SCTRL_B2_ENA; + cs->hw.hfcsx.sctrl_r |= SCTRL_B2_ENA; + } else { + cs->hw.hfcsx.sctrl |= SCTRL_B1_ENA; + cs->hw.hfcsx.sctrl_r |= SCTRL_B1_ENA; + } + if (fifo2) { + cs->hw.hfcsx.int_m1 |= (HFCSX_INTS_B2TRANS + HFCSX_INTS_B2REC); + cs->hw.hfcsx.ctmt &= ~2; + cs->hw.hfcsx.conn &= ~0x18; + } else { + cs->hw.hfcsx.int_m1 |= (HFCSX_INTS_B1TRANS + HFCSX_INTS_B1REC); + cs->hw.hfcsx.ctmt &= ~1; + cs->hw.hfcsx.conn &= ~0x03; + } + break; + case (L1_MODE_EXTRN): + if (bc) { + cs->hw.hfcsx.conn |= 0x10; + cs->hw.hfcsx.sctrl |= SCTRL_B2_ENA; + cs->hw.hfcsx.sctrl_r |= SCTRL_B2_ENA; + cs->hw.hfcsx.int_m1 &= ~(HFCSX_INTS_B2TRANS + HFCSX_INTS_B2REC); + } else { + cs->hw.hfcsx.conn |= 0x02; + cs->hw.hfcsx.sctrl |= SCTRL_B1_ENA; + cs->hw.hfcsx.sctrl_r |= SCTRL_B1_ENA; + cs->hw.hfcsx.int_m1 &= ~(HFCSX_INTS_B1TRANS + HFCSX_INTS_B1REC); + } + break; + } + Write_hfc(cs, HFCSX_SCTRL_E, cs->hw.hfcsx.sctrl_e); + Write_hfc(cs, HFCSX_INT_M1, cs->hw.hfcsx.int_m1); + Write_hfc(cs, HFCSX_SCTRL, cs->hw.hfcsx.sctrl); + Write_hfc(cs, HFCSX_SCTRL_R, cs->hw.hfcsx.sctrl_r); + Write_hfc(cs, HFCSX_CTMT, cs->hw.hfcsx.ctmt); + Write_hfc(cs, HFCSX_CONNECT, cs->hw.hfcsx.conn); + if (mode != L1_MODE_EXTRN) { + reset_fifo(cs, fifo2 ? HFCSX_SEL_B2_RX : HFCSX_SEL_B1_RX); + reset_fifo(cs, fifo2 ? HFCSX_SEL_B2_TX : HFCSX_SEL_B1_TX); + } +} + +/******************************/ +/* Layer2 -> Layer 1 Transfer */ +/******************************/ +static void +hfcsx_l2l1(struct PStack *st, int pr, void *arg) +{ + struct BCState *bcs = st->l1.bcs; + struct sk_buff *skb = arg; + u_long flags; + + switch (pr) { + case (PH_DATA | REQUEST): + spin_lock_irqsave(&bcs->cs->lock, flags); + if (bcs->tx_skb) { + skb_queue_tail(&bcs->squeue, skb); + } else { + bcs->tx_skb = skb; +// test_and_set_bit(BC_FLG_BUSY, &bcs->Flag); + bcs->cs->BC_Send_Data(bcs); + } + spin_unlock_irqrestore(&bcs->cs->lock, flags); + break; + case (PH_PULL | INDICATION): + spin_lock_irqsave(&bcs->cs->lock, flags); + if (bcs->tx_skb) { + printk(KERN_WARNING "%s: this shouldn't happen\n", + __func__); + } else { +// test_and_set_bit(BC_FLG_BUSY, &bcs->Flag); + bcs->tx_skb = skb; + bcs->cs->BC_Send_Data(bcs); + } + spin_unlock_irqrestore(&bcs->cs->lock, flags); + break; + case (PH_PULL | REQUEST): + if (!bcs->tx_skb) { + test_and_clear_bit(FLG_L1_PULL_REQ, &st->l1.Flags); + st->l1.l1l2(st, PH_PULL | CONFIRM, NULL); + } else + test_and_set_bit(FLG_L1_PULL_REQ, &st->l1.Flags); + break; + case (PH_ACTIVATE | REQUEST): + spin_lock_irqsave(&bcs->cs->lock, flags); + test_and_set_bit(BC_FLG_ACTIV, &bcs->Flag); + mode_hfcsx(bcs, st->l1.mode, st->l1.bc); + spin_unlock_irqrestore(&bcs->cs->lock, flags); + l1_msg_b(st, pr, arg); + break; + case (PH_DEACTIVATE | REQUEST): + l1_msg_b(st, pr, arg); + break; + case (PH_DEACTIVATE | CONFIRM): + spin_lock_irqsave(&bcs->cs->lock, flags); + test_and_clear_bit(BC_FLG_ACTIV, &bcs->Flag); + test_and_clear_bit(BC_FLG_BUSY, &bcs->Flag); + mode_hfcsx(bcs, 0, st->l1.bc); + spin_unlock_irqrestore(&bcs->cs->lock, flags); + st->l1.l1l2(st, PH_DEACTIVATE | CONFIRM, NULL); + break; + } +} + +/******************************************/ +/* deactivate B-channel access and queues */ +/******************************************/ +static void +close_hfcsx(struct BCState *bcs) +{ + mode_hfcsx(bcs, 0, bcs->channel); + if (test_and_clear_bit(BC_FLG_INIT, &bcs->Flag)) { + skb_queue_purge(&bcs->rqueue); + skb_queue_purge(&bcs->squeue); + if (bcs->tx_skb) { + dev_kfree_skb_any(bcs->tx_skb); + bcs->tx_skb = NULL; + test_and_clear_bit(BC_FLG_BUSY, &bcs->Flag); + } + } +} + +/*************************************/ +/* init B-channel queues and control */ +/*************************************/ +static int +open_hfcsxstate(struct IsdnCardState *cs, struct BCState *bcs) +{ + if (!test_and_set_bit(BC_FLG_INIT, &bcs->Flag)) { + skb_queue_head_init(&bcs->rqueue); + skb_queue_head_init(&bcs->squeue); + } + bcs->tx_skb = NULL; + test_and_clear_bit(BC_FLG_BUSY, &bcs->Flag); + bcs->event = 0; + bcs->tx_cnt = 0; + return (0); +} + +/*********************************/ +/* inits the stack for B-channel */ +/*********************************/ +static int +setstack_2b(struct PStack *st, struct BCState *bcs) +{ + bcs->channel = st->l1.bc; + if (open_hfcsxstate(st->l1.hardware, bcs)) + return (-1); + st->l1.bcs = bcs; + st->l2.l2l1 = hfcsx_l2l1; + setstack_manager(st); + bcs->st = st; + setstack_l1_B(st); + return (0); +} + +/***************************/ +/* handle L1 state changes */ +/***************************/ +static void +hfcsx_bh(struct work_struct *work) +{ + struct IsdnCardState *cs = + container_of(work, struct IsdnCardState, tqueue); + u_long flags; + + if (test_and_clear_bit(D_L1STATECHANGE, &cs->event)) { + if (!cs->hw.hfcsx.nt_mode) + switch (cs->dc.hfcsx.ph_state) { + case (0): + l1_msg(cs, HW_RESET | INDICATION, NULL); + break; + case (3): + l1_msg(cs, HW_DEACTIVATE | INDICATION, NULL); + break; + case (8): + l1_msg(cs, HW_RSYNC | INDICATION, NULL); + break; + case (6): + l1_msg(cs, HW_INFO2 | INDICATION, NULL); + break; + case (7): + l1_msg(cs, HW_INFO4_P8 | INDICATION, NULL); + break; + default: + break; + } else { + switch (cs->dc.hfcsx.ph_state) { + case (2): + spin_lock_irqsave(&cs->lock, flags); + if (cs->hw.hfcsx.nt_timer < 0) { + cs->hw.hfcsx.nt_timer = 0; + cs->hw.hfcsx.int_m1 &= ~HFCSX_INTS_TIMER; + Write_hfc(cs, HFCSX_INT_M1, cs->hw.hfcsx.int_m1); + /* Clear already pending ints */ + if (Read_hfc(cs, HFCSX_INT_S1)); + + Write_hfc(cs, HFCSX_STATES, 4 | HFCSX_LOAD_STATE); + udelay(10); + Write_hfc(cs, HFCSX_STATES, 4); + cs->dc.hfcsx.ph_state = 4; + } else { + cs->hw.hfcsx.int_m1 |= HFCSX_INTS_TIMER; + Write_hfc(cs, HFCSX_INT_M1, cs->hw.hfcsx.int_m1); + cs->hw.hfcsx.ctmt &= ~HFCSX_AUTO_TIMER; + cs->hw.hfcsx.ctmt |= HFCSX_TIM3_125; + Write_hfc(cs, HFCSX_CTMT, cs->hw.hfcsx.ctmt | HFCSX_CLTIMER); + Write_hfc(cs, HFCSX_CTMT, cs->hw.hfcsx.ctmt | HFCSX_CLTIMER); + cs->hw.hfcsx.nt_timer = NT_T1_COUNT; + Write_hfc(cs, HFCSX_STATES, 2 | HFCSX_NT_G2_G3); /* allow G2 -> G3 transition */ + } + spin_unlock_irqrestore(&cs->lock, flags); + break; + case (1): + case (3): + case (4): + spin_lock_irqsave(&cs->lock, flags); + cs->hw.hfcsx.nt_timer = 0; + cs->hw.hfcsx.int_m1 &= ~HFCSX_INTS_TIMER; + Write_hfc(cs, HFCSX_INT_M1, cs->hw.hfcsx.int_m1); + spin_unlock_irqrestore(&cs->lock, flags); + break; + default: + break; + } + } + } + if (test_and_clear_bit(D_RCVBUFREADY, &cs->event)) + DChannel_proc_rcv(cs); + if (test_and_clear_bit(D_XMTBUFREADY, &cs->event)) + DChannel_proc_xmt(cs); +} + + +/********************************/ +/* called for card init message */ +/********************************/ +static void inithfcsx(struct IsdnCardState *cs) +{ + cs->setstack_d = setstack_hfcsx; + cs->BC_Send_Data = &hfcsx_send_data; + cs->bcs[0].BC_SetStack = setstack_2b; + cs->bcs[1].BC_SetStack = setstack_2b; + cs->bcs[0].BC_Close = close_hfcsx; + cs->bcs[1].BC_Close = close_hfcsx; + mode_hfcsx(cs->bcs, 0, 0); + mode_hfcsx(cs->bcs + 1, 0, 1); +} + + + +/*******************************************/ +/* handle card messages from control layer */ +/*******************************************/ +static int +hfcsx_card_msg(struct IsdnCardState *cs, int mt, void *arg) +{ + u_long flags; + + if (cs->debug & L1_DEB_ISAC) + debugl1(cs, "HFCSX: card_msg %x", mt); + switch (mt) { + case CARD_RESET: + spin_lock_irqsave(&cs->lock, flags); + reset_hfcsx(cs); + spin_unlock_irqrestore(&cs->lock, flags); + return (0); + case CARD_RELEASE: + release_io_hfcsx(cs); + return (0); + case CARD_INIT: + spin_lock_irqsave(&cs->lock, flags); + inithfcsx(cs); + spin_unlock_irqrestore(&cs->lock, flags); + msleep(80); /* Timeout 80ms */ + /* now switch timer interrupt off */ + spin_lock_irqsave(&cs->lock, flags); + cs->hw.hfcsx.int_m1 &= ~HFCSX_INTS_TIMER; + Write_hfc(cs, HFCSX_INT_M1, cs->hw.hfcsx.int_m1); + /* reinit mode reg */ + Write_hfc(cs, HFCSX_MST_MODE, cs->hw.hfcsx.mst_m); + spin_unlock_irqrestore(&cs->lock, flags); + return (0); + case CARD_TEST: + return (0); + } + return (0); +} + +#ifdef __ISAPNP__ +static struct isapnp_device_id hfc_ids[] = { + { ISAPNP_VENDOR('T', 'A', 'G'), ISAPNP_FUNCTION(0x2620), + ISAPNP_VENDOR('T', 'A', 'G'), ISAPNP_FUNCTION(0x2620), + (unsigned long) "Teles 16.3c2" }, + { 0, } +}; + +static struct isapnp_device_id *ipid = &hfc_ids[0]; +static struct pnp_card *pnp_c = NULL; +#endif + +int setup_hfcsx(struct IsdnCard *card) +{ + struct IsdnCardState *cs = card->cs; + char tmp[64]; + + strcpy(tmp, hfcsx_revision); + printk(KERN_INFO "HiSax: HFC-SX driver Rev. %s\n", HiSax_getrev(tmp)); +#ifdef __ISAPNP__ + if (!card->para[1] && isapnp_present()) { + struct pnp_dev *pnp_d; + while (ipid->card_vendor) { + if ((pnp_c = pnp_find_card(ipid->card_vendor, + ipid->card_device, pnp_c))) { + pnp_d = NULL; + if ((pnp_d = pnp_find_dev(pnp_c, + ipid->vendor, ipid->function, pnp_d))) { + int err; + + printk(KERN_INFO "HiSax: %s detected\n", + (char *)ipid->driver_data); + pnp_disable_dev(pnp_d); + err = pnp_activate_dev(pnp_d); + if (err < 0) { + printk(KERN_WARNING "%s: pnp_activate_dev ret(%d)\n", + __func__, err); + return (0); + } + card->para[1] = pnp_port_start(pnp_d, 0); + card->para[0] = pnp_irq(pnp_d, 0); + if (card->para[0] == -1 || !card->para[1]) { + printk(KERN_ERR "HFC PnP:some resources are missing %ld/%lx\n", + card->para[0], card->para[1]); + pnp_disable_dev(pnp_d); + return (0); + } + break; + } else { + printk(KERN_ERR "HFC PnP: PnP error card found, no device\n"); + } + } + ipid++; + pnp_c = NULL; + } + if (!ipid->card_vendor) { + printk(KERN_INFO "HFC PnP: no ISAPnP card found\n"); + return (0); + } + } +#endif + cs->hw.hfcsx.base = card->para[1] & 0xfffe; + cs->irq = card->para[0]; + cs->hw.hfcsx.int_s1 = 0; + cs->dc.hfcsx.ph_state = 0; + cs->hw.hfcsx.fifo = 255; + if ((cs->typ == ISDN_CTYPE_HFC_SX) || + (cs->typ == ISDN_CTYPE_HFC_SP_PCMCIA)) { + if ((!cs->hw.hfcsx.base) || !request_region(cs->hw.hfcsx.base, 2, "HFCSX isdn")) { + printk(KERN_WARNING + "HiSax: HFC-SX io-base %#lx already in use\n", + cs->hw.hfcsx.base); + return (0); + } + byteout(cs->hw.hfcsx.base, cs->hw.hfcsx.base & 0xFF); + byteout(cs->hw.hfcsx.base + 1, + ((cs->hw.hfcsx.base >> 8) & 3) | 0x54); + udelay(10); + cs->hw.hfcsx.chip = Read_hfc(cs, HFCSX_CHIP_ID); + switch (cs->hw.hfcsx.chip >> 4) { + case 1: + tmp[0] = '+'; + break; + case 9: + tmp[0] = 'P'; + break; + default: + printk(KERN_WARNING + "HFC-SX: invalid chip id 0x%x\n", + cs->hw.hfcsx.chip >> 4); + release_region(cs->hw.hfcsx.base, 2); + return (0); + } + if (!ccd_sp_irqtab[cs->irq & 0xF]) { + printk(KERN_WARNING + "HFC_SX: invalid irq %d specified\n", cs->irq & 0xF); + release_region(cs->hw.hfcsx.base, 2); + return (0); + } + if (!(cs->hw.hfcsx.extra = + kmalloc(sizeof(struct hfcsx_extra), GFP_ATOMIC))) { + release_region(cs->hw.hfcsx.base, 2); + printk(KERN_WARNING "HFC-SX: unable to allocate memory\n"); + return (0); + } + printk(KERN_INFO "HFC-S%c chip detected at base 0x%x IRQ %d HZ %d\n", + tmp[0], (u_int) cs->hw.hfcsx.base, cs->irq, HZ); + cs->hw.hfcsx.int_m2 = 0; /* disable alle interrupts */ + cs->hw.hfcsx.int_m1 = 0; + Write_hfc(cs, HFCSX_INT_M1, cs->hw.hfcsx.int_m1); + Write_hfc(cs, HFCSX_INT_M2, cs->hw.hfcsx.int_m2); + } else + return (0); /* no valid card type */ + + timer_setup(&cs->dbusytimer, hfcsx_dbusy_timer, 0); + INIT_WORK(&cs->tqueue, hfcsx_bh); + cs->readisac = NULL; + cs->writeisac = NULL; + cs->readisacfifo = NULL; + cs->writeisacfifo = NULL; + cs->BC_Read_Reg = NULL; + cs->BC_Write_Reg = NULL; + cs->irq_func = &hfcsx_interrupt; + + cs->hw.hfcsx.b_fifo_size = 0; /* fifo size still unknown */ + cs->hw.hfcsx.cirm = ccd_sp_irqtab[cs->irq & 0xF]; /* RAM not evaluated */ + timer_setup(&cs->hw.hfcsx.timer, hfcsx_Timer, 0); + + reset_hfcsx(cs); + cs->cardmsg = &hfcsx_card_msg; + cs->auxcmd = &hfcsx_auxcmd; + return (1); +} diff --git a/drivers/isdn/hisax/hfc_sx.h b/drivers/isdn/hisax/hfc_sx.h new file mode 100644 index 000000000..eee85dbb0 --- /dev/null +++ b/drivers/isdn/hisax/hfc_sx.h @@ -0,0 +1,196 @@ +/* $Id: hfc_sx.h,v 1.2.6.1 2001/09/23 22:24:48 kai Exp $ + * + * specific defines for CCD's HFC 2BDS0 S+,SP chips + * + * Author Werner Cornelius + * based on existing driver for CCD HFC PCI cards + * Copyright by Werner Cornelius <werner@isdn4linux.de> + * + * This software may be used and distributed according to the terms + * of the GNU General Public License, incorporated herein by reference. + * + */ + +/*********************************************/ +/* thresholds for transparent B-channel mode */ +/* change mask and threshold simultaneously */ +/*********************************************/ +#define HFCSX_BTRANS_THRESHOLD 128 +#define HFCSX_BTRANS_THRESMASK 0x00 + +/* GCI/IOM bus monitor registers */ + +#define HFCSX_C_I 0x02 +#define HFCSX_TRxR 0x03 +#define HFCSX_MON1_D 0x0A +#define HFCSX_MON2_D 0x0B + + +/* GCI/IOM bus timeslot registers */ + +#define HFCSX_B1_SSL 0x20 +#define HFCSX_B2_SSL 0x21 +#define HFCSX_AUX1_SSL 0x22 +#define HFCSX_AUX2_SSL 0x23 +#define HFCSX_B1_RSL 0x24 +#define HFCSX_B2_RSL 0x25 +#define HFCSX_AUX1_RSL 0x26 +#define HFCSX_AUX2_RSL 0x27 + +/* GCI/IOM bus data registers */ + +#define HFCSX_B1_D 0x28 +#define HFCSX_B2_D 0x29 +#define HFCSX_AUX1_D 0x2A +#define HFCSX_AUX2_D 0x2B + +/* GCI/IOM bus configuration registers */ + +#define HFCSX_MST_EMOD 0x2D +#define HFCSX_MST_MODE 0x2E +#define HFCSX_CONNECT 0x2F + + +/* Interrupt and status registers */ + +#define HFCSX_TRM 0x12 +#define HFCSX_B_MODE 0x13 +#define HFCSX_CHIP_ID 0x16 +#define HFCSX_CIRM 0x18 +#define HFCSX_CTMT 0x19 +#define HFCSX_INT_M1 0x1A +#define HFCSX_INT_M2 0x1B +#define HFCSX_INT_S1 0x1E +#define HFCSX_INT_S2 0x1F +#define HFCSX_STATUS 0x1C + +/* S/T section registers */ + +#define HFCSX_STATES 0x30 +#define HFCSX_SCTRL 0x31 +#define HFCSX_SCTRL_E 0x32 +#define HFCSX_SCTRL_R 0x33 +#define HFCSX_SQ 0x34 +#define HFCSX_CLKDEL 0x37 +#define HFCSX_B1_REC 0x3C +#define HFCSX_B1_SEND 0x3C +#define HFCSX_B2_REC 0x3D +#define HFCSX_B2_SEND 0x3D +#define HFCSX_D_REC 0x3E +#define HFCSX_D_SEND 0x3E +#define HFCSX_E_REC 0x3F + +/****************/ +/* FIFO section */ +/****************/ +#define HFCSX_FIF_SEL 0x10 +#define HFCSX_FIF_Z1L 0x80 +#define HFCSX_FIF_Z1H 0x84 +#define HFCSX_FIF_Z2L 0x88 +#define HFCSX_FIF_Z2H 0x8C +#define HFCSX_FIF_INCF1 0xA8 +#define HFCSX_FIF_DWR 0xAC +#define HFCSX_FIF_F1 0xB0 +#define HFCSX_FIF_F2 0xB4 +#define HFCSX_FIF_INCF2 0xB8 +#define HFCSX_FIF_DRD 0xBC + +/* bits in status register (READ) */ +#define HFCSX_SX_PROC 0x02 +#define HFCSX_NBUSY 0x04 +#define HFCSX_TIMER_ELAP 0x10 +#define HFCSX_STATINT 0x20 +#define HFCSX_FRAMEINT 0x40 +#define HFCSX_ANYINT 0x80 + +/* bits in CTMT (Write) */ +#define HFCSX_CLTIMER 0x80 +#define HFCSX_TIM3_125 0x04 +#define HFCSX_TIM25 0x10 +#define HFCSX_TIM50 0x14 +#define HFCSX_TIM400 0x18 +#define HFCSX_TIM800 0x1C +#define HFCSX_AUTO_TIMER 0x20 +#define HFCSX_TRANSB2 0x02 +#define HFCSX_TRANSB1 0x01 + +/* bits in CIRM (Write) */ +#define HFCSX_IRQ_SELMSK 0x07 +#define HFCSX_IRQ_SELDIS 0x00 +#define HFCSX_RESET 0x08 +#define HFCSX_FIFO_RESET 0x80 + + +/* bits in INT_M1 and INT_S1 */ +#define HFCSX_INTS_B1TRANS 0x01 +#define HFCSX_INTS_B2TRANS 0x02 +#define HFCSX_INTS_DTRANS 0x04 +#define HFCSX_INTS_B1REC 0x08 +#define HFCSX_INTS_B2REC 0x10 +#define HFCSX_INTS_DREC 0x20 +#define HFCSX_INTS_L1STATE 0x40 +#define HFCSX_INTS_TIMER 0x80 + +/* bits in INT_M2 */ +#define HFCSX_PROC_TRANS 0x01 +#define HFCSX_GCI_I_CHG 0x02 +#define HFCSX_GCI_MON_REC 0x04 +#define HFCSX_IRQ_ENABLE 0x08 + +/* bits in STATES */ +#define HFCSX_STATE_MSK 0x0F +#define HFCSX_LOAD_STATE 0x10 +#define HFCSX_ACTIVATE 0x20 +#define HFCSX_DO_ACTION 0x40 +#define HFCSX_NT_G2_G3 0x80 + +/* bits in HFCD_MST_MODE */ +#define HFCSX_MASTER 0x01 +#define HFCSX_SLAVE 0x00 +/* remaining bits are for codecs control */ + +/* bits in HFCD_SCTRL */ +#define SCTRL_B1_ENA 0x01 +#define SCTRL_B2_ENA 0x02 +#define SCTRL_MODE_TE 0x00 +#define SCTRL_MODE_NT 0x04 +#define SCTRL_LOW_PRIO 0x08 +#define SCTRL_SQ_ENA 0x10 +#define SCTRL_TEST 0x20 +#define SCTRL_NONE_CAP 0x40 +#define SCTRL_PWR_DOWN 0x80 + +/* bits in SCTRL_E */ +#define HFCSX_AUTO_AWAKE 0x01 +#define HFCSX_DBIT_1 0x04 +#define HFCSX_IGNORE_COL 0x08 +#define HFCSX_CHG_B1_B2 0x80 + +/**********************************/ +/* definitions for FIFO selection */ +/**********************************/ +#define HFCSX_SEL_D_RX 5 +#define HFCSX_SEL_D_TX 4 +#define HFCSX_SEL_B1_RX 1 +#define HFCSX_SEL_B1_TX 0 +#define HFCSX_SEL_B2_RX 3 +#define HFCSX_SEL_B2_TX 2 + +#define MAX_D_FRAMES 15 +#define MAX_B_FRAMES 31 +#define B_SUB_VAL_32K 0x0200 +#define B_FIFO_SIZE_32K (0x2000 - B_SUB_VAL_32K) +#define B_SUB_VAL_8K 0x1A00 +#define B_FIFO_SIZE_8K (0x2000 - B_SUB_VAL_8K) +#define D_FIFO_SIZE 512 +#define D_FREG_MASK 0xF + +/************************************************************/ +/* structure holding additional dynamic data -> send marker */ +/************************************************************/ +struct hfcsx_extra { + unsigned short marker[2 * (MAX_B_FRAMES + 1) + (MAX_D_FRAMES + 1)]; +}; + +extern void main_irq_hfcsx(struct BCState *bcs); +extern void releasehfcsx(struct IsdnCardState *cs); diff --git a/drivers/isdn/hisax/hfc_usb.c b/drivers/isdn/hisax/hfc_usb.c new file mode 100644 index 000000000..1d4cd01d4 --- /dev/null +++ b/drivers/isdn/hisax/hfc_usb.c @@ -0,0 +1,1608 @@ +/* + * hfc_usb.c + * + * $Id: hfc_usb.c,v 2.3.2.24 2007/10/14 08:40:29 mbachem Exp $ + * + * modular HiSax ISDN driver for Colognechip HFC-S USB chip + * + * Authors : Peter Sprenger (sprenger@moving-bytes.de) + * Martin Bachem (m.bachem@gmx.de, info@colognechip.com) + * + * based on the first hfc_usb driver of + * Werner Cornelius (werner@isdn-development.de) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * See Version Histroy at the bottom of this file + * + */ + +#include <linux/types.h> +#include <linux/stddef.h> +#include <linux/timer.h> +#include <linux/init.h> +#include <linux/module.h> +#include <linux/kernel_stat.h> +#include <linux/usb.h> +#include <linux/kernel.h> +#include <linux/sched.h> +#include <linux/moduleparam.h> +#include <linux/slab.h> +#include "hisax.h" +#include "hisax_if.h" +#include "hfc_usb.h" + +static const char *hfcusb_revision = + "$Revision: 2.3.2.24 $ $Date: 2007/10/14 08:40:29 $ "; + +/* Hisax debug support + * debug flags defined in hfc_usb.h as HFCUSB_DBG_[*] + */ +#define __debug_variable hfc_debug +#include "hisax_debug.h" +static u_int debug; +module_param(debug, uint, 0); +static int hfc_debug; + + +/* private vendor specific data */ +typedef struct { + __u8 led_scheme; // led display scheme + signed short led_bits[8]; // array of 8 possible LED bitmask settings + char *vend_name; // device name +} hfcsusb_vdata; + +/* VID/PID device list */ +static const struct usb_device_id hfcusb_idtab[] = { + { + USB_DEVICE(0x0959, 0x2bd0), + .driver_info = (unsigned long) &((hfcsusb_vdata) + {LED_OFF, {4, 0, 2, 1}, + "ISDN USB TA (Cologne Chip HFC-S USB based)"}), + }, + { + USB_DEVICE(0x0675, 0x1688), + .driver_info = (unsigned long) &((hfcsusb_vdata) + {LED_SCHEME1, {1, 2, 0, 0}, + "DrayTek miniVigor 128 USB ISDN TA"}), + }, + { + USB_DEVICE(0x07b0, 0x0007), + .driver_info = (unsigned long) &((hfcsusb_vdata) + {LED_SCHEME1, {0x80, -64, -32, -16}, + "Billion tiny USB ISDN TA 128"}), + }, + { + USB_DEVICE(0x0742, 0x2008), + .driver_info = (unsigned long) &((hfcsusb_vdata) + {LED_SCHEME1, {4, 0, 2, 1}, + "Stollmann USB TA"}), + }, + { + USB_DEVICE(0x0742, 0x2009), + .driver_info = (unsigned long) &((hfcsusb_vdata) + {LED_SCHEME1, {4, 0, 2, 1}, + "Aceex USB ISDN TA"}), + }, + { + USB_DEVICE(0x0742, 0x200A), + .driver_info = (unsigned long) &((hfcsusb_vdata) + {LED_SCHEME1, {4, 0, 2, 1}, + "OEM USB ISDN TA"}), + }, + { + USB_DEVICE(0x08e3, 0x0301), + .driver_info = (unsigned long) &((hfcsusb_vdata) + {LED_SCHEME1, {2, 0, 1, 4}, + "Olitec USB RNIS"}), + }, + { + USB_DEVICE(0x07fa, 0x0846), + .driver_info = (unsigned long) &((hfcsusb_vdata) + {LED_SCHEME1, {0x80, -64, -32, -16}, + "Bewan Modem RNIS USB"}), + }, + { + USB_DEVICE(0x07fa, 0x0847), + .driver_info = (unsigned long) &((hfcsusb_vdata) + {LED_SCHEME1, {0x80, -64, -32, -16}, + "Djinn Numeris USB"}), + }, + { + USB_DEVICE(0x07b0, 0x0006), + .driver_info = (unsigned long) &((hfcsusb_vdata) + {LED_SCHEME1, {0x80, -64, -32, -16}, + "Twister ISDN TA"}), + }, + { + USB_DEVICE(0x071d, 0x1005), + .driver_info = (unsigned long) &((hfcsusb_vdata) + {LED_SCHEME1, {0x02, 0, 0x01, 0x04}, + "Eicon DIVA USB 4.0"}), + }, + { } +}; + +/* structure defining input+output fifos (interrupt/bulk mode) */ +struct usb_fifo; /* forward definition */ +typedef struct iso_urb_struct { + struct urb *purb; + __u8 buffer[ISO_BUFFER_SIZE]; /* buffer incoming/outgoing data */ + struct usb_fifo *owner_fifo; /* pointer to owner fifo */ +} iso_urb_struct; + +struct hfcusb_data; /* forward definition */ + +typedef struct usb_fifo { + int fifonum; /* fifo index attached to this structure */ + int active; /* fifo is currently active */ + struct hfcusb_data *hfc; /* pointer to main structure */ + int pipe; /* address of endpoint */ + __u8 usb_packet_maxlen; /* maximum length for usb transfer */ + unsigned int max_size; /* maximum size of receive/send packet */ + __u8 intervall; /* interrupt interval */ + struct sk_buff *skbuff; /* actual used buffer */ + struct urb *urb; /* transfer structure for usb routines */ + __u8 buffer[128]; /* buffer incoming/outgoing data */ + int bit_line; /* how much bits are in the fifo? */ + + volatile __u8 usb_transfer_mode; /* switched between ISO and INT */ + iso_urb_struct iso[2]; /* need two urbs to have one always for pending */ + struct hisax_if *hif; /* hisax interface */ + int delete_flg; /* only delete skbuff once */ + int last_urblen; /* remember length of last packet */ +} usb_fifo; + +/* structure holding all data for one device */ +typedef struct hfcusb_data { + /* HiSax Interface for loadable Layer1 drivers */ + struct hisax_d_if d_if; /* see hisax_if.h */ + struct hisax_b_if b_if[2]; /* see hisax_if.h */ + int protocol; + + struct usb_device *dev; /* our device */ + int if_used; /* used interface number */ + int alt_used; /* used alternate config */ + int ctrl_paksize; /* control pipe packet size */ + int ctrl_in_pipe, /* handles for control pipe */ + ctrl_out_pipe; + int cfg_used; /* configuration index used */ + int vend_idx; /* vendor found */ + int b_mode[2]; /* B-channel mode */ + int l1_activated; /* layer 1 activated */ + int disc_flag; /* TRUE if device was disonnected to avoid some USB actions */ + int packet_size, iso_packet_size; + + /* control pipe background handling */ + ctrl_buft ctrl_buff[HFC_CTRL_BUFSIZE]; /* buffer holding queued data */ + volatile int ctrl_in_idx, ctrl_out_idx, ctrl_cnt; /* input/output pointer + count */ + struct urb *ctrl_urb; /* transfer structure for control channel */ + + struct usb_ctrlrequest ctrl_write; /* buffer for control write request */ + struct usb_ctrlrequest ctrl_read; /* same for read request */ + + __u8 old_led_state, led_state; + + volatile __u8 threshold_mask; /* threshold actually reported */ + volatile __u8 bch_enables; /* or mask for sctrl_r and sctrl register values */ + + usb_fifo fifos[HFCUSB_NUM_FIFOS]; /* structure holding all fifo data */ + + volatile __u8 l1_state; /* actual l1 state */ + struct timer_list t3_timer; /* timer 3 for activation/deactivation */ + struct timer_list t4_timer; /* timer 4 for activation/deactivation */ +} hfcusb_data; + + +static void collect_rx_frame(usb_fifo *fifo, __u8 *data, int len, + int finish); + +static inline const char * +symbolic(struct hfcusb_symbolic_list list[], const int num) +{ + int i; + for (i = 0; list[i].name != NULL; i++) + if (list[i].num == num) + return (list[i].name); + return "<unknown ERROR>"; +} + +static void +ctrl_start_transfer(hfcusb_data *hfc) +{ + if (hfc->ctrl_cnt) { + hfc->ctrl_urb->pipe = hfc->ctrl_out_pipe; + hfc->ctrl_urb->setup_packet = (u_char *)&hfc->ctrl_write; + hfc->ctrl_urb->transfer_buffer = NULL; + hfc->ctrl_urb->transfer_buffer_length = 0; + hfc->ctrl_write.wIndex = + cpu_to_le16(hfc->ctrl_buff[hfc->ctrl_out_idx].hfc_reg); + hfc->ctrl_write.wValue = + cpu_to_le16(hfc->ctrl_buff[hfc->ctrl_out_idx].reg_val); + + usb_submit_urb(hfc->ctrl_urb, GFP_ATOMIC); /* start transfer */ + } +} /* ctrl_start_transfer */ + +static int +queue_control_request(hfcusb_data *hfc, __u8 reg, __u8 val, int action) +{ + ctrl_buft *buf; + + if (hfc->ctrl_cnt >= HFC_CTRL_BUFSIZE) + return (1); /* no space left */ + buf = &hfc->ctrl_buff[hfc->ctrl_in_idx]; /* pointer to new index */ + buf->hfc_reg = reg; + buf->reg_val = val; + buf->action = action; + if (++hfc->ctrl_in_idx >= HFC_CTRL_BUFSIZE) + hfc->ctrl_in_idx = 0; /* pointer wrap */ + if (++hfc->ctrl_cnt == 1) + ctrl_start_transfer(hfc); + return (0); +} + +static void +ctrl_complete(struct urb *urb) +{ + hfcusb_data *hfc = (hfcusb_data *) urb->context; + + urb->dev = hfc->dev; + if (hfc->ctrl_cnt) { + hfc->ctrl_cnt--; /* decrement actual count */ + if (++hfc->ctrl_out_idx >= HFC_CTRL_BUFSIZE) + hfc->ctrl_out_idx = 0; /* pointer wrap */ + + ctrl_start_transfer(hfc); /* start next transfer */ + } +} + +/* write led data to auxport & invert if necessary */ +static void +write_led(hfcusb_data *hfc, __u8 led_state) +{ + if (led_state != hfc->old_led_state) { + hfc->old_led_state = led_state; + queue_control_request(hfc, HFCUSB_P_DATA, led_state, 1); + } +} + +static void +set_led_bit(hfcusb_data *hfc, signed short led_bits, int on) +{ + if (on) { + if (led_bits < 0) + hfc->led_state &= ~abs(led_bits); + else + hfc->led_state |= led_bits; + } else { + if (led_bits < 0) + hfc->led_state |= abs(led_bits); + else + hfc->led_state &= ~led_bits; + } +} + +/* handle LED requests */ +static void +handle_led(hfcusb_data *hfc, int event) +{ + hfcsusb_vdata *driver_info = + (hfcsusb_vdata *) hfcusb_idtab[hfc->vend_idx].driver_info; + + /* if no scheme -> no LED action */ + if (driver_info->led_scheme == LED_OFF) + return; + + switch (event) { + case LED_POWER_ON: + set_led_bit(hfc, driver_info->led_bits[0], 1); + set_led_bit(hfc, driver_info->led_bits[1], 0); + set_led_bit(hfc, driver_info->led_bits[2], 0); + set_led_bit(hfc, driver_info->led_bits[3], 0); + break; + case LED_POWER_OFF: + set_led_bit(hfc, driver_info->led_bits[0], 0); + set_led_bit(hfc, driver_info->led_bits[1], 0); + set_led_bit(hfc, driver_info->led_bits[2], 0); + set_led_bit(hfc, driver_info->led_bits[3], 0); + break; + case LED_S0_ON: + set_led_bit(hfc, driver_info->led_bits[1], 1); + break; + case LED_S0_OFF: + set_led_bit(hfc, driver_info->led_bits[1], 0); + break; + case LED_B1_ON: + set_led_bit(hfc, driver_info->led_bits[2], 1); + break; + case LED_B1_OFF: + set_led_bit(hfc, driver_info->led_bits[2], 0); + break; + case LED_B2_ON: + set_led_bit(hfc, driver_info->led_bits[3], 1); + break; + case LED_B2_OFF: + set_led_bit(hfc, driver_info->led_bits[3], 0); + break; + } + write_led(hfc, hfc->led_state); +} + +/* ISDN l1 timer T3 expires */ +static void +l1_timer_expire_t3(struct timer_list *t) +{ + hfcusb_data *hfc = from_timer(hfc, t, t3_timer); + hfc->d_if.ifc.l1l2(&hfc->d_if.ifc, PH_DEACTIVATE | INDICATION, + NULL); + + DBG(HFCUSB_DBG_STATES, + "HFC-S USB: PH_DEACTIVATE | INDICATION sent (T3 expire)"); + + hfc->l1_activated = 0; + handle_led(hfc, LED_S0_OFF); + /* deactivate : */ + queue_control_request(hfc, HFCUSB_STATES, 0x10, 1); + queue_control_request(hfc, HFCUSB_STATES, 3, 1); +} + +/* ISDN l1 timer T4 expires */ +static void +l1_timer_expire_t4(struct timer_list *t) +{ + hfcusb_data *hfc = from_timer(hfc, t, t4_timer); + hfc->d_if.ifc.l1l2(&hfc->d_if.ifc, PH_DEACTIVATE | INDICATION, + NULL); + + DBG(HFCUSB_DBG_STATES, + "HFC-S USB: PH_DEACTIVATE | INDICATION sent (T4 expire)"); + + hfc->l1_activated = 0; + handle_led(hfc, LED_S0_OFF); +} + +/* S0 state changed */ +static void +s0_state_handler(hfcusb_data *hfc, __u8 state) +{ + __u8 old_state; + + old_state = hfc->l1_state; + if (state == old_state || state < 1 || state > 8) + return; + + DBG(HFCUSB_DBG_STATES, "HFC-S USB: S0 statechange(%d -> %d)", + old_state, state); + + if (state < 4 || state == 7 || state == 8) { + if (timer_pending(&hfc->t3_timer)) + del_timer(&hfc->t3_timer); + DBG(HFCUSB_DBG_STATES, "HFC-S USB: T3 deactivated"); + } + if (state >= 7) { + if (timer_pending(&hfc->t4_timer)) + del_timer(&hfc->t4_timer); + DBG(HFCUSB_DBG_STATES, "HFC-S USB: T4 deactivated"); + } + + if (state == 7 && !hfc->l1_activated) { + hfc->d_if.ifc.l1l2(&hfc->d_if.ifc, + PH_ACTIVATE | INDICATION, NULL); + DBG(HFCUSB_DBG_STATES, "HFC-S USB: PH_ACTIVATE | INDICATION sent"); + hfc->l1_activated = 1; + handle_led(hfc, LED_S0_ON); + } else if (state <= 3 /* && activated */) { + if (old_state == 7 || old_state == 8) { + DBG(HFCUSB_DBG_STATES, "HFC-S USB: T4 activated"); + if (!timer_pending(&hfc->t4_timer)) { + hfc->t4_timer.expires = + jiffies + (HFC_TIMER_T4 * HZ) / 1000; + add_timer(&hfc->t4_timer); + } + } else { + hfc->d_if.ifc.l1l2(&hfc->d_if.ifc, + PH_DEACTIVATE | INDICATION, + NULL); + DBG(HFCUSB_DBG_STATES, + "HFC-S USB: PH_DEACTIVATE | INDICATION sent"); + hfc->l1_activated = 0; + handle_led(hfc, LED_S0_OFF); + } + } + hfc->l1_state = state; +} + +static void +fill_isoc_urb(struct urb *urb, struct usb_device *dev, unsigned int pipe, + void *buf, int num_packets, int packet_size, int interval, + usb_complete_t complete, void *context) +{ + int k; + + usb_fill_int_urb(urb, dev, pipe, buf, packet_size * num_packets, + complete, context, interval); + + urb->number_of_packets = num_packets; + urb->transfer_flags = URB_ISO_ASAP; + urb->actual_length = 0; + for (k = 0; k < num_packets; k++) { + urb->iso_frame_desc[k].offset = packet_size * k; + urb->iso_frame_desc[k].length = packet_size; + urb->iso_frame_desc[k].actual_length = 0; + } +} + +/* allocs urbs and start isoc transfer with two pending urbs to avoid + * gaps in the transfer chain + */ +static int +start_isoc_chain(usb_fifo *fifo, int num_packets_per_urb, + usb_complete_t complete, int packet_size) +{ + int i, k, errcode; + + DBG(HFCUSB_DBG_INIT, "HFC-S USB: starting ISO-URBs for fifo:%d\n", + fifo->fifonum); + + /* allocate Memory for Iso out Urbs */ + for (i = 0; i < 2; i++) { + if (!(fifo->iso[i].purb)) { + fifo->iso[i].purb = + usb_alloc_urb(num_packets_per_urb, GFP_KERNEL); + if (!(fifo->iso[i].purb)) { + printk(KERN_INFO + "alloc urb for fifo %i failed!!!", + fifo->fifonum); + } + fifo->iso[i].owner_fifo = (struct usb_fifo *) fifo; + + /* Init the first iso */ + if (ISO_BUFFER_SIZE >= + (fifo->usb_packet_maxlen * + num_packets_per_urb)) { + fill_isoc_urb(fifo->iso[i].purb, + fifo->hfc->dev, fifo->pipe, + fifo->iso[i].buffer, + num_packets_per_urb, + fifo->usb_packet_maxlen, + fifo->intervall, complete, + &fifo->iso[i]); + memset(fifo->iso[i].buffer, 0, + sizeof(fifo->iso[i].buffer)); + /* defining packet delimeters in fifo->buffer */ + for (k = 0; k < num_packets_per_urb; k++) { + fifo->iso[i].purb-> + iso_frame_desc[k].offset = + k * packet_size; + fifo->iso[i].purb-> + iso_frame_desc[k].length = + packet_size; + } + } else { + printk(KERN_INFO + "HFC-S USB: ISO Buffer size to small!\n"); + } + } + fifo->bit_line = BITLINE_INF; + + errcode = usb_submit_urb(fifo->iso[i].purb, GFP_KERNEL); + fifo->active = (errcode >= 0) ? 1 : 0; + if (errcode < 0) + printk(KERN_INFO "HFC-S USB: usb_submit_urb URB nr:%d, error(%i): '%s'\n", + i, errcode, symbolic(urb_errlist, errcode)); + } + return (fifo->active); +} + +/* stops running iso chain and frees their pending urbs */ +static void +stop_isoc_chain(usb_fifo *fifo) +{ + int i; + + for (i = 0; i < 2; i++) { + if (fifo->iso[i].purb) { + DBG(HFCUSB_DBG_INIT, + "HFC-S USB: Stopping iso chain for fifo %i.%i", + fifo->fifonum, i); + usb_kill_urb(fifo->iso[i].purb); + usb_free_urb(fifo->iso[i].purb); + fifo->iso[i].purb = NULL; + } + } + + usb_kill_urb(fifo->urb); + usb_free_urb(fifo->urb); + fifo->urb = NULL; + fifo->active = 0; +} + +/* defines how much ISO packets are handled in one URB */ +static int iso_packets[8] = +{ ISOC_PACKETS_B, ISOC_PACKETS_B, ISOC_PACKETS_B, ISOC_PACKETS_B, + ISOC_PACKETS_D, ISOC_PACKETS_D, ISOC_PACKETS_D, ISOC_PACKETS_D +}; + +static void +tx_iso_complete(struct urb *urb) +{ + iso_urb_struct *context_iso_urb = (iso_urb_struct *) urb->context; + usb_fifo *fifo = context_iso_urb->owner_fifo; + hfcusb_data *hfc = fifo->hfc; + int k, tx_offset, num_isoc_packets, sink, len, current_len, + errcode; + int frame_complete, transp_mode, fifon, status; + __u8 threshbit; + + fifon = fifo->fifonum; + status = urb->status; + + tx_offset = 0; + + /* ISO transfer only partially completed, + look at individual frame status for details */ + if (status == -EXDEV) { + DBG(HFCUSB_DBG_VERBOSE_USB, "HFC-S USB: tx_iso_complete with -EXDEV" + ", urb->status %d, fifonum %d\n", + status, fifon); + + for (k = 0; k < iso_packets[fifon]; ++k) { + errcode = urb->iso_frame_desc[k].status; + if (errcode) + DBG(HFCUSB_DBG_VERBOSE_USB, "HFC-S USB: tx_iso_complete " + "packet %i, status: %i\n", + k, errcode); + } + + // clear status, so go on with ISO transfers + status = 0; + } + + if (fifo->active && !status) { + transp_mode = 0; + if (fifon < 4 && hfc->b_mode[fifon / 2] == L1_MODE_TRANS) + transp_mode = 1; + + /* is FifoFull-threshold set for our channel? */ + threshbit = (hfc->threshold_mask & (1 << fifon)); + num_isoc_packets = iso_packets[fifon]; + + /* predict dataflow to avoid fifo overflow */ + if (fifon >= HFCUSB_D_TX) { + sink = (threshbit) ? SINK_DMIN : SINK_DMAX; + } else { + sink = (threshbit) ? SINK_MIN : SINK_MAX; + } + fill_isoc_urb(urb, fifo->hfc->dev, fifo->pipe, + context_iso_urb->buffer, num_isoc_packets, + fifo->usb_packet_maxlen, fifo->intervall, + tx_iso_complete, urb->context); + memset(context_iso_urb->buffer, 0, + sizeof(context_iso_urb->buffer)); + frame_complete = 0; + + /* Generate next ISO Packets */ + for (k = 0; k < num_isoc_packets; ++k) { + if (fifo->skbuff) { + len = fifo->skbuff->len; + /* we lower data margin every msec */ + fifo->bit_line -= sink; + current_len = (0 - fifo->bit_line) / 8; + /* maximum 15 byte for every ISO packet makes our life easier */ + if (current_len > 14) + current_len = 14; + current_len = + (len <= + current_len) ? len : current_len; + /* how much bit do we put on the line? */ + fifo->bit_line += current_len * 8; + + context_iso_urb->buffer[tx_offset] = 0; + if (current_len == len) { + if (!transp_mode) { + /* here frame completion */ + context_iso_urb-> + buffer[tx_offset] = 1; + /* add 2 byte flags and 16bit CRC at end of ISDN frame */ + fifo->bit_line += 32; + } + frame_complete = 1; + } + + memcpy(context_iso_urb->buffer + + tx_offset + 1, fifo->skbuff->data, + current_len); + skb_pull(fifo->skbuff, current_len); + + /* define packet delimeters within the URB buffer */ + urb->iso_frame_desc[k].offset = tx_offset; + urb->iso_frame_desc[k].length = + current_len + 1; + + tx_offset += (current_len + 1); + } else { + urb->iso_frame_desc[k].offset = + tx_offset++; + + urb->iso_frame_desc[k].length = 1; + fifo->bit_line -= sink; /* we lower data margin every msec */ + + if (fifo->bit_line < BITLINE_INF) { + fifo->bit_line = BITLINE_INF; + } + } + + if (frame_complete) { + fifo->delete_flg = 1; + fifo->hif->l1l2(fifo->hif, + PH_DATA | CONFIRM, + (void *) (unsigned long) fifo->skbuff-> + truesize); + if (fifo->skbuff && fifo->delete_flg) { + dev_kfree_skb_any(fifo->skbuff); + fifo->skbuff = NULL; + fifo->delete_flg = 0; + } + frame_complete = 0; + } + } + errcode = usb_submit_urb(urb, GFP_ATOMIC); + if (errcode < 0) { + printk(KERN_INFO + "HFC-S USB: error submitting ISO URB: %d\n", + errcode); + } + } else { + if (status && !hfc->disc_flag) { + printk(KERN_INFO + "HFC-S USB: tx_iso_complete: error(%i): '%s', fifonum=%d\n", + status, symbolic(urb_errlist, status), fifon); + } + } +} + +static void +rx_iso_complete(struct urb *urb) +{ + iso_urb_struct *context_iso_urb = (iso_urb_struct *) urb->context; + usb_fifo *fifo = context_iso_urb->owner_fifo; + hfcusb_data *hfc = fifo->hfc; + int k, len, errcode, offset, num_isoc_packets, fifon, maxlen, + status; + unsigned int iso_status; + __u8 *buf; + static __u8 eof[8]; + + fifon = fifo->fifonum; + status = urb->status; + + if (urb->status == -EOVERFLOW) { + DBG(HFCUSB_DBG_VERBOSE_USB, + "HFC-USB: ignoring USB DATAOVERRUN fifo(%i)", fifon); + status = 0; + } + + /* ISO transfer only partially completed, + look at individual frame status for details */ + if (status == -EXDEV) { + DBG(HFCUSB_DBG_VERBOSE_USB, "HFC-S USB: rx_iso_complete with -EXDEV " + "urb->status %d, fifonum %d\n", + status, fifon); + status = 0; + } + + if (fifo->active && !status) { + num_isoc_packets = iso_packets[fifon]; + maxlen = fifo->usb_packet_maxlen; + for (k = 0; k < num_isoc_packets; ++k) { + len = urb->iso_frame_desc[k].actual_length; + offset = urb->iso_frame_desc[k].offset; + buf = context_iso_urb->buffer + offset; + iso_status = urb->iso_frame_desc[k].status; + + if (iso_status && !hfc->disc_flag) + DBG(HFCUSB_DBG_VERBOSE_USB, + "HFC-S USB: rx_iso_complete " + "ISO packet %i, status: %i\n", + k, iso_status); + + if (fifon == HFCUSB_D_RX) { + DBG(HFCUSB_DBG_VERBOSE_USB, + "HFC-S USB: ISO-D-RX lst_urblen:%2d " + "act_urblen:%2d max-urblen:%2d EOF:0x%0x", + fifo->last_urblen, len, maxlen, + eof[5]); + + DBG_PACKET(HFCUSB_DBG_VERBOSE_USB, buf, len); + } + + if (fifo->last_urblen != maxlen) { + /* the threshold mask is in the 2nd status byte */ + hfc->threshold_mask = buf[1]; + /* care for L1 state only for D-Channel + to avoid overlapped iso completions */ + if (fifon == HFCUSB_D_RX) { + /* the S0 state is in the upper half + of the 1st status byte */ + s0_state_handler(hfc, buf[0] >> 4); + } + eof[fifon] = buf[0] & 1; + if (len > 2) + collect_rx_frame(fifo, buf + 2, + len - 2, + (len < maxlen) ? + eof[fifon] : 0); + } else { + collect_rx_frame(fifo, buf, len, + (len < + maxlen) ? eof[fifon] : + 0); + } + fifo->last_urblen = len; + } + + fill_isoc_urb(urb, fifo->hfc->dev, fifo->pipe, + context_iso_urb->buffer, num_isoc_packets, + fifo->usb_packet_maxlen, fifo->intervall, + rx_iso_complete, urb->context); + errcode = usb_submit_urb(urb, GFP_ATOMIC); + if (errcode < 0) { + printk(KERN_ERR + "HFC-S USB: error submitting ISO URB: %d\n", + errcode); + } + } else { + if (status && !hfc->disc_flag) { + printk(KERN_ERR + "HFC-S USB: rx_iso_complete : " + "urb->status %d, fifonum %d\n", + status, fifon); + } + } +} + +/* collect rx data from INT- and ISO-URBs */ +static void +collect_rx_frame(usb_fifo *fifo, __u8 *data, int len, int finish) +{ + hfcusb_data *hfc = fifo->hfc; + int transp_mode, fifon; + + fifon = fifo->fifonum; + transp_mode = 0; + if (fifon < 4 && hfc->b_mode[fifon / 2] == L1_MODE_TRANS) + transp_mode = 1; + + if (!fifo->skbuff) { + fifo->skbuff = dev_alloc_skb(fifo->max_size + 3); + if (!fifo->skbuff) { + printk(KERN_ERR + "HFC-S USB: cannot allocate buffer for fifo(%d)\n", + fifon); + return; + } + } + if (len) { + if (fifo->skbuff->len + len < fifo->max_size) { + skb_put_data(fifo->skbuff, data, len); + } else { + DBG(HFCUSB_DBG_FIFO_ERR, + "HCF-USB: got frame exceeded fifo->max_size(%d) fifo(%d)", + fifo->max_size, fifon); + DBG_SKB(HFCUSB_DBG_VERBOSE_USB, fifo->skbuff); + skb_trim(fifo->skbuff, 0); + } + } + if (transp_mode && fifo->skbuff->len >= 128) { + fifo->hif->l1l2(fifo->hif, PH_DATA | INDICATION, + fifo->skbuff); + fifo->skbuff = NULL; + return; + } + /* we have a complete hdlc packet */ + if (finish) { + if (fifo->skbuff->len > 3 && + !fifo->skbuff->data[fifo->skbuff->len - 1]) { + + if (fifon == HFCUSB_D_RX) { + DBG(HFCUSB_DBG_DCHANNEL, + "HFC-S USB: D-RX len(%d)", fifo->skbuff->len); + DBG_SKB(HFCUSB_DBG_DCHANNEL, fifo->skbuff); + } + + /* remove CRC & status */ + skb_trim(fifo->skbuff, fifo->skbuff->len - 3); + if (fifon == HFCUSB_PCM_RX) { + fifo->hif->l1l2(fifo->hif, + PH_DATA_E | INDICATION, + fifo->skbuff); + } else + fifo->hif->l1l2(fifo->hif, + PH_DATA | INDICATION, + fifo->skbuff); + fifo->skbuff = NULL; /* buffer was freed from upper layer */ + } else { + DBG(HFCUSB_DBG_FIFO_ERR, + "HFC-S USB: ERROR frame len(%d) fifo(%d)", + fifo->skbuff->len, fifon); + DBG_SKB(HFCUSB_DBG_VERBOSE_USB, fifo->skbuff); + skb_trim(fifo->skbuff, 0); + } + } +} + +static void +rx_int_complete(struct urb *urb) +{ + int len; + int status; + __u8 *buf, maxlen, fifon; + usb_fifo *fifo = (usb_fifo *) urb->context; + hfcusb_data *hfc = fifo->hfc; + static __u8 eof[8]; + + urb->dev = hfc->dev; /* security init */ + + fifon = fifo->fifonum; + if ((!fifo->active) || (urb->status)) { + DBG(HFCUSB_DBG_INIT, "HFC-S USB: RX-Fifo %i is going down (%i)", + fifon, urb->status); + + fifo->urb->interval = 0; /* cancel automatic rescheduling */ + if (fifo->skbuff) { + dev_kfree_skb_any(fifo->skbuff); + fifo->skbuff = NULL; + } + return; + } + len = urb->actual_length; + buf = fifo->buffer; + maxlen = fifo->usb_packet_maxlen; + + if (fifon == HFCUSB_D_RX) { + DBG(HFCUSB_DBG_VERBOSE_USB, + "HFC-S USB: INT-D-RX lst_urblen:%2d " + "act_urblen:%2d max-urblen:%2d EOF:0x%0x", + fifo->last_urblen, len, maxlen, + eof[5]); + DBG_PACKET(HFCUSB_DBG_VERBOSE_USB, buf, len); + } + + if (fifo->last_urblen != fifo->usb_packet_maxlen) { + /* the threshold mask is in the 2nd status byte */ + hfc->threshold_mask = buf[1]; + /* the S0 state is in the upper half of the 1st status byte */ + s0_state_handler(hfc, buf[0] >> 4); + eof[fifon] = buf[0] & 1; + /* if we have more than the 2 status bytes -> collect data */ + if (len > 2) + collect_rx_frame(fifo, buf + 2, + urb->actual_length - 2, + (len < maxlen) ? eof[fifon] : 0); + } else { + collect_rx_frame(fifo, buf, urb->actual_length, + (len < maxlen) ? eof[fifon] : 0); + } + fifo->last_urblen = urb->actual_length; + status = usb_submit_urb(urb, GFP_ATOMIC); + if (status) { + printk(KERN_INFO + "HFC-S USB: %s error resubmitting URB fifo(%d)\n", + __func__, fifon); + } +} + +/* start initial INT-URB for certain fifo */ +static void +start_int_fifo(usb_fifo *fifo) +{ + int errcode; + + DBG(HFCUSB_DBG_INIT, "HFC-S USB: starting RX INT-URB for fifo:%d\n", + fifo->fifonum); + + if (!fifo->urb) { + fifo->urb = usb_alloc_urb(0, GFP_KERNEL); + if (!fifo->urb) + return; + } + usb_fill_int_urb(fifo->urb, fifo->hfc->dev, fifo->pipe, + fifo->buffer, fifo->usb_packet_maxlen, + rx_int_complete, fifo, fifo->intervall); + fifo->active = 1; /* must be marked active */ + errcode = usb_submit_urb(fifo->urb, GFP_KERNEL); + if (errcode) { + printk(KERN_ERR "HFC-S USB: submit URB error(%s): status:%i\n", + __func__, errcode); + fifo->active = 0; + fifo->skbuff = NULL; + } +} + +static void +setup_bchannel(hfcusb_data *hfc, int channel, int mode) +{ + __u8 val, idx_table[2] = { 0, 2 }; + + if (hfc->disc_flag) { + return; + } + DBG(HFCUSB_DBG_STATES, "HFC-S USB: setting channel %d to mode %d", + channel, mode); + hfc->b_mode[channel] = mode; + + /* setup CON_HDLC */ + val = 0; + if (mode != L1_MODE_NULL) + val = 8; /* enable fifo? */ + if (mode == L1_MODE_TRANS) + val |= 2; /* set transparent bit */ + + /* set FIFO to transmit register */ + queue_control_request(hfc, HFCUSB_FIFO, idx_table[channel], 1); + queue_control_request(hfc, HFCUSB_CON_HDLC, val, 1); + /* reset fifo */ + queue_control_request(hfc, HFCUSB_INC_RES_F, 2, 1); + /* set FIFO to receive register */ + queue_control_request(hfc, HFCUSB_FIFO, idx_table[channel] + 1, 1); + queue_control_request(hfc, HFCUSB_CON_HDLC, val, 1); + /* reset fifo */ + queue_control_request(hfc, HFCUSB_INC_RES_F, 2, 1); + + val = 0x40; + if (hfc->b_mode[0]) + val |= 1; + if (hfc->b_mode[1]) + val |= 2; + queue_control_request(hfc, HFCUSB_SCTRL, val, 1); + + val = 0; + if (hfc->b_mode[0]) + val |= 1; + if (hfc->b_mode[1]) + val |= 2; + queue_control_request(hfc, HFCUSB_SCTRL_R, val, 1); + + if (mode == L1_MODE_NULL) { + if (channel) + handle_led(hfc, LED_B2_OFF); + else + handle_led(hfc, LED_B1_OFF); + } else { + if (channel) + handle_led(hfc, LED_B2_ON); + else + handle_led(hfc, LED_B1_ON); + } +} + +static void +hfc_usb_l2l1(struct hisax_if *my_hisax_if, int pr, void *arg) +{ + usb_fifo *fifo = my_hisax_if->priv; + hfcusb_data *hfc = fifo->hfc; + + switch (pr) { + case PH_ACTIVATE | REQUEST: + if (fifo->fifonum == HFCUSB_D_TX) { + DBG(HFCUSB_DBG_STATES, + "HFC_USB: hfc_usb_d_l2l1 D-chan: PH_ACTIVATE | REQUEST"); + + if (hfc->l1_state != 3 + && hfc->l1_state != 7) { + hfc->d_if.ifc.l1l2(&hfc->d_if.ifc, + PH_DEACTIVATE | + INDICATION, + NULL); + DBG(HFCUSB_DBG_STATES, + "HFC-S USB: PH_DEACTIVATE | INDICATION sent (not state 3 or 7)"); + } else { + if (hfc->l1_state == 7) { /* l1 already active */ + hfc->d_if.ifc.l1l2(&hfc-> + d_if. + ifc, + PH_ACTIVATE + | + INDICATION, + NULL); + DBG(HFCUSB_DBG_STATES, + "HFC-S USB: PH_ACTIVATE | INDICATION sent again ;)"); + } else { + /* force sending sending INFO1 */ + queue_control_request(hfc, + HFCUSB_STATES, + 0x14, + 1); + mdelay(1); + /* start l1 activation */ + queue_control_request(hfc, + HFCUSB_STATES, + 0x04, + 1); + if (!timer_pending + (&hfc->t3_timer)) { + hfc->t3_timer. + expires = + jiffies + + (HFC_TIMER_T3 * + HZ) / 1000; + add_timer(&hfc-> + t3_timer); + } + } + } + } else { + DBG(HFCUSB_DBG_STATES, + "HFC_USB: hfc_usb_d_l2l1 B-chan: PH_ACTIVATE | REQUEST"); + setup_bchannel(hfc, + (fifo->fifonum == + HFCUSB_B1_TX) ? 0 : 1, + (long) arg); + fifo->hif->l1l2(fifo->hif, + PH_ACTIVATE | INDICATION, + NULL); + } + break; + case PH_DEACTIVATE | REQUEST: + if (fifo->fifonum == HFCUSB_D_TX) { + DBG(HFCUSB_DBG_STATES, + "HFC_USB: hfc_usb_d_l2l1 D-chan: PH_DEACTIVATE | REQUEST"); + } else { + DBG(HFCUSB_DBG_STATES, + "HFC_USB: hfc_usb_d_l2l1 Bx-chan: PH_DEACTIVATE | REQUEST"); + setup_bchannel(hfc, + (fifo->fifonum == + HFCUSB_B1_TX) ? 0 : 1, + (int) L1_MODE_NULL); + fifo->hif->l1l2(fifo->hif, + PH_DEACTIVATE | INDICATION, + NULL); + } + break; + case PH_DATA | REQUEST: + if (fifo->skbuff && fifo->delete_flg) { + dev_kfree_skb_any(fifo->skbuff); + fifo->skbuff = NULL; + fifo->delete_flg = 0; + } + fifo->skbuff = arg; /* we have a new buffer */ + break; + default: + DBG(HFCUSB_DBG_STATES, + "HFC_USB: hfc_usb_d_l2l1: unknown state : %#x", pr); + break; + } +} + +/* initial init HFC-S USB chip registers, HiSax interface, USB URBs */ +static int +hfc_usb_init(hfcusb_data *hfc) +{ + usb_fifo *fifo; + int i; + u_char b; + struct hisax_b_if *p_b_if[2]; + + /* check the chip id */ + if (read_usb(hfc, HFCUSB_CHIP_ID, &b) != 1) { + printk(KERN_INFO "HFC-USB: cannot read chip id\n"); + return (1); + } + if (b != HFCUSB_CHIPID) { + printk(KERN_INFO "HFC-S USB: Invalid chip id 0x%02x\n", b); + return (1); + } + + /* first set the needed config, interface and alternate */ + usb_set_interface(hfc->dev, hfc->if_used, hfc->alt_used); + + /* do Chip reset */ + write_usb(hfc, HFCUSB_CIRM, 8); + /* aux = output, reset off */ + write_usb(hfc, HFCUSB_CIRM, 0x10); + + /* set USB_SIZE to match wMaxPacketSize for INT or BULK transfers */ + write_usb(hfc, HFCUSB_USB_SIZE, + (hfc->packet_size / 8) | ((hfc->packet_size / 8) << 4)); + + /* set USB_SIZE_I to match wMaxPacketSize for ISO transfers */ + write_usb(hfc, HFCUSB_USB_SIZE_I, hfc->iso_packet_size); + + /* enable PCM/GCI master mode */ + write_usb(hfc, HFCUSB_MST_MODE1, 0); /* set default values */ + write_usb(hfc, HFCUSB_MST_MODE0, 1); /* enable master mode */ + + /* init the fifos */ + write_usb(hfc, HFCUSB_F_THRES, + (HFCUSB_TX_THRESHOLD / + 8) | ((HFCUSB_RX_THRESHOLD / 8) << 4)); + + fifo = hfc->fifos; + for (i = 0; i < HFCUSB_NUM_FIFOS; i++) { + write_usb(hfc, HFCUSB_FIFO, i); /* select the desired fifo */ + fifo[i].skbuff = NULL; /* init buffer pointer */ + fifo[i].max_size = + (i <= HFCUSB_B2_RX) ? MAX_BCH_SIZE : MAX_DFRAME_LEN; + fifo[i].last_urblen = 0; + /* set 2 bit for D- & E-channel */ + write_usb(hfc, HFCUSB_HDLC_PAR, + ((i <= HFCUSB_B2_RX) ? 0 : 2)); + /* rx hdlc, enable IFF for D-channel */ + write_usb(hfc, HFCUSB_CON_HDLC, + ((i == HFCUSB_D_TX) ? 0x09 : 0x08)); + write_usb(hfc, HFCUSB_INC_RES_F, 2); /* reset the fifo */ + } + + write_usb(hfc, HFCUSB_CLKDEL, 0x0f); /* clock delay value */ + write_usb(hfc, HFCUSB_STATES, 3 | 0x10); /* set deactivated mode */ + write_usb(hfc, HFCUSB_STATES, 3); /* enable state machine */ + + write_usb(hfc, HFCUSB_SCTRL_R, 0); /* disable both B receivers */ + write_usb(hfc, HFCUSB_SCTRL, 0x40); /* disable B transmitters + capacitive mode */ + + /* set both B-channel to not connected */ + hfc->b_mode[0] = L1_MODE_NULL; + hfc->b_mode[1] = L1_MODE_NULL; + + hfc->l1_activated = 0; + hfc->disc_flag = 0; + hfc->led_state = 0; + hfc->old_led_state = 0; + + /* init the t3 timer */ + timer_setup(&hfc->t3_timer, l1_timer_expire_t3, 0); + + /* init the t4 timer */ + timer_setup(&hfc->t4_timer, l1_timer_expire_t4, 0); + + /* init the background machinery for control requests */ + hfc->ctrl_read.bRequestType = 0xc0; + hfc->ctrl_read.bRequest = 1; + hfc->ctrl_read.wLength = cpu_to_le16(1); + hfc->ctrl_write.bRequestType = 0x40; + hfc->ctrl_write.bRequest = 0; + hfc->ctrl_write.wLength = 0; + usb_fill_control_urb(hfc->ctrl_urb, + hfc->dev, + hfc->ctrl_out_pipe, + (u_char *)&hfc->ctrl_write, + NULL, 0, ctrl_complete, hfc); + /* Init All Fifos */ + for (i = 0; i < HFCUSB_NUM_FIFOS; i++) { + hfc->fifos[i].iso[0].purb = NULL; + hfc->fifos[i].iso[1].purb = NULL; + hfc->fifos[i].active = 0; + } + /* register Modul to upper Hisax Layers */ + hfc->d_if.owner = THIS_MODULE; + hfc->d_if.ifc.priv = &hfc->fifos[HFCUSB_D_TX]; + hfc->d_if.ifc.l2l1 = hfc_usb_l2l1; + for (i = 0; i < 2; i++) { + hfc->b_if[i].ifc.priv = &hfc->fifos[HFCUSB_B1_TX + i * 2]; + hfc->b_if[i].ifc.l2l1 = hfc_usb_l2l1; + p_b_if[i] = &hfc->b_if[i]; + } + /* default Prot: EURO ISDN, should be a module_param */ + hfc->protocol = 2; + i = hisax_register(&hfc->d_if, p_b_if, "hfc_usb", hfc->protocol); + if (i) { + printk(KERN_INFO "HFC-S USB: hisax_register -> %d\n", i); + return i; + } + +#ifdef CONFIG_HISAX_DEBUG + hfc_debug = debug; +#endif + + for (i = 0; i < 4; i++) + hfc->fifos[i].hif = &p_b_if[i / 2]->ifc; + for (i = 4; i < 8; i++) + hfc->fifos[i].hif = &hfc->d_if.ifc; + + /* 3 (+1) INT IN + 3 ISO OUT */ + if (hfc->cfg_used == CNF_3INT3ISO || hfc->cfg_used == CNF_4INT3ISO) { + start_int_fifo(hfc->fifos + HFCUSB_D_RX); + if (hfc->fifos[HFCUSB_PCM_RX].pipe) + start_int_fifo(hfc->fifos + HFCUSB_PCM_RX); + start_int_fifo(hfc->fifos + HFCUSB_B1_RX); + start_int_fifo(hfc->fifos + HFCUSB_B2_RX); + } + /* 3 (+1) ISO IN + 3 ISO OUT */ + if (hfc->cfg_used == CNF_3ISO3ISO || hfc->cfg_used == CNF_4ISO3ISO) { + start_isoc_chain(hfc->fifos + HFCUSB_D_RX, ISOC_PACKETS_D, + rx_iso_complete, 16); + if (hfc->fifos[HFCUSB_PCM_RX].pipe) + start_isoc_chain(hfc->fifos + HFCUSB_PCM_RX, + ISOC_PACKETS_D, rx_iso_complete, + 16); + start_isoc_chain(hfc->fifos + HFCUSB_B1_RX, ISOC_PACKETS_B, + rx_iso_complete, 16); + start_isoc_chain(hfc->fifos + HFCUSB_B2_RX, ISOC_PACKETS_B, + rx_iso_complete, 16); + } + + start_isoc_chain(hfc->fifos + HFCUSB_D_TX, ISOC_PACKETS_D, + tx_iso_complete, 1); + start_isoc_chain(hfc->fifos + HFCUSB_B1_TX, ISOC_PACKETS_B, + tx_iso_complete, 1); + start_isoc_chain(hfc->fifos + HFCUSB_B2_TX, ISOC_PACKETS_B, + tx_iso_complete, 1); + + handle_led(hfc, LED_POWER_ON); + + return (0); +} + +/* initial callback for each plugged USB device */ +static int +hfc_usb_probe(struct usb_interface *intf, const struct usb_device_id *id) +{ + struct usb_device *dev = interface_to_usbdev(intf); + hfcusb_data *context; + struct usb_host_interface *iface = intf->cur_altsetting; + struct usb_host_interface *iface_used = NULL; + struct usb_host_endpoint *ep; + int ifnum = iface->desc.bInterfaceNumber; + int i, idx, alt_idx, probe_alt_setting, vend_idx, cfg_used, *vcf, + attr, cfg_found, cidx, ep_addr; + int cmptbl[16], small_match, iso_packet_size, packet_size, + alt_used = 0; + hfcsusb_vdata *driver_info; + + vend_idx = 0xffff; + for (i = 0; hfcusb_idtab[i].idVendor; i++) { + if ((le16_to_cpu(dev->descriptor.idVendor) == hfcusb_idtab[i].idVendor) + && (le16_to_cpu(dev->descriptor.idProduct) == hfcusb_idtab[i].idProduct)) { + vend_idx = i; + continue; + } + } + + printk(KERN_INFO + "HFC-S USB: probing interface(%d) actalt(%d) minor(%d)\n", + ifnum, iface->desc.bAlternateSetting, intf->minor); + + if (vend_idx != 0xffff) { + /* if vendor and product ID is OK, start probing alternate settings */ + alt_idx = 0; + small_match = 0xffff; + + /* default settings */ + iso_packet_size = 16; + packet_size = 64; + + while (alt_idx < intf->num_altsetting) { + iface = intf->altsetting + alt_idx; + probe_alt_setting = iface->desc.bAlternateSetting; + cfg_used = 0; + + /* check for config EOL element */ + while (validconf[cfg_used][0]) { + cfg_found = 1; + vcf = validconf[cfg_used]; + /* first endpoint descriptor */ + ep = iface->endpoint; + + memcpy(cmptbl, vcf, 16 * sizeof(int)); + + /* check for all endpoints in this alternate setting */ + for (i = 0; i < iface->desc.bNumEndpoints; + i++) { + ep_addr = + ep->desc.bEndpointAddress; + /* get endpoint base */ + idx = ((ep_addr & 0x7f) - 1) * 2; + if (ep_addr & 0x80) + idx++; + attr = ep->desc.bmAttributes; + if (cmptbl[idx] == EP_NUL) { + cfg_found = 0; + } + if (attr == USB_ENDPOINT_XFER_INT + && cmptbl[idx] == EP_INT) + cmptbl[idx] = EP_NUL; + if (attr == USB_ENDPOINT_XFER_BULK + && cmptbl[idx] == EP_BLK) + cmptbl[idx] = EP_NUL; + if (attr == USB_ENDPOINT_XFER_ISOC + && cmptbl[idx] == EP_ISO) + cmptbl[idx] = EP_NUL; + + /* check if all INT endpoints match minimum interval */ + if ((attr == USB_ENDPOINT_XFER_INT) + && (ep->desc.bInterval < vcf[17])) { + cfg_found = 0; + } + ep++; + } + for (i = 0; i < 16; i++) { + /* all entries must be EP_NOP or EP_NUL for a valid config */ + if (cmptbl[i] != EP_NOP + && cmptbl[i] != EP_NUL) + cfg_found = 0; + } + if (cfg_found) { + if (cfg_used < small_match) { + small_match = cfg_used; + alt_used = + probe_alt_setting; + iface_used = iface; + } + } + cfg_used++; + } + alt_idx++; + } /* (alt_idx < intf->num_altsetting) */ + + /* found a valid USB Ta Endpint config */ + if (small_match != 0xffff) { + iface = iface_used; + if (!(context = kzalloc(sizeof(hfcusb_data), GFP_KERNEL))) + return (-ENOMEM); /* got no mem */ + + ep = iface->endpoint; + vcf = validconf[small_match]; + + for (i = 0; i < iface->desc.bNumEndpoints; i++) { + ep_addr = ep->desc.bEndpointAddress; + /* get endpoint base */ + idx = ((ep_addr & 0x7f) - 1) * 2; + if (ep_addr & 0x80) + idx++; + cidx = idx & 7; + attr = ep->desc.bmAttributes; + + /* init Endpoints */ + if (vcf[idx] != EP_NOP + && vcf[idx] != EP_NUL) { + switch (attr) { + case USB_ENDPOINT_XFER_INT: + context-> + fifos[cidx]. + pipe = + usb_rcvintpipe + (dev, + ep->desc. + bEndpointAddress); + context-> + fifos[cidx]. + usb_transfer_mode + = USB_INT; + packet_size = + le16_to_cpu(ep->desc.wMaxPacketSize); + break; + case USB_ENDPOINT_XFER_BULK: + if (ep_addr & 0x80) + context-> + fifos + [cidx]. + pipe = + usb_rcvbulkpipe + (dev, + ep-> + desc. + bEndpointAddress); + else + context-> + fifos + [cidx]. + pipe = + usb_sndbulkpipe + (dev, + ep-> + desc. + bEndpointAddress); + context-> + fifos[cidx]. + usb_transfer_mode + = USB_BULK; + packet_size = + le16_to_cpu(ep->desc.wMaxPacketSize); + break; + case USB_ENDPOINT_XFER_ISOC: + if (ep_addr & 0x80) + context-> + fifos + [cidx]. + pipe = + usb_rcvisocpipe + (dev, + ep-> + desc. + bEndpointAddress); + else + context-> + fifos + [cidx]. + pipe = + usb_sndisocpipe + (dev, + ep-> + desc. + bEndpointAddress); + context-> + fifos[cidx]. + usb_transfer_mode + = USB_ISOC; + iso_packet_size = + le16_to_cpu(ep->desc.wMaxPacketSize); + break; + default: + context-> + fifos[cidx]. + pipe = 0; + } /* switch attribute */ + + if (context->fifos[cidx].pipe) { + context->fifos[cidx]. + fifonum = cidx; + context->fifos[cidx].hfc = + context; + context->fifos[cidx].usb_packet_maxlen = + le16_to_cpu(ep->desc.wMaxPacketSize); + context->fifos[cidx]. + intervall = + ep->desc.bInterval; + context->fifos[cidx]. + skbuff = NULL; + } + } + ep++; + } + context->dev = dev; /* save device */ + context->if_used = ifnum; /* save used interface */ + context->alt_used = alt_used; /* and alternate config */ + context->ctrl_paksize = dev->descriptor.bMaxPacketSize0; /* control size */ + context->cfg_used = vcf[16]; /* store used config */ + context->vend_idx = vend_idx; /* store found vendor */ + context->packet_size = packet_size; + context->iso_packet_size = iso_packet_size; + + /* create the control pipes needed for register access */ + context->ctrl_in_pipe = + usb_rcvctrlpipe(context->dev, 0); + context->ctrl_out_pipe = + usb_sndctrlpipe(context->dev, 0); + + driver_info = (hfcsusb_vdata *) + hfcusb_idtab[vend_idx].driver_info; + + context->ctrl_urb = usb_alloc_urb(0, GFP_KERNEL); + + if (!context->ctrl_urb) { + pr_warn("%s: No memory for control urb\n", + driver_info->vend_name); + kfree(context); + return -ENOMEM; + } + + pr_info("HFC-S USB: detected \"%s\"\n", + driver_info->vend_name); + + DBG(HFCUSB_DBG_INIT, + "HFC-S USB: Endpoint-Config: %s (if=%d alt=%d), E-Channel(%d)", + conf_str[small_match], context->if_used, + context->alt_used, + validconf[small_match][18]); + + /* init the chip and register the driver */ + if (hfc_usb_init(context)) { + usb_kill_urb(context->ctrl_urb); + usb_free_urb(context->ctrl_urb); + context->ctrl_urb = NULL; + kfree(context); + return (-EIO); + } + usb_set_intfdata(intf, context); + return (0); + } + } else { + printk(KERN_INFO + "HFC-S USB: no valid vendor found in USB descriptor\n"); + } + return (-EIO); +} + +/* callback for unplugged USB device */ +static void +hfc_usb_disconnect(struct usb_interface *intf) +{ + hfcusb_data *context = usb_get_intfdata(intf); + int i; + + handle_led(context, LED_POWER_OFF); + schedule_timeout(HZ / 100); + + printk(KERN_INFO "HFC-S USB: device disconnect\n"); + context->disc_flag = 1; + usb_set_intfdata(intf, NULL); + + if (timer_pending(&context->t3_timer)) + del_timer(&context->t3_timer); + if (timer_pending(&context->t4_timer)) + del_timer(&context->t4_timer); + + /* tell all fifos to terminate */ + for (i = 0; i < HFCUSB_NUM_FIFOS; i++) { + if (context->fifos[i].usb_transfer_mode == USB_ISOC) { + if (context->fifos[i].active > 0) { + stop_isoc_chain(&context->fifos[i]); + DBG(HFCUSB_DBG_INIT, + "HFC-S USB: %s stopping ISOC chain Fifo(%i)", + __func__, i); + } + } else { + if (context->fifos[i].active > 0) { + context->fifos[i].active = 0; + DBG(HFCUSB_DBG_INIT, + "HFC-S USB: %s unlinking URB for Fifo(%i)", + __func__, i); + } + usb_kill_urb(context->fifos[i].urb); + usb_free_urb(context->fifos[i].urb); + context->fifos[i].urb = NULL; + } + context->fifos[i].active = 0; + } + usb_kill_urb(context->ctrl_urb); + usb_free_urb(context->ctrl_urb); + context->ctrl_urb = NULL; + hisax_unregister(&context->d_if); + kfree(context); /* free our structure again */ +} + +static struct usb_driver hfc_drv = { + .name = "hfc_usb", + .id_table = hfcusb_idtab, + .probe = hfc_usb_probe, + .disconnect = hfc_usb_disconnect, + .disable_hub_initiated_lpm = 1, +}; + +static void __exit +hfc_usb_mod_exit(void) +{ + usb_deregister(&hfc_drv); /* release our driver */ + printk(KERN_INFO "HFC-S USB: module removed\n"); +} + +static int __init +hfc_usb_mod_init(void) +{ + char revstr[30], datestr[30], dummy[30]; +#ifndef CONFIG_HISAX_DEBUG + hfc_debug = debug; +#endif + sscanf(hfcusb_revision, + "%s %s $ %s %s %s $ ", dummy, revstr, + dummy, datestr, dummy); + printk(KERN_INFO + "HFC-S USB: driver module revision %s date %s loaded, (debug=%i)\n", + revstr, datestr, debug); + if (usb_register(&hfc_drv)) { + printk(KERN_INFO + "HFC-S USB: Unable to register HFC-S USB module at usb stack\n"); + return (-1); /* unable to register */ + } + return (0); +} + +module_init(hfc_usb_mod_init); +module_exit(hfc_usb_mod_exit); +MODULE_AUTHOR(DRIVER_AUTHOR); +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_LICENSE("GPL"); +MODULE_DEVICE_TABLE(usb, hfcusb_idtab); diff --git a/drivers/isdn/hisax/hfc_usb.h b/drivers/isdn/hisax/hfc_usb.h new file mode 100644 index 000000000..9a212330e --- /dev/null +++ b/drivers/isdn/hisax/hfc_usb.h @@ -0,0 +1,208 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * hfc_usb.h + * + * $Id: hfc_usb.h,v 1.1.2.5 2007/08/20 14:36:03 mbachem Exp $ + */ + +#ifndef __HFC_USB_H__ +#define __HFC_USB_H__ + +#define DRIVER_AUTHOR "Peter Sprenger (sprenger@moving-byters.de)" +#define DRIVER_DESC "HFC-S USB based HiSAX ISDN driver" + + +#define HFC_CTRL_TIMEOUT 20 /* 5ms timeout writing/reading regs */ +#define HFC_TIMER_T3 8000 /* timeout for l1 activation timer */ +#define HFC_TIMER_T4 500 /* time for state change interval */ + +#define HFCUSB_L1_STATECHANGE 0 /* L1 state changed */ +#define HFCUSB_L1_DRX 1 /* D-frame received */ +#define HFCUSB_L1_ERX 2 /* E-frame received */ +#define HFCUSB_L1_DTX 4 /* D-frames completed */ + +#define MAX_BCH_SIZE 2048 /* allowed B-channel packet size */ + +#define HFCUSB_RX_THRESHOLD 64 /* threshold for fifo report bit rx */ +#define HFCUSB_TX_THRESHOLD 64 /* threshold for fifo report bit tx */ + +#define HFCUSB_CHIP_ID 0x16 /* Chip ID register index */ +#define HFCUSB_CIRM 0x00 /* cirm register index */ +#define HFCUSB_USB_SIZE 0x07 /* int length register */ +#define HFCUSB_USB_SIZE_I 0x06 /* iso length register */ +#define HFCUSB_F_CROSS 0x0b /* bit order register */ +#define HFCUSB_CLKDEL 0x37 /* bit delay register */ +#define HFCUSB_CON_HDLC 0xfa /* channel connect register */ +#define HFCUSB_HDLC_PAR 0xfb +#define HFCUSB_SCTRL 0x31 /* S-bus control register (tx) */ +#define HFCUSB_SCTRL_E 0x32 /* same for E and special funcs */ +#define HFCUSB_SCTRL_R 0x33 /* S-bus control register (rx) */ +#define HFCUSB_F_THRES 0x0c /* threshold register */ +#define HFCUSB_FIFO 0x0f /* fifo select register */ +#define HFCUSB_F_USAGE 0x1a /* fifo usage register */ +#define HFCUSB_MST_MODE0 0x14 +#define HFCUSB_MST_MODE1 0x15 +#define HFCUSB_P_DATA 0x1f +#define HFCUSB_INC_RES_F 0x0e +#define HFCUSB_STATES 0x30 + +#define HFCUSB_CHIPID 0x40 /* ID value of HFC-S USB */ + + +/* fifo registers */ +#define HFCUSB_NUM_FIFOS 8 /* maximum number of fifos */ +#define HFCUSB_B1_TX 0 /* index for B1 transmit bulk/int */ +#define HFCUSB_B1_RX 1 /* index for B1 receive bulk/int */ +#define HFCUSB_B2_TX 2 +#define HFCUSB_B2_RX 3 +#define HFCUSB_D_TX 4 +#define HFCUSB_D_RX 5 +#define HFCUSB_PCM_TX 6 +#define HFCUSB_PCM_RX 7 + +/* + * used to switch snd_transfer_mode for different TA modes e.g. the Billion USB TA just + * supports ISO out, while the Cologne Chip EVAL TA just supports BULK out + */ +#define USB_INT 0 +#define USB_BULK 1 +#define USB_ISOC 2 + +#define ISOC_PACKETS_D 8 +#define ISOC_PACKETS_B 8 +#define ISO_BUFFER_SIZE 128 + +/* Fifo flow Control for TX ISO */ +#define SINK_MAX 68 +#define SINK_MIN 48 +#define SINK_DMIN 12 +#define SINK_DMAX 18 +#define BITLINE_INF (-64 * 8) + +/* HFC-S USB register access by Control-URSs */ +#define write_usb(a, b, c) usb_control_msg((a)->dev, (a)->ctrl_out_pipe, 0, 0x40, (c), (b), NULL, 0, HFC_CTRL_TIMEOUT) +#define read_usb(a, b, c) usb_control_msg((a)->dev, (a)->ctrl_in_pipe, 1, 0xC0, 0, (b), (c), 1, HFC_CTRL_TIMEOUT) +#define HFC_CTRL_BUFSIZE 32 + +/* entry and size of output/input control buffer */ +typedef struct { + __u8 hfc_reg; /* register number */ + __u8 reg_val; /* value to be written (or read) */ + int action; /* data for action handler */ +} ctrl_buft; + +/* Debugging Flags */ +#define HFCUSB_DBG_INIT 0x0001 +#define HFCUSB_DBG_STATES 0x0002 +#define HFCUSB_DBG_DCHANNEL 0x0080 +#define HFCUSB_DBG_FIFO_ERR 0x4000 +#define HFCUSB_DBG_VERBOSE_USB 0x8000 + +/* + * URB error codes: + * Used to represent a list of values and their respective symbolic names + */ +struct hfcusb_symbolic_list { + const int num; + const char *name; +}; + +static struct hfcusb_symbolic_list urb_errlist[] = { + {-ENOMEM, "No memory for allocation of internal structures"}, + {-ENOSPC, "The host controller's bandwidth is already consumed"}, + {-ENOENT, "URB was canceled by unlink_urb"}, + {-EXDEV, "ISO transfer only partially completed"}, + {-EAGAIN, "Too match scheduled for the future"}, + {-ENXIO, "URB already queued"}, + {-EFBIG, "Too much ISO frames requested"}, + {-ENOSR, "Buffer error (overrun)"}, + {-EPIPE, "Specified endpoint is stalled (device not responding)"}, + {-EOVERFLOW, "Babble (bad cable?)"}, + {-EPROTO, "Bit-stuff error (bad cable?)"}, + {-EILSEQ, "CRC/Timeout"}, + {-ETIMEDOUT, "NAK (device does not respond)"}, + {-ESHUTDOWN, "Device unplugged"}, + {-1, NULL} +}; + + +/* + * device dependent information to support different + * ISDN Ta's using the HFC-S USB chip + */ + +/* USB descriptor need to contain one of the following EndPoint combination: */ +#define CNF_4INT3ISO 1 // 4 INT IN, 3 ISO OUT +#define CNF_3INT3ISO 2 // 3 INT IN, 3 ISO OUT +#define CNF_4ISO3ISO 3 // 4 ISO IN, 3 ISO OUT +#define CNF_3ISO3ISO 4 // 3 ISO IN, 3 ISO OUT + +#define EP_NUL 1 // Endpoint at this position not allowed +#define EP_NOP 2 // all type of endpoints allowed at this position +#define EP_ISO 3 // Isochron endpoint mandatory at this position +#define EP_BLK 4 // Bulk endpoint mandatory at this position +#define EP_INT 5 // Interrupt endpoint mandatory at this position + +/* + * List of all supported endpoint configuration sets, used to find the + * best matching endpoint configuration within a devices' USB descriptor. + * We need at least 3 RX endpoints, and 3 TX endpoints, either + * INT-in and ISO-out, or ISO-in and ISO-out) + * with 4 RX endpoints even E-Channel logging is possible + */ +static int validconf[][19] = { + // INT in, ISO out config + {EP_NUL, EP_INT, EP_NUL, EP_INT, EP_NUL, EP_INT, EP_NOP, EP_INT, + EP_ISO, EP_NUL, EP_ISO, EP_NUL, EP_ISO, EP_NUL, EP_NUL, EP_NUL, + CNF_4INT3ISO, 2, 1}, + {EP_NUL, EP_INT, EP_NUL, EP_INT, EP_NUL, EP_INT, EP_NUL, EP_NUL, + EP_ISO, EP_NUL, EP_ISO, EP_NUL, EP_ISO, EP_NUL, EP_NUL, EP_NUL, + CNF_3INT3ISO, 2, 0}, + // ISO in, ISO out config + {EP_NUL, EP_NUL, EP_NUL, EP_NUL, EP_NUL, EP_NUL, EP_NUL, EP_NUL, + EP_ISO, EP_ISO, EP_ISO, EP_ISO, EP_ISO, EP_ISO, EP_NOP, EP_ISO, + CNF_4ISO3ISO, 2, 1}, + {EP_NUL, EP_NUL, EP_NUL, EP_NUL, EP_NUL, EP_NUL, EP_NUL, EP_NUL, + EP_ISO, EP_ISO, EP_ISO, EP_ISO, EP_ISO, EP_ISO, EP_NUL, EP_NUL, + CNF_3ISO3ISO, 2, 0}, + {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} // EOL element +}; + +#ifdef CONFIG_HISAX_DEBUG +// string description of chosen config +static char *conf_str[] = { + "4 Interrupt IN + 3 Isochron OUT", + "3 Interrupt IN + 3 Isochron OUT", + "4 Isochron IN + 3 Isochron OUT", + "3 Isochron IN + 3 Isochron OUT" +}; +#endif + +typedef struct { + int vendor; // vendor id + int prod_id; // product id + char *vend_name; // vendor string + __u8 led_scheme; // led display scheme + signed short led_bits[8]; // array of 8 possible LED bitmask settings +} vendor_data; + +#define LED_OFF 0 // no LED support +#define LED_SCHEME1 1 // LED standard scheme +#define LED_SCHEME2 2 // not used yet... + +#define LED_POWER_ON 1 +#define LED_POWER_OFF 2 +#define LED_S0_ON 3 +#define LED_S0_OFF 4 +#define LED_B1_ON 5 +#define LED_B1_OFF 6 +#define LED_B1_DATA 7 +#define LED_B2_ON 8 +#define LED_B2_OFF 9 +#define LED_B2_DATA 10 + +#define LED_NORMAL 0 // LEDs are normal +#define LED_INVERTED 1 // LEDs are inverted + + +#endif // __HFC_USB_H__ diff --git a/drivers/isdn/hisax/hfcscard.c b/drivers/isdn/hisax/hfcscard.c new file mode 100644 index 000000000..91b521949 --- /dev/null +++ b/drivers/isdn/hisax/hfcscard.c @@ -0,0 +1,261 @@ +/* $Id: hfcscard.c,v 1.10.2.4 2004/01/14 16:04:48 keil Exp $ + * + * low level stuff for hfcs based cards (Teles3c, ACER P10) + * + * Author Karsten Keil + * Copyright by Karsten Keil <keil@isdn4linux.de> + * + * This software may be used and distributed according to the terms + * of the GNU General Public License, incorporated herein by reference. + * + */ + +#include <linux/init.h> +#include <linux/isapnp.h> +#include "hisax.h" +#include "hfc_2bds0.h" +#include "isdnl1.h" + +static const char *hfcs_revision = "$Revision: 1.10.2.4 $"; + +static irqreturn_t +hfcs_interrupt(int intno, void *dev_id) +{ + struct IsdnCardState *cs = dev_id; + u_char val, stat; + u_long flags; + + spin_lock_irqsave(&cs->lock, flags); + if ((HFCD_ANYINT | HFCD_BUSY_NBUSY) & + (stat = cs->BC_Read_Reg(cs, HFCD_DATA, HFCD_STAT))) { + val = cs->BC_Read_Reg(cs, HFCD_DATA, HFCD_INT_S1); + if (cs->debug & L1_DEB_ISAC) + debugl1(cs, "HFCS: stat(%02x) s1(%02x)", stat, val); + hfc2bds0_interrupt(cs, val); + } else { + if (cs->debug & L1_DEB_ISAC) + debugl1(cs, "HFCS: irq_no_irq stat(%02x)", stat); + } + spin_unlock_irqrestore(&cs->lock, flags); + return IRQ_HANDLED; +} + +static void +hfcs_Timer(struct timer_list *t) +{ + struct IsdnCardState *cs = from_timer(cs, t, hw.hfcD.timer); + cs->hw.hfcD.timer.expires = jiffies + 75; + /* WD RESET */ +/* WriteReg(cs, HFCD_DATA, HFCD_CTMT, cs->hw.hfcD.ctmt | 0x80); + add_timer(&cs->hw.hfcD.timer); +*/ +} + +static void +release_io_hfcs(struct IsdnCardState *cs) +{ + release2bds0(cs); + del_timer(&cs->hw.hfcD.timer); + if (cs->hw.hfcD.addr) + release_region(cs->hw.hfcD.addr, 2); +} + +static void +reset_hfcs(struct IsdnCardState *cs) +{ + printk(KERN_INFO "HFCS: resetting card\n"); + cs->hw.hfcD.cirm = HFCD_RESET; + if (cs->typ == ISDN_CTYPE_TELES3C) + cs->hw.hfcD.cirm |= HFCD_MEM8K; + cs->BC_Write_Reg(cs, HFCD_DATA, HFCD_CIRM, cs->hw.hfcD.cirm); /* Reset On */ + mdelay(10); + cs->hw.hfcD.cirm = 0; + if (cs->typ == ISDN_CTYPE_TELES3C) + cs->hw.hfcD.cirm |= HFCD_MEM8K; + cs->BC_Write_Reg(cs, HFCD_DATA, HFCD_CIRM, cs->hw.hfcD.cirm); /* Reset Off */ + mdelay(10); + if (cs->typ == ISDN_CTYPE_TELES3C) + cs->hw.hfcD.cirm |= HFCD_INTB; + else if (cs->typ == ISDN_CTYPE_ACERP10) + cs->hw.hfcD.cirm |= HFCD_INTA; + cs->BC_Write_Reg(cs, HFCD_DATA, HFCD_CIRM, cs->hw.hfcD.cirm); + cs->BC_Write_Reg(cs, HFCD_DATA, HFCD_CLKDEL, 0x0e); + cs->BC_Write_Reg(cs, HFCD_DATA, HFCD_TEST, HFCD_AUTO_AWAKE); /* S/T Auto awake */ + cs->hw.hfcD.ctmt = HFCD_TIM25 | HFCD_AUTO_TIMER; + cs->BC_Write_Reg(cs, HFCD_DATA, HFCD_CTMT, cs->hw.hfcD.ctmt); + cs->hw.hfcD.int_m2 = HFCD_IRQ_ENABLE; + cs->hw.hfcD.int_m1 = HFCD_INTS_B1TRANS | HFCD_INTS_B2TRANS | + HFCD_INTS_DTRANS | HFCD_INTS_B1REC | HFCD_INTS_B2REC | + HFCD_INTS_DREC | HFCD_INTS_L1STATE; + cs->BC_Write_Reg(cs, HFCD_DATA, HFCD_INT_M1, cs->hw.hfcD.int_m1); + cs->BC_Write_Reg(cs, HFCD_DATA, HFCD_INT_M2, cs->hw.hfcD.int_m2); + cs->BC_Write_Reg(cs, HFCD_DATA, HFCD_STATES, HFCD_LOAD_STATE | 2); /* HFC ST 2 */ + udelay(10); + cs->BC_Write_Reg(cs, HFCD_DATA, HFCD_STATES, 2); /* HFC ST 2 */ + cs->hw.hfcD.mst_m = HFCD_MASTER; + cs->BC_Write_Reg(cs, HFCD_DATA, HFCD_MST_MODE, cs->hw.hfcD.mst_m); /* HFC Master */ + cs->hw.hfcD.sctrl = 0; + cs->BC_Write_Reg(cs, HFCD_DATA, HFCD_SCTRL, cs->hw.hfcD.sctrl); +} + +static int +hfcs_card_msg(struct IsdnCardState *cs, int mt, void *arg) +{ + u_long flags; + int delay; + + if (cs->debug & L1_DEB_ISAC) + debugl1(cs, "HFCS: card_msg %x", mt); + switch (mt) { + case CARD_RESET: + spin_lock_irqsave(&cs->lock, flags); + reset_hfcs(cs); + spin_unlock_irqrestore(&cs->lock, flags); + return (0); + case CARD_RELEASE: + release_io_hfcs(cs); + return (0); + case CARD_INIT: + delay = (75 * HZ) / 100 + 1; + mod_timer(&cs->hw.hfcD.timer, jiffies + delay); + spin_lock_irqsave(&cs->lock, flags); + reset_hfcs(cs); + init2bds0(cs); + spin_unlock_irqrestore(&cs->lock, flags); + delay = (80 * HZ) / 1000 + 1; + msleep(80); + spin_lock_irqsave(&cs->lock, flags); + cs->hw.hfcD.ctmt |= HFCD_TIM800; + cs->BC_Write_Reg(cs, HFCD_DATA, HFCD_CTMT, cs->hw.hfcD.ctmt); + cs->BC_Write_Reg(cs, HFCD_DATA, HFCD_MST_MODE, cs->hw.hfcD.mst_m); + spin_unlock_irqrestore(&cs->lock, flags); + return (0); + case CARD_TEST: + return (0); + } + return (0); +} + +#ifdef __ISAPNP__ +static struct isapnp_device_id hfc_ids[] = { + { ISAPNP_VENDOR('A', 'N', 'X'), ISAPNP_FUNCTION(0x1114), + ISAPNP_VENDOR('A', 'N', 'X'), ISAPNP_FUNCTION(0x1114), + (unsigned long) "Acer P10" }, + { ISAPNP_VENDOR('B', 'I', 'L'), ISAPNP_FUNCTION(0x0002), + ISAPNP_VENDOR('B', 'I', 'L'), ISAPNP_FUNCTION(0x0002), + (unsigned long) "Billion 2" }, + { ISAPNP_VENDOR('B', 'I', 'L'), ISAPNP_FUNCTION(0x0001), + ISAPNP_VENDOR('B', 'I', 'L'), ISAPNP_FUNCTION(0x0001), + (unsigned long) "Billion 1" }, + { ISAPNP_VENDOR('T', 'A', 'G'), ISAPNP_FUNCTION(0x7410), + ISAPNP_VENDOR('T', 'A', 'G'), ISAPNP_FUNCTION(0x7410), + (unsigned long) "IStar PnP" }, + { ISAPNP_VENDOR('T', 'A', 'G'), ISAPNP_FUNCTION(0x2610), + ISAPNP_VENDOR('T', 'A', 'G'), ISAPNP_FUNCTION(0x2610), + (unsigned long) "Teles 16.3c" }, + { ISAPNP_VENDOR('S', 'F', 'M'), ISAPNP_FUNCTION(0x0001), + ISAPNP_VENDOR('S', 'F', 'M'), ISAPNP_FUNCTION(0x0001), + (unsigned long) "Tornado Tipa C" }, + { ISAPNP_VENDOR('K', 'Y', 'E'), ISAPNP_FUNCTION(0x0001), + ISAPNP_VENDOR('K', 'Y', 'E'), ISAPNP_FUNCTION(0x0001), + (unsigned long) "Genius Speed Surfer" }, + { 0, } +}; + +static struct isapnp_device_id *ipid = &hfc_ids[0]; +static struct pnp_card *pnp_c = NULL; +#endif + +int setup_hfcs(struct IsdnCard *card) +{ + struct IsdnCardState *cs = card->cs; + char tmp[64]; + + strcpy(tmp, hfcs_revision); + printk(KERN_INFO "HiSax: HFC-S driver Rev. %s\n", HiSax_getrev(tmp)); + +#ifdef __ISAPNP__ + if (!card->para[1] && isapnp_present()) { + struct pnp_dev *pnp_d; + while (ipid->card_vendor) { + if ((pnp_c = pnp_find_card(ipid->card_vendor, + ipid->card_device, pnp_c))) { + pnp_d = NULL; + if ((pnp_d = pnp_find_dev(pnp_c, + ipid->vendor, ipid->function, pnp_d))) { + int err; + + printk(KERN_INFO "HiSax: %s detected\n", + (char *)ipid->driver_data); + pnp_disable_dev(pnp_d); + err = pnp_activate_dev(pnp_d); + if (err < 0) { + printk(KERN_WARNING "%s: pnp_activate_dev ret(%d)\n", + __func__, err); + return (0); + } + card->para[1] = pnp_port_start(pnp_d, 0); + card->para[0] = pnp_irq(pnp_d, 0); + if (card->para[0] == -1 || !card->para[1]) { + printk(KERN_ERR "HFC PnP:some resources are missing %ld/%lx\n", + card->para[0], card->para[1]); + pnp_disable_dev(pnp_d); + return (0); + } + break; + } else { + printk(KERN_ERR "HFC PnP: PnP error card found, no device\n"); + } + } + ipid++; + pnp_c = NULL; + } + if (!ipid->card_vendor) { + printk(KERN_INFO "HFC PnP: no ISAPnP card found\n"); + return (0); + } + } +#endif + cs->hw.hfcD.addr = card->para[1] & 0xfffe; + cs->irq = card->para[0]; + cs->hw.hfcD.cip = 0; + cs->hw.hfcD.int_s1 = 0; + cs->hw.hfcD.send = NULL; + cs->bcs[0].hw.hfc.send = NULL; + cs->bcs[1].hw.hfc.send = NULL; + cs->hw.hfcD.dfifosize = 512; + cs->dc.hfcd.ph_state = 0; + cs->hw.hfcD.fifo = 255; + if (cs->typ == ISDN_CTYPE_TELES3C) { + cs->hw.hfcD.bfifosize = 1024 + 512; + } else if (cs->typ == ISDN_CTYPE_ACERP10) { + cs->hw.hfcD.bfifosize = 7 * 1024 + 512; + } else + return (0); + if (!request_region(cs->hw.hfcD.addr, 2, "HFCS isdn")) { + printk(KERN_WARNING + "HiSax: %s config port %x-%x already in use\n", + CardType[card->typ], + cs->hw.hfcD.addr, + cs->hw.hfcD.addr + 2); + return (0); + } + printk(KERN_INFO + "HFCS: defined at 0x%x IRQ %d HZ %d\n", + cs->hw.hfcD.addr, + cs->irq, HZ); + if (cs->typ == ISDN_CTYPE_TELES3C) { + /* Teles 16.3c IO ADR is 0x200 | YY0U (YY Bit 15/14 address) */ + outb(0x00, cs->hw.hfcD.addr); + outb(0x56, cs->hw.hfcD.addr | 1); + } else if (cs->typ == ISDN_CTYPE_ACERP10) { + /* Acer P10 IO ADR is 0x300 */ + outb(0x00, cs->hw.hfcD.addr); + outb(0x57, cs->hw.hfcD.addr | 1); + } + set_cs_func(cs); + timer_setup(&cs->hw.hfcD.timer, hfcs_Timer, 0); + cs->cardmsg = &hfcs_card_msg; + cs->irq_func = &hfcs_interrupt; + return (1); +} diff --git a/drivers/isdn/hisax/hisax.h b/drivers/isdn/hisax/hisax.h new file mode 100644 index 000000000..338d0408b --- /dev/null +++ b/drivers/isdn/hisax/hisax.h @@ -0,0 +1,1352 @@ +/* $Id: hisax.h,v 2.64.2.4 2004/02/11 13:21:33 keil Exp $ + * + * Basic declarations, defines and prototypes + * + * This software may be used and distributed according to the terms + * of the GNU General Public License, incorporated herein by reference. + * + */ +#include <linux/errno.h> +#include <linux/fs.h> +#include <linux/major.h> +#include <asm/io.h> +#include <linux/delay.h> +#include <linux/kernel.h> +#include <linux/signal.h> +#include <linux/slab.h> +#include <linux/mm.h> +#include <linux/mman.h> +#include <linux/interrupt.h> +#include <linux/ioport.h> +#include <linux/timer.h> +#include <linux/wait.h> +#include <linux/isdnif.h> +#include <linux/tty.h> +#include <linux/serial_reg.h> +#include <linux/netdevice.h> + +#define ERROR_STATISTIC + +#define REQUEST 0 +#define CONFIRM 1 +#define INDICATION 2 +#define RESPONSE 3 + +#define HW_ENABLE 0x0000 +#define HW_RESET 0x0004 +#define HW_POWERUP 0x0008 +#define HW_ACTIVATE 0x0010 +#define HW_DEACTIVATE 0x0018 + +#define HW_INFO1 0x0010 +#define HW_INFO2 0x0020 +#define HW_INFO3 0x0030 +#define HW_INFO4 0x0040 +#define HW_INFO4_P8 0x0040 +#define HW_INFO4_P10 0x0048 +#define HW_RSYNC 0x0060 +#define HW_TESTLOOP 0x0070 +#define CARD_RESET 0x00F0 +#define CARD_INIT 0x00F2 +#define CARD_RELEASE 0x00F3 +#define CARD_TEST 0x00F4 +#define CARD_AUX_IND 0x00F5 + +#define PH_ACTIVATE 0x0100 +#define PH_DEACTIVATE 0x0110 +#define PH_DATA 0x0120 +#define PH_PULL 0x0130 +#define PH_TESTLOOP 0x0140 +#define PH_PAUSE 0x0150 +#define MPH_ACTIVATE 0x0180 +#define MPH_DEACTIVATE 0x0190 +#define MPH_INFORMATION 0x01A0 + +#define DL_ESTABLISH 0x0200 +#define DL_RELEASE 0x0210 +#define DL_DATA 0x0220 +#define DL_FLUSH 0x0224 +#define DL_UNIT_DATA 0x0230 + +#define MDL_BC_RELEASE 0x0278 // Formula-n enter:now +#define MDL_BC_ASSIGN 0x027C // Formula-n enter:now +#define MDL_ASSIGN 0x0280 +#define MDL_REMOVE 0x0284 +#define MDL_ERROR 0x0288 +#define MDL_INFO_SETUP 0x02E0 +#define MDL_INFO_CONN 0x02E4 +#define MDL_INFO_REL 0x02E8 + +#define CC_SETUP 0x0300 +#define CC_RESUME 0x0304 +#define CC_MORE_INFO 0x0310 +#define CC_IGNORE 0x0320 +#define CC_REJECT 0x0324 +#define CC_SETUP_COMPL 0x0330 +#define CC_PROCEEDING 0x0340 +#define CC_ALERTING 0x0344 +#define CC_PROGRESS 0x0348 +#define CC_CONNECT 0x0350 +#define CC_CHARGE 0x0354 +#define CC_NOTIFY 0x0358 +#define CC_DISCONNECT 0x0360 +#define CC_RELEASE 0x0368 +#define CC_SUSPEND 0x0370 +#define CC_PROCEED_SEND 0x0374 +#define CC_REDIR 0x0378 +#define CC_T302 0x0382 +#define CC_T303 0x0383 +#define CC_T304 0x0384 +#define CC_T305 0x0385 +#define CC_T308_1 0x0388 +#define CC_T308_2 0x038A +#define CC_T309 0x0309 +#define CC_T310 0x0390 +#define CC_T313 0x0393 +#define CC_T318 0x0398 +#define CC_T319 0x0399 +#define CC_TSPID 0x03A0 +#define CC_NOSETUP_RSP 0x03E0 +#define CC_SETUP_ERR 0x03E1 +#define CC_SUSPEND_ERR 0x03E2 +#define CC_RESUME_ERR 0x03E3 +#define CC_CONNECT_ERR 0x03E4 +#define CC_RELEASE_ERR 0x03E5 +#define CC_RESTART 0x03F4 +#define CC_TDSS1_IO 0x13F4 /* DSS1 IO user timer */ +#define CC_TNI1_IO 0x13F5 /* NI1 IO user timer */ + +/* define maximum number of possible waiting incoming calls */ +#define MAX_WAITING_CALLS 2 + + +#ifdef __KERNEL__ + +extern const char *CardType[]; +extern int nrcards; + +extern const char *l1_revision; +extern const char *l2_revision; +extern const char *l3_revision; +extern const char *lli_revision; +extern const char *tei_revision; + +/* include l3dss1 & ni1 specific process structures, but no other defines */ +#ifdef CONFIG_HISAX_EURO +#define l3dss1_process +#include "l3dss1.h" +#undef l3dss1_process +#endif /* CONFIG_HISAX_EURO */ + +#ifdef CONFIG_HISAX_NI1 +#define l3ni1_process +#include "l3ni1.h" +#undef l3ni1_process +#endif /* CONFIG_HISAX_NI1 */ + +#define MAX_DFRAME_LEN 260 +#define MAX_DFRAME_LEN_L1 300 +#define HSCX_BUFMAX 4096 +#define MAX_DATA_SIZE (HSCX_BUFMAX - 4) +#define MAX_DATA_MEM (HSCX_BUFMAX + 64) +#define RAW_BUFMAX (((HSCX_BUFMAX * 6) / 5) + 5) +#define MAX_HEADER_LEN 4 +#define MAX_WINDOW 8 +#define MAX_MON_FRAME 32 +#define MAX_DLOG_SPACE 2048 +#define MAX_BLOG_SPACE 256 + +/* #define I4L_IRQ_FLAG SA_INTERRUPT */ +#define I4L_IRQ_FLAG 0 + +/* + * Statemachine + */ + +struct FsmInst; + +typedef void (*FSMFNPTR)(struct FsmInst *, int, void *); + +struct Fsm { + FSMFNPTR *jumpmatrix; + int state_count, event_count; + char **strEvent, **strState; +}; + +struct FsmInst { + struct Fsm *fsm; + int state; + int debug; + void *userdata; + int userint; + void (*printdebug) (struct FsmInst *, char *, ...); +}; + +struct FsmNode { + int state, event; + void (*routine) (struct FsmInst *, int, void *); +}; + +struct FsmTimer { + struct FsmInst *fi; + struct timer_list tl; + int event; + void *arg; +}; + +struct L3Timer { + struct l3_process *pc; + struct timer_list tl; + int event; +}; + +#define FLG_L1_ACTIVATING 1 +#define FLG_L1_ACTIVATED 2 +#define FLG_L1_DEACTTIMER 3 +#define FLG_L1_ACTTIMER 4 +#define FLG_L1_T3RUN 5 +#define FLG_L1_PULL_REQ 6 +#define FLG_L1_UINT 7 + +struct Layer1 { + void *hardware; + struct BCState *bcs; + struct PStack **stlistp; + unsigned long Flags; + struct FsmInst l1m; + struct FsmTimer timer; + void (*l1l2) (struct PStack *, int, void *); + void (*l1hw) (struct PStack *, int, void *); + void (*l1tei) (struct PStack *, int, void *); + int mode, bc; + int delay; +}; + +#define GROUP_TEI 127 +#define TEI_SAPI 63 +#define CTRL_SAPI 0 +#define PACKET_NOACK 7 + +/* Layer2 Flags */ + +#define FLG_LAPB 0 +#define FLG_LAPD 1 +#define FLG_ORIG 2 +#define FLG_MOD128 3 +#define FLG_PEND_REL 4 +#define FLG_L3_INIT 5 +#define FLG_T200_RUN 6 +#define FLG_ACK_PEND 7 +#define FLG_REJEXC 8 +#define FLG_OWN_BUSY 9 +#define FLG_PEER_BUSY 10 +#define FLG_DCHAN_BUSY 11 +#define FLG_L1_ACTIV 12 +#define FLG_ESTAB_PEND 13 +#define FLG_PTP 14 +#define FLG_FIXED_TEI 15 +#define FLG_L2BLOCK 16 + +struct Layer2 { + int tei; + int sap; + int maxlen; + u_long flag; + spinlock_t lock; + u_int vs, va, vr; + int rc; + unsigned int window; + unsigned int sow; + struct sk_buff *windowar[MAX_WINDOW]; + struct sk_buff_head i_queue; + struct sk_buff_head ui_queue; + void (*l2l1) (struct PStack *, int, void *); + void (*l2l3) (struct PStack *, int, void *); + void (*l2tei) (struct PStack *, int, void *); + struct FsmInst l2m; + struct FsmTimer t200, t203; + int T200, N200, T203; + int debug; + char debug_id[16]; +}; + +struct Layer3 { + void (*l3l4) (struct PStack *, int, void *); + void (*l3ml3) (struct PStack *, int, void *); + void (*l3l2) (struct PStack *, int, void *); + struct FsmInst l3m; + struct FsmTimer l3m_timer; + struct sk_buff_head squeue; + struct l3_process *proc; + struct l3_process *global; + int N303; + int debug; + char debug_id[8]; +}; + +struct LLInterface { + void (*l4l3) (struct PStack *, int, void *); + int (*l4l3_proto) (struct PStack *, isdn_ctrl *); + void *userdata; + u_long flag; +}; + +#define FLG_LLI_L1WAKEUP 1 +#define FLG_LLI_L2WAKEUP 2 + +struct Management { + int ri; + struct FsmInst tei_m; + struct FsmTimer t202; + int T202, N202, debug; + void (*layer) (struct PStack *, int, void *); +}; + +#define NO_CAUSE 254 + +struct Param { + u_char cause; + u_char loc; + u_char diag[6]; + int bchannel; + int chargeinfo; + int spv; /* SPV Flag */ + setup_parm setup; /* from isdnif.h numbers and Serviceindicator */ + u_char moderate; /* transfer mode and rate (bearer octet 4) */ +}; + + +struct PStack { + struct PStack *next; + struct Layer1 l1; + struct Layer2 l2; + struct Layer3 l3; + struct LLInterface lli; + struct Management ma; + int protocol; /* EDSS1, 1TR6 or NI1 */ + + /* protocol specific data fields */ + union + { u_char uuuu; /* only as dummy */ +#ifdef CONFIG_HISAX_EURO + dss1_stk_priv dss1; /* private dss1 data */ +#endif /* CONFIG_HISAX_EURO */ +#ifdef CONFIG_HISAX_NI1 + ni1_stk_priv ni1; /* private ni1 data */ +#endif /* CONFIG_HISAX_NI1 */ + } prot; +}; + +struct l3_process { + int callref; + int state; + struct L3Timer timer; + int N303; + int debug; + struct Param para; + struct Channel *chan; + struct PStack *st; + struct l3_process *next; + ulong redir_result; + + /* protocol specific data fields */ + union + { u_char uuuu; /* only when euro not defined, avoiding empty union */ +#ifdef CONFIG_HISAX_EURO + dss1_proc_priv dss1; /* private dss1 data */ +#endif /* CONFIG_HISAX_EURO */ +#ifdef CONFIG_HISAX_NI1 + ni1_proc_priv ni1; /* private ni1 data */ +#endif /* CONFIG_HISAX_NI1 */ + } prot; +}; + +struct hscx_hw { + int hscx; + int rcvidx; + int count; /* Current skb sent count */ + u_char *rcvbuf; /* B-Channel receive Buffer */ + u_char tsaxr0; + u_char tsaxr1; +}; + +struct w6692B_hw { + int bchan; + int rcvidx; + int count; /* Current skb sent count */ + u_char *rcvbuf; /* B-Channel receive Buffer */ +}; + +struct isar_reg { + unsigned long Flags; + volatile u_char bstat; + volatile u_char iis; + volatile u_char cmsb; + volatile u_char clsb; + volatile u_char par[8]; +}; + +struct isar_hw { + int dpath; + int rcvidx; + int txcnt; + int mml; + u_char state; + u_char cmd; + u_char mod; + u_char newcmd; + u_char newmod; + char try_mod; + struct timer_list ftimer; + u_char *rcvbuf; /* B-Channel receive Buffer */ + u_char conmsg[16]; + struct isar_reg *reg; +}; + +struct hdlc_stat_reg { +#ifdef __BIG_ENDIAN + u_char fill; + u_char mode; + u_char xml; + u_char cmd; +#else + u_char cmd; + u_char xml; + u_char mode; + u_char fill; +#endif +} __attribute__((packed)); + +struct hdlc_hw { + union { + u_int ctrl; + struct hdlc_stat_reg sr; + } ctrl; + u_int stat; + int rcvidx; + int count; /* Current skb sent count */ + u_char *rcvbuf; /* B-Channel receive Buffer */ +}; + +struct hfcB_hw { + unsigned int *send; + int f1; + int f2; +}; + +struct tiger_hw { + u_int *send; + u_int *s_irq; + u_int *s_end; + u_int *sendp; + u_int *rec; + int free; + u_char *rcvbuf; + u_char *sendbuf; + u_char *sp; + int sendcnt; + u_int s_tot; + u_int r_bitcnt; + u_int r_tot; + u_int r_err; + u_int r_fcs; + u_char r_state; + u_char r_one; + u_char r_val; + u_char s_state; +}; + +struct amd7930_hw { + u_char *tx_buff; + u_char *rv_buff; + int rv_buff_in; + int rv_buff_out; + struct sk_buff *rv_skb; + struct hdlc_state *hdlc_state; + struct work_struct tq_rcv; + struct work_struct tq_xmt; +}; + +#define BC_FLG_INIT 1 +#define BC_FLG_ACTIV 2 +#define BC_FLG_BUSY 3 +#define BC_FLG_NOFRAME 4 +#define BC_FLG_HALF 5 +#define BC_FLG_EMPTY 6 +#define BC_FLG_ORIG 7 +#define BC_FLG_DLEETX 8 +#define BC_FLG_LASTDLE 9 +#define BC_FLG_FIRST 10 +#define BC_FLG_LASTDATA 11 +#define BC_FLG_NMD_DATA 12 +#define BC_FLG_FTI_RUN 13 +#define BC_FLG_LL_OK 14 +#define BC_FLG_LL_CONN 15 +#define BC_FLG_FTI_FTS 16 +#define BC_FLG_FRH_WAIT 17 + +#define L1_MODE_NULL 0 +#define L1_MODE_TRANS 1 +#define L1_MODE_HDLC 2 +#define L1_MODE_EXTRN 3 +#define L1_MODE_HDLC_56K 4 +#define L1_MODE_MODEM 7 +#define L1_MODE_V32 8 +#define L1_MODE_FAX 9 + +struct BCState { + int channel; + int mode; + u_long Flag; + struct IsdnCardState *cs; + int tx_cnt; /* B-Channel transmit counter */ + struct sk_buff *tx_skb; /* B-Channel transmit Buffer */ + struct sk_buff_head rqueue; /* B-Channel receive Queue */ + struct sk_buff_head squeue; /* B-Channel send Queue */ + int ackcnt; + spinlock_t aclock; + struct PStack *st; + u_char *blog; + u_char *conmsg; + struct timer_list transbusy; + struct work_struct tqueue; + u_long event; + int (*BC_SetStack) (struct PStack *, struct BCState *); + void (*BC_Close) (struct BCState *); +#ifdef ERROR_STATISTIC + int err_crc; + int err_tx; + int err_rdo; + int err_inv; +#endif + union { + struct hscx_hw hscx; + struct hdlc_hw hdlc; + struct isar_hw isar; + struct hfcB_hw hfc; + struct tiger_hw tiger; + struct amd7930_hw amd7930; + struct w6692B_hw w6692; + struct hisax_b_if *b_if; + } hw; +}; + +struct Channel { + struct PStack *b_st, *d_st; + struct IsdnCardState *cs; + struct BCState *bcs; + int chan; + int incoming; + struct FsmInst fi; + struct FsmTimer drel_timer, dial_timer; + int debug; + int l2_protocol, l2_active_protocol; + int l3_protocol; + int data_open; + struct l3_process *proc; + setup_parm setup; /* from isdnif.h numbers and Serviceindicator */ + u_long Flags; /* for remembering action done in l4 */ + int leased; +}; + +struct elsa_hw { + struct pci_dev *dev; + unsigned long base; + unsigned int cfg; + unsigned int ctrl; + unsigned int ale; + unsigned int isac; + unsigned int itac; + unsigned int hscx; + unsigned int trig; + unsigned int timer; + unsigned int counter; + unsigned int status; + struct timer_list tl; + unsigned int MFlag; + struct BCState *bcs; + u_char *transbuf; + u_char *rcvbuf; + unsigned int transp; + unsigned int rcvp; + unsigned int transcnt; + unsigned int rcvcnt; + u_char IER; + u_char FCR; + u_char LCR; + u_char MCR; + u_char ctrl_reg; +}; + +struct teles3_hw { + unsigned int cfg_reg; + signed int isac; + signed int hscx[2]; + signed int isacfifo; + signed int hscxfifo[2]; +}; + +struct teles0_hw { + unsigned int cfg_reg; + void __iomem *membase; + unsigned long phymem; +}; + +struct avm_hw { + unsigned int cfg_reg; + unsigned int isac; + unsigned int hscx[2]; + unsigned int isacfifo; + unsigned int hscxfifo[2]; + unsigned int counter; + struct pci_dev *dev; +}; + +struct ix1_hw { + unsigned int cfg_reg; + unsigned int isac_ale; + unsigned int isac; + unsigned int hscx_ale; + unsigned int hscx; +}; + +struct diva_hw { + unsigned long cfg_reg; + unsigned long pci_cfg; + unsigned int ctrl; + unsigned long isac_adr; + unsigned int isac; + unsigned long hscx_adr; + unsigned int hscx; + unsigned int status; + struct timer_list tl; + u_char ctrl_reg; + struct pci_dev *dev; +}; + +struct asus_hw { + unsigned int cfg_reg; + unsigned int adr; + unsigned int isac; + unsigned int hscx; + unsigned int u7; + unsigned int pots; +}; + + +struct hfc_hw { + unsigned int addr; + unsigned int fifosize; + unsigned char cirm; + unsigned char ctmt; + unsigned char cip; + u_char isac_spcr; + struct timer_list timer; +}; + +struct sedl_hw { + unsigned int cfg_reg; + unsigned int adr; + unsigned int isac; + unsigned int hscx; + unsigned int reset_on; + unsigned int reset_off; + struct isar_reg isar; + unsigned int chip; + unsigned int bus; + struct pci_dev *dev; +}; + +struct spt_hw { + unsigned int cfg_reg; + unsigned int isac; + unsigned int hscx[2]; + unsigned char res_irq; +}; + +struct mic_hw { + unsigned int cfg_reg; + unsigned int adr; + unsigned int isac; + unsigned int hscx; +}; + +struct njet_hw { + unsigned long base; + unsigned int isac; + unsigned int auxa; + unsigned char auxd; + unsigned char dmactrl; + unsigned char ctrl_reg; + unsigned char irqmask0; + unsigned char irqstat0; + unsigned char last_is0; + struct pci_dev *dev; +}; + +struct hfcPCI_hw { + unsigned char cirm; + unsigned char ctmt; + unsigned char conn; + unsigned char mst_m; + unsigned char int_m1; + unsigned char int_m2; + unsigned char int_s1; + unsigned char sctrl; + unsigned char sctrl_r; + unsigned char sctrl_e; + unsigned char trm; + unsigned char stat; + unsigned char fifo; + unsigned char fifo_en; + unsigned char bswapped; + unsigned char nt_mode; + int nt_timer; + struct pci_dev *dev; + unsigned char *pci_io; /* start of PCI IO memory */ + dma_addr_t dma; /* dma handle for Fifos */ + void *fifos; /* FIFO memory */ + int last_bfifo_cnt[2]; /* marker saving last b-fifo frame count */ + struct timer_list timer; +}; + +struct hfcSX_hw { + unsigned long base; + unsigned char cirm; + unsigned char ctmt; + unsigned char conn; + unsigned char mst_m; + unsigned char int_m1; + unsigned char int_m2; + unsigned char int_s1; + unsigned char sctrl; + unsigned char sctrl_r; + unsigned char sctrl_e; + unsigned char trm; + unsigned char stat; + unsigned char fifo; + unsigned char bswapped; + unsigned char nt_mode; + unsigned char chip; + int b_fifo_size; + unsigned char last_fifo; + void *extra; + int nt_timer; + struct timer_list timer; +}; + +struct hfcD_hw { + unsigned int addr; + unsigned int bfifosize; + unsigned int dfifosize; + unsigned char cirm; + unsigned char ctmt; + unsigned char cip; + unsigned char conn; + unsigned char mst_m; + unsigned char int_m1; + unsigned char int_m2; + unsigned char int_s1; + unsigned char sctrl; + unsigned char stat; + unsigned char fifo; + unsigned char f1; + unsigned char f2; + unsigned int *send; + struct timer_list timer; +}; + +struct isurf_hw { + unsigned int reset; + unsigned long phymem; + void __iomem *isac; + void __iomem *isar; + struct isar_reg isar_r; +}; + +struct saphir_hw { + struct pci_dev *dev; + unsigned int cfg_reg; + unsigned int ale; + unsigned int isac; + unsigned int hscx; + struct timer_list timer; +}; + +struct bkm_hw { + struct pci_dev *dev; + unsigned long base; + /* A4T stuff */ + unsigned long isac_adr; + unsigned int isac_ale; + unsigned long jade_adr; + unsigned int jade_ale; + /* Scitel Quadro stuff */ + unsigned long plx_adr; + unsigned long data_adr; +}; + +struct gazel_hw { + struct pci_dev *dev; + unsigned int cfg_reg; + unsigned int pciaddr[2]; + signed int ipac; + signed int isac; + signed int hscx[2]; + signed int isacfifo; + signed int hscxfifo[2]; + unsigned char timeslot; + unsigned char iom2; +}; + +struct w6692_hw { + struct pci_dev *dev; + unsigned int iobase; + struct timer_list timer; +}; + +struct arcofi_msg { + struct arcofi_msg *next; + u_char receive; + u_char len; + u_char msg[10]; +}; + +struct isac_chip { + int ph_state; + u_char *mon_tx; + u_char *mon_rx; + int mon_txp; + int mon_txc; + int mon_rxp; + struct arcofi_msg *arcofi_list; + struct timer_list arcofitimer; + wait_queue_head_t arcofi_wait; + u_char arcofi_bc; + u_char arcofi_state; + u_char mocr; + u_char adf2; +}; + +struct hfcd_chip { + int ph_state; +}; + +struct hfcpci_chip { + int ph_state; +}; + +struct hfcsx_chip { + int ph_state; +}; + +struct w6692_chip { + int ph_state; +}; + +struct amd7930_chip { + u_char lmr1; + u_char ph_state; + u_char old_state; + u_char flg_t3; + unsigned int tx_xmtlen; + struct timer_list timer3; + void (*ph_command) (struct IsdnCardState *, u_char, char *); + void (*setIrqMask) (struct IsdnCardState *, u_char); +}; + +struct icc_chip { + int ph_state; + u_char *mon_tx; + u_char *mon_rx; + int mon_txp; + int mon_txc; + int mon_rxp; + struct arcofi_msg *arcofi_list; + struct timer_list arcofitimer; + wait_queue_head_t arcofi_wait; + u_char arcofi_bc; + u_char arcofi_state; + u_char mocr; + u_char adf2; +}; + +#define HW_IOM1 0 +#define HW_IPAC 1 +#define HW_ISAR 2 +#define HW_ARCOFI 3 +#define FLG_TWO_DCHAN 4 +#define FLG_L1_DBUSY 5 +#define FLG_DBUSY_TIMER 6 +#define FLG_LOCK_ATOMIC 7 +#define FLG_ARCOFI_TIMER 8 +#define FLG_ARCOFI_ERROR 9 +#define FLG_HW_L1_UINT 10 + +struct IsdnCardState { + spinlock_t lock; + u_char typ; + u_char subtyp; + int protocol; + u_int irq; + u_long irq_flags; + u_long HW_Flags; + int *busy_flag; + int chanlimit; /* limited number of B-chans to use */ + int logecho; /* log echo if supported by card */ + union { + struct elsa_hw elsa; + struct teles0_hw teles0; + struct teles3_hw teles3; + struct avm_hw avm; + struct ix1_hw ix1; + struct diva_hw diva; + struct asus_hw asus; + struct hfc_hw hfc; + struct sedl_hw sedl; + struct spt_hw spt; + struct mic_hw mic; + struct njet_hw njet; + struct hfcD_hw hfcD; + struct hfcPCI_hw hfcpci; + struct hfcSX_hw hfcsx; + struct ix1_hw niccy; + struct isurf_hw isurf; + struct saphir_hw saphir; + struct bkm_hw ax; + struct gazel_hw gazel; + struct w6692_hw w6692; + struct hisax_d_if *hisax_d_if; + } hw; + int myid; + isdn_if iif; + spinlock_t statlock; + u_char *status_buf; + u_char *status_read; + u_char *status_write; + u_char *status_end; + u_char (*readisac) (struct IsdnCardState *, u_char); + void (*writeisac) (struct IsdnCardState *, u_char, u_char); + void (*readisacfifo) (struct IsdnCardState *, u_char *, int); + void (*writeisacfifo) (struct IsdnCardState *, u_char *, int); + u_char (*BC_Read_Reg) (struct IsdnCardState *, int, u_char); + void (*BC_Write_Reg) (struct IsdnCardState *, int, u_char, u_char); + void (*BC_Send_Data) (struct BCState *); + int (*cardmsg) (struct IsdnCardState *, int, void *); + void (*setstack_d) (struct PStack *, struct IsdnCardState *); + void (*DC_Close) (struct IsdnCardState *); + irq_handler_t irq_func; + int (*auxcmd) (struct IsdnCardState *, isdn_ctrl *); + struct Channel channel[2 + MAX_WAITING_CALLS]; + struct BCState bcs[2 + MAX_WAITING_CALLS]; + struct PStack *stlist; + struct sk_buff_head rq, sq; /* D-channel queues */ + int cardnr; + char *dlog; + int debug; + union { + struct isac_chip isac; + struct hfcd_chip hfcd; + struct hfcpci_chip hfcpci; + struct hfcsx_chip hfcsx; + struct w6692_chip w6692; + struct amd7930_chip amd7930; + struct icc_chip icc; + } dc; + u_char *rcvbuf; + int rcvidx; + struct sk_buff *tx_skb; + int tx_cnt; + u_long event; + struct work_struct tqueue; + struct timer_list dbusytimer; + unsigned int irq_cnt; +#ifdef ERROR_STATISTIC + int err_crc; + int err_tx; + int err_rx; +#endif +}; + + +#define schedule_event(s, ev) do { test_and_set_bit(ev, &s->event); schedule_work(&s->tqueue); } while (0) + +#define MON0_RX 1 +#define MON1_RX 2 +#define MON0_TX 4 +#define MON1_TX 8 + + +#ifdef ISDN_CHIP_ISAC +#undef ISDN_CHIP_ISAC +#endif + +#ifdef CONFIG_HISAX_16_0 +#define CARD_TELES0 1 +#ifndef ISDN_CHIP_ISAC +#define ISDN_CHIP_ISAC 1 +#endif +#else +#define CARD_TELES0 0 +#endif + +#ifdef CONFIG_HISAX_16_3 +#define CARD_TELES3 1 +#ifndef ISDN_CHIP_ISAC +#define ISDN_CHIP_ISAC 1 +#endif +#else +#define CARD_TELES3 0 +#endif + +#ifdef CONFIG_HISAX_TELESPCI +#define CARD_TELESPCI 1 +#ifndef ISDN_CHIP_ISAC +#define ISDN_CHIP_ISAC 1 +#endif +#else +#define CARD_TELESPCI 0 +#endif + +#ifdef CONFIG_HISAX_AVM_A1 +#define CARD_AVM_A1 1 +#ifndef ISDN_CHIP_ISAC +#define ISDN_CHIP_ISAC 1 +#endif +#else +#define CARD_AVM_A1 0 +#endif + +#ifdef CONFIG_HISAX_AVM_A1_PCMCIA +#define CARD_AVM_A1_PCMCIA 1 +#ifndef ISDN_CHIP_ISAC +#define ISDN_CHIP_ISAC 1 +#endif +#else +#define CARD_AVM_A1_PCMCIA 0 +#endif + +#ifdef CONFIG_HISAX_FRITZPCI +#define CARD_FRITZPCI 1 +#ifndef ISDN_CHIP_ISAC +#define ISDN_CHIP_ISAC 1 +#endif +#else +#define CARD_FRITZPCI 0 +#endif + +#ifdef CONFIG_HISAX_ELSA +#define CARD_ELSA 1 +#ifndef ISDN_CHIP_ISAC +#define ISDN_CHIP_ISAC 1 +#endif +#else +#define CARD_ELSA 0 +#endif + +#ifdef CONFIG_HISAX_IX1MICROR2 +#define CARD_IX1MICROR2 1 +#ifndef ISDN_CHIP_ISAC +#define ISDN_CHIP_ISAC 1 +#endif +#else +#define CARD_IX1MICROR2 0 +#endif + +#ifdef CONFIG_HISAX_DIEHLDIVA +#define CARD_DIEHLDIVA 1 +#ifndef ISDN_CHIP_ISAC +#define ISDN_CHIP_ISAC 1 +#endif +#else +#define CARD_DIEHLDIVA 0 +#endif + +#ifdef CONFIG_HISAX_ASUSCOM +#define CARD_ASUSCOM 1 +#ifndef ISDN_CHIP_ISAC +#define ISDN_CHIP_ISAC 1 +#endif +#else +#define CARD_ASUSCOM 0 +#endif + +#ifdef CONFIG_HISAX_TELEINT +#define CARD_TELEINT 1 +#ifndef ISDN_CHIP_ISAC +#define ISDN_CHIP_ISAC 1 +#endif +#else +#define CARD_TELEINT 0 +#endif + +#ifdef CONFIG_HISAX_SEDLBAUER +#define CARD_SEDLBAUER 1 +#ifndef ISDN_CHIP_ISAC +#define ISDN_CHIP_ISAC 1 +#endif +#else +#define CARD_SEDLBAUER 0 +#endif + +#ifdef CONFIG_HISAX_SPORTSTER +#define CARD_SPORTSTER 1 +#ifndef ISDN_CHIP_ISAC +#define ISDN_CHIP_ISAC 1 +#endif +#else +#define CARD_SPORTSTER 0 +#endif + +#ifdef CONFIG_HISAX_MIC +#define CARD_MIC 1 +#ifndef ISDN_CHIP_ISAC +#define ISDN_CHIP_ISAC 1 +#endif +#else +#define CARD_MIC 0 +#endif + +#ifdef CONFIG_HISAX_NETJET +#define CARD_NETJET_S 1 +#ifndef ISDN_CHIP_ISAC +#define ISDN_CHIP_ISAC 1 +#endif +#else +#define CARD_NETJET_S 0 +#endif + +#ifdef CONFIG_HISAX_HFCS +#define CARD_HFCS 1 +#else +#define CARD_HFCS 0 +#endif + +#ifdef CONFIG_HISAX_HFC_PCI +#define CARD_HFC_PCI 1 +#else +#define CARD_HFC_PCI 0 +#endif + +#ifdef CONFIG_HISAX_HFC_SX +#define CARD_HFC_SX 1 +#else +#define CARD_HFC_SX 0 +#endif + +#ifdef CONFIG_HISAX_NICCY +#define CARD_NICCY 1 +#ifndef ISDN_CHIP_ISAC +#define ISDN_CHIP_ISAC 1 +#endif +#else +#define CARD_NICCY 0 +#endif + +#ifdef CONFIG_HISAX_ISURF +#define CARD_ISURF 1 +#ifndef ISDN_CHIP_ISAC +#define ISDN_CHIP_ISAC 1 +#endif +#else +#define CARD_ISURF 0 +#endif + +#ifdef CONFIG_HISAX_S0BOX +#define CARD_S0BOX 1 +#ifndef ISDN_CHIP_ISAC +#define ISDN_CHIP_ISAC 1 +#endif +#else +#define CARD_S0BOX 0 +#endif + +#ifdef CONFIG_HISAX_HSTSAPHIR +#define CARD_HSTSAPHIR 1 +#ifndef ISDN_CHIP_ISAC +#define ISDN_CHIP_ISAC 1 +#endif +#else +#define CARD_HSTSAPHIR 0 +#endif + +#ifdef CONFIG_HISAX_BKM_A4T +#define CARD_BKM_A4T 1 +#ifndef ISDN_CHIP_ISAC +#define ISDN_CHIP_ISAC 1 +#endif +#else +#define CARD_BKM_A4T 0 +#endif + +#ifdef CONFIG_HISAX_SCT_QUADRO +#define CARD_SCT_QUADRO 1 +#ifndef ISDN_CHIP_ISAC +#define ISDN_CHIP_ISAC 1 +#endif +#else +#define CARD_SCT_QUADRO 0 +#endif + +#ifdef CONFIG_HISAX_GAZEL +#define CARD_GAZEL 1 +#ifndef ISDN_CHIP_ISAC +#define ISDN_CHIP_ISAC 1 +#endif +#else +#define CARD_GAZEL 0 +#endif + +#ifdef CONFIG_HISAX_W6692 +#define CARD_W6692 1 +#ifndef ISDN_CHIP_W6692 +#define ISDN_CHIP_W6692 1 +#endif +#else +#define CARD_W6692 0 +#endif + +#ifdef CONFIG_HISAX_NETJET_U +#define CARD_NETJET_U 1 +#ifndef ISDN_CHIP_ICC +#define ISDN_CHIP_ICC 1 +#endif +#ifndef HISAX_UINTERFACE +#define HISAX_UINTERFACE 1 +#endif +#else +#define CARD_NETJET_U 0 +#endif + +#ifdef CONFIG_HISAX_ENTERNOW_PCI +#define CARD_FN_ENTERNOW_PCI 1 +#else +#define CARD_FN_ENTERNOW_PCI 0 +#endif + +#define TEI_PER_CARD 1 + +/* L1 Debug */ +#define L1_DEB_WARN 0x01 +#define L1_DEB_INTSTAT 0x02 +#define L1_DEB_ISAC 0x04 +#define L1_DEB_ISAC_FIFO 0x08 +#define L1_DEB_HSCX 0x10 +#define L1_DEB_HSCX_FIFO 0x20 +#define L1_DEB_LAPD 0x40 +#define L1_DEB_IPAC 0x80 +#define L1_DEB_RECEIVE_FRAME 0x100 +#define L1_DEB_MONITOR 0x200 +#define DEB_DLOG_HEX 0x400 +#define DEB_DLOG_VERBOSE 0x800 + +#define L2FRAME_DEBUG + +#ifdef L2FRAME_DEBUG +extern void Logl2Frame(struct IsdnCardState *cs, struct sk_buff *skb, char *buf, int dir); +#endif + +#include "hisax_cfg.h" + +void init_bcstate(struct IsdnCardState *cs, int bc); + +void setstack_HiSax(struct PStack *st, struct IsdnCardState *cs); +void HiSax_addlist(struct IsdnCardState *sp, struct PStack *st); +void HiSax_rmlist(struct IsdnCardState *sp, struct PStack *st); + +void setstack_l1_B(struct PStack *st); + +void setstack_tei(struct PStack *st); +void setstack_manager(struct PStack *st); + +void setstack_isdnl2(struct PStack *st, char *debug_id); +void releasestack_isdnl2(struct PStack *st); +void setstack_transl2(struct PStack *st); +void releasestack_transl2(struct PStack *st); +void lli_writewakeup(struct PStack *st, int len); + +void setstack_l3dc(struct PStack *st, struct Channel *chanp); +void setstack_l3bc(struct PStack *st, struct Channel *chanp); +void releasestack_isdnl3(struct PStack *st); + +u_char *findie(u_char *p, int size, u_char ie, int wanted_set); +int getcallref(u_char *p); +int newcallref(void); + +int FsmNew(struct Fsm *fsm, struct FsmNode *fnlist, int fncount); +void FsmFree(struct Fsm *fsm); +int FsmEvent(struct FsmInst *fi, int event, void *arg); +void FsmChangeState(struct FsmInst *fi, int newstate); +void FsmInitTimer(struct FsmInst *fi, struct FsmTimer *ft); +int FsmAddTimer(struct FsmTimer *ft, int millisec, int event, + void *arg, int where); +void FsmRestartTimer(struct FsmTimer *ft, int millisec, int event, + void *arg, int where); +void FsmDelTimer(struct FsmTimer *ft, int where); +int jiftime(char *s, long mark); + +int HiSax_command(isdn_ctrl *ic); +int HiSax_writebuf_skb(int id, int chan, int ack, struct sk_buff *skb); +__printf(3, 4) +void HiSax_putstatus(struct IsdnCardState *cs, char *head, const char *fmt, ...); +__printf(3, 0) +void VHiSax_putstatus(struct IsdnCardState *cs, char *head, const char *fmt, va_list args); +void HiSax_reportcard(int cardnr, int sel); +int QuickHex(char *txt, u_char *p, int cnt); +void LogFrame(struct IsdnCardState *cs, u_char *p, int size); +void dlogframe(struct IsdnCardState *cs, struct sk_buff *skb, int dir); +void iecpy(u_char *dest, u_char *iestart, int ieoffset); +#endif /* __KERNEL__ */ + +/* + * Busywait delay for `jiffs' jiffies + */ +#define HZDELAY(jiffs) do { \ + int tout = jiffs; \ + \ + while (tout--) { \ + int loops = USEC_PER_SEC / HZ; \ + while (loops--) \ + udelay(1); \ + } \ + } while (0) + +int ll_run(struct IsdnCardState *cs, int addfeatures); +int CallcNew(void); +void CallcFree(void); +int CallcNewChan(struct IsdnCardState *cs); +void CallcFreeChan(struct IsdnCardState *cs); +int Isdnl1New(void); +void Isdnl1Free(void); +int Isdnl2New(void); +void Isdnl2Free(void); +int Isdnl3New(void); +void Isdnl3Free(void); +void init_tei(struct IsdnCardState *cs, int protocol); +void release_tei(struct IsdnCardState *cs); +char *HiSax_getrev(const char *revision); +int TeiNew(void); +void TeiFree(void); + +#ifdef CONFIG_PCI + +#include <linux/pci.h> + +/* adaptation wrapper for old usage + * WARNING! This is unfit for use in a PCI hotplug environment, + * as the returned PCI device can disappear at any moment in time. + * Callers should be converted to use pci_get_device() instead. + */ +static inline struct pci_dev *hisax_find_pci_device(unsigned int vendor, + unsigned int device, + struct pci_dev *from) +{ + struct pci_dev *pdev; + + pci_dev_get(from); + pdev = pci_get_subsys(vendor, device, PCI_ANY_ID, PCI_ANY_ID, from); + pci_dev_put(pdev); + return pdev; +} + +#endif diff --git a/drivers/isdn/hisax/hisax_cfg.h b/drivers/isdn/hisax/hisax_cfg.h new file mode 100644 index 000000000..487dcfe9e --- /dev/null +++ b/drivers/isdn/hisax/hisax_cfg.h @@ -0,0 +1,66 @@ +/* $Id: hisax_cfg.h,v 1.1.2.1 2004/01/24 20:47:23 keil Exp $ + * define of the basic HiSax configuration structures + * and pcmcia interface + * + * This software may be used and distributed according to the terms + * of the GNU General Public License, incorporated herein by reference. + * + */ + +#define ISDN_CTYPE_16_0 1 +#define ISDN_CTYPE_8_0 2 +#define ISDN_CTYPE_16_3 3 +#define ISDN_CTYPE_PNP 4 +#define ISDN_CTYPE_A1 5 +#define ISDN_CTYPE_ELSA 6 +#define ISDN_CTYPE_ELSA_PNP 7 +#define ISDN_CTYPE_TELESPCMCIA 8 +#define ISDN_CTYPE_IX1MICROR2 9 +#define ISDN_CTYPE_ELSA_PCMCIA 10 +#define ISDN_CTYPE_DIEHLDIVA 11 +#define ISDN_CTYPE_ASUSCOM 12 +#define ISDN_CTYPE_TELEINT 13 +#define ISDN_CTYPE_TELES3C 14 +#define ISDN_CTYPE_SEDLBAUER 15 +#define ISDN_CTYPE_SPORTSTER 16 +#define ISDN_CTYPE_MIC 17 +#define ISDN_CTYPE_ELSA_PCI 18 +#define ISDN_CTYPE_COMPAQ_ISA 19 +#define ISDN_CTYPE_NETJET_S 20 +#define ISDN_CTYPE_TELESPCI 21 +#define ISDN_CTYPE_SEDLBAUER_PCMCIA 22 +#define ISDN_CTYPE_AMD7930 23 +#define ISDN_CTYPE_NICCY 24 +#define ISDN_CTYPE_S0BOX 25 +#define ISDN_CTYPE_A1_PCMCIA 26 +#define ISDN_CTYPE_FRITZPCI 27 +#define ISDN_CTYPE_SEDLBAUER_FAX 28 +#define ISDN_CTYPE_ISURF 29 +#define ISDN_CTYPE_ACERP10 30 +#define ISDN_CTYPE_HSTSAPHIR 31 +#define ISDN_CTYPE_BKM_A4T 32 +#define ISDN_CTYPE_SCT_QUADRO 33 +#define ISDN_CTYPE_GAZEL 34 +#define ISDN_CTYPE_HFC_PCI 35 +#define ISDN_CTYPE_W6692 36 +#define ISDN_CTYPE_HFC_SX 37 +#define ISDN_CTYPE_NETJET_U 38 +#define ISDN_CTYPE_HFC_SP_PCMCIA 39 +#define ISDN_CTYPE_DYNAMIC 40 +#define ISDN_CTYPE_ENTERNOW 41 +#define ISDN_CTYPE_COUNT 41 + +typedef struct IsdnCardState IsdnCardState_t; +typedef struct IsdnCard IsdnCard_t; + +struct IsdnCard { + int typ; + int protocol; /* EDSS1, 1TR6 or NI1 */ + unsigned long para[4]; + IsdnCardState_t *cs; +}; + +typedef int (*hisax_setup_func_t)(struct IsdnCard *card); + +extern void HiSax_closecard(int); +extern int hisax_init_pcmcia(void *, int *, IsdnCard_t *); diff --git a/drivers/isdn/hisax/hisax_debug.h b/drivers/isdn/hisax/hisax_debug.h new file mode 100644 index 000000000..7b3093d08 --- /dev/null +++ b/drivers/isdn/hisax/hisax_debug.h @@ -0,0 +1,80 @@ +/* + * Common debugging macros for use with the hisax driver + * + * Author Frode Isaksen + * Copyright 2001 by Frode Isaksen <fisaksen@bewan.com> + * 2001 by Kai Germaschewski <kai.germaschewski@gmx.de> + * + * This software may be used and distributed according to the terms + * of the GNU General Public License, incorporated herein by reference. + * + * How to use: + * + * Before including this file, you need to + * #define __debug_variable my_debug + * where my_debug is a variable in your code which + * determines the debug bitmask. + * + * If CONFIG_HISAX_DEBUG is not set, all macros evaluate to nothing + * + */ + +#ifndef __HISAX_DEBUG_H__ +#define __HISAX_DEBUG_H__ + + +#ifdef CONFIG_HISAX_DEBUG + +#define DBG(level, format, arg...) do { \ + if (level & __debug_variable) \ + printk(KERN_DEBUG "%s: " format "\n" , __func__ , ## arg); \ + } while (0) + +#define DBG_PACKET(level, data, count) \ + if (level & __debug_variable) dump_packet(__func__, data, count) + +#define DBG_SKB(level, skb) \ + if ((level & __debug_variable) && skb) dump_packet(__func__, skb->data, skb->len) + + +static void __attribute__((unused)) +dump_packet(const char *name, const u_char *data, int pkt_len) +{ +#define DUMP_HDR_SIZE 20 +#define DUMP_TLR_SIZE 8 + if (pkt_len) { + int i, len1, len2; + + printk(KERN_DEBUG "%s: length=%d,data=", name, pkt_len); + + if (pkt_len > DUMP_HDR_SIZE + DUMP_TLR_SIZE) { + len1 = DUMP_HDR_SIZE; + len2 = DUMP_TLR_SIZE; + } else { + len1 = pkt_len > DUMP_HDR_SIZE ? DUMP_HDR_SIZE : pkt_len; + len2 = 0; + } + for (i = 0; i < len1; ++i) { + printk("%.2x", data[i]); + } + if (len2) { + printk(".."); + for (i = pkt_len-DUMP_TLR_SIZE; i < pkt_len; ++i) { + printk("%.2x", data[i]); + } + } + printk("\n"); + } +#undef DUMP_HDR_SIZE +#undef DUMP_TLR_SIZE +} + +#else + +#define DBG(level, format, arg...) do {} while (0) +#define DBG_PACKET(level, data, count) do {} while (0) +#define DBG_SKB(level, skb) do {} while (0) + +#endif + +#endif diff --git a/drivers/isdn/hisax/hisax_fcpcipnp.c b/drivers/isdn/hisax/hisax_fcpcipnp.c new file mode 100644 index 000000000..7a7137d86 --- /dev/null +++ b/drivers/isdn/hisax/hisax_fcpcipnp.c @@ -0,0 +1,1024 @@ +/* + * Driver for AVM Fritz!PCI, Fritz!PCI v2, Fritz!PnP ISDN cards + * + * Author Kai Germaschewski + * Copyright 2001 by Kai Germaschewski <kai.germaschewski@gmx.de> + * 2001 by Karsten Keil <keil@isdn4linux.de> + * + * based upon Karsten Keil's original avm_pci.c driver + * + * This software may be used and distributed according to the terms + * of the GNU General Public License, incorporated herein by reference. + * + * Thanks to Wizard Computersysteme GmbH, Bremervoerde and + * SoHaNet Technology GmbH, Berlin + * for supporting the development of this driver + */ + + +/* TODO: + * + * o POWER PC + * o clean up debugging + * o tx_skb at PH_DEACTIVATE time + */ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/interrupt.h> +#include <linux/pci.h> +#include <linux/isapnp.h> +#include <linux/kmod.h> +#include <linux/slab.h> +#include <linux/skbuff.h> +#include <linux/netdevice.h> +#include <linux/delay.h> + +#include <asm/io.h> + +#include "hisax_fcpcipnp.h" + +// debugging cruft +#define __debug_variable debug +#include "hisax_debug.h" + +#ifdef CONFIG_HISAX_DEBUG +static int debug = 0; +/* static int hdlcfifosize = 32; */ +module_param(debug, int, 0); +/* module_param(hdlcfifosize, int, 0); */ +#endif + +MODULE_AUTHOR("Kai Germaschewski <kai.germaschewski@gmx.de>/Karsten Keil <kkeil@suse.de>"); +MODULE_DESCRIPTION("AVM Fritz!PCI/PnP ISDN driver"); + +static const struct pci_device_id fcpci_ids[] = { + { .vendor = PCI_VENDOR_ID_AVM, + .device = PCI_DEVICE_ID_AVM_A1, + .subvendor = PCI_ANY_ID, + .subdevice = PCI_ANY_ID, + .driver_data = (unsigned long) "Fritz!Card PCI", + }, + { .vendor = PCI_VENDOR_ID_AVM, + .device = PCI_DEVICE_ID_AVM_A1_V2, + .subvendor = PCI_ANY_ID, + .subdevice = PCI_ANY_ID, + .driver_data = (unsigned long) "Fritz!Card PCI v2" }, + {} +}; + +MODULE_DEVICE_TABLE(pci, fcpci_ids); + +#ifdef CONFIG_PNP +static struct pnp_device_id fcpnp_ids[] = { + { + .id = "AVM0900", + .driver_data = (unsigned long) "Fritz!Card PnP", + }, + { .id = "" } +}; + +MODULE_DEVICE_TABLE(pnp, fcpnp_ids); +#endif + +static int protocol = 2; /* EURO-ISDN Default */ +module_param(protocol, int, 0); +MODULE_LICENSE("GPL"); + +// ---------------------------------------------------------------------- + +#define AVM_INDEX 0x04 +#define AVM_DATA 0x10 + +#define AVM_IDX_HDLC_1 0x00 +#define AVM_IDX_HDLC_2 0x01 +#define AVM_IDX_ISAC_FIFO 0x02 +#define AVM_IDX_ISAC_REG_LOW 0x04 +#define AVM_IDX_ISAC_REG_HIGH 0x06 + +#define AVM_STATUS0 0x02 + +#define AVM_STATUS0_IRQ_ISAC 0x01 +#define AVM_STATUS0_IRQ_HDLC 0x02 +#define AVM_STATUS0_IRQ_TIMER 0x04 +#define AVM_STATUS0_IRQ_MASK 0x07 + +#define AVM_STATUS0_RESET 0x01 +#define AVM_STATUS0_DIS_TIMER 0x02 +#define AVM_STATUS0_RES_TIMER 0x04 +#define AVM_STATUS0_ENA_IRQ 0x08 +#define AVM_STATUS0_TESTBIT 0x10 + +#define AVM_STATUS1 0x03 +#define AVM_STATUS1_ENA_IOM 0x80 + +#define HDLC_FIFO 0x0 +#define HDLC_STATUS 0x4 +#define HDLC_CTRL 0x4 + +#define HDLC_MODE_ITF_FLG 0x01 +#define HDLC_MODE_TRANS 0x02 +#define HDLC_MODE_CCR_7 0x04 +#define HDLC_MODE_CCR_16 0x08 +#define HDLC_MODE_TESTLOOP 0x80 + +#define HDLC_INT_XPR 0x80 +#define HDLC_INT_XDU 0x40 +#define HDLC_INT_RPR 0x20 +#define HDLC_INT_MASK 0xE0 + +#define HDLC_STAT_RME 0x01 +#define HDLC_STAT_RDO 0x10 +#define HDLC_STAT_CRCVFRRAB 0x0E +#define HDLC_STAT_CRCVFR 0x06 +#define HDLC_STAT_RML_MASK 0xff00 + +#define HDLC_CMD_XRS 0x80 +#define HDLC_CMD_XME 0x01 +#define HDLC_CMD_RRS 0x20 +#define HDLC_CMD_XML_MASK 0xff00 + +#define AVM_HDLC_FIFO_1 0x10 +#define AVM_HDLC_FIFO_2 0x18 + +#define AVM_HDLC_STATUS_1 0x14 +#define AVM_HDLC_STATUS_2 0x1c + +#define AVM_ISACSX_INDEX 0x04 +#define AVM_ISACSX_DATA 0x08 + +// ---------------------------------------------------------------------- +// Fritz!PCI + +static unsigned char fcpci_read_isac(struct isac *isac, unsigned char offset) +{ + struct fritz_adapter *adapter = isac->priv; + unsigned char idx = (offset > 0x2f) ? + AVM_IDX_ISAC_REG_HIGH : AVM_IDX_ISAC_REG_LOW; + unsigned char val; + unsigned long flags; + + spin_lock_irqsave(&adapter->hw_lock, flags); + outb(idx, adapter->io + AVM_INDEX); + val = inb(adapter->io + AVM_DATA + (offset & 0xf)); + spin_unlock_irqrestore(&adapter->hw_lock, flags); + DBG(0x1000, " port %#x, value %#x", + offset, val); + return val; +} + +static void fcpci_write_isac(struct isac *isac, unsigned char offset, + unsigned char value) +{ + struct fritz_adapter *adapter = isac->priv; + unsigned char idx = (offset > 0x2f) ? + AVM_IDX_ISAC_REG_HIGH : AVM_IDX_ISAC_REG_LOW; + unsigned long flags; + + DBG(0x1000, " port %#x, value %#x", + offset, value); + spin_lock_irqsave(&adapter->hw_lock, flags); + outb(idx, adapter->io + AVM_INDEX); + outb(value, adapter->io + AVM_DATA + (offset & 0xf)); + spin_unlock_irqrestore(&adapter->hw_lock, flags); +} + +static void fcpci_read_isac_fifo(struct isac *isac, unsigned char *data, + int size) +{ + struct fritz_adapter *adapter = isac->priv; + unsigned long flags; + + spin_lock_irqsave(&adapter->hw_lock, flags); + outb(AVM_IDX_ISAC_FIFO, adapter->io + AVM_INDEX); + insb(adapter->io + AVM_DATA, data, size); + spin_unlock_irqrestore(&adapter->hw_lock, flags); +} + +static void fcpci_write_isac_fifo(struct isac *isac, unsigned char *data, + int size) +{ + struct fritz_adapter *adapter = isac->priv; + unsigned long flags; + + spin_lock_irqsave(&adapter->hw_lock, flags); + outb(AVM_IDX_ISAC_FIFO, adapter->io + AVM_INDEX); + outsb(adapter->io + AVM_DATA, data, size); + spin_unlock_irqrestore(&adapter->hw_lock, flags); +} + +static u32 fcpci_read_hdlc_status(struct fritz_adapter *adapter, int nr) +{ + u32 val; + int idx = nr ? AVM_IDX_HDLC_2 : AVM_IDX_HDLC_1; + unsigned long flags; + + spin_lock_irqsave(&adapter->hw_lock, flags); + outl(idx, adapter->io + AVM_INDEX); + val = inl(adapter->io + AVM_DATA + HDLC_STATUS); + spin_unlock_irqrestore(&adapter->hw_lock, flags); + return val; +} + +static void __fcpci_write_ctrl(struct fritz_bcs *bcs, int which) +{ + struct fritz_adapter *adapter = bcs->adapter; + int idx = bcs->channel ? AVM_IDX_HDLC_2 : AVM_IDX_HDLC_1; + + DBG(0x40, "hdlc %c wr%x ctrl %x", + 'A' + bcs->channel, which, bcs->ctrl.ctrl); + + outl(idx, adapter->io + AVM_INDEX); + outl(bcs->ctrl.ctrl, adapter->io + AVM_DATA + HDLC_CTRL); +} + +static void fcpci_write_ctrl(struct fritz_bcs *bcs, int which) +{ + struct fritz_adapter *adapter = bcs->adapter; + unsigned long flags; + + spin_lock_irqsave(&adapter->hw_lock, flags); + __fcpci_write_ctrl(bcs, which); + spin_unlock_irqrestore(&adapter->hw_lock, flags); +} + +// ---------------------------------------------------------------------- +// Fritz!PCI v2 + +static unsigned char fcpci2_read_isac(struct isac *isac, unsigned char offset) +{ + struct fritz_adapter *adapter = isac->priv; + unsigned char val; + unsigned long flags; + + spin_lock_irqsave(&adapter->hw_lock, flags); + outl(offset, adapter->io + AVM_ISACSX_INDEX); + val = inl(adapter->io + AVM_ISACSX_DATA); + spin_unlock_irqrestore(&adapter->hw_lock, flags); + DBG(0x1000, " port %#x, value %#x", + offset, val); + + return val; +} + +static void fcpci2_write_isac(struct isac *isac, unsigned char offset, + unsigned char value) +{ + struct fritz_adapter *adapter = isac->priv; + unsigned long flags; + + DBG(0x1000, " port %#x, value %#x", + offset, value); + spin_lock_irqsave(&adapter->hw_lock, flags); + outl(offset, adapter->io + AVM_ISACSX_INDEX); + outl(value, adapter->io + AVM_ISACSX_DATA); + spin_unlock_irqrestore(&adapter->hw_lock, flags); +} + +static void fcpci2_read_isac_fifo(struct isac *isac, unsigned char *data, + int size) +{ + struct fritz_adapter *adapter = isac->priv; + int i; + unsigned long flags; + + spin_lock_irqsave(&adapter->hw_lock, flags); + outl(0, adapter->io + AVM_ISACSX_INDEX); + for (i = 0; i < size; i++) + data[i] = inl(adapter->io + AVM_ISACSX_DATA); + spin_unlock_irqrestore(&adapter->hw_lock, flags); +} + +static void fcpci2_write_isac_fifo(struct isac *isac, unsigned char *data, + int size) +{ + struct fritz_adapter *adapter = isac->priv; + int i; + unsigned long flags; + + spin_lock_irqsave(&adapter->hw_lock, flags); + outl(0, adapter->io + AVM_ISACSX_INDEX); + for (i = 0; i < size; i++) + outl(data[i], adapter->io + AVM_ISACSX_DATA); + spin_unlock_irqrestore(&adapter->hw_lock, flags); +} + +static u32 fcpci2_read_hdlc_status(struct fritz_adapter *adapter, int nr) +{ + int offset = nr ? AVM_HDLC_STATUS_2 : AVM_HDLC_STATUS_1; + + return inl(adapter->io + offset); +} + +static void fcpci2_write_ctrl(struct fritz_bcs *bcs, int which) +{ + struct fritz_adapter *adapter = bcs->adapter; + int offset = bcs->channel ? AVM_HDLC_STATUS_2 : AVM_HDLC_STATUS_1; + + DBG(0x40, "hdlc %c wr%x ctrl %x", + 'A' + bcs->channel, which, bcs->ctrl.ctrl); + + outl(bcs->ctrl.ctrl, adapter->io + offset); +} + +// ---------------------------------------------------------------------- +// Fritz!PnP (ISAC access as for Fritz!PCI) + +static u32 fcpnp_read_hdlc_status(struct fritz_adapter *adapter, int nr) +{ + unsigned char idx = nr ? AVM_IDX_HDLC_2 : AVM_IDX_HDLC_1; + u32 val; + unsigned long flags; + + spin_lock_irqsave(&adapter->hw_lock, flags); + outb(idx, adapter->io + AVM_INDEX); + val = inb(adapter->io + AVM_DATA + HDLC_STATUS); + if (val & HDLC_INT_RPR) + val |= inb(adapter->io + AVM_DATA + HDLC_STATUS + 1) << 8; + spin_unlock_irqrestore(&adapter->hw_lock, flags); + return val; +} + +static void __fcpnp_write_ctrl(struct fritz_bcs *bcs, int which) +{ + struct fritz_adapter *adapter = bcs->adapter; + unsigned char idx = bcs->channel ? AVM_IDX_HDLC_2 : AVM_IDX_HDLC_1; + + DBG(0x40, "hdlc %c wr%x ctrl %x", + 'A' + bcs->channel, which, bcs->ctrl.ctrl); + + outb(idx, adapter->io + AVM_INDEX); + if (which & 4) + outb(bcs->ctrl.sr.mode, + adapter->io + AVM_DATA + HDLC_STATUS + 2); + if (which & 2) + outb(bcs->ctrl.sr.xml, + adapter->io + AVM_DATA + HDLC_STATUS + 1); + if (which & 1) + outb(bcs->ctrl.sr.cmd, + adapter->io + AVM_DATA + HDLC_STATUS + 0); +} + +static void fcpnp_write_ctrl(struct fritz_bcs *bcs, int which) +{ + struct fritz_adapter *adapter = bcs->adapter; + unsigned long flags; + + spin_lock_irqsave(&adapter->hw_lock, flags); + __fcpnp_write_ctrl(bcs, which); + spin_unlock_irqrestore(&adapter->hw_lock, flags); +} + +// ---------------------------------------------------------------------- + +static inline void B_L1L2(struct fritz_bcs *bcs, int pr, void *arg) +{ + struct hisax_if *ifc = (struct hisax_if *) &bcs->b_if; + + DBG(2, "pr %#x", pr); + ifc->l1l2(ifc, pr, arg); +} + +static void hdlc_fill_fifo(struct fritz_bcs *bcs) +{ + struct fritz_adapter *adapter = bcs->adapter; + struct sk_buff *skb = bcs->tx_skb; + int count; + unsigned long flags; + unsigned char *p; + + DBG(0x40, "hdlc_fill_fifo"); + + BUG_ON(skb->len == 0); + + bcs->ctrl.sr.cmd &= ~HDLC_CMD_XME; + if (bcs->tx_skb->len > bcs->fifo_size) { + count = bcs->fifo_size; + } else { + count = bcs->tx_skb->len; + if (bcs->mode != L1_MODE_TRANS) + bcs->ctrl.sr.cmd |= HDLC_CMD_XME; + } + DBG(0x40, "hdlc_fill_fifo %d/%d", count, bcs->tx_skb->len); + p = bcs->tx_skb->data; + skb_pull(bcs->tx_skb, count); + bcs->tx_cnt += count; + bcs->ctrl.sr.xml = ((count == bcs->fifo_size) ? 0 : count); + + switch (adapter->type) { + case AVM_FRITZ_PCI: + spin_lock_irqsave(&adapter->hw_lock, flags); + // sets the correct AVM_INDEX, too + __fcpci_write_ctrl(bcs, 3); + outsl(adapter->io + AVM_DATA + HDLC_FIFO, + p, (count + 3) / 4); + spin_unlock_irqrestore(&adapter->hw_lock, flags); + break; + case AVM_FRITZ_PCIV2: + fcpci2_write_ctrl(bcs, 3); + outsl(adapter->io + + (bcs->channel ? AVM_HDLC_FIFO_2 : AVM_HDLC_FIFO_1), + p, (count + 3) / 4); + break; + case AVM_FRITZ_PNP: + spin_lock_irqsave(&adapter->hw_lock, flags); + // sets the correct AVM_INDEX, too + __fcpnp_write_ctrl(bcs, 3); + outsb(adapter->io + AVM_DATA, p, count); + spin_unlock_irqrestore(&adapter->hw_lock, flags); + break; + } +} + +static inline void hdlc_empty_fifo(struct fritz_bcs *bcs, int count) +{ + struct fritz_adapter *adapter = bcs->adapter; + unsigned char *p; + unsigned char idx = bcs->channel ? AVM_IDX_HDLC_2 : AVM_IDX_HDLC_1; + + DBG(0x10, "hdlc_empty_fifo %d", count); + if (bcs->rcvidx + count > HSCX_BUFMAX) { + DBG(0x10, "hdlc_empty_fifo: incoming packet too large"); + return; + } + p = bcs->rcvbuf + bcs->rcvidx; + bcs->rcvidx += count; + switch (adapter->type) { + case AVM_FRITZ_PCI: + spin_lock(&adapter->hw_lock); + outl(idx, adapter->io + AVM_INDEX); + insl(adapter->io + AVM_DATA + HDLC_FIFO, + p, (count + 3) / 4); + spin_unlock(&adapter->hw_lock); + break; + case AVM_FRITZ_PCIV2: + insl(adapter->io + + (bcs->channel ? AVM_HDLC_FIFO_2 : AVM_HDLC_FIFO_1), + p, (count + 3) / 4); + break; + case AVM_FRITZ_PNP: + spin_lock(&adapter->hw_lock); + outb(idx, adapter->io + AVM_INDEX); + insb(adapter->io + AVM_DATA, p, count); + spin_unlock(&adapter->hw_lock); + break; + } +} + +static inline void hdlc_rpr_irq(struct fritz_bcs *bcs, u32 stat) +{ + struct fritz_adapter *adapter = bcs->adapter; + struct sk_buff *skb; + int len; + + if (stat & HDLC_STAT_RDO) { + DBG(0x10, "RDO"); + bcs->ctrl.sr.xml = 0; + bcs->ctrl.sr.cmd |= HDLC_CMD_RRS; + adapter->write_ctrl(bcs, 1); + bcs->ctrl.sr.cmd &= ~HDLC_CMD_RRS; + adapter->write_ctrl(bcs, 1); + bcs->rcvidx = 0; + return; + } + + len = (stat & HDLC_STAT_RML_MASK) >> 8; + if (len == 0) + len = bcs->fifo_size; + + hdlc_empty_fifo(bcs, len); + + if ((stat & HDLC_STAT_RME) || (bcs->mode == L1_MODE_TRANS)) { + if (((stat & HDLC_STAT_CRCVFRRAB) == HDLC_STAT_CRCVFR) || + (bcs->mode == L1_MODE_TRANS)) { + skb = dev_alloc_skb(bcs->rcvidx); + if (!skb) { + printk(KERN_WARNING "HDLC: receive out of memory\n"); + } else { + skb_put_data(skb, bcs->rcvbuf, bcs->rcvidx); + DBG_SKB(1, skb); + B_L1L2(bcs, PH_DATA | INDICATION, skb); + } + bcs->rcvidx = 0; + } else { + DBG(0x10, "ch%d invalid frame %#x", + bcs->channel, stat); + bcs->rcvidx = 0; + } + } +} + +static inline void hdlc_xdu_irq(struct fritz_bcs *bcs) +{ + struct fritz_adapter *adapter = bcs->adapter; + + + /* Here we lost an TX interrupt, so + * restart transmitting the whole frame. + */ + bcs->ctrl.sr.xml = 0; + bcs->ctrl.sr.cmd |= HDLC_CMD_XRS; + adapter->write_ctrl(bcs, 1); + bcs->ctrl.sr.cmd &= ~HDLC_CMD_XRS; + + if (!bcs->tx_skb) { + DBG(0x10, "XDU without skb"); + adapter->write_ctrl(bcs, 1); + return; + } + /* only hdlc restarts the frame, transparent mode must continue */ + if (bcs->mode == L1_MODE_HDLC) { + skb_push(bcs->tx_skb, bcs->tx_cnt); + bcs->tx_cnt = 0; + } +} + +static inline void hdlc_xpr_irq(struct fritz_bcs *bcs) +{ + struct sk_buff *skb; + + skb = bcs->tx_skb; + if (!skb) + return; + + if (skb->len) { + hdlc_fill_fifo(bcs); + return; + } + bcs->tx_cnt = 0; + bcs->tx_skb = NULL; + B_L1L2(bcs, PH_DATA | CONFIRM, (void *)(unsigned long)skb->truesize); + dev_kfree_skb_irq(skb); +} + +static void hdlc_irq_one(struct fritz_bcs *bcs, u32 stat) +{ + DBG(0x10, "ch%d stat %#x", bcs->channel, stat); + if (stat & HDLC_INT_RPR) { + DBG(0x10, "RPR"); + hdlc_rpr_irq(bcs, stat); + } + if (stat & HDLC_INT_XDU) { + DBG(0x10, "XDU"); + hdlc_xdu_irq(bcs); + hdlc_xpr_irq(bcs); + return; + } + if (stat & HDLC_INT_XPR) { + DBG(0x10, "XPR"); + hdlc_xpr_irq(bcs); + } +} + +static inline void hdlc_irq(struct fritz_adapter *adapter) +{ + int nr; + u32 stat; + + for (nr = 0; nr < 2; nr++) { + stat = adapter->read_hdlc_status(adapter, nr); + DBG(0x10, "HDLC %c stat %#x", 'A' + nr, stat); + if (stat & HDLC_INT_MASK) + hdlc_irq_one(&adapter->bcs[nr], stat); + } +} + +static void modehdlc(struct fritz_bcs *bcs, int mode) +{ + struct fritz_adapter *adapter = bcs->adapter; + + DBG(0x40, "hdlc %c mode %d --> %d", + 'A' + bcs->channel, bcs->mode, mode); + + if (bcs->mode == mode) + return; + + bcs->fifo_size = 32; + bcs->ctrl.ctrl = 0; + bcs->ctrl.sr.cmd = HDLC_CMD_XRS | HDLC_CMD_RRS; + switch (mode) { + case L1_MODE_NULL: + bcs->ctrl.sr.mode = HDLC_MODE_TRANS; + adapter->write_ctrl(bcs, 5); + break; + case L1_MODE_TRANS: + case L1_MODE_HDLC: + bcs->rcvidx = 0; + bcs->tx_cnt = 0; + bcs->tx_skb = NULL; + if (mode == L1_MODE_TRANS) { + bcs->ctrl.sr.mode = HDLC_MODE_TRANS; + } else { + bcs->ctrl.sr.mode = HDLC_MODE_ITF_FLG; + } + adapter->write_ctrl(bcs, 5); + bcs->ctrl.sr.cmd = HDLC_CMD_XRS; + adapter->write_ctrl(bcs, 1); + bcs->ctrl.sr.cmd = 0; + break; + } + bcs->mode = mode; +} + +static void fritz_b_l2l1(struct hisax_if *ifc, int pr, void *arg) +{ + struct fritz_bcs *bcs = ifc->priv; + struct sk_buff *skb = arg; + int mode; + + DBG(0x10, "pr %#x", pr); + + switch (pr) { + case PH_DATA | REQUEST: + BUG_ON(bcs->tx_skb); + bcs->tx_skb = skb; + DBG_SKB(1, skb); + hdlc_fill_fifo(bcs); + break; + case PH_ACTIVATE | REQUEST: + mode = (long) arg; + DBG(4, "B%d,PH_ACTIVATE_REQUEST %d", bcs->channel + 1, mode); + modehdlc(bcs, mode); + B_L1L2(bcs, PH_ACTIVATE | INDICATION, NULL); + break; + case PH_DEACTIVATE | REQUEST: + DBG(4, "B%d,PH_DEACTIVATE_REQUEST", bcs->channel + 1); + modehdlc(bcs, L1_MODE_NULL); + B_L1L2(bcs, PH_DEACTIVATE | INDICATION, NULL); + break; + } +} + +// ---------------------------------------------------------------------- + +static irqreturn_t +fcpci2_irq(int intno, void *dev) +{ + struct fritz_adapter *adapter = dev; + unsigned char val; + + val = inb(adapter->io + AVM_STATUS0); + if (!(val & AVM_STATUS0_IRQ_MASK)) + /* hopefully a shared IRQ reqest */ + return IRQ_NONE; + DBG(2, "STATUS0 %#x", val); + if (val & AVM_STATUS0_IRQ_ISAC) + isacsx_irq(&adapter->isac); + if (val & AVM_STATUS0_IRQ_HDLC) + hdlc_irq(adapter); + if (val & AVM_STATUS0_IRQ_ISAC) + isacsx_irq(&adapter->isac); + return IRQ_HANDLED; +} + +static irqreturn_t +fcpci_irq(int intno, void *dev) +{ + struct fritz_adapter *adapter = dev; + unsigned char sval; + + sval = inb(adapter->io + 2); + if ((sval & AVM_STATUS0_IRQ_MASK) == AVM_STATUS0_IRQ_MASK) + /* possibly a shared IRQ reqest */ + return IRQ_NONE; + DBG(2, "sval %#x", sval); + if (!(sval & AVM_STATUS0_IRQ_ISAC)) + isac_irq(&adapter->isac); + + if (!(sval & AVM_STATUS0_IRQ_HDLC)) + hdlc_irq(adapter); + return IRQ_HANDLED; +} + +// ---------------------------------------------------------------------- + +static inline void fcpci2_init(struct fritz_adapter *adapter) +{ + outb(AVM_STATUS0_RES_TIMER, adapter->io + AVM_STATUS0); + outb(AVM_STATUS0_ENA_IRQ, adapter->io + AVM_STATUS0); + +} + +static inline void fcpci_init(struct fritz_adapter *adapter) +{ + outb(AVM_STATUS0_DIS_TIMER | AVM_STATUS0_RES_TIMER | + AVM_STATUS0_ENA_IRQ, adapter->io + AVM_STATUS0); + + outb(AVM_STATUS1_ENA_IOM | adapter->irq, + adapter->io + AVM_STATUS1); + mdelay(10); +} + +// ---------------------------------------------------------------------- + +static int fcpcipnp_setup(struct fritz_adapter *adapter) +{ + u32 val = 0; + int retval; + + DBG(1, ""); + + isac_init(&adapter->isac); // FIXME is this okay now + + retval = -EBUSY; + if (!request_region(adapter->io, 32, "fcpcipnp")) + goto err; + + switch (adapter->type) { + case AVM_FRITZ_PCIV2: + case AVM_FRITZ_PCI: + val = inl(adapter->io); + break; + case AVM_FRITZ_PNP: + val = inb(adapter->io); + val |= inb(adapter->io + 1) << 8; + break; + } + + DBG(1, "stat %#x Class %X Rev %d", + val, val & 0xff, (val >> 8) & 0xff); + + spin_lock_init(&adapter->hw_lock); + adapter->isac.priv = adapter; + switch (adapter->type) { + case AVM_FRITZ_PCIV2: + adapter->isac.read_isac = &fcpci2_read_isac; + adapter->isac.write_isac = &fcpci2_write_isac; + adapter->isac.read_isac_fifo = &fcpci2_read_isac_fifo; + adapter->isac.write_isac_fifo = &fcpci2_write_isac_fifo; + + adapter->read_hdlc_status = &fcpci2_read_hdlc_status; + adapter->write_ctrl = &fcpci2_write_ctrl; + break; + case AVM_FRITZ_PCI: + adapter->isac.read_isac = &fcpci_read_isac; + adapter->isac.write_isac = &fcpci_write_isac; + adapter->isac.read_isac_fifo = &fcpci_read_isac_fifo; + adapter->isac.write_isac_fifo = &fcpci_write_isac_fifo; + + adapter->read_hdlc_status = &fcpci_read_hdlc_status; + adapter->write_ctrl = &fcpci_write_ctrl; + break; + case AVM_FRITZ_PNP: + adapter->isac.read_isac = &fcpci_read_isac; + adapter->isac.write_isac = &fcpci_write_isac; + adapter->isac.read_isac_fifo = &fcpci_read_isac_fifo; + adapter->isac.write_isac_fifo = &fcpci_write_isac_fifo; + + adapter->read_hdlc_status = &fcpnp_read_hdlc_status; + adapter->write_ctrl = &fcpnp_write_ctrl; + break; + } + + // Reset + outb(0, adapter->io + AVM_STATUS0); + mdelay(10); + outb(AVM_STATUS0_RESET, adapter->io + AVM_STATUS0); + mdelay(10); + outb(0, adapter->io + AVM_STATUS0); + mdelay(10); + + switch (adapter->type) { + case AVM_FRITZ_PCIV2: + retval = request_irq(adapter->irq, fcpci2_irq, IRQF_SHARED, + "fcpcipnp", adapter); + break; + case AVM_FRITZ_PCI: + retval = request_irq(adapter->irq, fcpci_irq, IRQF_SHARED, + "fcpcipnp", adapter); + break; + case AVM_FRITZ_PNP: + retval = request_irq(adapter->irq, fcpci_irq, 0, + "fcpcipnp", adapter); + break; + } + if (retval) + goto err_region; + + switch (adapter->type) { + case AVM_FRITZ_PCIV2: + fcpci2_init(adapter); + isacsx_setup(&adapter->isac); + break; + case AVM_FRITZ_PCI: + case AVM_FRITZ_PNP: + fcpci_init(adapter); + isac_setup(&adapter->isac); + break; + } + val = adapter->read_hdlc_status(adapter, 0); + DBG(0x20, "HDLC A STA %x", val); + val = adapter->read_hdlc_status(adapter, 1); + DBG(0x20, "HDLC B STA %x", val); + + adapter->bcs[0].mode = -1; + adapter->bcs[1].mode = -1; + modehdlc(&adapter->bcs[0], L1_MODE_NULL); + modehdlc(&adapter->bcs[1], L1_MODE_NULL); + + return 0; + +err_region: + release_region(adapter->io, 32); +err: + return retval; +} + +static void fcpcipnp_release(struct fritz_adapter *adapter) +{ + DBG(1, ""); + + outb(0, adapter->io + AVM_STATUS0); + free_irq(adapter->irq, adapter); + release_region(adapter->io, 32); +} + +// ---------------------------------------------------------------------- + +static struct fritz_adapter *new_adapter(void) +{ + struct fritz_adapter *adapter; + struct hisax_b_if *b_if[2]; + int i; + + adapter = kzalloc(sizeof(struct fritz_adapter), GFP_KERNEL); + if (!adapter) + return NULL; + + adapter->isac.hisax_d_if.owner = THIS_MODULE; + adapter->isac.hisax_d_if.ifc.priv = &adapter->isac; + adapter->isac.hisax_d_if.ifc.l2l1 = isac_d_l2l1; + + for (i = 0; i < 2; i++) { + adapter->bcs[i].adapter = adapter; + adapter->bcs[i].channel = i; + adapter->bcs[i].b_if.ifc.priv = &adapter->bcs[i]; + adapter->bcs[i].b_if.ifc.l2l1 = fritz_b_l2l1; + } + + for (i = 0; i < 2; i++) + b_if[i] = &adapter->bcs[i].b_if; + + if (hisax_register(&adapter->isac.hisax_d_if, b_if, "fcpcipnp", + protocol) != 0) { + kfree(adapter); + adapter = NULL; + } + + return adapter; +} + +static void delete_adapter(struct fritz_adapter *adapter) +{ + hisax_unregister(&adapter->isac.hisax_d_if); + kfree(adapter); +} + +static int fcpci_probe(struct pci_dev *pdev, const struct pci_device_id *ent) +{ + struct fritz_adapter *adapter; + int retval; + + retval = -ENOMEM; + adapter = new_adapter(); + if (!adapter) + goto err; + + pci_set_drvdata(pdev, adapter); + + if (pdev->device == PCI_DEVICE_ID_AVM_A1_V2) + adapter->type = AVM_FRITZ_PCIV2; + else + adapter->type = AVM_FRITZ_PCI; + + retval = pci_enable_device(pdev); + if (retval) + goto err_free; + + adapter->io = pci_resource_start(pdev, 1); + adapter->irq = pdev->irq; + + printk(KERN_INFO "hisax_fcpcipnp: found adapter %s at %s\n", + (char *) ent->driver_data, pci_name(pdev)); + + retval = fcpcipnp_setup(adapter); + if (retval) + goto err_free; + + return 0; + +err_free: + delete_adapter(adapter); +err: + return retval; +} + +#ifdef CONFIG_PNP +static int fcpnp_probe(struct pnp_dev *pdev, const struct pnp_device_id *dev_id) +{ + struct fritz_adapter *adapter; + int retval; + + if (!pdev) + return (-ENODEV); + + retval = -ENOMEM; + adapter = new_adapter(); + if (!adapter) + goto err; + + pnp_set_drvdata(pdev, adapter); + + adapter->type = AVM_FRITZ_PNP; + + pnp_disable_dev(pdev); + retval = pnp_activate_dev(pdev); + if (retval < 0) { + printk(KERN_WARNING "%s: pnp_activate_dev(%s) ret(%d)\n", __func__, + (char *)dev_id->driver_data, retval); + goto err_free; + } + adapter->io = pnp_port_start(pdev, 0); + adapter->irq = pnp_irq(pdev, 0); + if (!adapter->io || adapter->irq == -1) + goto err_free; + + printk(KERN_INFO "hisax_fcpcipnp: found adapter %s at IO %#x irq %d\n", + (char *) dev_id->driver_data, adapter->io, adapter->irq); + + retval = fcpcipnp_setup(adapter); + if (retval) + goto err_free; + + return 0; + +err_free: + delete_adapter(adapter); +err: + return retval; +} + +static void fcpnp_remove(struct pnp_dev *pdev) +{ + struct fritz_adapter *adapter = pnp_get_drvdata(pdev); + + if (adapter) { + fcpcipnp_release(adapter); + delete_adapter(adapter); + } + pnp_disable_dev(pdev); +} + +static struct pnp_driver fcpnp_driver = { + .name = "fcpnp", + .probe = fcpnp_probe, + .remove = fcpnp_remove, + .id_table = fcpnp_ids, +}; +#endif + +static void fcpci_remove(struct pci_dev *pdev) +{ + struct fritz_adapter *adapter = pci_get_drvdata(pdev); + + fcpcipnp_release(adapter); + pci_disable_device(pdev); + delete_adapter(adapter); +} + +static struct pci_driver fcpci_driver = { + .name = "fcpci", + .probe = fcpci_probe, + .remove = fcpci_remove, + .id_table = fcpci_ids, +}; + +static int __init hisax_fcpcipnp_init(void) +{ + int retval; + + printk(KERN_INFO "hisax_fcpcipnp: Fritz!Card PCI/PCIv2/PnP ISDN driver v0.0.1\n"); + + retval = pci_register_driver(&fcpci_driver); + if (retval) + return retval; +#ifdef CONFIG_PNP + retval = pnp_register_driver(&fcpnp_driver); + if (retval < 0) { + pci_unregister_driver(&fcpci_driver); + return retval; + } +#endif + return 0; +} + +static void __exit hisax_fcpcipnp_exit(void) +{ +#ifdef CONFIG_PNP + pnp_unregister_driver(&fcpnp_driver); +#endif + pci_unregister_driver(&fcpci_driver); +} + +module_init(hisax_fcpcipnp_init); +module_exit(hisax_fcpcipnp_exit); diff --git a/drivers/isdn/hisax/hisax_fcpcipnp.h b/drivers/isdn/hisax/hisax_fcpcipnp.h new file mode 100644 index 000000000..1f64e9937 --- /dev/null +++ b/drivers/isdn/hisax/hisax_fcpcipnp.h @@ -0,0 +1,58 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#include "hisax_if.h" +#include "hisax_isac.h" +#include <linux/pci.h> + +#define HSCX_BUFMAX 4096 + +enum { + AVM_FRITZ_PCI, + AVM_FRITZ_PNP, + AVM_FRITZ_PCIV2, +}; + +struct hdlc_stat_reg { +#ifdef __BIG_ENDIAN + u_char fill; + u_char mode; + u_char xml; + u_char cmd; +#else + u_char cmd; + u_char xml; + u_char mode; + u_char fill; +#endif +} __attribute__((packed)); + +struct fritz_bcs { + struct hisax_b_if b_if; + struct fritz_adapter *adapter; + int mode; + int channel; + + union { + u_int ctrl; + struct hdlc_stat_reg sr; + } ctrl; + u_int stat; + int rcvidx; + int fifo_size; + u_char rcvbuf[HSCX_BUFMAX]; /* B-Channel receive Buffer */ + + int tx_cnt; /* B-Channel transmit counter */ + struct sk_buff *tx_skb; /* B-Channel transmit Buffer */ +}; + +struct fritz_adapter { + int type; + spinlock_t hw_lock; + unsigned int io; + unsigned int irq; + struct isac isac; + + struct fritz_bcs bcs[2]; + + u32 (*read_hdlc_status) (struct fritz_adapter *adapter, int nr); + void (*write_ctrl) (struct fritz_bcs *bcs, int which); +}; diff --git a/drivers/isdn/hisax/hisax_if.h b/drivers/isdn/hisax/hisax_if.h new file mode 100644 index 000000000..7098d6bd5 --- /dev/null +++ b/drivers/isdn/hisax/hisax_if.h @@ -0,0 +1,66 @@ +/* + * Interface between low level (hardware) drivers and + * HiSax protocol stack + * + * Author Kai Germaschewski + * Copyright 2001 by Kai Germaschewski <kai.germaschewski@gmx.de> + * + * This software may be used and distributed according to the terms + * of the GNU General Public License, incorporated herein by reference. + * + */ + +#ifndef __HISAX_IF_H__ +#define __HISAX_IF_H__ + +#include <linux/skbuff.h> + +#define REQUEST 0 +#define CONFIRM 1 +#define INDICATION 2 +#define RESPONSE 3 + +#define PH_ACTIVATE 0x0100 +#define PH_DEACTIVATE 0x0110 +#define PH_DATA 0x0120 +#define PH_PULL 0x0130 +#define PH_DATA_E 0x0140 + +#define L1_MODE_NULL 0 +#define L1_MODE_TRANS 1 +#define L1_MODE_HDLC 2 +#define L1_MODE_EXTRN 3 +#define L1_MODE_HDLC_56K 4 +#define L1_MODE_MODEM 7 +#define L1_MODE_V32 8 +#define L1_MODE_FAX 9 + +struct hisax_if { + void *priv; // private to driver + void (*l1l2)(struct hisax_if *, int pr, void *arg); + void (*l2l1)(struct hisax_if *, int pr, void *arg); +}; + +struct hisax_b_if { + struct hisax_if ifc; + + // private to hisax + struct BCState *bcs; +}; + +struct hisax_d_if { + struct hisax_if ifc; + + // private to hisax + struct module *owner; + struct IsdnCardState *cs; + struct hisax_b_if *b_if[2]; + struct sk_buff_head erq; + unsigned long ph_state; +}; + +int hisax_register(struct hisax_d_if *hisax_if, struct hisax_b_if *b_if[], + char *name, int protocol); +void hisax_unregister(struct hisax_d_if *hisax_if); + +#endif diff --git a/drivers/isdn/hisax/hisax_isac.c b/drivers/isdn/hisax/hisax_isac.c new file mode 100644 index 000000000..0f3637547 --- /dev/null +++ b/drivers/isdn/hisax/hisax_isac.c @@ -0,0 +1,895 @@ +/* + * Driver for ISAC-S and ISAC-SX + * ISDN Subscriber Access Controller for Terminals + * + * Author Kai Germaschewski + * Copyright 2001 by Kai Germaschewski <kai.germaschewski@gmx.de> + * 2001 by Karsten Keil <keil@isdn4linux.de> + * + * based upon Karsten Keil's original isac.c driver + * + * This software may be used and distributed according to the terms + * of the GNU General Public License, incorporated herein by reference. + * + * Thanks to Wizard Computersysteme GmbH, Bremervoerde and + * SoHaNet Technology GmbH, Berlin + * for supporting the development of this driver + */ + +/* TODO: + * specifically handle level vs edge triggered? + */ + +#include <linux/module.h> +#include <linux/gfp.h> +#include <linux/init.h> +#include <linux/netdevice.h> +#include "hisax_isac.h" + +// debugging cruft + +#define __debug_variable debug +#include "hisax_debug.h" + +#ifdef CONFIG_HISAX_DEBUG +static int debug = 1; +module_param(debug, int, 0); + +static char *ISACVer[] = { + "2086/2186 V1.1", + "2085 B1", + "2085 B2", + "2085 V2.3" +}; +#endif + +MODULE_AUTHOR("Kai Germaschewski <kai.germaschewski@gmx.de>/Karsten Keil <kkeil@suse.de>"); +MODULE_DESCRIPTION("ISAC/ISAC-SX driver"); +MODULE_LICENSE("GPL"); + +#define DBG_WARN 0x0001 +#define DBG_IRQ 0x0002 +#define DBG_L1M 0x0004 +#define DBG_PR 0x0008 +#define DBG_RFIFO 0x0100 +#define DBG_RPACKET 0x0200 +#define DBG_XFIFO 0x1000 +#define DBG_XPACKET 0x2000 + +// we need to distinguish ISAC-S and ISAC-SX +#define TYPE_ISAC 0x00 +#define TYPE_ISACSX 0x01 + +// registers etc. +#define ISAC_MASK 0x20 +#define ISAC_ISTA 0x20 +#define ISAC_ISTA_EXI 0x01 +#define ISAC_ISTA_SIN 0x02 +#define ISAC_ISTA_CISQ 0x04 +#define ISAC_ISTA_XPR 0x10 +#define ISAC_ISTA_RSC 0x20 +#define ISAC_ISTA_RPF 0x40 +#define ISAC_ISTA_RME 0x80 + +#define ISAC_STAR 0x21 +#define ISAC_CMDR 0x21 +#define ISAC_CMDR_XRES 0x01 +#define ISAC_CMDR_XME 0x02 +#define ISAC_CMDR_XTF 0x08 +#define ISAC_CMDR_RRES 0x40 +#define ISAC_CMDR_RMC 0x80 + +#define ISAC_EXIR 0x24 +#define ISAC_EXIR_MOS 0x04 +#define ISAC_EXIR_XDU 0x40 +#define ISAC_EXIR_XMR 0x80 + +#define ISAC_ADF2 0x39 +#define ISAC_SPCR 0x30 +#define ISAC_ADF1 0x38 + +#define ISAC_CIR0 0x31 +#define ISAC_CIX0 0x31 +#define ISAC_CIR0_CIC0 0x02 +#define ISAC_CIR0_CIC1 0x01 + +#define ISAC_CIR1 0x33 +#define ISAC_CIX1 0x33 +#define ISAC_STCR 0x37 +#define ISAC_MODE 0x22 + +#define ISAC_RSTA 0x27 +#define ISAC_RSTA_RDO 0x40 +#define ISAC_RSTA_CRC 0x20 +#define ISAC_RSTA_RAB 0x10 + +#define ISAC_RBCL 0x25 +#define ISAC_RBCH 0x2A +#define ISAC_TIMR 0x23 +#define ISAC_SQXR 0x3b +#define ISAC_MOSR 0x3a +#define ISAC_MOCR 0x3a +#define ISAC_MOR0 0x32 +#define ISAC_MOX0 0x32 +#define ISAC_MOR1 0x34 +#define ISAC_MOX1 0x34 + +#define ISAC_RBCH_XAC 0x80 + +#define ISAC_CMD_TIM 0x0 +#define ISAC_CMD_RES 0x1 +#define ISAC_CMD_SSP 0x2 +#define ISAC_CMD_SCP 0x3 +#define ISAC_CMD_AR8 0x8 +#define ISAC_CMD_AR10 0x9 +#define ISAC_CMD_ARL 0xa +#define ISAC_CMD_DI 0xf + +#define ISACSX_MASK 0x60 +#define ISACSX_ISTA 0x60 +#define ISACSX_ISTA_ICD 0x01 +#define ISACSX_ISTA_CIC 0x10 + +#define ISACSX_MASKD 0x20 +#define ISACSX_ISTAD 0x20 +#define ISACSX_ISTAD_XDU 0x04 +#define ISACSX_ISTAD_XMR 0x08 +#define ISACSX_ISTAD_XPR 0x10 +#define ISACSX_ISTAD_RFO 0x20 +#define ISACSX_ISTAD_RPF 0x40 +#define ISACSX_ISTAD_RME 0x80 + +#define ISACSX_CMDRD 0x21 +#define ISACSX_CMDRD_XRES 0x01 +#define ISACSX_CMDRD_XME 0x02 +#define ISACSX_CMDRD_XTF 0x08 +#define ISACSX_CMDRD_RRES 0x40 +#define ISACSX_CMDRD_RMC 0x80 + +#define ISACSX_MODED 0x22 + +#define ISACSX_RBCLD 0x26 + +#define ISACSX_RSTAD 0x28 +#define ISACSX_RSTAD_RAB 0x10 +#define ISACSX_RSTAD_CRC 0x20 +#define ISACSX_RSTAD_RDO 0x40 +#define ISACSX_RSTAD_VFR 0x80 + +#define ISACSX_CIR0 0x2e +#define ISACSX_CIR0_CIC0 0x08 +#define ISACSX_CIX0 0x2e + +#define ISACSX_TR_CONF0 0x30 + +#define ISACSX_TR_CONF2 0x32 + +static struct Fsm l1fsm; + +enum { + ST_L1_RESET, + ST_L1_F3_PDOWN, + ST_L1_F3_PUP, + ST_L1_F3_PEND_DEACT, + ST_L1_F4, + ST_L1_F5, + ST_L1_F6, + ST_L1_F7, + ST_L1_F8, +}; + +#define L1_STATE_COUNT (ST_L1_F8 + 1) + +static char *strL1State[] = +{ + "ST_L1_RESET", + "ST_L1_F3_PDOWN", + "ST_L1_F3_PUP", + "ST_L1_F3_PEND_DEACT", + "ST_L1_F4", + "ST_L1_F5", + "ST_L1_F6", + "ST_L1_F7", + "ST_L1_F8", +}; + +enum { + EV_PH_DR, // 0000 + EV_PH_RES, // 0001 + EV_PH_TMA, // 0010 + EV_PH_SLD, // 0011 + EV_PH_RSY, // 0100 + EV_PH_DR6, // 0101 + EV_PH_EI, // 0110 + EV_PH_PU, // 0111 + EV_PH_AR, // 1000 + EV_PH_9, // 1001 + EV_PH_ARL, // 1010 + EV_PH_CVR, // 1011 + EV_PH_AI8, // 1100 + EV_PH_AI10, // 1101 + EV_PH_AIL, // 1110 + EV_PH_DC, // 1111 + EV_PH_ACTIVATE_REQ, + EV_PH_DEACTIVATE_REQ, + EV_TIMER3, +}; + +#define L1_EVENT_COUNT (EV_TIMER3 + 1) + +static char *strL1Event[] = +{ + "EV_PH_DR", // 0000 + "EV_PH_RES", // 0001 + "EV_PH_TMA", // 0010 + "EV_PH_SLD", // 0011 + "EV_PH_RSY", // 0100 + "EV_PH_DR6", // 0101 + "EV_PH_EI", // 0110 + "EV_PH_PU", // 0111 + "EV_PH_AR", // 1000 + "EV_PH_9", // 1001 + "EV_PH_ARL", // 1010 + "EV_PH_CVR", // 1011 + "EV_PH_AI8", // 1100 + "EV_PH_AI10", // 1101 + "EV_PH_AIL", // 1110 + "EV_PH_DC", // 1111 + "EV_PH_ACTIVATE_REQ", + "EV_PH_DEACTIVATE_REQ", + "EV_TIMER3", +}; + +static inline void D_L1L2(struct isac *isac, int pr, void *arg) +{ + struct hisax_if *ifc = (struct hisax_if *) &isac->hisax_d_if; + + DBG(DBG_PR, "pr %#x", pr); + ifc->l1l2(ifc, pr, arg); +} + +static void ph_command(struct isac *isac, unsigned int command) +{ + DBG(DBG_L1M, "ph_command %#x", command); + switch (isac->type) { + case TYPE_ISAC: + isac->write_isac(isac, ISAC_CIX0, (command << 2) | 3); + break; + case TYPE_ISACSX: + isac->write_isac(isac, ISACSX_CIX0, (command << 4) | (7 << 1)); + break; + } +} + +// ---------------------------------------------------------------------- + +static void l1_di(struct FsmInst *fi, int event, void *arg) +{ + struct isac *isac = fi->userdata; + + FsmChangeState(fi, ST_L1_RESET); + ph_command(isac, ISAC_CMD_DI); +} + +static void l1_di_deact_ind(struct FsmInst *fi, int event, void *arg) +{ + struct isac *isac = fi->userdata; + + FsmChangeState(fi, ST_L1_RESET); + D_L1L2(isac, PH_DEACTIVATE | INDICATION, NULL); + ph_command(isac, ISAC_CMD_DI); +} + +static void l1_go_f3pdown(struct FsmInst *fi, int event, void *arg) +{ + FsmChangeState(fi, ST_L1_F3_PDOWN); +} + +static void l1_go_f3pend_deact_ind(struct FsmInst *fi, int event, void *arg) +{ + struct isac *isac = fi->userdata; + + FsmChangeState(fi, ST_L1_F3_PEND_DEACT); + D_L1L2(isac, PH_DEACTIVATE | INDICATION, NULL); + ph_command(isac, ISAC_CMD_DI); +} + +static void l1_go_f3pend(struct FsmInst *fi, int event, void *arg) +{ + struct isac *isac = fi->userdata; + + FsmChangeState(fi, ST_L1_F3_PEND_DEACT); + ph_command(isac, ISAC_CMD_DI); +} + +static void l1_go_f4(struct FsmInst *fi, int event, void *arg) +{ + FsmChangeState(fi, ST_L1_F4); +} + +static void l1_go_f5(struct FsmInst *fi, int event, void *arg) +{ + FsmChangeState(fi, ST_L1_F5); +} + +static void l1_go_f6(struct FsmInst *fi, int event, void *arg) +{ + FsmChangeState(fi, ST_L1_F6); +} + +static void l1_go_f6_deact_ind(struct FsmInst *fi, int event, void *arg) +{ + struct isac *isac = fi->userdata; + + FsmChangeState(fi, ST_L1_F6); + D_L1L2(isac, PH_DEACTIVATE | INDICATION, NULL); +} + +static void l1_go_f7_act_ind(struct FsmInst *fi, int event, void *arg) +{ + struct isac *isac = fi->userdata; + + FsmDelTimer(&isac->timer, 0); + FsmChangeState(fi, ST_L1_F7); + ph_command(isac, ISAC_CMD_AR8); + D_L1L2(isac, PH_ACTIVATE | INDICATION, NULL); +} + +static void l1_go_f8(struct FsmInst *fi, int event, void *arg) +{ + FsmChangeState(fi, ST_L1_F8); +} + +static void l1_go_f8_deact_ind(struct FsmInst *fi, int event, void *arg) +{ + struct isac *isac = fi->userdata; + + FsmChangeState(fi, ST_L1_F8); + D_L1L2(isac, PH_DEACTIVATE | INDICATION, NULL); +} + +static void l1_ar8(struct FsmInst *fi, int event, void *arg) +{ + struct isac *isac = fi->userdata; + + FsmRestartTimer(&isac->timer, TIMER3_VALUE, EV_TIMER3, NULL, 2); + ph_command(isac, ISAC_CMD_AR8); +} + +static void l1_timer3(struct FsmInst *fi, int event, void *arg) +{ + struct isac *isac = fi->userdata; + + ph_command(isac, ISAC_CMD_DI); + D_L1L2(isac, PH_DEACTIVATE | INDICATION, NULL); +} + +// state machines according to data sheet PSB 2186 / 3186 + +static struct FsmNode L1FnList[] __initdata = +{ + {ST_L1_RESET, EV_PH_RES, l1_di}, + {ST_L1_RESET, EV_PH_EI, l1_di}, + {ST_L1_RESET, EV_PH_DC, l1_go_f3pdown}, + {ST_L1_RESET, EV_PH_AR, l1_go_f6}, + {ST_L1_RESET, EV_PH_AI8, l1_go_f7_act_ind}, + + {ST_L1_F3_PDOWN, EV_PH_RES, l1_di}, + {ST_L1_F3_PDOWN, EV_PH_EI, l1_di}, + {ST_L1_F3_PDOWN, EV_PH_AR, l1_go_f6}, + {ST_L1_F3_PDOWN, EV_PH_RSY, l1_go_f5}, + {ST_L1_F3_PDOWN, EV_PH_PU, l1_go_f4}, + {ST_L1_F3_PDOWN, EV_PH_AI8, l1_go_f7_act_ind}, + {ST_L1_F3_PDOWN, EV_PH_ACTIVATE_REQ, l1_ar8}, + {ST_L1_F3_PDOWN, EV_TIMER3, l1_timer3}, + + {ST_L1_F3_PEND_DEACT, EV_PH_RES, l1_di}, + {ST_L1_F3_PEND_DEACT, EV_PH_EI, l1_di}, + {ST_L1_F3_PEND_DEACT, EV_PH_DC, l1_go_f3pdown}, + {ST_L1_F3_PEND_DEACT, EV_PH_RSY, l1_go_f5}, + {ST_L1_F3_PEND_DEACT, EV_PH_AR, l1_go_f6}, + {ST_L1_F3_PEND_DEACT, EV_PH_AI8, l1_go_f7_act_ind}, + + {ST_L1_F4, EV_PH_RES, l1_di}, + {ST_L1_F4, EV_PH_EI, l1_di}, + {ST_L1_F4, EV_PH_RSY, l1_go_f5}, + {ST_L1_F4, EV_PH_AI8, l1_go_f7_act_ind}, + {ST_L1_F4, EV_TIMER3, l1_timer3}, + {ST_L1_F4, EV_PH_DC, l1_go_f3pdown}, + + {ST_L1_F5, EV_PH_RES, l1_di}, + {ST_L1_F5, EV_PH_EI, l1_di}, + {ST_L1_F5, EV_PH_AR, l1_go_f6}, + {ST_L1_F5, EV_PH_AI8, l1_go_f7_act_ind}, + {ST_L1_F5, EV_TIMER3, l1_timer3}, + {ST_L1_F5, EV_PH_DR, l1_go_f3pend}, + {ST_L1_F5, EV_PH_DC, l1_go_f3pdown}, + + {ST_L1_F6, EV_PH_RES, l1_di}, + {ST_L1_F6, EV_PH_EI, l1_di}, + {ST_L1_F6, EV_PH_RSY, l1_go_f8}, + {ST_L1_F6, EV_PH_AI8, l1_go_f7_act_ind}, + {ST_L1_F6, EV_PH_DR6, l1_go_f3pend}, + {ST_L1_F6, EV_TIMER3, l1_timer3}, + {ST_L1_F6, EV_PH_DC, l1_go_f3pdown}, + + {ST_L1_F7, EV_PH_RES, l1_di_deact_ind}, + {ST_L1_F7, EV_PH_EI, l1_di_deact_ind}, + {ST_L1_F7, EV_PH_AR, l1_go_f6_deact_ind}, + {ST_L1_F7, EV_PH_RSY, l1_go_f8_deact_ind}, + {ST_L1_F7, EV_PH_DR, l1_go_f3pend_deact_ind}, + + {ST_L1_F8, EV_PH_RES, l1_di}, + {ST_L1_F8, EV_PH_EI, l1_di}, + {ST_L1_F8, EV_PH_AR, l1_go_f6}, + {ST_L1_F8, EV_PH_DR, l1_go_f3pend}, + {ST_L1_F8, EV_PH_AI8, l1_go_f7_act_ind}, + {ST_L1_F8, EV_TIMER3, l1_timer3}, + {ST_L1_F8, EV_PH_DC, l1_go_f3pdown}, +}; + +static void l1m_debug(struct FsmInst *fi, char *fmt, ...) +{ + va_list args; + char buf[256]; + + va_start(args, fmt); + vsnprintf(buf, sizeof(buf), fmt, args); + DBG(DBG_L1M, "%s", buf); + va_end(args); +} + +static void isac_version(struct isac *cs) +{ + int val; + + val = cs->read_isac(cs, ISAC_RBCH); + DBG(1, "ISAC version (%x): %s", val, ISACVer[(val >> 5) & 3]); +} + +static void isac_empty_fifo(struct isac *isac, int count) +{ + // this also works for isacsx, since + // CMDR(D) register works the same + u_char *ptr; + + DBG(DBG_IRQ, "count %d", count); + + if ((isac->rcvidx + count) >= MAX_DFRAME_LEN_L1) { + DBG(DBG_WARN, "overrun %d", isac->rcvidx + count); + isac->write_isac(isac, ISAC_CMDR, ISAC_CMDR_RMC); + isac->rcvidx = 0; + return; + } + ptr = isac->rcvbuf + isac->rcvidx; + isac->rcvidx += count; + isac->read_isac_fifo(isac, ptr, count); + isac->write_isac(isac, ISAC_CMDR, ISAC_CMDR_RMC); + DBG_PACKET(DBG_RFIFO, ptr, count); +} + +static void isac_fill_fifo(struct isac *isac) +{ + // this also works for isacsx, since + // CMDR(D) register works the same + + int count; + unsigned char cmd; + u_char *ptr; + + BUG_ON(!isac->tx_skb); + + count = isac->tx_skb->len; + BUG_ON(count <= 0); + + DBG(DBG_IRQ, "count %d", count); + + if (count > 0x20) { + count = 0x20; + cmd = ISAC_CMDR_XTF; + } else { + cmd = ISAC_CMDR_XTF | ISAC_CMDR_XME; + } + + ptr = isac->tx_skb->data; + skb_pull(isac->tx_skb, count); + isac->tx_cnt += count; + DBG_PACKET(DBG_XFIFO, ptr, count); + isac->write_isac_fifo(isac, ptr, count); + isac->write_isac(isac, ISAC_CMDR, cmd); +} + +static void isac_retransmit(struct isac *isac) +{ + if (!isac->tx_skb) { + DBG(DBG_WARN, "no skb"); + return; + } + skb_push(isac->tx_skb, isac->tx_cnt); + isac->tx_cnt = 0; +} + + +static inline void isac_cisq_interrupt(struct isac *isac) +{ + unsigned char val; + + val = isac->read_isac(isac, ISAC_CIR0); + DBG(DBG_IRQ, "CIR0 %#x", val); + if (val & ISAC_CIR0_CIC0) { + DBG(DBG_IRQ, "CODR0 %#x", (val >> 2) & 0xf); + FsmEvent(&isac->l1m, (val >> 2) & 0xf, NULL); + } + if (val & ISAC_CIR0_CIC1) { + val = isac->read_isac(isac, ISAC_CIR1); + DBG(DBG_WARN, "ISAC CIR1 %#x", val); + } +} + +static inline void isac_rme_interrupt(struct isac *isac) +{ + unsigned char val; + int count; + struct sk_buff *skb; + + val = isac->read_isac(isac, ISAC_RSTA); + if ((val & (ISAC_RSTA_RDO | ISAC_RSTA_CRC | ISAC_RSTA_RAB)) + != ISAC_RSTA_CRC) { + DBG(DBG_WARN, "RSTA %#x, dropped", val); + isac->write_isac(isac, ISAC_CMDR, ISAC_CMDR_RMC); + goto out; + } + + count = isac->read_isac(isac, ISAC_RBCL) & 0x1f; + DBG(DBG_IRQ, "RBCL %#x", count); + if (count == 0) + count = 0x20; + + isac_empty_fifo(isac, count); + count = isac->rcvidx; + if (count < 1) { + DBG(DBG_WARN, "count %d < 1", count); + goto out; + } + + skb = alloc_skb(count, GFP_ATOMIC); + if (!skb) { + DBG(DBG_WARN, "no memory, dropping\n"); + goto out; + } + skb_put_data(skb, isac->rcvbuf, count); + DBG_SKB(DBG_RPACKET, skb); + D_L1L2(isac, PH_DATA | INDICATION, skb); +out: + isac->rcvidx = 0; +} + +static inline void isac_xpr_interrupt(struct isac *isac) +{ + if (!isac->tx_skb) + return; + + if (isac->tx_skb->len > 0) { + isac_fill_fifo(isac); + return; + } + dev_kfree_skb_irq(isac->tx_skb); + isac->tx_cnt = 0; + isac->tx_skb = NULL; + D_L1L2(isac, PH_DATA | CONFIRM, NULL); +} + +static inline void isac_exi_interrupt(struct isac *isac) +{ + unsigned char val; + + val = isac->read_isac(isac, ISAC_EXIR); + DBG(2, "EXIR %#x", val); + + if (val & ISAC_EXIR_XMR) { + DBG(DBG_WARN, "ISAC XMR"); + isac_retransmit(isac); + } + if (val & ISAC_EXIR_XDU) { + DBG(DBG_WARN, "ISAC XDU"); + isac_retransmit(isac); + } + if (val & ISAC_EXIR_MOS) { /* MOS */ + DBG(DBG_WARN, "MOS"); + val = isac->read_isac(isac, ISAC_MOSR); + DBG(2, "ISAC MOSR %#x", val); + } +} + +void isac_irq(struct isac *isac) +{ + unsigned char val; + + val = isac->read_isac(isac, ISAC_ISTA); + DBG(DBG_IRQ, "ISTA %#x", val); + + if (val & ISAC_ISTA_EXI) { + DBG(DBG_IRQ, "EXI"); + isac_exi_interrupt(isac); + } + if (val & ISAC_ISTA_XPR) { + DBG(DBG_IRQ, "XPR"); + isac_xpr_interrupt(isac); + } + if (val & ISAC_ISTA_RME) { + DBG(DBG_IRQ, "RME"); + isac_rme_interrupt(isac); + } + if (val & ISAC_ISTA_RPF) { + DBG(DBG_IRQ, "RPF"); + isac_empty_fifo(isac, 0x20); + } + if (val & ISAC_ISTA_CISQ) { + DBG(DBG_IRQ, "CISQ"); + isac_cisq_interrupt(isac); + } + if (val & ISAC_ISTA_RSC) { + DBG(DBG_WARN, "RSC"); + } + if (val & ISAC_ISTA_SIN) { + DBG(DBG_WARN, "SIN"); + } + isac->write_isac(isac, ISAC_MASK, 0xff); + isac->write_isac(isac, ISAC_MASK, 0x00); +} + +// ====================================================================== + +static inline void isacsx_cic_interrupt(struct isac *isac) +{ + unsigned char val; + + val = isac->read_isac(isac, ISACSX_CIR0); + DBG(DBG_IRQ, "CIR0 %#x", val); + if (val & ISACSX_CIR0_CIC0) { + DBG(DBG_IRQ, "CODR0 %#x", val >> 4); + FsmEvent(&isac->l1m, val >> 4, NULL); + } +} + +static inline void isacsx_rme_interrupt(struct isac *isac) +{ + int count; + struct sk_buff *skb; + unsigned char val; + + val = isac->read_isac(isac, ISACSX_RSTAD); + if ((val & (ISACSX_RSTAD_VFR | + ISACSX_RSTAD_RDO | + ISACSX_RSTAD_CRC | + ISACSX_RSTAD_RAB)) + != (ISACSX_RSTAD_VFR | ISACSX_RSTAD_CRC)) { + DBG(DBG_WARN, "RSTAD %#x, dropped", val); + isac->write_isac(isac, ISACSX_CMDRD, ISACSX_CMDRD_RMC); + goto out; + } + + count = isac->read_isac(isac, ISACSX_RBCLD) & 0x1f; + DBG(DBG_IRQ, "RBCLD %#x", count); + if (count == 0) + count = 0x20; + + isac_empty_fifo(isac, count); + // strip trailing status byte + count = isac->rcvidx - 1; + if (count < 1) { + DBG(DBG_WARN, "count %d < 1", count); + goto out; + } + + skb = dev_alloc_skb(count); + if (!skb) { + DBG(DBG_WARN, "no memory, dropping"); + goto out; + } + skb_put_data(skb, isac->rcvbuf, count); + DBG_SKB(DBG_RPACKET, skb); + D_L1L2(isac, PH_DATA | INDICATION, skb); +out: + isac->rcvidx = 0; +} + +static inline void isacsx_xpr_interrupt(struct isac *isac) +{ + if (!isac->tx_skb) + return; + + if (isac->tx_skb->len > 0) { + isac_fill_fifo(isac); + return; + } + dev_kfree_skb_irq(isac->tx_skb); + isac->tx_skb = NULL; + isac->tx_cnt = 0; + D_L1L2(isac, PH_DATA | CONFIRM, NULL); +} + +static inline void isacsx_icd_interrupt(struct isac *isac) +{ + unsigned char val; + + val = isac->read_isac(isac, ISACSX_ISTAD); + DBG(DBG_IRQ, "ISTAD %#x", val); + if (val & ISACSX_ISTAD_XDU) { + DBG(DBG_WARN, "ISTAD XDU"); + isac_retransmit(isac); + } + if (val & ISACSX_ISTAD_XMR) { + DBG(DBG_WARN, "ISTAD XMR"); + isac_retransmit(isac); + } + if (val & ISACSX_ISTAD_XPR) { + DBG(DBG_IRQ, "ISTAD XPR"); + isacsx_xpr_interrupt(isac); + } + if (val & ISACSX_ISTAD_RFO) { + DBG(DBG_WARN, "ISTAD RFO"); + isac->write_isac(isac, ISACSX_CMDRD, ISACSX_CMDRD_RMC); + } + if (val & ISACSX_ISTAD_RME) { + DBG(DBG_IRQ, "ISTAD RME"); + isacsx_rme_interrupt(isac); + } + if (val & ISACSX_ISTAD_RPF) { + DBG(DBG_IRQ, "ISTAD RPF"); + isac_empty_fifo(isac, 0x20); + } +} + +void isacsx_irq(struct isac *isac) +{ + unsigned char val; + + val = isac->read_isac(isac, ISACSX_ISTA); + DBG(DBG_IRQ, "ISTA %#x", val); + + if (val & ISACSX_ISTA_ICD) + isacsx_icd_interrupt(isac); + if (val & ISACSX_ISTA_CIC) + isacsx_cic_interrupt(isac); +} + +void isac_init(struct isac *isac) +{ + isac->tx_skb = NULL; + isac->l1m.fsm = &l1fsm; + isac->l1m.state = ST_L1_RESET; +#ifdef CONFIG_HISAX_DEBUG + isac->l1m.debug = 1; +#else + isac->l1m.debug = 0; +#endif + isac->l1m.userdata = isac; + isac->l1m.printdebug = l1m_debug; + FsmInitTimer(&isac->l1m, &isac->timer); +} + +void isac_setup(struct isac *isac) +{ + int val, eval; + + isac->type = TYPE_ISAC; + isac_version(isac); + + ph_command(isac, ISAC_CMD_RES); + + isac->write_isac(isac, ISAC_MASK, 0xff); + isac->mocr = 0xaa; + if (test_bit(ISAC_IOM1, &isac->flags)) { + /* IOM 1 Mode */ + isac->write_isac(isac, ISAC_ADF2, 0x0); + isac->write_isac(isac, ISAC_SPCR, 0xa); + isac->write_isac(isac, ISAC_ADF1, 0x2); + isac->write_isac(isac, ISAC_STCR, 0x70); + isac->write_isac(isac, ISAC_MODE, 0xc9); + } else { + /* IOM 2 Mode */ + if (!isac->adf2) + isac->adf2 = 0x80; + isac->write_isac(isac, ISAC_ADF2, isac->adf2); + isac->write_isac(isac, ISAC_SQXR, 0x2f); + isac->write_isac(isac, ISAC_SPCR, 0x00); + isac->write_isac(isac, ISAC_STCR, 0x70); + isac->write_isac(isac, ISAC_MODE, 0xc9); + isac->write_isac(isac, ISAC_TIMR, 0x00); + isac->write_isac(isac, ISAC_ADF1, 0x00); + } + val = isac->read_isac(isac, ISAC_STAR); + DBG(2, "ISAC STAR %x", val); + val = isac->read_isac(isac, ISAC_MODE); + DBG(2, "ISAC MODE %x", val); + val = isac->read_isac(isac, ISAC_ADF2); + DBG(2, "ISAC ADF2 %x", val); + val = isac->read_isac(isac, ISAC_ISTA); + DBG(2, "ISAC ISTA %x", val); + if (val & 0x01) { + eval = isac->read_isac(isac, ISAC_EXIR); + DBG(2, "ISAC EXIR %x", eval); + } + val = isac->read_isac(isac, ISAC_CIR0); + DBG(2, "ISAC CIR0 %x", val); + FsmEvent(&isac->l1m, (val >> 2) & 0xf, NULL); + + isac->write_isac(isac, ISAC_MASK, 0x0); + // RESET Receiver and Transmitter + isac->write_isac(isac, ISAC_CMDR, ISAC_CMDR_XRES | ISAC_CMDR_RRES); +} + +void isacsx_setup(struct isac *isac) +{ + isac->type = TYPE_ISACSX; + // clear LDD + isac->write_isac(isac, ISACSX_TR_CONF0, 0x00); + // enable transmitter + isac->write_isac(isac, ISACSX_TR_CONF2, 0x00); + // transparent mode 0, RAC, stop/go + isac->write_isac(isac, ISACSX_MODED, 0xc9); + // all HDLC IRQ unmasked + isac->write_isac(isac, ISACSX_MASKD, 0x03); + // unmask ICD, CID IRQs + isac->write_isac(isac, ISACSX_MASK, + ~(ISACSX_ISTA_ICD | ISACSX_ISTA_CIC)); +} + +void isac_d_l2l1(struct hisax_if *hisax_d_if, int pr, void *arg) +{ + struct isac *isac = hisax_d_if->priv; + struct sk_buff *skb = arg; + + DBG(DBG_PR, "pr %#x", pr); + + switch (pr) { + case PH_ACTIVATE | REQUEST: + FsmEvent(&isac->l1m, EV_PH_ACTIVATE_REQ, NULL); + break; + case PH_DEACTIVATE | REQUEST: + FsmEvent(&isac->l1m, EV_PH_DEACTIVATE_REQ, NULL); + break; + case PH_DATA | REQUEST: + DBG(DBG_PR, "PH_DATA REQUEST len %d", skb->len); + DBG_SKB(DBG_XPACKET, skb); + if (isac->l1m.state != ST_L1_F7) { + DBG(1, "L1 wrong state %d\n", isac->l1m.state); + dev_kfree_skb(skb); + break; + } + BUG_ON(isac->tx_skb); + + isac->tx_skb = skb; + isac_fill_fifo(isac); + break; + } +} + +static int __init hisax_isac_init(void) +{ + printk(KERN_INFO "hisax_isac: ISAC-S/ISAC-SX ISDN driver v0.1.0\n"); + + l1fsm.state_count = L1_STATE_COUNT; + l1fsm.event_count = L1_EVENT_COUNT; + l1fsm.strState = strL1State; + l1fsm.strEvent = strL1Event; + return FsmNew(&l1fsm, L1FnList, ARRAY_SIZE(L1FnList)); +} + +static void __exit hisax_isac_exit(void) +{ + FsmFree(&l1fsm); +} + +EXPORT_SYMBOL(isac_init); +EXPORT_SYMBOL(isac_d_l2l1); + +EXPORT_SYMBOL(isacsx_setup); +EXPORT_SYMBOL(isacsx_irq); + +EXPORT_SYMBOL(isac_setup); +EXPORT_SYMBOL(isac_irq); + +module_init(hisax_isac_init); +module_exit(hisax_isac_exit); diff --git a/drivers/isdn/hisax/hisax_isac.h b/drivers/isdn/hisax/hisax_isac.h new file mode 100644 index 000000000..d7301da97 --- /dev/null +++ b/drivers/isdn/hisax/hisax_isac.h @@ -0,0 +1,46 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef __HISAX_ISAC_H__ +#define __HISAX_ISAC_H__ + +#include <linux/kernel.h> +#include "fsm.h" +#include "hisax_if.h" + +#define TIMER3_VALUE 7000 +#define MAX_DFRAME_LEN_L1 300 + +#define ISAC_IOM1 0 + +struct isac { + void *priv; + + u_long flags; + struct hisax_d_if hisax_d_if; + struct FsmInst l1m; + struct FsmTimer timer; + u_char mocr; + u_char adf2; + int type; + + u_char rcvbuf[MAX_DFRAME_LEN_L1]; + int rcvidx; + + struct sk_buff *tx_skb; + int tx_cnt; + + u_char (*read_isac) (struct isac *, u_char); + void (*write_isac) (struct isac *, u_char, u_char); + void (*read_isac_fifo) (struct isac *, u_char *, int); + void (*write_isac_fifo)(struct isac *, u_char *, int); +}; + +void isac_init(struct isac *isac); +void isac_d_l2l1(struct hisax_if *hisax_d_if, int pr, void *arg); + +void isac_setup(struct isac *isac); +void isac_irq(struct isac *isac); + +void isacsx_setup(struct isac *isac); +void isacsx_irq(struct isac *isac); + +#endif diff --git a/drivers/isdn/hisax/hscx.c b/drivers/isdn/hisax/hscx.c new file mode 100644 index 000000000..3e305fec0 --- /dev/null +++ b/drivers/isdn/hisax/hscx.c @@ -0,0 +1,277 @@ +/* $Id: hscx.c,v 1.24.2.4 2004/01/24 20:47:23 keil Exp $ + * + * HSCX specific routines + * + * Author Karsten Keil + * Copyright by Karsten Keil <keil@isdn4linux.de> + * + * This software may be used and distributed according to the terms + * of the GNU General Public License, incorporated herein by reference. + * + */ + +#include <linux/init.h> +#include "hisax.h" +#include "hscx.h" +#include "isac.h" +#include "isdnl1.h" +#include <linux/interrupt.h> +#include <linux/slab.h> + +static char *HSCXVer[] = +{"A1", "?1", "A2", "?3", "A3", "V2.1", "?6", "?7", + "?8", "?9", "?10", "?11", "?12", "?13", "?14", "???"}; + +int +HscxVersion(struct IsdnCardState *cs, char *s) +{ + int verA, verB; + + verA = cs->BC_Read_Reg(cs, 0, HSCX_VSTR) & 0xf; + verB = cs->BC_Read_Reg(cs, 1, HSCX_VSTR) & 0xf; + printk(KERN_INFO "%s HSCX version A: %s B: %s\n", s, + HSCXVer[verA], HSCXVer[verB]); + if ((verA == 0) | (verA == 0xf) | (verB == 0) | (verB == 0xf)) + return (1); + else + return (0); +} + +void +modehscx(struct BCState *bcs, int mode, int bc) +{ + struct IsdnCardState *cs = bcs->cs; + int hscx = bcs->hw.hscx.hscx; + + if (cs->debug & L1_DEB_HSCX) + debugl1(cs, "hscx %c mode %d ichan %d", + 'A' + hscx, mode, bc); + bcs->mode = mode; + bcs->channel = bc; + cs->BC_Write_Reg(cs, hscx, HSCX_XAD1, 0xFF); + cs->BC_Write_Reg(cs, hscx, HSCX_XAD2, 0xFF); + cs->BC_Write_Reg(cs, hscx, HSCX_RAH2, 0xFF); + cs->BC_Write_Reg(cs, hscx, HSCX_XBCH, 0x0); + cs->BC_Write_Reg(cs, hscx, HSCX_RLCR, 0x0); + cs->BC_Write_Reg(cs, hscx, HSCX_CCR1, + test_bit(HW_IPAC, &cs->HW_Flags) ? 0x82 : 0x85); + cs->BC_Write_Reg(cs, hscx, HSCX_CCR2, 0x30); + cs->BC_Write_Reg(cs, hscx, HSCX_XCCR, 7); + cs->BC_Write_Reg(cs, hscx, HSCX_RCCR, 7); + + /* Switch IOM 1 SSI */ + if (test_bit(HW_IOM1, &cs->HW_Flags) && (hscx == 0)) + bc = 1 - bc; + + if (bc == 0) { + cs->BC_Write_Reg(cs, hscx, HSCX_TSAX, + test_bit(HW_IOM1, &cs->HW_Flags) ? 0x7 : bcs->hw.hscx.tsaxr0); + cs->BC_Write_Reg(cs, hscx, HSCX_TSAR, + test_bit(HW_IOM1, &cs->HW_Flags) ? 0x7 : bcs->hw.hscx.tsaxr0); + } else { + cs->BC_Write_Reg(cs, hscx, HSCX_TSAX, bcs->hw.hscx.tsaxr1); + cs->BC_Write_Reg(cs, hscx, HSCX_TSAR, bcs->hw.hscx.tsaxr1); + } + switch (mode) { + case (L1_MODE_NULL): + cs->BC_Write_Reg(cs, hscx, HSCX_TSAX, 0x1f); + cs->BC_Write_Reg(cs, hscx, HSCX_TSAR, 0x1f); + cs->BC_Write_Reg(cs, hscx, HSCX_MODE, 0x84); + break; + case (L1_MODE_TRANS): + cs->BC_Write_Reg(cs, hscx, HSCX_MODE, 0xe4); + break; + case (L1_MODE_HDLC): + cs->BC_Write_Reg(cs, hscx, HSCX_CCR1, + test_bit(HW_IPAC, &cs->HW_Flags) ? 0x8a : 0x8d); + cs->BC_Write_Reg(cs, hscx, HSCX_MODE, 0x8c); + break; + } + if (mode) + cs->BC_Write_Reg(cs, hscx, HSCX_CMDR, 0x41); + cs->BC_Write_Reg(cs, hscx, HSCX_ISTA, 0x00); +} + +void +hscx_l2l1(struct PStack *st, int pr, void *arg) +{ + struct BCState *bcs = st->l1.bcs; + u_long flags; + struct sk_buff *skb = arg; + + switch (pr) { + case (PH_DATA | REQUEST): + spin_lock_irqsave(&bcs->cs->lock, flags); + if (bcs->tx_skb) { + skb_queue_tail(&bcs->squeue, skb); + } else { + bcs->tx_skb = skb; + test_and_set_bit(BC_FLG_BUSY, &bcs->Flag); + bcs->hw.hscx.count = 0; + bcs->cs->BC_Send_Data(bcs); + } + spin_unlock_irqrestore(&bcs->cs->lock, flags); + break; + case (PH_PULL | INDICATION): + spin_lock_irqsave(&bcs->cs->lock, flags); + if (bcs->tx_skb) { + printk(KERN_WARNING "hscx_l2l1: this shouldn't happen\n"); + } else { + test_and_set_bit(BC_FLG_BUSY, &bcs->Flag); + bcs->tx_skb = skb; + bcs->hw.hscx.count = 0; + bcs->cs->BC_Send_Data(bcs); + } + spin_unlock_irqrestore(&bcs->cs->lock, flags); + break; + case (PH_PULL | REQUEST): + if (!bcs->tx_skb) { + test_and_clear_bit(FLG_L1_PULL_REQ, &st->l1.Flags); + st->l1.l1l2(st, PH_PULL | CONFIRM, NULL); + } else + test_and_set_bit(FLG_L1_PULL_REQ, &st->l1.Flags); + break; + case (PH_ACTIVATE | REQUEST): + spin_lock_irqsave(&bcs->cs->lock, flags); + test_and_set_bit(BC_FLG_ACTIV, &bcs->Flag); + modehscx(bcs, st->l1.mode, st->l1.bc); + spin_unlock_irqrestore(&bcs->cs->lock, flags); + l1_msg_b(st, pr, arg); + break; + case (PH_DEACTIVATE | REQUEST): + l1_msg_b(st, pr, arg); + break; + case (PH_DEACTIVATE | CONFIRM): + spin_lock_irqsave(&bcs->cs->lock, flags); + test_and_clear_bit(BC_FLG_ACTIV, &bcs->Flag); + test_and_clear_bit(BC_FLG_BUSY, &bcs->Flag); + modehscx(bcs, 0, st->l1.bc); + spin_unlock_irqrestore(&bcs->cs->lock, flags); + st->l1.l1l2(st, PH_DEACTIVATE | CONFIRM, NULL); + break; + } +} + +static void +close_hscxstate(struct BCState *bcs) +{ + modehscx(bcs, 0, bcs->channel); + if (test_and_clear_bit(BC_FLG_INIT, &bcs->Flag)) { + kfree(bcs->hw.hscx.rcvbuf); + bcs->hw.hscx.rcvbuf = NULL; + kfree(bcs->blog); + bcs->blog = NULL; + skb_queue_purge(&bcs->rqueue); + skb_queue_purge(&bcs->squeue); + if (bcs->tx_skb) { + dev_kfree_skb_any(bcs->tx_skb); + bcs->tx_skb = NULL; + test_and_clear_bit(BC_FLG_BUSY, &bcs->Flag); + } + } +} + +int +open_hscxstate(struct IsdnCardState *cs, struct BCState *bcs) +{ + if (!test_and_set_bit(BC_FLG_INIT, &bcs->Flag)) { + if (!(bcs->hw.hscx.rcvbuf = kmalloc(HSCX_BUFMAX, GFP_ATOMIC))) { + printk(KERN_WARNING + "HiSax: No memory for hscx.rcvbuf\n"); + test_and_clear_bit(BC_FLG_INIT, &bcs->Flag); + return (1); + } + if (!(bcs->blog = kmalloc(MAX_BLOG_SPACE, GFP_ATOMIC))) { + printk(KERN_WARNING + "HiSax: No memory for bcs->blog\n"); + test_and_clear_bit(BC_FLG_INIT, &bcs->Flag); + kfree(bcs->hw.hscx.rcvbuf); + bcs->hw.hscx.rcvbuf = NULL; + return (2); + } + skb_queue_head_init(&bcs->rqueue); + skb_queue_head_init(&bcs->squeue); + } + bcs->tx_skb = NULL; + test_and_clear_bit(BC_FLG_BUSY, &bcs->Flag); + bcs->event = 0; + bcs->hw.hscx.rcvidx = 0; + bcs->tx_cnt = 0; + return (0); +} + +static int +setstack_hscx(struct PStack *st, struct BCState *bcs) +{ + bcs->channel = st->l1.bc; + if (open_hscxstate(st->l1.hardware, bcs)) + return (-1); + st->l1.bcs = bcs; + st->l2.l2l1 = hscx_l2l1; + setstack_manager(st); + bcs->st = st; + setstack_l1_B(st); + return (0); +} + +void +clear_pending_hscx_ints(struct IsdnCardState *cs) +{ + int val, eval; + + val = cs->BC_Read_Reg(cs, 1, HSCX_ISTA); + debugl1(cs, "HSCX B ISTA %x", val); + if (val & 0x01) { + eval = cs->BC_Read_Reg(cs, 1, HSCX_EXIR); + debugl1(cs, "HSCX B EXIR %x", eval); + } + if (val & 0x02) { + eval = cs->BC_Read_Reg(cs, 0, HSCX_EXIR); + debugl1(cs, "HSCX A EXIR %x", eval); + } + val = cs->BC_Read_Reg(cs, 0, HSCX_ISTA); + debugl1(cs, "HSCX A ISTA %x", val); + val = cs->BC_Read_Reg(cs, 1, HSCX_STAR); + debugl1(cs, "HSCX B STAR %x", val); + val = cs->BC_Read_Reg(cs, 0, HSCX_STAR); + debugl1(cs, "HSCX A STAR %x", val); + /* disable all IRQ */ + cs->BC_Write_Reg(cs, 0, HSCX_MASK, 0xFF); + cs->BC_Write_Reg(cs, 1, HSCX_MASK, 0xFF); +} + +void +inithscx(struct IsdnCardState *cs) +{ + cs->bcs[0].BC_SetStack = setstack_hscx; + cs->bcs[1].BC_SetStack = setstack_hscx; + cs->bcs[0].BC_Close = close_hscxstate; + cs->bcs[1].BC_Close = close_hscxstate; + cs->bcs[0].hw.hscx.hscx = 0; + cs->bcs[1].hw.hscx.hscx = 1; + cs->bcs[0].hw.hscx.tsaxr0 = 0x2f; + cs->bcs[0].hw.hscx.tsaxr1 = 3; + cs->bcs[1].hw.hscx.tsaxr0 = 0x2f; + cs->bcs[1].hw.hscx.tsaxr1 = 3; + modehscx(cs->bcs, 0, 0); + modehscx(cs->bcs + 1, 0, 0); +} + +void +inithscxisac(struct IsdnCardState *cs, int part) +{ + if (part & 1) { + clear_pending_isac_ints(cs); + clear_pending_hscx_ints(cs); + initisac(cs); + inithscx(cs); + } + if (part & 2) { + /* Reenable all IRQ */ + cs->writeisac(cs, ISAC_MASK, 0); + cs->BC_Write_Reg(cs, 0, HSCX_MASK, 0); + cs->BC_Write_Reg(cs, 1, HSCX_MASK, 0); + /* RESET Receiver and Transmitter */ + cs->writeisac(cs, ISAC_CMDR, 0x41); + } +} diff --git a/drivers/isdn/hisax/hscx.h b/drivers/isdn/hisax/hscx.h new file mode 100644 index 000000000..1148b4bbe --- /dev/null +++ b/drivers/isdn/hisax/hscx.h @@ -0,0 +1,41 @@ +/* $Id: hscx.h,v 1.8.2.2 2004/01/12 22:52:26 keil Exp $ + * + * HSCX specific defines + * + * Author Karsten Keil + * Copyright by Karsten Keil <keil@isdn4linux.de> + * + * This software may be used and distributed according to the terms + * of the GNU General Public License, incorporated herein by reference. + * + */ + +/* All Registers original Siemens Spec */ + +#define HSCX_ISTA 0x20 +#define HSCX_CCR1 0x2f +#define HSCX_CCR2 0x2c +#define HSCX_TSAR 0x31 +#define HSCX_TSAX 0x30 +#define HSCX_XCCR 0x32 +#define HSCX_RCCR 0x33 +#define HSCX_MODE 0x22 +#define HSCX_CMDR 0x21 +#define HSCX_EXIR 0x24 +#define HSCX_XAD1 0x24 +#define HSCX_XAD2 0x25 +#define HSCX_RAH2 0x27 +#define HSCX_RSTA 0x27 +#define HSCX_TIMR 0x23 +#define HSCX_STAR 0x21 +#define HSCX_RBCL 0x25 +#define HSCX_XBCH 0x2d +#define HSCX_VSTR 0x2e +#define HSCX_RLCR 0x2e +#define HSCX_MASK 0x20 + +extern int HscxVersion(struct IsdnCardState *cs, char *s); +extern void modehscx(struct BCState *bcs, int mode, int bc); +extern void clear_pending_hscx_ints(struct IsdnCardState *cs); +extern void inithscx(struct IsdnCardState *cs); +extern void inithscxisac(struct IsdnCardState *cs, int part); diff --git a/drivers/isdn/hisax/hscx_irq.c b/drivers/isdn/hisax/hscx_irq.c new file mode 100644 index 000000000..0d7e783c8 --- /dev/null +++ b/drivers/isdn/hisax/hscx_irq.c @@ -0,0 +1,294 @@ +/* $Id: hscx_irq.c,v 1.18.2.3 2004/02/11 13:21:34 keil Exp $ + * + * low level b-channel stuff for Siemens HSCX + * + * Author Karsten Keil + * Copyright by Karsten Keil <keil@isdn4linux.de> + * + * This software may be used and distributed according to the terms + * of the GNU General Public License, incorporated herein by reference. + * + * This is an include file for fast inline IRQ stuff + * + */ + + +static inline void +waitforCEC(struct IsdnCardState *cs, int hscx) +{ + int to = 50; + + while ((READHSCX(cs, hscx, HSCX_STAR) & 0x04) && to) { + udelay(1); + to--; + } + if (!to) + printk(KERN_WARNING "HiSax: waitforCEC timeout\n"); +} + + +static inline void +waitforXFW(struct IsdnCardState *cs, int hscx) +{ + int to = 50; + + while (((READHSCX(cs, hscx, HSCX_STAR) & 0x44) != 0x40) && to) { + udelay(1); + to--; + } + if (!to) + printk(KERN_WARNING "HiSax: waitforXFW timeout\n"); +} + +static inline void +WriteHSCXCMDR(struct IsdnCardState *cs, int hscx, u_char data) +{ + waitforCEC(cs, hscx); + WRITEHSCX(cs, hscx, HSCX_CMDR, data); +} + + + +static void +hscx_empty_fifo(struct BCState *bcs, int count) +{ + u_char *ptr; + struct IsdnCardState *cs = bcs->cs; + + if ((cs->debug & L1_DEB_HSCX) && !(cs->debug & L1_DEB_HSCX_FIFO)) + debugl1(cs, "hscx_empty_fifo"); + + if (bcs->hw.hscx.rcvidx + count > HSCX_BUFMAX) { + if (cs->debug & L1_DEB_WARN) + debugl1(cs, "hscx_empty_fifo: incoming packet too large"); + WriteHSCXCMDR(cs, bcs->hw.hscx.hscx, 0x80); + bcs->hw.hscx.rcvidx = 0; + return; + } + ptr = bcs->hw.hscx.rcvbuf + bcs->hw.hscx.rcvidx; + bcs->hw.hscx.rcvidx += count; + READHSCXFIFO(cs, bcs->hw.hscx.hscx, ptr, count); + WriteHSCXCMDR(cs, bcs->hw.hscx.hscx, 0x80); + if (cs->debug & L1_DEB_HSCX_FIFO) { + char *t = bcs->blog; + + t += sprintf(t, "hscx_empty_fifo %c cnt %d", + bcs->hw.hscx.hscx ? 'B' : 'A', count); + QuickHex(t, ptr, count); + debugl1(cs, "%s", bcs->blog); + } +} + +static void +hscx_fill_fifo(struct BCState *bcs) +{ + struct IsdnCardState *cs = bcs->cs; + int more, count; + int fifo_size = test_bit(HW_IPAC, &cs->HW_Flags) ? 64 : 32; + u_char *ptr; + + if ((cs->debug & L1_DEB_HSCX) && !(cs->debug & L1_DEB_HSCX_FIFO)) + debugl1(cs, "hscx_fill_fifo"); + + if (!bcs->tx_skb) + return; + if (bcs->tx_skb->len <= 0) + return; + + more = (bcs->mode == L1_MODE_TRANS) ? 1 : 0; + if (bcs->tx_skb->len > fifo_size) { + more = !0; + count = fifo_size; + } else + count = bcs->tx_skb->len; + + waitforXFW(cs, bcs->hw.hscx.hscx); + ptr = bcs->tx_skb->data; + skb_pull(bcs->tx_skb, count); + bcs->tx_cnt -= count; + bcs->hw.hscx.count += count; + WRITEHSCXFIFO(cs, bcs->hw.hscx.hscx, ptr, count); + WriteHSCXCMDR(cs, bcs->hw.hscx.hscx, more ? 0x8 : 0xa); + if (cs->debug & L1_DEB_HSCX_FIFO) { + char *t = bcs->blog; + + t += sprintf(t, "hscx_fill_fifo %c cnt %d", + bcs->hw.hscx.hscx ? 'B' : 'A', count); + QuickHex(t, ptr, count); + debugl1(cs, "%s", bcs->blog); + } +} + +static void +hscx_interrupt(struct IsdnCardState *cs, u_char val, u_char hscx) +{ + u_char r; + struct BCState *bcs = cs->bcs + hscx; + struct sk_buff *skb; + int fifo_size = test_bit(HW_IPAC, &cs->HW_Flags) ? 64 : 32; + int count; + + if (!test_bit(BC_FLG_INIT, &bcs->Flag)) + return; + + if (val & 0x80) { /* RME */ + r = READHSCX(cs, hscx, HSCX_RSTA); + if ((r & 0xf0) != 0xa0) { + if (!(r & 0x80)) { + if (cs->debug & L1_DEB_WARN) + debugl1(cs, "HSCX invalid frame"); +#ifdef ERROR_STATISTIC + bcs->err_inv++; +#endif + } + if ((r & 0x40) && bcs->mode) { + if (cs->debug & L1_DEB_WARN) + debugl1(cs, "HSCX RDO mode=%d", + bcs->mode); +#ifdef ERROR_STATISTIC + bcs->err_rdo++; +#endif + } + if (!(r & 0x20)) { + if (cs->debug & L1_DEB_WARN) + debugl1(cs, "HSCX CRC error"); +#ifdef ERROR_STATISTIC + bcs->err_crc++; +#endif + } + WriteHSCXCMDR(cs, hscx, 0x80); + } else { + count = READHSCX(cs, hscx, HSCX_RBCL) & ( + test_bit(HW_IPAC, &cs->HW_Flags) ? 0x3f : 0x1f); + if (count == 0) + count = fifo_size; + hscx_empty_fifo(bcs, count); + if ((count = bcs->hw.hscx.rcvidx - 1) > 0) { + if (cs->debug & L1_DEB_HSCX_FIFO) + debugl1(cs, "HX Frame %d", count); + if (!(skb = dev_alloc_skb(count))) + printk(KERN_WARNING "HSCX: receive out of memory\n"); + else { + skb_put_data(skb, bcs->hw.hscx.rcvbuf, + count); + skb_queue_tail(&bcs->rqueue, skb); + } + } + } + bcs->hw.hscx.rcvidx = 0; + schedule_event(bcs, B_RCVBUFREADY); + } + if (val & 0x40) { /* RPF */ + hscx_empty_fifo(bcs, fifo_size); + if (bcs->mode == L1_MODE_TRANS) { + /* receive audio data */ + if (!(skb = dev_alloc_skb(fifo_size))) + printk(KERN_WARNING "HiSax: receive out of memory\n"); + else { + skb_put_data(skb, bcs->hw.hscx.rcvbuf, + fifo_size); + skb_queue_tail(&bcs->rqueue, skb); + } + bcs->hw.hscx.rcvidx = 0; + schedule_event(bcs, B_RCVBUFREADY); + } + } + if (val & 0x10) { /* XPR */ + if (bcs->tx_skb) { + if (bcs->tx_skb->len) { + hscx_fill_fifo(bcs); + return; + } else { + if (test_bit(FLG_LLI_L1WAKEUP, &bcs->st->lli.flag) && + (PACKET_NOACK != bcs->tx_skb->pkt_type)) { + u_long flags; + spin_lock_irqsave(&bcs->aclock, flags); + bcs->ackcnt += bcs->hw.hscx.count; + spin_unlock_irqrestore(&bcs->aclock, flags); + schedule_event(bcs, B_ACKPENDING); + } + dev_kfree_skb_irq(bcs->tx_skb); + bcs->hw.hscx.count = 0; + bcs->tx_skb = NULL; + } + } + if ((bcs->tx_skb = skb_dequeue(&bcs->squeue))) { + bcs->hw.hscx.count = 0; + test_and_set_bit(BC_FLG_BUSY, &bcs->Flag); + hscx_fill_fifo(bcs); + } else { + test_and_clear_bit(BC_FLG_BUSY, &bcs->Flag); + schedule_event(bcs, B_XMTBUFREADY); + } + } +} + +static void +hscx_int_main(struct IsdnCardState *cs, u_char val) +{ + + u_char exval; + struct BCState *bcs; + + if (val & 0x01) { + bcs = cs->bcs + 1; + exval = READHSCX(cs, 1, HSCX_EXIR); + if (exval & 0x40) { + if (bcs->mode == 1) + hscx_fill_fifo(bcs); + else { +#ifdef ERROR_STATISTIC + bcs->err_tx++; +#endif + /* Here we lost an TX interrupt, so + * restart transmitting the whole frame. + */ + if (bcs->tx_skb) { + skb_push(bcs->tx_skb, bcs->hw.hscx.count); + bcs->tx_cnt += bcs->hw.hscx.count; + bcs->hw.hscx.count = 0; + } + WriteHSCXCMDR(cs, bcs->hw.hscx.hscx, 0x01); + if (cs->debug & L1_DEB_WARN) + debugl1(cs, "HSCX B EXIR %x Lost TX", exval); + } + } else if (cs->debug & L1_DEB_HSCX) + debugl1(cs, "HSCX B EXIR %x", exval); + } + if (val & 0xf8) { + if (cs->debug & L1_DEB_HSCX) + debugl1(cs, "HSCX B interrupt %x", val); + hscx_interrupt(cs, val, 1); + } + if (val & 0x02) { + bcs = cs->bcs; + exval = READHSCX(cs, 0, HSCX_EXIR); + if (exval & 0x40) { + if (bcs->mode == L1_MODE_TRANS) + hscx_fill_fifo(bcs); + else { + /* Here we lost an TX interrupt, so + * restart transmitting the whole frame. + */ +#ifdef ERROR_STATISTIC + bcs->err_tx++; +#endif + if (bcs->tx_skb) { + skb_push(bcs->tx_skb, bcs->hw.hscx.count); + bcs->tx_cnt += bcs->hw.hscx.count; + bcs->hw.hscx.count = 0; + } + WriteHSCXCMDR(cs, bcs->hw.hscx.hscx, 0x01); + if (cs->debug & L1_DEB_WARN) + debugl1(cs, "HSCX A EXIR %x Lost TX", exval); + } + } else if (cs->debug & L1_DEB_HSCX) + debugl1(cs, "HSCX A EXIR %x", exval); + } + if (val & 0x04) { + exval = READHSCX(cs, 0, HSCX_ISTA); + if (cs->debug & L1_DEB_HSCX) + debugl1(cs, "HSCX A interrupt %x", exval); + hscx_interrupt(cs, exval, 0); + } +} diff --git a/drivers/isdn/hisax/icc.c b/drivers/isdn/hisax/icc.c new file mode 100644 index 000000000..831dd1bb8 --- /dev/null +++ b/drivers/isdn/hisax/icc.c @@ -0,0 +1,680 @@ +/* $Id: icc.c,v 1.8.2.3 2004/01/13 14:31:25 keil Exp $ + * + * ICC specific routines + * + * Author Matt Henderson & Guy Ellis + * Copyright by Traverse Technologies Pty Ltd, www.travers.com.au + * + * This software may be used and distributed according to the terms + * of the GNU General Public License, incorporated herein by reference. + * + * 1999.6.25 Initial implementation of routines for Siemens ISDN + * Communication Controller PEB 2070 based on the ISAC routines + * written by Karsten Keil. + * + */ + +#include <linux/init.h> +#include "hisax.h" +#include "icc.h" +// #include "arcofi.h" +#include "isdnl1.h" +#include <linux/interrupt.h> +#include <linux/slab.h> + +#define DBUSY_TIMER_VALUE 80 +#define ARCOFI_USE 0 + +static char *ICCVer[] = +{"2070 A1/A3", "2070 B1", "2070 B2/B3", "2070 V2.4"}; + +void +ICCVersion(struct IsdnCardState *cs, char *s) +{ + int val; + + val = cs->readisac(cs, ICC_RBCH); + printk(KERN_INFO "%s ICC version (%x): %s\n", s, val, ICCVer[(val >> 5) & 3]); +} + +static void +ph_command(struct IsdnCardState *cs, unsigned int command) +{ + if (cs->debug & L1_DEB_ISAC) + debugl1(cs, "ph_command %x", command); + cs->writeisac(cs, ICC_CIX0, (command << 2) | 3); +} + + +static void +icc_new_ph(struct IsdnCardState *cs) +{ + switch (cs->dc.icc.ph_state) { + case (ICC_IND_EI1): + ph_command(cs, ICC_CMD_DI); + l1_msg(cs, HW_RESET | INDICATION, NULL); + break; + case (ICC_IND_DC): + l1_msg(cs, HW_DEACTIVATE | CONFIRM, NULL); + break; + case (ICC_IND_DR): + l1_msg(cs, HW_DEACTIVATE | INDICATION, NULL); + break; + case (ICC_IND_PU): + l1_msg(cs, HW_POWERUP | CONFIRM, NULL); + break; + case (ICC_IND_FJ): + l1_msg(cs, HW_RSYNC | INDICATION, NULL); + break; + case (ICC_IND_AR): + l1_msg(cs, HW_INFO2 | INDICATION, NULL); + break; + case (ICC_IND_AI): + l1_msg(cs, HW_INFO4 | INDICATION, NULL); + break; + default: + break; + } +} + +static void +icc_bh(struct work_struct *work) +{ + struct IsdnCardState *cs = + container_of(work, struct IsdnCardState, tqueue); + struct PStack *stptr; + + if (test_and_clear_bit(D_CLEARBUSY, &cs->event)) { + if (cs->debug) + debugl1(cs, "D-Channel Busy cleared"); + stptr = cs->stlist; + while (stptr != NULL) { + stptr->l1.l1l2(stptr, PH_PAUSE | CONFIRM, NULL); + stptr = stptr->next; + } + } + if (test_and_clear_bit(D_L1STATECHANGE, &cs->event)) + icc_new_ph(cs); + if (test_and_clear_bit(D_RCVBUFREADY, &cs->event)) + DChannel_proc_rcv(cs); + if (test_and_clear_bit(D_XMTBUFREADY, &cs->event)) + DChannel_proc_xmt(cs); +#if ARCOFI_USE + if (!test_bit(HW_ARCOFI, &cs->HW_Flags)) + return; + if (test_and_clear_bit(D_RX_MON1, &cs->event)) + arcofi_fsm(cs, ARCOFI_RX_END, NULL); + if (test_and_clear_bit(D_TX_MON1, &cs->event)) + arcofi_fsm(cs, ARCOFI_TX_END, NULL); +#endif +} + +static void +icc_empty_fifo(struct IsdnCardState *cs, int count) +{ + u_char *ptr; + + if ((cs->debug & L1_DEB_ISAC) && !(cs->debug & L1_DEB_ISAC_FIFO)) + debugl1(cs, "icc_empty_fifo"); + + if ((cs->rcvidx + count) >= MAX_DFRAME_LEN_L1) { + if (cs->debug & L1_DEB_WARN) + debugl1(cs, "icc_empty_fifo overrun %d", + cs->rcvidx + count); + cs->writeisac(cs, ICC_CMDR, 0x80); + cs->rcvidx = 0; + return; + } + ptr = cs->rcvbuf + cs->rcvidx; + cs->rcvidx += count; + cs->readisacfifo(cs, ptr, count); + cs->writeisac(cs, ICC_CMDR, 0x80); + if (cs->debug & L1_DEB_ISAC_FIFO) { + char *t = cs->dlog; + + t += sprintf(t, "icc_empty_fifo cnt %d", count); + QuickHex(t, ptr, count); + debugl1(cs, "%s", cs->dlog); + } +} + +static void +icc_fill_fifo(struct IsdnCardState *cs) +{ + int count, more; + u_char *ptr; + + if ((cs->debug & L1_DEB_ISAC) && !(cs->debug & L1_DEB_ISAC_FIFO)) + debugl1(cs, "icc_fill_fifo"); + + if (!cs->tx_skb) + return; + + count = cs->tx_skb->len; + if (count <= 0) + return; + + more = 0; + if (count > 32) { + more = !0; + count = 32; + } + ptr = cs->tx_skb->data; + skb_pull(cs->tx_skb, count); + cs->tx_cnt += count; + cs->writeisacfifo(cs, ptr, count); + cs->writeisac(cs, ICC_CMDR, more ? 0x8 : 0xa); + if (test_and_set_bit(FLG_DBUSY_TIMER, &cs->HW_Flags)) { + debugl1(cs, "icc_fill_fifo dbusytimer running"); + del_timer(&cs->dbusytimer); + } + cs->dbusytimer.expires = jiffies + ((DBUSY_TIMER_VALUE * HZ)/1000); + add_timer(&cs->dbusytimer); + if (cs->debug & L1_DEB_ISAC_FIFO) { + char *t = cs->dlog; + + t += sprintf(t, "icc_fill_fifo cnt %d", count); + QuickHex(t, ptr, count); + debugl1(cs, "%s", cs->dlog); + } +} + +void +icc_interrupt(struct IsdnCardState *cs, u_char val) +{ + u_char exval, v1; + struct sk_buff *skb; + unsigned int count; + + if (cs->debug & L1_DEB_ISAC) + debugl1(cs, "ICC interrupt %x", val); + if (val & 0x80) { /* RME */ + exval = cs->readisac(cs, ICC_RSTA); + if ((exval & 0x70) != 0x20) { + if (exval & 0x40) { + if (cs->debug & L1_DEB_WARN) + debugl1(cs, "ICC RDO"); +#ifdef ERROR_STATISTIC + cs->err_rx++; +#endif + } + if (!(exval & 0x20)) { + if (cs->debug & L1_DEB_WARN) + debugl1(cs, "ICC CRC error"); +#ifdef ERROR_STATISTIC + cs->err_crc++; +#endif + } + cs->writeisac(cs, ICC_CMDR, 0x80); + } else { + count = cs->readisac(cs, ICC_RBCL) & 0x1f; + if (count == 0) + count = 32; + icc_empty_fifo(cs, count); + if ((count = cs->rcvidx) > 0) { + cs->rcvidx = 0; + if (!(skb = alloc_skb(count, GFP_ATOMIC))) + printk(KERN_WARNING "HiSax: D receive out of memory\n"); + else { + skb_put_data(skb, cs->rcvbuf, count); + skb_queue_tail(&cs->rq, skb); + } + } + } + cs->rcvidx = 0; + schedule_event(cs, D_RCVBUFREADY); + } + if (val & 0x40) { /* RPF */ + icc_empty_fifo(cs, 32); + } + if (val & 0x20) { /* RSC */ + /* never */ + if (cs->debug & L1_DEB_WARN) + debugl1(cs, "ICC RSC interrupt"); + } + if (val & 0x10) { /* XPR */ + if (test_and_clear_bit(FLG_DBUSY_TIMER, &cs->HW_Flags)) + del_timer(&cs->dbusytimer); + if (test_and_clear_bit(FLG_L1_DBUSY, &cs->HW_Flags)) + schedule_event(cs, D_CLEARBUSY); + if (cs->tx_skb) { + if (cs->tx_skb->len) { + icc_fill_fifo(cs); + goto afterXPR; + } else { + dev_kfree_skb_irq(cs->tx_skb); + cs->tx_cnt = 0; + cs->tx_skb = NULL; + } + } + if ((cs->tx_skb = skb_dequeue(&cs->sq))) { + cs->tx_cnt = 0; + icc_fill_fifo(cs); + } else + schedule_event(cs, D_XMTBUFREADY); + } +afterXPR: + if (val & 0x04) { /* CISQ */ + exval = cs->readisac(cs, ICC_CIR0); + if (cs->debug & L1_DEB_ISAC) + debugl1(cs, "ICC CIR0 %02X", exval); + if (exval & 2) { + cs->dc.icc.ph_state = (exval >> 2) & 0xf; + if (cs->debug & L1_DEB_ISAC) + debugl1(cs, "ph_state change %x", cs->dc.icc.ph_state); + schedule_event(cs, D_L1STATECHANGE); + } + if (exval & 1) { + exval = cs->readisac(cs, ICC_CIR1); + if (cs->debug & L1_DEB_ISAC) + debugl1(cs, "ICC CIR1 %02X", exval); + } + } + if (val & 0x02) { /* SIN */ + /* never */ + if (cs->debug & L1_DEB_WARN) + debugl1(cs, "ICC SIN interrupt"); + } + if (val & 0x01) { /* EXI */ + exval = cs->readisac(cs, ICC_EXIR); + if (cs->debug & L1_DEB_WARN) + debugl1(cs, "ICC EXIR %02x", exval); + if (exval & 0x80) { /* XMR */ + debugl1(cs, "ICC XMR"); + printk(KERN_WARNING "HiSax: ICC XMR\n"); + } + if (exval & 0x40) { /* XDU */ + debugl1(cs, "ICC XDU"); + printk(KERN_WARNING "HiSax: ICC XDU\n"); +#ifdef ERROR_STATISTIC + cs->err_tx++; +#endif + if (test_and_clear_bit(FLG_DBUSY_TIMER, &cs->HW_Flags)) + del_timer(&cs->dbusytimer); + if (test_and_clear_bit(FLG_L1_DBUSY, &cs->HW_Flags)) + schedule_event(cs, D_CLEARBUSY); + if (cs->tx_skb) { /* Restart frame */ + skb_push(cs->tx_skb, cs->tx_cnt); + cs->tx_cnt = 0; + icc_fill_fifo(cs); + } else { + printk(KERN_WARNING "HiSax: ICC XDU no skb\n"); + debugl1(cs, "ICC XDU no skb"); + } + } + if (exval & 0x04) { /* MOS */ + v1 = cs->readisac(cs, ICC_MOSR); + if (cs->debug & L1_DEB_MONITOR) + debugl1(cs, "ICC MOSR %02x", v1); +#if ARCOFI_USE + if (v1 & 0x08) { + if (!cs->dc.icc.mon_rx) { + if (!(cs->dc.icc.mon_rx = kmalloc(MAX_MON_FRAME, GFP_ATOMIC))) { + if (cs->debug & L1_DEB_WARN) + debugl1(cs, "ICC MON RX out of memory!"); + cs->dc.icc.mocr &= 0xf0; + cs->dc.icc.mocr |= 0x0a; + cs->writeisac(cs, ICC_MOCR, cs->dc.icc.mocr); + goto afterMONR0; + } else + cs->dc.icc.mon_rxp = 0; + } + if (cs->dc.icc.mon_rxp >= MAX_MON_FRAME) { + cs->dc.icc.mocr &= 0xf0; + cs->dc.icc.mocr |= 0x0a; + cs->writeisac(cs, ICC_MOCR, cs->dc.icc.mocr); + cs->dc.icc.mon_rxp = 0; + if (cs->debug & L1_DEB_WARN) + debugl1(cs, "ICC MON RX overflow!"); + goto afterMONR0; + } + cs->dc.icc.mon_rx[cs->dc.icc.mon_rxp++] = cs->readisac(cs, ICC_MOR0); + if (cs->debug & L1_DEB_MONITOR) + debugl1(cs, "ICC MOR0 %02x", cs->dc.icc.mon_rx[cs->dc.icc.mon_rxp - 1]); + if (cs->dc.icc.mon_rxp == 1) { + cs->dc.icc.mocr |= 0x04; + cs->writeisac(cs, ICC_MOCR, cs->dc.icc.mocr); + } + } + afterMONR0: + if (v1 & 0x80) { + if (!cs->dc.icc.mon_rx) { + if (!(cs->dc.icc.mon_rx = kmalloc(MAX_MON_FRAME, GFP_ATOMIC))) { + if (cs->debug & L1_DEB_WARN) + debugl1(cs, "ICC MON RX out of memory!"); + cs->dc.icc.mocr &= 0x0f; + cs->dc.icc.mocr |= 0xa0; + cs->writeisac(cs, ICC_MOCR, cs->dc.icc.mocr); + goto afterMONR1; + } else + cs->dc.icc.mon_rxp = 0; + } + if (cs->dc.icc.mon_rxp >= MAX_MON_FRAME) { + cs->dc.icc.mocr &= 0x0f; + cs->dc.icc.mocr |= 0xa0; + cs->writeisac(cs, ICC_MOCR, cs->dc.icc.mocr); + cs->dc.icc.mon_rxp = 0; + if (cs->debug & L1_DEB_WARN) + debugl1(cs, "ICC MON RX overflow!"); + goto afterMONR1; + } + cs->dc.icc.mon_rx[cs->dc.icc.mon_rxp++] = cs->readisac(cs, ICC_MOR1); + if (cs->debug & L1_DEB_MONITOR) + debugl1(cs, "ICC MOR1 %02x", cs->dc.icc.mon_rx[cs->dc.icc.mon_rxp - 1]); + cs->dc.icc.mocr |= 0x40; + cs->writeisac(cs, ICC_MOCR, cs->dc.icc.mocr); + } + afterMONR1: + if (v1 & 0x04) { + cs->dc.icc.mocr &= 0xf0; + cs->writeisac(cs, ICC_MOCR, cs->dc.icc.mocr); + cs->dc.icc.mocr |= 0x0a; + cs->writeisac(cs, ICC_MOCR, cs->dc.icc.mocr); + schedule_event(cs, D_RX_MON0); + } + if (v1 & 0x40) { + cs->dc.icc.mocr &= 0x0f; + cs->writeisac(cs, ICC_MOCR, cs->dc.icc.mocr); + cs->dc.icc.mocr |= 0xa0; + cs->writeisac(cs, ICC_MOCR, cs->dc.icc.mocr); + schedule_event(cs, D_RX_MON1); + } + if (v1 & 0x02) { + if ((!cs->dc.icc.mon_tx) || (cs->dc.icc.mon_txc && + (cs->dc.icc.mon_txp >= cs->dc.icc.mon_txc) && + !(v1 & 0x08))) { + cs->dc.icc.mocr &= 0xf0; + cs->writeisac(cs, ICC_MOCR, cs->dc.icc.mocr); + cs->dc.icc.mocr |= 0x0a; + cs->writeisac(cs, ICC_MOCR, cs->dc.icc.mocr); + if (cs->dc.icc.mon_txc && + (cs->dc.icc.mon_txp >= cs->dc.icc.mon_txc)) + schedule_event(cs, D_TX_MON0); + goto AfterMOX0; + } + if (cs->dc.icc.mon_txc && (cs->dc.icc.mon_txp >= cs->dc.icc.mon_txc)) { + schedule_event(cs, D_TX_MON0); + goto AfterMOX0; + } + cs->writeisac(cs, ICC_MOX0, + cs->dc.icc.mon_tx[cs->dc.icc.mon_txp++]); + if (cs->debug & L1_DEB_MONITOR) + debugl1(cs, "ICC %02x -> MOX0", cs->dc.icc.mon_tx[cs->dc.icc.mon_txp - 1]); + } + AfterMOX0: + if (v1 & 0x20) { + if ((!cs->dc.icc.mon_tx) || (cs->dc.icc.mon_txc && + (cs->dc.icc.mon_txp >= cs->dc.icc.mon_txc) && + !(v1 & 0x80))) { + cs->dc.icc.mocr &= 0x0f; + cs->writeisac(cs, ICC_MOCR, cs->dc.icc.mocr); + cs->dc.icc.mocr |= 0xa0; + cs->writeisac(cs, ICC_MOCR, cs->dc.icc.mocr); + if (cs->dc.icc.mon_txc && + (cs->dc.icc.mon_txp >= cs->dc.icc.mon_txc)) + schedule_event(cs, D_TX_MON1); + goto AfterMOX1; + } + if (cs->dc.icc.mon_txc && (cs->dc.icc.mon_txp >= cs->dc.icc.mon_txc)) { + schedule_event(cs, D_TX_MON1); + goto AfterMOX1; + } + cs->writeisac(cs, ICC_MOX1, + cs->dc.icc.mon_tx[cs->dc.icc.mon_txp++]); + if (cs->debug & L1_DEB_MONITOR) + debugl1(cs, "ICC %02x -> MOX1", cs->dc.icc.mon_tx[cs->dc.icc.mon_txp - 1]); + } + AfterMOX1: ; +#endif + } + } +} + +static void +ICC_l1hw(struct PStack *st, int pr, void *arg) +{ + struct IsdnCardState *cs = (struct IsdnCardState *) st->l1.hardware; + struct sk_buff *skb = arg; + u_long flags; + int val; + + switch (pr) { + case (PH_DATA | REQUEST): + if (cs->debug & DEB_DLOG_HEX) + LogFrame(cs, skb->data, skb->len); + if (cs->debug & DEB_DLOG_VERBOSE) + dlogframe(cs, skb, 0); + spin_lock_irqsave(&cs->lock, flags); + if (cs->tx_skb) { + skb_queue_tail(&cs->sq, skb); +#ifdef L2FRAME_DEBUG /* psa */ + if (cs->debug & L1_DEB_LAPD) + Logl2Frame(cs, skb, "PH_DATA Queued", 0); +#endif + } else { + cs->tx_skb = skb; + cs->tx_cnt = 0; +#ifdef L2FRAME_DEBUG /* psa */ + if (cs->debug & L1_DEB_LAPD) + Logl2Frame(cs, skb, "PH_DATA", 0); +#endif + icc_fill_fifo(cs); + } + spin_unlock_irqrestore(&cs->lock, flags); + break; + case (PH_PULL | INDICATION): + spin_lock_irqsave(&cs->lock, flags); + if (cs->tx_skb) { + if (cs->debug & L1_DEB_WARN) + debugl1(cs, " l2l1 tx_skb exist this shouldn't happen"); + skb_queue_tail(&cs->sq, skb); + spin_unlock_irqrestore(&cs->lock, flags); + break; + } + if (cs->debug & DEB_DLOG_HEX) + LogFrame(cs, skb->data, skb->len); + if (cs->debug & DEB_DLOG_VERBOSE) + dlogframe(cs, skb, 0); + cs->tx_skb = skb; + cs->tx_cnt = 0; +#ifdef L2FRAME_DEBUG /* psa */ + if (cs->debug & L1_DEB_LAPD) + Logl2Frame(cs, skb, "PH_DATA_PULLED", 0); +#endif + icc_fill_fifo(cs); + spin_unlock_irqrestore(&cs->lock, flags); + break; + case (PH_PULL | REQUEST): +#ifdef L2FRAME_DEBUG /* psa */ + if (cs->debug & L1_DEB_LAPD) + debugl1(cs, "-> PH_REQUEST_PULL"); +#endif + if (!cs->tx_skb) { + test_and_clear_bit(FLG_L1_PULL_REQ, &st->l1.Flags); + st->l1.l1l2(st, PH_PULL | CONFIRM, NULL); + } else + test_and_set_bit(FLG_L1_PULL_REQ, &st->l1.Flags); + break; + case (HW_RESET | REQUEST): + spin_lock_irqsave(&cs->lock, flags); + if ((cs->dc.icc.ph_state == ICC_IND_EI1) || + (cs->dc.icc.ph_state == ICC_IND_DR)) + ph_command(cs, ICC_CMD_DI); + else + ph_command(cs, ICC_CMD_RES); + spin_unlock_irqrestore(&cs->lock, flags); + break; + case (HW_ENABLE | REQUEST): + spin_lock_irqsave(&cs->lock, flags); + ph_command(cs, ICC_CMD_DI); + spin_unlock_irqrestore(&cs->lock, flags); + break; + case (HW_INFO1 | REQUEST): + spin_lock_irqsave(&cs->lock, flags); + ph_command(cs, ICC_CMD_AR); + spin_unlock_irqrestore(&cs->lock, flags); + break; + case (HW_INFO3 | REQUEST): + spin_lock_irqsave(&cs->lock, flags); + ph_command(cs, ICC_CMD_AI); + spin_unlock_irqrestore(&cs->lock, flags); + break; + case (HW_TESTLOOP | REQUEST): + spin_lock_irqsave(&cs->lock, flags); + val = 0; + if (1 & (long) arg) + val |= 0x0c; + if (2 & (long) arg) + val |= 0x3; + if (test_bit(HW_IOM1, &cs->HW_Flags)) { + /* IOM 1 Mode */ + if (!val) { + cs->writeisac(cs, ICC_SPCR, 0xa); + cs->writeisac(cs, ICC_ADF1, 0x2); + } else { + cs->writeisac(cs, ICC_SPCR, val); + cs->writeisac(cs, ICC_ADF1, 0xa); + } + } else { + /* IOM 2 Mode */ + cs->writeisac(cs, ICC_SPCR, val); + if (val) + cs->writeisac(cs, ICC_ADF1, 0x8); + else + cs->writeisac(cs, ICC_ADF1, 0x0); + } + spin_unlock_irqrestore(&cs->lock, flags); + break; + case (HW_DEACTIVATE | RESPONSE): + skb_queue_purge(&cs->rq); + skb_queue_purge(&cs->sq); + if (cs->tx_skb) { + dev_kfree_skb_any(cs->tx_skb); + cs->tx_skb = NULL; + } + if (test_and_clear_bit(FLG_DBUSY_TIMER, &cs->HW_Flags)) + del_timer(&cs->dbusytimer); + if (test_and_clear_bit(FLG_L1_DBUSY, &cs->HW_Flags)) + schedule_event(cs, D_CLEARBUSY); + break; + default: + if (cs->debug & L1_DEB_WARN) + debugl1(cs, "icc_l1hw unknown %04x", pr); + break; + } +} + +static void +setstack_icc(struct PStack *st, struct IsdnCardState *cs) +{ + st->l1.l1hw = ICC_l1hw; +} + +static void +DC_Close_icc(struct IsdnCardState *cs) { + kfree(cs->dc.icc.mon_rx); + cs->dc.icc.mon_rx = NULL; + kfree(cs->dc.icc.mon_tx); + cs->dc.icc.mon_tx = NULL; +} + +static void +dbusy_timer_handler(struct timer_list *t) +{ + struct IsdnCardState *cs = from_timer(cs, t, dbusytimer); + struct PStack *stptr; + int rbch, star; + + if (test_bit(FLG_DBUSY_TIMER, &cs->HW_Flags)) { + rbch = cs->readisac(cs, ICC_RBCH); + star = cs->readisac(cs, ICC_STAR); + if (cs->debug) + debugl1(cs, "D-Channel Busy RBCH %02x STAR %02x", + rbch, star); + if (rbch & ICC_RBCH_XAC) { /* D-Channel Busy */ + test_and_set_bit(FLG_L1_DBUSY, &cs->HW_Flags); + stptr = cs->stlist; + while (stptr != NULL) { + stptr->l1.l1l2(stptr, PH_PAUSE | INDICATION, NULL); + stptr = stptr->next; + } + } else { + /* discard frame; reset transceiver */ + test_and_clear_bit(FLG_DBUSY_TIMER, &cs->HW_Flags); + if (cs->tx_skb) { + dev_kfree_skb_any(cs->tx_skb); + cs->tx_cnt = 0; + cs->tx_skb = NULL; + } else { + printk(KERN_WARNING "HiSax: ICC D-Channel Busy no skb\n"); + debugl1(cs, "D-Channel Busy no skb"); + } + cs->writeisac(cs, ICC_CMDR, 0x01); /* Transmitter reset */ + cs->irq_func(cs->irq, cs); + } + } +} + +void +initicc(struct IsdnCardState *cs) +{ + cs->setstack_d = setstack_icc; + cs->DC_Close = DC_Close_icc; + cs->dc.icc.mon_tx = NULL; + cs->dc.icc.mon_rx = NULL; + cs->writeisac(cs, ICC_MASK, 0xff); + cs->dc.icc.mocr = 0xaa; + if (test_bit(HW_IOM1, &cs->HW_Flags)) { + /* IOM 1 Mode */ + cs->writeisac(cs, ICC_ADF2, 0x0); + cs->writeisac(cs, ICC_SPCR, 0xa); + cs->writeisac(cs, ICC_ADF1, 0x2); + cs->writeisac(cs, ICC_STCR, 0x70); + cs->writeisac(cs, ICC_MODE, 0xc9); + } else { + /* IOM 2 Mode */ + if (!cs->dc.icc.adf2) + cs->dc.icc.adf2 = 0x80; + cs->writeisac(cs, ICC_ADF2, cs->dc.icc.adf2); + cs->writeisac(cs, ICC_SQXR, 0xa0); + cs->writeisac(cs, ICC_SPCR, 0x20); + cs->writeisac(cs, ICC_STCR, 0x70); + cs->writeisac(cs, ICC_MODE, 0xca); + cs->writeisac(cs, ICC_TIMR, 0x00); + cs->writeisac(cs, ICC_ADF1, 0x20); + } + ph_command(cs, ICC_CMD_RES); + cs->writeisac(cs, ICC_MASK, 0x0); + ph_command(cs, ICC_CMD_DI); +} + +void +clear_pending_icc_ints(struct IsdnCardState *cs) +{ + int val, eval; + + val = cs->readisac(cs, ICC_STAR); + debugl1(cs, "ICC STAR %x", val); + val = cs->readisac(cs, ICC_MODE); + debugl1(cs, "ICC MODE %x", val); + val = cs->readisac(cs, ICC_ADF2); + debugl1(cs, "ICC ADF2 %x", val); + val = cs->readisac(cs, ICC_ISTA); + debugl1(cs, "ICC ISTA %x", val); + if (val & 0x01) { + eval = cs->readisac(cs, ICC_EXIR); + debugl1(cs, "ICC EXIR %x", eval); + } + val = cs->readisac(cs, ICC_CIR0); + debugl1(cs, "ICC CIR0 %x", val); + cs->dc.icc.ph_state = (val >> 2) & 0xf; + schedule_event(cs, D_L1STATECHANGE); + /* Disable all IRQ */ + cs->writeisac(cs, ICC_MASK, 0xFF); +} + +void setup_icc(struct IsdnCardState *cs) +{ + INIT_WORK(&cs->tqueue, icc_bh); + timer_setup(&cs->dbusytimer, dbusy_timer_handler, 0); +} diff --git a/drivers/isdn/hisax/icc.h b/drivers/isdn/hisax/icc.h new file mode 100644 index 000000000..f367df5d3 --- /dev/null +++ b/drivers/isdn/hisax/icc.h @@ -0,0 +1,72 @@ +/* $Id: icc.h,v 1.4.2.2 2004/01/12 22:52:26 keil Exp $ + * + * ICC specific routines + * + * Author Matt Henderson & Guy Ellis + * Copyright by Traverse Technologies Pty Ltd, www.travers.com.au + * + * This software may be used and distributed according to the terms + * of the GNU General Public License, incorporated herein by reference. + * + * 1999.7.14 Initial implementation of routines for Siemens ISDN + * Communication Controller PEB 2070 based on the ISAC routines + * written by Karsten Keil. + */ + +/* All Registers original Siemens Spec */ + +#define ICC_MASK 0x20 +#define ICC_ISTA 0x20 +#define ICC_STAR 0x21 +#define ICC_CMDR 0x21 +#define ICC_EXIR 0x24 +#define ICC_ADF2 0x39 +#define ICC_SPCR 0x30 +#define ICC_ADF1 0x38 +#define ICC_CIR0 0x31 +#define ICC_CIX0 0x31 +#define ICC_CIR1 0x33 +#define ICC_CIX1 0x33 +#define ICC_STCR 0x37 +#define ICC_MODE 0x22 +#define ICC_RSTA 0x27 +#define ICC_RBCL 0x25 +#define ICC_RBCH 0x2A +#define ICC_TIMR 0x23 +#define ICC_SQXR 0x3b +#define ICC_MOSR 0x3a +#define ICC_MOCR 0x3a +#define ICC_MOR0 0x32 +#define ICC_MOX0 0x32 +#define ICC_MOR1 0x34 +#define ICC_MOX1 0x34 + +#define ICC_RBCH_XAC 0x80 + +#define ICC_CMD_TIM 0x0 +#define ICC_CMD_RES 0x1 +#define ICC_CMD_DU 0x3 +#define ICC_CMD_EI1 0x4 +#define ICC_CMD_SSP 0x5 +#define ICC_CMD_DT 0x6 +#define ICC_CMD_AR 0x8 +#define ICC_CMD_ARL 0xA +#define ICC_CMD_AI 0xC +#define ICC_CMD_DI 0xF + +#define ICC_IND_DR 0x0 +#define ICC_IND_FJ 0x2 +#define ICC_IND_EI1 0x4 +#define ICC_IND_INT 0x6 +#define ICC_IND_PU 0x7 +#define ICC_IND_AR 0x8 +#define ICC_IND_ARL 0xA +#define ICC_IND_AI 0xC +#define ICC_IND_AIL 0xE +#define ICC_IND_DC 0xF + +extern void ICCVersion(struct IsdnCardState *cs, char *s); +extern void initicc(struct IsdnCardState *cs); +extern void icc_interrupt(struct IsdnCardState *cs, u_char val); +extern void clear_pending_icc_ints(struct IsdnCardState *cs); +extern void setup_icc(struct IsdnCardState *); diff --git a/drivers/isdn/hisax/ipac.h b/drivers/isdn/hisax/ipac.h new file mode 100644 index 000000000..4f937f02e --- /dev/null +++ b/drivers/isdn/hisax/ipac.h @@ -0,0 +1,29 @@ +/* $Id: ipac.h,v 1.7.2.2 2004/01/12 22:52:26 keil Exp $ + * + * IPAC specific defines + * + * Author Karsten Keil + * Copyright by Karsten Keil <keil@isdn4linux.de> + * + * This software may be used and distributed according to the terms + * of the GNU General Public License, incorporated herein by reference. + * + */ + +/* All Registers original Siemens Spec */ + +#define IPAC_CONF 0xC0 +#define IPAC_MASK 0xC1 +#define IPAC_ISTA 0xC1 +#define IPAC_ID 0xC2 +#define IPAC_ACFG 0xC3 +#define IPAC_AOE 0xC4 +#define IPAC_ARX 0xC5 +#define IPAC_ATX 0xC5 +#define IPAC_PITA1 0xC6 +#define IPAC_PITA2 0xC7 +#define IPAC_POTA1 0xC8 +#define IPAC_POTA2 0xC9 +#define IPAC_PCFG 0xCA +#define IPAC_SCFG 0xCB +#define IPAC_TIMR2 0xCC diff --git a/drivers/isdn/hisax/ipacx.c b/drivers/isdn/hisax/ipacx.c new file mode 100644 index 000000000..c7086c153 --- /dev/null +++ b/drivers/isdn/hisax/ipacx.c @@ -0,0 +1,913 @@ +/* + * + * IPACX specific routines + * + * Author Joerg Petersohn + * Derived from hisax_isac.c, isac.c, hscx.c and others + * + * This software may be used and distributed according to the terms + * of the GNU General Public License, incorporated herein by reference. + * + */ +#include <linux/kernel.h> +#include <linux/slab.h> +#include <linux/init.h> +#include "hisax_if.h" +#include "hisax.h" +#include "isdnl1.h" +#include "ipacx.h" + +#define DBUSY_TIMER_VALUE 80 +#define TIMER3_VALUE 7000 +#define MAX_DFRAME_LEN_L1 300 +#define B_FIFO_SIZE 64 +#define D_FIFO_SIZE 32 + + +// ipacx interrupt mask values +#define _MASK_IMASK 0x2E // global mask +#define _MASKB_IMASK 0x0B +#define _MASKD_IMASK 0x03 // all on + +//---------------------------------------------------------- +// local function declarations +//---------------------------------------------------------- +static void ph_command(struct IsdnCardState *cs, unsigned int command); +static inline void cic_int(struct IsdnCardState *cs); +static void dch_l2l1(struct PStack *st, int pr, void *arg); +static void dbusy_timer_handler(struct timer_list *t); +static void dch_empty_fifo(struct IsdnCardState *cs, int count); +static void dch_fill_fifo(struct IsdnCardState *cs); +static inline void dch_int(struct IsdnCardState *cs); +static void dch_setstack(struct PStack *st, struct IsdnCardState *cs); +static void dch_init(struct IsdnCardState *cs); +static void bch_l2l1(struct PStack *st, int pr, void *arg); +static void bch_empty_fifo(struct BCState *bcs, int count); +static void bch_fill_fifo(struct BCState *bcs); +static void bch_int(struct IsdnCardState *cs, u_char hscx); +static void bch_mode(struct BCState *bcs, int mode, int bc); +static void bch_close_state(struct BCState *bcs); +static int bch_open_state(struct IsdnCardState *cs, struct BCState *bcs); +static int bch_setstack(struct PStack *st, struct BCState *bcs); +static void bch_init(struct IsdnCardState *cs, int hscx); +static void clear_pending_ints(struct IsdnCardState *cs); + +//---------------------------------------------------------- +// Issue Layer 1 command to chip +//---------------------------------------------------------- +static void +ph_command(struct IsdnCardState *cs, unsigned int command) +{ + if (cs->debug & L1_DEB_ISAC) + debugl1(cs, "ph_command (%#x) in (%#x)", command, + cs->dc.isac.ph_state); +//################################### +// printk(KERN_INFO "ph_command (%#x)\n", command); +//################################### + cs->writeisac(cs, IPACX_CIX0, (command << 4) | 0x0E); +} + +//---------------------------------------------------------- +// Transceiver interrupt handler +//---------------------------------------------------------- +static inline void +cic_int(struct IsdnCardState *cs) +{ + u_char event; + + event = cs->readisac(cs, IPACX_CIR0) >> 4; + if (cs->debug & L1_DEB_ISAC) debugl1(cs, "cic_int(event=%#x)", event); +//######################################### +// printk(KERN_INFO "cic_int(%x)\n", event); +//######################################### + cs->dc.isac.ph_state = event; + schedule_event(cs, D_L1STATECHANGE); +} + +//========================================================== +// D channel functions +//========================================================== + +//---------------------------------------------------------- +// Command entry point +//---------------------------------------------------------- +static void +dch_l2l1(struct PStack *st, int pr, void *arg) +{ + struct IsdnCardState *cs = (struct IsdnCardState *) st->l1.hardware; + struct sk_buff *skb = arg; + u_char cda1_cr; + + switch (pr) { + case (PH_DATA | REQUEST): + if (cs->debug & DEB_DLOG_HEX) LogFrame(cs, skb->data, skb->len); + if (cs->debug & DEB_DLOG_VERBOSE) dlogframe(cs, skb, 0); + if (cs->tx_skb) { + skb_queue_tail(&cs->sq, skb); +#ifdef L2FRAME_DEBUG + if (cs->debug & L1_DEB_LAPD) Logl2Frame(cs, skb, "PH_DATA Queued", 0); +#endif + } else { + cs->tx_skb = skb; + cs->tx_cnt = 0; +#ifdef L2FRAME_DEBUG + if (cs->debug & L1_DEB_LAPD) Logl2Frame(cs, skb, "PH_DATA", 0); +#endif + dch_fill_fifo(cs); + } + break; + + case (PH_PULL | INDICATION): + if (cs->tx_skb) { + if (cs->debug & L1_DEB_WARN) + debugl1(cs, " l2l1 tx_skb exist this shouldn't happen"); + skb_queue_tail(&cs->sq, skb); + break; + } + if (cs->debug & DEB_DLOG_HEX) LogFrame(cs, skb->data, skb->len); + if (cs->debug & DEB_DLOG_VERBOSE) dlogframe(cs, skb, 0); + cs->tx_skb = skb; + cs->tx_cnt = 0; +#ifdef L2FRAME_DEBUG + if (cs->debug & L1_DEB_LAPD) Logl2Frame(cs, skb, "PH_DATA_PULLED", 0); +#endif + dch_fill_fifo(cs); + break; + + case (PH_PULL | REQUEST): +#ifdef L2FRAME_DEBUG + if (cs->debug & L1_DEB_LAPD) debugl1(cs, "-> PH_REQUEST_PULL"); +#endif + if (!cs->tx_skb) { + clear_bit(FLG_L1_PULL_REQ, &st->l1.Flags); + st->l1.l1l2(st, PH_PULL | CONFIRM, NULL); + } else + set_bit(FLG_L1_PULL_REQ, &st->l1.Flags); + break; + + case (HW_RESET | REQUEST): + case (HW_ENABLE | REQUEST): + if ((cs->dc.isac.ph_state == IPACX_IND_RES) || + (cs->dc.isac.ph_state == IPACX_IND_DR) || + (cs->dc.isac.ph_state == IPACX_IND_DC)) + ph_command(cs, IPACX_CMD_TIM); + else + ph_command(cs, IPACX_CMD_RES); + break; + + case (HW_INFO3 | REQUEST): + ph_command(cs, IPACX_CMD_AR8); + break; + + case (HW_TESTLOOP | REQUEST): + cs->writeisac(cs, IPACX_CDA_TSDP10, 0x80); // Timeslot 0 is B1 + cs->writeisac(cs, IPACX_CDA_TSDP11, 0x81); // Timeslot 0 is B1 + cda1_cr = cs->readisac(cs, IPACX_CDA1_CR); + (void) cs->readisac(cs, IPACX_CDA2_CR); + if ((long)arg & 1) { // loop B1 + cs->writeisac(cs, IPACX_CDA1_CR, cda1_cr | 0x0a); + } + else { // B1 off + cs->writeisac(cs, IPACX_CDA1_CR, cda1_cr & ~0x0a); + } + if ((long)arg & 2) { // loop B2 + cs->writeisac(cs, IPACX_CDA1_CR, cda1_cr | 0x14); + } + else { // B2 off + cs->writeisac(cs, IPACX_CDA1_CR, cda1_cr & ~0x14); + } + break; + + case (HW_DEACTIVATE | RESPONSE): + skb_queue_purge(&cs->rq); + skb_queue_purge(&cs->sq); + if (cs->tx_skb) { + dev_kfree_skb_any(cs->tx_skb); + cs->tx_skb = NULL; + } + if (test_and_clear_bit(FLG_DBUSY_TIMER, &cs->HW_Flags)) + del_timer(&cs->dbusytimer); + break; + + default: + if (cs->debug & L1_DEB_WARN) debugl1(cs, "dch_l2l1 unknown %04x", pr); + break; + } +} + +//---------------------------------------------------------- +//---------------------------------------------------------- +static void +dbusy_timer_handler(struct timer_list *t) +{ + struct IsdnCardState *cs = from_timer(cs, t, dbusytimer); + struct PStack *st; + int rbchd, stard; + + if (test_bit(FLG_DBUSY_TIMER, &cs->HW_Flags)) { + rbchd = cs->readisac(cs, IPACX_RBCHD); + stard = cs->readisac(cs, IPACX_STARD); + if (cs->debug) + debugl1(cs, "D-Channel Busy RBCHD %02x STARD %02x", rbchd, stard); + if (!(stard & 0x40)) { // D-Channel Busy + set_bit(FLG_L1_DBUSY, &cs->HW_Flags); + for (st = cs->stlist; st; st = st->next) { + st->l1.l1l2(st, PH_PAUSE | INDICATION, NULL); // flow control on + } + } else { + // seems we lost an interrupt; reset transceiver */ + clear_bit(FLG_DBUSY_TIMER, &cs->HW_Flags); + if (cs->tx_skb) { + dev_kfree_skb_any(cs->tx_skb); + cs->tx_cnt = 0; + cs->tx_skb = NULL; + } else { + printk(KERN_WARNING "HiSax: ISAC D-Channel Busy no skb\n"); + debugl1(cs, "D-Channel Busy no skb"); + } + cs->writeisac(cs, IPACX_CMDRD, 0x01); // Tx reset, generates XPR + } + } +} + +//---------------------------------------------------------- +// Fill buffer from receive FIFO +//---------------------------------------------------------- +static void +dch_empty_fifo(struct IsdnCardState *cs, int count) +{ + u_char *ptr; + + if ((cs->debug & L1_DEB_ISAC) && !(cs->debug & L1_DEB_ISAC_FIFO)) + debugl1(cs, "dch_empty_fifo()"); + + // message too large, remove + if ((cs->rcvidx + count) >= MAX_DFRAME_LEN_L1) { + if (cs->debug & L1_DEB_WARN) + debugl1(cs, "dch_empty_fifo() incoming message too large"); + cs->writeisac(cs, IPACX_CMDRD, 0x80); // RMC + cs->rcvidx = 0; + return; + } + + ptr = cs->rcvbuf + cs->rcvidx; + cs->rcvidx += count; + + cs->readisacfifo(cs, ptr, count); + cs->writeisac(cs, IPACX_CMDRD, 0x80); // RMC + + if (cs->debug & L1_DEB_ISAC_FIFO) { + char *t = cs->dlog; + + t += sprintf(t, "dch_empty_fifo() cnt %d", count); + QuickHex(t, ptr, count); + debugl1(cs, "%s", cs->dlog); + } +} + +//---------------------------------------------------------- +// Fill transmit FIFO +//---------------------------------------------------------- +static void +dch_fill_fifo(struct IsdnCardState *cs) +{ + int count; + u_char cmd, *ptr; + + if ((cs->debug & L1_DEB_ISAC) && !(cs->debug & L1_DEB_ISAC_FIFO)) + debugl1(cs, "dch_fill_fifo()"); + + if (!cs->tx_skb) return; + count = cs->tx_skb->len; + if (count <= 0) return; + + if (count > D_FIFO_SIZE) { + count = D_FIFO_SIZE; + cmd = 0x08; // XTF + } else { + cmd = 0x0A; // XTF | XME + } + + ptr = cs->tx_skb->data; + skb_pull(cs->tx_skb, count); + cs->tx_cnt += count; + cs->writeisacfifo(cs, ptr, count); + cs->writeisac(cs, IPACX_CMDRD, cmd); + + // set timeout for transmission contol + if (test_and_set_bit(FLG_DBUSY_TIMER, &cs->HW_Flags)) { + debugl1(cs, "dch_fill_fifo dbusytimer running"); + del_timer(&cs->dbusytimer); + } + cs->dbusytimer.expires = jiffies + ((DBUSY_TIMER_VALUE * HZ)/1000); + add_timer(&cs->dbusytimer); + + if (cs->debug & L1_DEB_ISAC_FIFO) { + char *t = cs->dlog; + + t += sprintf(t, "dch_fill_fifo() cnt %d", count); + QuickHex(t, ptr, count); + debugl1(cs, "%s", cs->dlog); + } +} + +//---------------------------------------------------------- +// D channel interrupt handler +//---------------------------------------------------------- +static inline void +dch_int(struct IsdnCardState *cs) +{ + struct sk_buff *skb; + u_char istad, rstad; + int count; + + istad = cs->readisac(cs, IPACX_ISTAD); +//############################################## +// printk(KERN_WARNING "dch_int(istad=%02x)\n", istad); +//############################################## + + if (istad & 0x80) { // RME + rstad = cs->readisac(cs, IPACX_RSTAD); + if ((rstad & 0xf0) != 0xa0) { // !(VFR && !RDO && CRC && !RAB) + if (!(rstad & 0x80)) + if (cs->debug & L1_DEB_WARN) + debugl1(cs, "dch_int(): invalid frame"); + if ((rstad & 0x40)) + if (cs->debug & L1_DEB_WARN) + debugl1(cs, "dch_int(): RDO"); + if (!(rstad & 0x20)) + if (cs->debug & L1_DEB_WARN) + debugl1(cs, "dch_int(): CRC error"); + cs->writeisac(cs, IPACX_CMDRD, 0x80); // RMC + } else { // received frame ok + count = cs->readisac(cs, IPACX_RBCLD); + if (count) count--; // RSTAB is last byte + count &= D_FIFO_SIZE - 1; + if (count == 0) count = D_FIFO_SIZE; + dch_empty_fifo(cs, count); + if ((count = cs->rcvidx) > 0) { + cs->rcvidx = 0; + if (!(skb = dev_alloc_skb(count))) + printk(KERN_WARNING "HiSax dch_int(): receive out of memory\n"); + else { + skb_put_data(skb, cs->rcvbuf, count); + skb_queue_tail(&cs->rq, skb); + } + } + } + cs->rcvidx = 0; + schedule_event(cs, D_RCVBUFREADY); + } + + if (istad & 0x40) { // RPF + dch_empty_fifo(cs, D_FIFO_SIZE); + } + + if (istad & 0x20) { // RFO + if (cs->debug & L1_DEB_WARN) debugl1(cs, "dch_int(): RFO"); + cs->writeisac(cs, IPACX_CMDRD, 0x40); //RRES + } + + if (istad & 0x10) { // XPR + if (test_and_clear_bit(FLG_DBUSY_TIMER, &cs->HW_Flags)) + del_timer(&cs->dbusytimer); + if (test_and_clear_bit(FLG_L1_DBUSY, &cs->HW_Flags)) + schedule_event(cs, D_CLEARBUSY); + if (cs->tx_skb) { + if (cs->tx_skb->len) { + dch_fill_fifo(cs); + goto afterXPR; + } + else { + dev_kfree_skb_irq(cs->tx_skb); + cs->tx_skb = NULL; + cs->tx_cnt = 0; + } + } + if ((cs->tx_skb = skb_dequeue(&cs->sq))) { + cs->tx_cnt = 0; + dch_fill_fifo(cs); + } + else { + schedule_event(cs, D_XMTBUFREADY); + } + } +afterXPR: + + if (istad & 0x0C) { // XDU or XMR + if (cs->debug & L1_DEB_WARN) debugl1(cs, "dch_int(): XDU"); + if (cs->tx_skb) { + skb_push(cs->tx_skb, cs->tx_cnt); // retransmit + cs->tx_cnt = 0; + dch_fill_fifo(cs); + } else { + printk(KERN_WARNING "HiSax: ISAC XDU no skb\n"); + debugl1(cs, "ISAC XDU no skb"); + } + } +} + +//---------------------------------------------------------- +//---------------------------------------------------------- +static void +dch_setstack(struct PStack *st, struct IsdnCardState *cs) +{ + st->l1.l1hw = dch_l2l1; +} + +//---------------------------------------------------------- +//---------------------------------------------------------- +static void +dch_init(struct IsdnCardState *cs) +{ + printk(KERN_INFO "HiSax: IPACX ISDN driver v0.1.0\n"); + + cs->setstack_d = dch_setstack; + + timer_setup(&cs->dbusytimer, dbusy_timer_handler, 0); + + cs->writeisac(cs, IPACX_TR_CONF0, 0x00); // clear LDD + cs->writeisac(cs, IPACX_TR_CONF2, 0x00); // enable transmitter + cs->writeisac(cs, IPACX_MODED, 0xC9); // transparent mode 0, RAC, stop/go + cs->writeisac(cs, IPACX_MON_CR, 0x00); // disable monitor channel +} + + +//========================================================== +// B channel functions +//========================================================== + +//---------------------------------------------------------- +// Entry point for commands +//---------------------------------------------------------- +static void +bch_l2l1(struct PStack *st, int pr, void *arg) +{ + struct BCState *bcs = st->l1.bcs; + struct sk_buff *skb = arg; + u_long flags; + + switch (pr) { + case (PH_DATA | REQUEST): + spin_lock_irqsave(&bcs->cs->lock, flags); + if (bcs->tx_skb) { + skb_queue_tail(&bcs->squeue, skb); + } else { + bcs->tx_skb = skb; + set_bit(BC_FLG_BUSY, &bcs->Flag); + bcs->hw.hscx.count = 0; + bch_fill_fifo(bcs); + } + spin_unlock_irqrestore(&bcs->cs->lock, flags); + break; + case (PH_PULL | INDICATION): + spin_lock_irqsave(&bcs->cs->lock, flags); + if (bcs->tx_skb) { + printk(KERN_WARNING "HiSax bch_l2l1(): this shouldn't happen\n"); + } else { + set_bit(BC_FLG_BUSY, &bcs->Flag); + bcs->tx_skb = skb; + bcs->hw.hscx.count = 0; + bch_fill_fifo(bcs); + } + spin_unlock_irqrestore(&bcs->cs->lock, flags); + break; + case (PH_PULL | REQUEST): + if (!bcs->tx_skb) { + clear_bit(FLG_L1_PULL_REQ, &st->l1.Flags); + st->l1.l1l2(st, PH_PULL | CONFIRM, NULL); + } else + set_bit(FLG_L1_PULL_REQ, &st->l1.Flags); + break; + case (PH_ACTIVATE | REQUEST): + spin_lock_irqsave(&bcs->cs->lock, flags); + set_bit(BC_FLG_ACTIV, &bcs->Flag); + bch_mode(bcs, st->l1.mode, st->l1.bc); + spin_unlock_irqrestore(&bcs->cs->lock, flags); + l1_msg_b(st, pr, arg); + break; + case (PH_DEACTIVATE | REQUEST): + l1_msg_b(st, pr, arg); + break; + case (PH_DEACTIVATE | CONFIRM): + spin_lock_irqsave(&bcs->cs->lock, flags); + clear_bit(BC_FLG_ACTIV, &bcs->Flag); + clear_bit(BC_FLG_BUSY, &bcs->Flag); + bch_mode(bcs, 0, st->l1.bc); + spin_unlock_irqrestore(&bcs->cs->lock, flags); + st->l1.l1l2(st, PH_DEACTIVATE | CONFIRM, NULL); + break; + } +} + +//---------------------------------------------------------- +// Read B channel fifo to receive buffer +//---------------------------------------------------------- +static void +bch_empty_fifo(struct BCState *bcs, int count) +{ + u_char *ptr, hscx; + struct IsdnCardState *cs; + int cnt; + + cs = bcs->cs; + hscx = bcs->hw.hscx.hscx; + if ((cs->debug & L1_DEB_HSCX) && !(cs->debug & L1_DEB_HSCX_FIFO)) + debugl1(cs, "bch_empty_fifo()"); + + // message too large, remove + if (bcs->hw.hscx.rcvidx + count > HSCX_BUFMAX) { + if (cs->debug & L1_DEB_WARN) + debugl1(cs, "bch_empty_fifo() incoming packet too large"); + cs->BC_Write_Reg(cs, hscx, IPACX_CMDRB, 0x80); // RMC + bcs->hw.hscx.rcvidx = 0; + return; + } + + ptr = bcs->hw.hscx.rcvbuf + bcs->hw.hscx.rcvidx; + cnt = count; + while (cnt--) *ptr++ = cs->BC_Read_Reg(cs, hscx, IPACX_RFIFOB); + cs->BC_Write_Reg(cs, hscx, IPACX_CMDRB, 0x80); // RMC + + ptr = bcs->hw.hscx.rcvbuf + bcs->hw.hscx.rcvidx; + bcs->hw.hscx.rcvidx += count; + + if (cs->debug & L1_DEB_HSCX_FIFO) { + char *t = bcs->blog; + + t += sprintf(t, "bch_empty_fifo() B-%d cnt %d", hscx, count); + QuickHex(t, ptr, count); + debugl1(cs, "%s", bcs->blog); + } +} + +//---------------------------------------------------------- +// Fill buffer to transmit FIFO +//---------------------------------------------------------- +static void +bch_fill_fifo(struct BCState *bcs) +{ + struct IsdnCardState *cs; + int more, count, cnt; + u_char *ptr, *p, hscx; + + cs = bcs->cs; + if ((cs->debug & L1_DEB_HSCX) && !(cs->debug & L1_DEB_HSCX_FIFO)) + debugl1(cs, "bch_fill_fifo()"); + + if (!bcs->tx_skb) return; + if (bcs->tx_skb->len <= 0) return; + + hscx = bcs->hw.hscx.hscx; + more = (bcs->mode == L1_MODE_TRANS) ? 1 : 0; + if (bcs->tx_skb->len > B_FIFO_SIZE) { + more = 1; + count = B_FIFO_SIZE; + } else { + count = bcs->tx_skb->len; + } + cnt = count; + + p = ptr = bcs->tx_skb->data; + skb_pull(bcs->tx_skb, count); + bcs->tx_cnt -= count; + bcs->hw.hscx.count += count; + while (cnt--) cs->BC_Write_Reg(cs, hscx, IPACX_XFIFOB, *p++); + cs->BC_Write_Reg(cs, hscx, IPACX_CMDRB, (more ? 0x08 : 0x0a)); + + if (cs->debug & L1_DEB_HSCX_FIFO) { + char *t = bcs->blog; + + t += sprintf(t, "%s() B-%d cnt %d", __func__, hscx, count); + QuickHex(t, ptr, count); + debugl1(cs, "%s", bcs->blog); + } +} + +//---------------------------------------------------------- +// B channel interrupt handler +//---------------------------------------------------------- +static void +bch_int(struct IsdnCardState *cs, u_char hscx) +{ + u_char istab; + struct BCState *bcs; + struct sk_buff *skb; + int count; + u_char rstab; + + bcs = cs->bcs + hscx; + istab = cs->BC_Read_Reg(cs, hscx, IPACX_ISTAB); +//############################################## +// printk(KERN_WARNING "bch_int(istab=%02x)\n", istab); +//############################################## + if (!test_bit(BC_FLG_INIT, &bcs->Flag)) return; + + if (istab & 0x80) { // RME + rstab = cs->BC_Read_Reg(cs, hscx, IPACX_RSTAB); + if ((rstab & 0xf0) != 0xa0) { // !(VFR && !RDO && CRC && !RAB) + if (!(rstab & 0x80)) + if (cs->debug & L1_DEB_WARN) + debugl1(cs, "bch_int() B-%d: invalid frame", hscx); + if ((rstab & 0x40) && (bcs->mode != L1_MODE_NULL)) + if (cs->debug & L1_DEB_WARN) + debugl1(cs, "bch_int() B-%d: RDO mode=%d", hscx, bcs->mode); + if (!(rstab & 0x20)) + if (cs->debug & L1_DEB_WARN) + debugl1(cs, "bch_int() B-%d: CRC error", hscx); + cs->BC_Write_Reg(cs, hscx, IPACX_CMDRB, 0x80); // RMC + } + else { // received frame ok + count = cs->BC_Read_Reg(cs, hscx, IPACX_RBCLB) & (B_FIFO_SIZE - 1); + if (count == 0) count = B_FIFO_SIZE; + bch_empty_fifo(bcs, count); + if ((count = bcs->hw.hscx.rcvidx - 1) > 0) { + if (cs->debug & L1_DEB_HSCX_FIFO) + debugl1(cs, "bch_int Frame %d", count); + if (!(skb = dev_alloc_skb(count))) + printk(KERN_WARNING "HiSax bch_int(): receive frame out of memory\n"); + else { + skb_put_data(skb, bcs->hw.hscx.rcvbuf, + count); + skb_queue_tail(&bcs->rqueue, skb); + } + } + } + bcs->hw.hscx.rcvidx = 0; + schedule_event(bcs, B_RCVBUFREADY); + } + + if (istab & 0x40) { // RPF + bch_empty_fifo(bcs, B_FIFO_SIZE); + + if (bcs->mode == L1_MODE_TRANS) { // queue every chunk + // receive transparent audio data + if (!(skb = dev_alloc_skb(B_FIFO_SIZE))) + printk(KERN_WARNING "HiSax bch_int(): receive transparent out of memory\n"); + else { + skb_put_data(skb, bcs->hw.hscx.rcvbuf, + B_FIFO_SIZE); + skb_queue_tail(&bcs->rqueue, skb); + } + bcs->hw.hscx.rcvidx = 0; + schedule_event(bcs, B_RCVBUFREADY); + } + } + + if (istab & 0x20) { // RFO + if (cs->debug & L1_DEB_WARN) + debugl1(cs, "bch_int() B-%d: RFO error", hscx); + cs->BC_Write_Reg(cs, hscx, IPACX_CMDRB, 0x40); // RRES + } + + if (istab & 0x10) { // XPR + if (bcs->tx_skb) { + if (bcs->tx_skb->len) { + bch_fill_fifo(bcs); + goto afterXPR; + } else { + if (test_bit(FLG_LLI_L1WAKEUP, &bcs->st->lli.flag) && + (PACKET_NOACK != bcs->tx_skb->pkt_type)) { + u_long flags; + spin_lock_irqsave(&bcs->aclock, flags); + bcs->ackcnt += bcs->hw.hscx.count; + spin_unlock_irqrestore(&bcs->aclock, flags); + schedule_event(bcs, B_ACKPENDING); + } + } + dev_kfree_skb_irq(bcs->tx_skb); + bcs->hw.hscx.count = 0; + bcs->tx_skb = NULL; + } + if ((bcs->tx_skb = skb_dequeue(&bcs->squeue))) { + bcs->hw.hscx.count = 0; + set_bit(BC_FLG_BUSY, &bcs->Flag); + bch_fill_fifo(bcs); + } else { + clear_bit(BC_FLG_BUSY, &bcs->Flag); + schedule_event(bcs, B_XMTBUFREADY); + } + } +afterXPR: + + if (istab & 0x04) { // XDU + if (bcs->mode == L1_MODE_TRANS) { + bch_fill_fifo(bcs); + } + else { + if (bcs->tx_skb) { // restart transmitting the whole frame + skb_push(bcs->tx_skb, bcs->hw.hscx.count); + bcs->tx_cnt += bcs->hw.hscx.count; + bcs->hw.hscx.count = 0; + } + cs->BC_Write_Reg(cs, hscx, IPACX_CMDRB, 0x01); // XRES + if (cs->debug & L1_DEB_WARN) + debugl1(cs, "bch_int() B-%d XDU error", hscx); + } + } +} + +//---------------------------------------------------------- +//---------------------------------------------------------- +static void +bch_mode(struct BCState *bcs, int mode, int bc) +{ + struct IsdnCardState *cs = bcs->cs; + int hscx = bcs->hw.hscx.hscx; + + bc = bc ? 1 : 0; // in case bc is greater than 1 + if (cs->debug & L1_DEB_HSCX) + debugl1(cs, "mode_bch() switch B-%d mode %d chan %d", hscx, mode, bc); + bcs->mode = mode; + bcs->channel = bc; + + // map controller to according timeslot + if (!hscx) + { + cs->writeisac(cs, IPACX_BCHA_TSDP_BC1, 0x80 | bc); + cs->writeisac(cs, IPACX_BCHA_CR, 0x88); + } + else + { + cs->writeisac(cs, IPACX_BCHB_TSDP_BC1, 0x80 | bc); + cs->writeisac(cs, IPACX_BCHB_CR, 0x88); + } + + switch (mode) { + case (L1_MODE_NULL): + cs->BC_Write_Reg(cs, hscx, IPACX_MODEB, 0xC0); // rec off + cs->BC_Write_Reg(cs, hscx, IPACX_EXMB, 0x30); // std adj. + cs->BC_Write_Reg(cs, hscx, IPACX_MASKB, 0xFF); // ints off + cs->BC_Write_Reg(cs, hscx, IPACX_CMDRB, 0x41); // validate adjustments + break; + case (L1_MODE_TRANS): + cs->BC_Write_Reg(cs, hscx, IPACX_MODEB, 0x88); // ext transp mode + cs->BC_Write_Reg(cs, hscx, IPACX_EXMB, 0x00); // xxx00000 + cs->BC_Write_Reg(cs, hscx, IPACX_CMDRB, 0x41); // validate adjustments + cs->BC_Write_Reg(cs, hscx, IPACX_MASKB, _MASKB_IMASK); + break; + case (L1_MODE_HDLC): + cs->BC_Write_Reg(cs, hscx, IPACX_MODEB, 0xC8); // transp mode 0 + cs->BC_Write_Reg(cs, hscx, IPACX_EXMB, 0x01); // idle=hdlc flags crc enabled + cs->BC_Write_Reg(cs, hscx, IPACX_CMDRB, 0x41); // validate adjustments + cs->BC_Write_Reg(cs, hscx, IPACX_MASKB, _MASKB_IMASK); + break; + } +} + +//---------------------------------------------------------- +//---------------------------------------------------------- +static void +bch_close_state(struct BCState *bcs) +{ + bch_mode(bcs, 0, bcs->channel); + if (test_and_clear_bit(BC_FLG_INIT, &bcs->Flag)) { + kfree(bcs->hw.hscx.rcvbuf); + bcs->hw.hscx.rcvbuf = NULL; + kfree(bcs->blog); + bcs->blog = NULL; + skb_queue_purge(&bcs->rqueue); + skb_queue_purge(&bcs->squeue); + if (bcs->tx_skb) { + dev_kfree_skb_any(bcs->tx_skb); + bcs->tx_skb = NULL; + clear_bit(BC_FLG_BUSY, &bcs->Flag); + } + } +} + +//---------------------------------------------------------- +//---------------------------------------------------------- +static int +bch_open_state(struct IsdnCardState *cs, struct BCState *bcs) +{ + if (!test_and_set_bit(BC_FLG_INIT, &bcs->Flag)) { + if (!(bcs->hw.hscx.rcvbuf = kmalloc(HSCX_BUFMAX, GFP_ATOMIC))) { + printk(KERN_WARNING + "HiSax open_bchstate(): No memory for hscx.rcvbuf\n"); + clear_bit(BC_FLG_INIT, &bcs->Flag); + return (1); + } + if (!(bcs->blog = kmalloc(MAX_BLOG_SPACE, GFP_ATOMIC))) { + printk(KERN_WARNING + "HiSax open_bchstate: No memory for bcs->blog\n"); + clear_bit(BC_FLG_INIT, &bcs->Flag); + kfree(bcs->hw.hscx.rcvbuf); + bcs->hw.hscx.rcvbuf = NULL; + return (2); + } + skb_queue_head_init(&bcs->rqueue); + skb_queue_head_init(&bcs->squeue); + } + bcs->tx_skb = NULL; + clear_bit(BC_FLG_BUSY, &bcs->Flag); + bcs->event = 0; + bcs->hw.hscx.rcvidx = 0; + bcs->tx_cnt = 0; + return (0); +} + +//---------------------------------------------------------- +//---------------------------------------------------------- +static int +bch_setstack(struct PStack *st, struct BCState *bcs) +{ + bcs->channel = st->l1.bc; + if (bch_open_state(st->l1.hardware, bcs)) return (-1); + st->l1.bcs = bcs; + st->l2.l2l1 = bch_l2l1; + setstack_manager(st); + bcs->st = st; + setstack_l1_B(st); + return (0); +} + +//---------------------------------------------------------- +//---------------------------------------------------------- +static void +bch_init(struct IsdnCardState *cs, int hscx) +{ + cs->bcs[hscx].BC_SetStack = bch_setstack; + cs->bcs[hscx].BC_Close = bch_close_state; + cs->bcs[hscx].hw.hscx.hscx = hscx; + cs->bcs[hscx].cs = cs; + bch_mode(cs->bcs + hscx, 0, hscx); +} + + +//========================================================== +// Shared functions +//========================================================== + +//---------------------------------------------------------- +// Main interrupt handler +//---------------------------------------------------------- +void +interrupt_ipacx(struct IsdnCardState *cs) +{ + u_char ista; + + while ((ista = cs->readisac(cs, IPACX_ISTA))) { +//################################################# +// printk(KERN_WARNING "interrupt_ipacx(ista=%02x)\n", ista); +//################################################# + if (ista & 0x80) bch_int(cs, 0); // B channel interrupts + if (ista & 0x40) bch_int(cs, 1); + + if (ista & 0x01) dch_int(cs); // D channel + if (ista & 0x10) cic_int(cs); // Layer 1 state + } +} + +//---------------------------------------------------------- +// Clears chip interrupt status +//---------------------------------------------------------- +static void +clear_pending_ints(struct IsdnCardState *cs) +{ + int ista; + + // all interrupts off + cs->writeisac(cs, IPACX_MASK, 0xff); + cs->writeisac(cs, IPACX_MASKD, 0xff); + cs->BC_Write_Reg(cs, 0, IPACX_MASKB, 0xff); + cs->BC_Write_Reg(cs, 1, IPACX_MASKB, 0xff); + + ista = cs->readisac(cs, IPACX_ISTA); + if (ista & 0x80) cs->BC_Read_Reg(cs, 0, IPACX_ISTAB); + if (ista & 0x40) cs->BC_Read_Reg(cs, 1, IPACX_ISTAB); + if (ista & 0x10) cs->readisac(cs, IPACX_CIR0); + if (ista & 0x01) cs->readisac(cs, IPACX_ISTAD); +} + +//---------------------------------------------------------- +// Does chip configuration work +// Work to do depends on bit mask in part +//---------------------------------------------------------- +void +init_ipacx(struct IsdnCardState *cs, int part) +{ + if (part & 1) { // initialise chip +//################################################## +// printk(KERN_INFO "init_ipacx(%x)\n", part); +//################################################## + clear_pending_ints(cs); + bch_init(cs, 0); + bch_init(cs, 1); + dch_init(cs); + } + if (part & 2) { // reenable all interrupts and start chip + cs->BC_Write_Reg(cs, 0, IPACX_MASKB, _MASKB_IMASK); + cs->BC_Write_Reg(cs, 1, IPACX_MASKB, _MASKB_IMASK); + cs->writeisac(cs, IPACX_MASKD, _MASKD_IMASK); + cs->writeisac(cs, IPACX_MASK, _MASK_IMASK); // global mask register + + // reset HDLC Transmitters/receivers + cs->writeisac(cs, IPACX_CMDRD, 0x41); + cs->BC_Write_Reg(cs, 0, IPACX_CMDRB, 0x41); + cs->BC_Write_Reg(cs, 1, IPACX_CMDRB, 0x41); + ph_command(cs, IPACX_CMD_RES); + } +} + +//----------------- end of file ----------------------- diff --git a/drivers/isdn/hisax/ipacx.h b/drivers/isdn/hisax/ipacx.h new file mode 100644 index 000000000..e8a22e8f3 --- /dev/null +++ b/drivers/isdn/hisax/ipacx.h @@ -0,0 +1,162 @@ +/* + * + * IPACX specific defines + * + * This software may be used and distributed according to the terms + * of the GNU General Public License, incorporated herein by reference. + * + */ + +/* All Registers original Siemens Spec */ + +#ifndef INCLUDE_IPACX_H +#define INCLUDE_IPACX_H + +/* D-channel registers */ +#define IPACX_RFIFOD 0x00 /* RD */ +#define IPACX_XFIFOD 0x00 /* WR */ +#define IPACX_ISTAD 0x20 /* RD */ +#define IPACX_MASKD 0x20 /* WR */ +#define IPACX_STARD 0x21 /* RD */ +#define IPACX_CMDRD 0x21 /* WR */ +#define IPACX_MODED 0x22 /* RD/WR */ +#define IPACX_EXMD1 0x23 /* RD/WR */ +#define IPACX_TIMR1 0x24 /* RD/WR */ +#define IPACX_SAP1 0x25 /* WR */ +#define IPACX_SAP2 0x26 /* WR */ +#define IPACX_RBCLD 0x26 /* RD */ +#define IPACX_RBCHD 0x27 /* RD */ +#define IPACX_TEI1 0x27 /* WR */ +#define IPACX_TEI2 0x28 /* WR */ +#define IPACX_RSTAD 0x28 /* RD */ +#define IPACX_TMD 0x29 /* RD/WR */ +#define IPACX_CIR0 0x2E /* RD */ +#define IPACX_CIX0 0x2E /* WR */ +#define IPACX_CIR1 0x2F /* RD */ +#define IPACX_CIX1 0x2F /* WR */ + +/* Transceiver registers */ +#define IPACX_TR_CONF0 0x30 /* RD/WR */ +#define IPACX_TR_CONF1 0x31 /* RD/WR */ +#define IPACX_TR_CONF2 0x32 /* RD/WR */ +#define IPACX_TR_STA 0x33 /* RD */ +#define IPACX_TR_CMD 0x34 /* RD/WR */ +#define IPACX_SQRR1 0x35 /* RD */ +#define IPACX_SQXR1 0x35 /* WR */ +#define IPACX_SQRR2 0x36 /* RD */ +#define IPACX_SQXR2 0x36 /* WR */ +#define IPACX_SQRR3 0x37 /* RD */ +#define IPACX_SQXR3 0x37 /* WR */ +#define IPACX_ISTATR 0x38 /* RD */ +#define IPACX_MASKTR 0x39 /* RD/WR */ +#define IPACX_TR_MODE 0x3A /* RD/WR */ +#define IPACX_ACFG1 0x3C /* RD/WR */ +#define IPACX_ACFG2 0x3D /* RD/WR */ +#define IPACX_AOE 0x3E /* RD/WR */ +#define IPACX_ARX 0x3F /* RD */ +#define IPACX_ATX 0x3F /* WR */ + +/* IOM: Timeslot, DPS, CDA */ +#define IPACX_CDA10 0x40 /* RD/WR */ +#define IPACX_CDA11 0x41 /* RD/WR */ +#define IPACX_CDA20 0x42 /* RD/WR */ +#define IPACX_CDA21 0x43 /* RD/WR */ +#define IPACX_CDA_TSDP10 0x44 /* RD/WR */ +#define IPACX_CDA_TSDP11 0x45 /* RD/WR */ +#define IPACX_CDA_TSDP20 0x46 /* RD/WR */ +#define IPACX_CDA_TSDP21 0x47 /* RD/WR */ +#define IPACX_BCHA_TSDP_BC1 0x48 /* RD/WR */ +#define IPACX_BCHA_TSDP_BC2 0x49 /* RD/WR */ +#define IPACX_BCHB_TSDP_BC1 0x4A /* RD/WR */ +#define IPACX_BCHB_TSDP_BC2 0x4B /* RD/WR */ +#define IPACX_TR_TSDP_BC1 0x4C /* RD/WR */ +#define IPACX_TR_TSDP_BC2 0x4D /* RD/WR */ +#define IPACX_CDA1_CR 0x4E /* RD/WR */ +#define IPACX_CDA2_CR 0x4F /* RD/WR */ + +/* IOM: Contol, Sync transfer, Monitor */ +#define IPACX_TR_CR 0x50 /* RD/WR */ +#define IPACX_TRC_CR 0x50 /* RD/WR */ +#define IPACX_BCHA_CR 0x51 /* RD/WR */ +#define IPACX_BCHB_CR 0x52 /* RD/WR */ +#define IPACX_DCI_CR 0x53 /* RD/WR */ +#define IPACX_DCIC_CR 0x53 /* RD/WR */ +#define IPACX_MON_CR 0x54 /* RD/WR */ +#define IPACX_SDS1_CR 0x55 /* RD/WR */ +#define IPACX_SDS2_CR 0x56 /* RD/WR */ +#define IPACX_IOM_CR 0x57 /* RD/WR */ +#define IPACX_STI 0x58 /* RD */ +#define IPACX_ASTI 0x58 /* WR */ +#define IPACX_MSTI 0x59 /* RD/WR */ +#define IPACX_SDS_CONF 0x5A /* RD/WR */ +#define IPACX_MCDA 0x5B /* RD */ +#define IPACX_MOR 0x5C /* RD */ +#define IPACX_MOX 0x5C /* WR */ +#define IPACX_MOSR 0x5D /* RD */ +#define IPACX_MOCR 0x5E /* RD/WR */ +#define IPACX_MSTA 0x5F /* RD */ +#define IPACX_MCONF 0x5F /* WR */ + +/* Interrupt and general registers */ +#define IPACX_ISTA 0x60 /* RD */ +#define IPACX_MASK 0x60 /* WR */ +#define IPACX_AUXI 0x61 /* RD */ +#define IPACX_AUXM 0x61 /* WR */ +#define IPACX_MODE1 0x62 /* RD/WR */ +#define IPACX_MODE2 0x63 /* RD/WR */ +#define IPACX_ID 0x64 /* RD */ +#define IPACX_SRES 0x64 /* WR */ +#define IPACX_TIMR2 0x65 /* RD/WR */ + +/* B-channel registers */ +#define IPACX_OFF_B1 0x70 +#define IPACX_OFF_B2 0x80 + +#define IPACX_ISTAB 0x00 /* RD */ +#define IPACX_MASKB 0x00 /* WR */ +#define IPACX_STARB 0x01 /* RD */ +#define IPACX_CMDRB 0x01 /* WR */ +#define IPACX_MODEB 0x02 /* RD/WR */ +#define IPACX_EXMB 0x03 /* RD/WR */ +#define IPACX_RAH1 0x05 /* WR */ +#define IPACX_RAH2 0x06 /* WR */ +#define IPACX_RBCLB 0x06 /* RD */ +#define IPACX_RBCHB 0x07 /* RD */ +#define IPACX_RAL1 0x07 /* WR */ +#define IPACX_RAL2 0x08 /* WR */ +#define IPACX_RSTAB 0x08 /* RD */ +#define IPACX_TMB 0x09 /* RD/WR */ +#define IPACX_RFIFOB 0x0A /*- RD */ +#define IPACX_XFIFOB 0x0A /*- WR */ + +/* Layer 1 Commands */ +#define IPACX_CMD_TIM 0x0 +#define IPACX_CMD_RES 0x1 +#define IPACX_CMD_SSP 0x2 +#define IPACX_CMD_SCP 0x3 +#define IPACX_CMD_AR8 0x8 +#define IPACX_CMD_AR10 0x9 +#define IPACX_CMD_ARL 0xa +#define IPACX_CMD_DI 0xf + +/* Layer 1 Indications */ +#define IPACX_IND_DR 0x0 +#define IPACX_IND_RES 0x1 +#define IPACX_IND_TMA 0x2 +#define IPACX_IND_SLD 0x3 +#define IPACX_IND_RSY 0x4 +#define IPACX_IND_DR6 0x5 +#define IPACX_IND_PU 0x7 +#define IPACX_IND_AR 0x8 +#define IPACX_IND_ARL 0xa +#define IPACX_IND_CVR 0xb +#define IPACX_IND_AI8 0xc +#define IPACX_IND_AI10 0xd +#define IPACX_IND_AIL 0xe +#define IPACX_IND_DC 0xf + +extern void init_ipacx(struct IsdnCardState *, int); +extern void interrupt_ipacx(struct IsdnCardState *); +extern void setup_isac(struct IsdnCardState *); + +#endif diff --git a/drivers/isdn/hisax/isac.c b/drivers/isdn/hisax/isac.c new file mode 100644 index 000000000..bd40e0671 --- /dev/null +++ b/drivers/isdn/hisax/isac.c @@ -0,0 +1,681 @@ +/* $Id: isac.c,v 1.31.2.3 2004/01/13 14:31:25 keil Exp $ + * + * ISAC specific routines + * + * Author Karsten Keil + * Copyright by Karsten Keil <keil@isdn4linux.de> + * + * This software may be used and distributed according to the terms + * of the GNU General Public License, incorporated herein by reference. + * + * For changes and modifications please read + * Documentation/isdn/HiSax.cert + * + */ + +#include "hisax.h" +#include "isac.h" +#include "arcofi.h" +#include "isdnl1.h" +#include <linux/interrupt.h> +#include <linux/slab.h> +#include <linux/init.h> + +#define DBUSY_TIMER_VALUE 80 +#define ARCOFI_USE 1 + +static char *ISACVer[] = +{"2086/2186 V1.1", "2085 B1", "2085 B2", + "2085 V2.3"}; + +void ISACVersion(struct IsdnCardState *cs, char *s) +{ + int val; + + val = cs->readisac(cs, ISAC_RBCH); + printk(KERN_INFO "%s ISAC version (%x): %s\n", s, val, ISACVer[(val >> 5) & 3]); +} + +static void +ph_command(struct IsdnCardState *cs, unsigned int command) +{ + if (cs->debug & L1_DEB_ISAC) + debugl1(cs, "ph_command %x", command); + cs->writeisac(cs, ISAC_CIX0, (command << 2) | 3); +} + + +static void +isac_new_ph(struct IsdnCardState *cs) +{ + switch (cs->dc.isac.ph_state) { + case (ISAC_IND_RS): + case (ISAC_IND_EI): + ph_command(cs, ISAC_CMD_DUI); + l1_msg(cs, HW_RESET | INDICATION, NULL); + break; + case (ISAC_IND_DID): + l1_msg(cs, HW_DEACTIVATE | CONFIRM, NULL); + break; + case (ISAC_IND_DR): + l1_msg(cs, HW_DEACTIVATE | INDICATION, NULL); + break; + case (ISAC_IND_PU): + l1_msg(cs, HW_POWERUP | CONFIRM, NULL); + break; + case (ISAC_IND_RSY): + l1_msg(cs, HW_RSYNC | INDICATION, NULL); + break; + case (ISAC_IND_ARD): + l1_msg(cs, HW_INFO2 | INDICATION, NULL); + break; + case (ISAC_IND_AI8): + l1_msg(cs, HW_INFO4_P8 | INDICATION, NULL); + break; + case (ISAC_IND_AI10): + l1_msg(cs, HW_INFO4_P10 | INDICATION, NULL); + break; + default: + break; + } +} + +static void +isac_bh(struct work_struct *work) +{ + struct IsdnCardState *cs = + container_of(work, struct IsdnCardState, tqueue); + struct PStack *stptr; + + if (test_and_clear_bit(D_CLEARBUSY, &cs->event)) { + if (cs->debug) + debugl1(cs, "D-Channel Busy cleared"); + stptr = cs->stlist; + while (stptr != NULL) { + stptr->l1.l1l2(stptr, PH_PAUSE | CONFIRM, NULL); + stptr = stptr->next; + } + } + if (test_and_clear_bit(D_L1STATECHANGE, &cs->event)) + isac_new_ph(cs); + if (test_and_clear_bit(D_RCVBUFREADY, &cs->event)) + DChannel_proc_rcv(cs); + if (test_and_clear_bit(D_XMTBUFREADY, &cs->event)) + DChannel_proc_xmt(cs); +#if ARCOFI_USE + if (!test_bit(HW_ARCOFI, &cs->HW_Flags)) + return; + if (test_and_clear_bit(D_RX_MON1, &cs->event)) + arcofi_fsm(cs, ARCOFI_RX_END, NULL); + if (test_and_clear_bit(D_TX_MON1, &cs->event)) + arcofi_fsm(cs, ARCOFI_TX_END, NULL); +#endif +} + +static void +isac_empty_fifo(struct IsdnCardState *cs, int count) +{ + u_char *ptr; + + if ((cs->debug & L1_DEB_ISAC) && !(cs->debug & L1_DEB_ISAC_FIFO)) + debugl1(cs, "isac_empty_fifo"); + + if ((cs->rcvidx + count) >= MAX_DFRAME_LEN_L1) { + if (cs->debug & L1_DEB_WARN) + debugl1(cs, "isac_empty_fifo overrun %d", + cs->rcvidx + count); + cs->writeisac(cs, ISAC_CMDR, 0x80); + cs->rcvidx = 0; + return; + } + ptr = cs->rcvbuf + cs->rcvidx; + cs->rcvidx += count; + cs->readisacfifo(cs, ptr, count); + cs->writeisac(cs, ISAC_CMDR, 0x80); + if (cs->debug & L1_DEB_ISAC_FIFO) { + char *t = cs->dlog; + + t += sprintf(t, "isac_empty_fifo cnt %d", count); + QuickHex(t, ptr, count); + debugl1(cs, "%s", cs->dlog); + } +} + +static void +isac_fill_fifo(struct IsdnCardState *cs) +{ + int count, more; + u_char *ptr; + + if ((cs->debug & L1_DEB_ISAC) && !(cs->debug & L1_DEB_ISAC_FIFO)) + debugl1(cs, "isac_fill_fifo"); + + if (!cs->tx_skb) + return; + + count = cs->tx_skb->len; + if (count <= 0) + return; + + more = 0; + if (count > 32) { + more = !0; + count = 32; + } + ptr = cs->tx_skb->data; + skb_pull(cs->tx_skb, count); + cs->tx_cnt += count; + cs->writeisacfifo(cs, ptr, count); + cs->writeisac(cs, ISAC_CMDR, more ? 0x8 : 0xa); + if (test_and_set_bit(FLG_DBUSY_TIMER, &cs->HW_Flags)) { + debugl1(cs, "isac_fill_fifo dbusytimer running"); + del_timer(&cs->dbusytimer); + } + cs->dbusytimer.expires = jiffies + ((DBUSY_TIMER_VALUE * HZ)/1000); + add_timer(&cs->dbusytimer); + if (cs->debug & L1_DEB_ISAC_FIFO) { + char *t = cs->dlog; + + t += sprintf(t, "isac_fill_fifo cnt %d", count); + QuickHex(t, ptr, count); + debugl1(cs, "%s", cs->dlog); + } +} + +void +isac_interrupt(struct IsdnCardState *cs, u_char val) +{ + u_char exval, v1; + struct sk_buff *skb; + unsigned int count; + + if (cs->debug & L1_DEB_ISAC) + debugl1(cs, "ISAC interrupt %x", val); + if (val & 0x80) { /* RME */ + exval = cs->readisac(cs, ISAC_RSTA); + if ((exval & 0x70) != 0x20) { + if (exval & 0x40) { + if (cs->debug & L1_DEB_WARN) + debugl1(cs, "ISAC RDO"); +#ifdef ERROR_STATISTIC + cs->err_rx++; +#endif + } + if (!(exval & 0x20)) { + if (cs->debug & L1_DEB_WARN) + debugl1(cs, "ISAC CRC error"); +#ifdef ERROR_STATISTIC + cs->err_crc++; +#endif + } + cs->writeisac(cs, ISAC_CMDR, 0x80); + } else { + count = cs->readisac(cs, ISAC_RBCL) & 0x1f; + if (count == 0) + count = 32; + isac_empty_fifo(cs, count); + count = cs->rcvidx; + if (count > 0) { + cs->rcvidx = 0; + skb = alloc_skb(count, GFP_ATOMIC); + if (!skb) + printk(KERN_WARNING "HiSax: D receive out of memory\n"); + else { + skb_put_data(skb, cs->rcvbuf, count); + skb_queue_tail(&cs->rq, skb); + } + } + } + cs->rcvidx = 0; + schedule_event(cs, D_RCVBUFREADY); + } + if (val & 0x40) { /* RPF */ + isac_empty_fifo(cs, 32); + } + if (val & 0x20) { /* RSC */ + /* never */ + if (cs->debug & L1_DEB_WARN) + debugl1(cs, "ISAC RSC interrupt"); + } + if (val & 0x10) { /* XPR */ + if (test_and_clear_bit(FLG_DBUSY_TIMER, &cs->HW_Flags)) + del_timer(&cs->dbusytimer); + if (test_and_clear_bit(FLG_L1_DBUSY, &cs->HW_Flags)) + schedule_event(cs, D_CLEARBUSY); + if (cs->tx_skb) { + if (cs->tx_skb->len) { + isac_fill_fifo(cs); + goto afterXPR; + } else { + dev_kfree_skb_irq(cs->tx_skb); + cs->tx_cnt = 0; + cs->tx_skb = NULL; + } + } + cs->tx_skb = skb_dequeue(&cs->sq); + if (cs->tx_skb) { + cs->tx_cnt = 0; + isac_fill_fifo(cs); + } else + schedule_event(cs, D_XMTBUFREADY); + } +afterXPR: + if (val & 0x04) { /* CISQ */ + exval = cs->readisac(cs, ISAC_CIR0); + if (cs->debug & L1_DEB_ISAC) + debugl1(cs, "ISAC CIR0 %02X", exval); + if (exval & 2) { + cs->dc.isac.ph_state = (exval >> 2) & 0xf; + if (cs->debug & L1_DEB_ISAC) + debugl1(cs, "ph_state change %x", cs->dc.isac.ph_state); + schedule_event(cs, D_L1STATECHANGE); + } + if (exval & 1) { + exval = cs->readisac(cs, ISAC_CIR1); + if (cs->debug & L1_DEB_ISAC) + debugl1(cs, "ISAC CIR1 %02X", exval); + } + } + if (val & 0x02) { /* SIN */ + /* never */ + if (cs->debug & L1_DEB_WARN) + debugl1(cs, "ISAC SIN interrupt"); + } + if (val & 0x01) { /* EXI */ + exval = cs->readisac(cs, ISAC_EXIR); + if (cs->debug & L1_DEB_WARN) + debugl1(cs, "ISAC EXIR %02x", exval); + if (exval & 0x80) { /* XMR */ + debugl1(cs, "ISAC XMR"); + printk(KERN_WARNING "HiSax: ISAC XMR\n"); + } + if (exval & 0x40) { /* XDU */ + debugl1(cs, "ISAC XDU"); + printk(KERN_WARNING "HiSax: ISAC XDU\n"); +#ifdef ERROR_STATISTIC + cs->err_tx++; +#endif + if (test_and_clear_bit(FLG_DBUSY_TIMER, &cs->HW_Flags)) + del_timer(&cs->dbusytimer); + if (test_and_clear_bit(FLG_L1_DBUSY, &cs->HW_Flags)) + schedule_event(cs, D_CLEARBUSY); + if (cs->tx_skb) { /* Restart frame */ + skb_push(cs->tx_skb, cs->tx_cnt); + cs->tx_cnt = 0; + isac_fill_fifo(cs); + } else { + printk(KERN_WARNING "HiSax: ISAC XDU no skb\n"); + debugl1(cs, "ISAC XDU no skb"); + } + } + if (exval & 0x04) { /* MOS */ + v1 = cs->readisac(cs, ISAC_MOSR); + if (cs->debug & L1_DEB_MONITOR) + debugl1(cs, "ISAC MOSR %02x", v1); +#if ARCOFI_USE + if (v1 & 0x08) { + if (!cs->dc.isac.mon_rx) { + cs->dc.isac.mon_rx = kmalloc(MAX_MON_FRAME, GFP_ATOMIC); + if (!cs->dc.isac.mon_rx) { + if (cs->debug & L1_DEB_WARN) + debugl1(cs, "ISAC MON RX out of memory!"); + cs->dc.isac.mocr &= 0xf0; + cs->dc.isac.mocr |= 0x0a; + cs->writeisac(cs, ISAC_MOCR, cs->dc.isac.mocr); + goto afterMONR0; + } else + cs->dc.isac.mon_rxp = 0; + } + if (cs->dc.isac.mon_rxp >= MAX_MON_FRAME) { + cs->dc.isac.mocr &= 0xf0; + cs->dc.isac.mocr |= 0x0a; + cs->writeisac(cs, ISAC_MOCR, cs->dc.isac.mocr); + cs->dc.isac.mon_rxp = 0; + if (cs->debug & L1_DEB_WARN) + debugl1(cs, "ISAC MON RX overflow!"); + goto afterMONR0; + } + cs->dc.isac.mon_rx[cs->dc.isac.mon_rxp++] = cs->readisac(cs, ISAC_MOR0); + if (cs->debug & L1_DEB_MONITOR) + debugl1(cs, "ISAC MOR0 %02x", cs->dc.isac.mon_rx[cs->dc.isac.mon_rxp - 1]); + if (cs->dc.isac.mon_rxp == 1) { + cs->dc.isac.mocr |= 0x04; + cs->writeisac(cs, ISAC_MOCR, cs->dc.isac.mocr); + } + } + afterMONR0: + if (v1 & 0x80) { + if (!cs->dc.isac.mon_rx) { + cs->dc.isac.mon_rx = kmalloc(MAX_MON_FRAME, GFP_ATOMIC); + if (!cs->dc.isac.mon_rx) { + if (cs->debug & L1_DEB_WARN) + debugl1(cs, "ISAC MON RX out of memory!"); + cs->dc.isac.mocr &= 0x0f; + cs->dc.isac.mocr |= 0xa0; + cs->writeisac(cs, ISAC_MOCR, cs->dc.isac.mocr); + goto afterMONR1; + } else + cs->dc.isac.mon_rxp = 0; + } + if (cs->dc.isac.mon_rxp >= MAX_MON_FRAME) { + cs->dc.isac.mocr &= 0x0f; + cs->dc.isac.mocr |= 0xa0; + cs->writeisac(cs, ISAC_MOCR, cs->dc.isac.mocr); + cs->dc.isac.mon_rxp = 0; + if (cs->debug & L1_DEB_WARN) + debugl1(cs, "ISAC MON RX overflow!"); + goto afterMONR1; + } + cs->dc.isac.mon_rx[cs->dc.isac.mon_rxp++] = cs->readisac(cs, ISAC_MOR1); + if (cs->debug & L1_DEB_MONITOR) + debugl1(cs, "ISAC MOR1 %02x", cs->dc.isac.mon_rx[cs->dc.isac.mon_rxp - 1]); + cs->dc.isac.mocr |= 0x40; + cs->writeisac(cs, ISAC_MOCR, cs->dc.isac.mocr); + } + afterMONR1: + if (v1 & 0x04) { + cs->dc.isac.mocr &= 0xf0; + cs->writeisac(cs, ISAC_MOCR, cs->dc.isac.mocr); + cs->dc.isac.mocr |= 0x0a; + cs->writeisac(cs, ISAC_MOCR, cs->dc.isac.mocr); + schedule_event(cs, D_RX_MON0); + } + if (v1 & 0x40) { + cs->dc.isac.mocr &= 0x0f; + cs->writeisac(cs, ISAC_MOCR, cs->dc.isac.mocr); + cs->dc.isac.mocr |= 0xa0; + cs->writeisac(cs, ISAC_MOCR, cs->dc.isac.mocr); + schedule_event(cs, D_RX_MON1); + } + if (v1 & 0x02) { + if ((!cs->dc.isac.mon_tx) || (cs->dc.isac.mon_txc && + (cs->dc.isac.mon_txp >= cs->dc.isac.mon_txc) && + !(v1 & 0x08))) { + cs->dc.isac.mocr &= 0xf0; + cs->writeisac(cs, ISAC_MOCR, cs->dc.isac.mocr); + cs->dc.isac.mocr |= 0x0a; + cs->writeisac(cs, ISAC_MOCR, cs->dc.isac.mocr); + if (cs->dc.isac.mon_txc && + (cs->dc.isac.mon_txp >= cs->dc.isac.mon_txc)) + schedule_event(cs, D_TX_MON0); + goto AfterMOX0; + } + if (cs->dc.isac.mon_txc && (cs->dc.isac.mon_txp >= cs->dc.isac.mon_txc)) { + schedule_event(cs, D_TX_MON0); + goto AfterMOX0; + } + cs->writeisac(cs, ISAC_MOX0, + cs->dc.isac.mon_tx[cs->dc.isac.mon_txp++]); + if (cs->debug & L1_DEB_MONITOR) + debugl1(cs, "ISAC %02x -> MOX0", cs->dc.isac.mon_tx[cs->dc.isac.mon_txp - 1]); + } + AfterMOX0: + if (v1 & 0x20) { + if ((!cs->dc.isac.mon_tx) || (cs->dc.isac.mon_txc && + (cs->dc.isac.mon_txp >= cs->dc.isac.mon_txc) && + !(v1 & 0x80))) { + cs->dc.isac.mocr &= 0x0f; + cs->writeisac(cs, ISAC_MOCR, cs->dc.isac.mocr); + cs->dc.isac.mocr |= 0xa0; + cs->writeisac(cs, ISAC_MOCR, cs->dc.isac.mocr); + if (cs->dc.isac.mon_txc && + (cs->dc.isac.mon_txp >= cs->dc.isac.mon_txc)) + schedule_event(cs, D_TX_MON1); + goto AfterMOX1; + } + if (cs->dc.isac.mon_txc && (cs->dc.isac.mon_txp >= cs->dc.isac.mon_txc)) { + schedule_event(cs, D_TX_MON1); + goto AfterMOX1; + } + cs->writeisac(cs, ISAC_MOX1, + cs->dc.isac.mon_tx[cs->dc.isac.mon_txp++]); + if (cs->debug & L1_DEB_MONITOR) + debugl1(cs, "ISAC %02x -> MOX1", cs->dc.isac.mon_tx[cs->dc.isac.mon_txp - 1]); + } + AfterMOX1:; +#endif + } + } +} + +static void +ISAC_l1hw(struct PStack *st, int pr, void *arg) +{ + struct IsdnCardState *cs = (struct IsdnCardState *) st->l1.hardware; + struct sk_buff *skb = arg; + u_long flags; + int val; + + switch (pr) { + case (PH_DATA | REQUEST): + if (cs->debug & DEB_DLOG_HEX) + LogFrame(cs, skb->data, skb->len); + if (cs->debug & DEB_DLOG_VERBOSE) + dlogframe(cs, skb, 0); + spin_lock_irqsave(&cs->lock, flags); + if (cs->tx_skb) { + skb_queue_tail(&cs->sq, skb); +#ifdef L2FRAME_DEBUG /* psa */ + if (cs->debug & L1_DEB_LAPD) + Logl2Frame(cs, skb, "PH_DATA Queued", 0); +#endif + } else { + cs->tx_skb = skb; + cs->tx_cnt = 0; +#ifdef L2FRAME_DEBUG /* psa */ + if (cs->debug & L1_DEB_LAPD) + Logl2Frame(cs, skb, "PH_DATA", 0); +#endif + isac_fill_fifo(cs); + } + spin_unlock_irqrestore(&cs->lock, flags); + break; + case (PH_PULL | INDICATION): + spin_lock_irqsave(&cs->lock, flags); + if (cs->tx_skb) { + if (cs->debug & L1_DEB_WARN) + debugl1(cs, " l2l1 tx_skb exist this shouldn't happen"); + skb_queue_tail(&cs->sq, skb); + } else { + if (cs->debug & DEB_DLOG_HEX) + LogFrame(cs, skb->data, skb->len); + if (cs->debug & DEB_DLOG_VERBOSE) + dlogframe(cs, skb, 0); + cs->tx_skb = skb; + cs->tx_cnt = 0; +#ifdef L2FRAME_DEBUG /* psa */ + if (cs->debug & L1_DEB_LAPD) + Logl2Frame(cs, skb, "PH_DATA_PULLED", 0); +#endif + isac_fill_fifo(cs); + } + spin_unlock_irqrestore(&cs->lock, flags); + break; + case (PH_PULL | REQUEST): +#ifdef L2FRAME_DEBUG /* psa */ + if (cs->debug & L1_DEB_LAPD) + debugl1(cs, "-> PH_REQUEST_PULL"); +#endif + if (!cs->tx_skb) { + test_and_clear_bit(FLG_L1_PULL_REQ, &st->l1.Flags); + st->l1.l1l2(st, PH_PULL | CONFIRM, NULL); + } else + test_and_set_bit(FLG_L1_PULL_REQ, &st->l1.Flags); + break; + case (HW_RESET | REQUEST): + spin_lock_irqsave(&cs->lock, flags); + if ((cs->dc.isac.ph_state == ISAC_IND_EI) || + (cs->dc.isac.ph_state == ISAC_IND_DR) || + (cs->dc.isac.ph_state == ISAC_IND_RS)) + ph_command(cs, ISAC_CMD_TIM); + else + ph_command(cs, ISAC_CMD_RS); + spin_unlock_irqrestore(&cs->lock, flags); + break; + case (HW_ENABLE | REQUEST): + spin_lock_irqsave(&cs->lock, flags); + ph_command(cs, ISAC_CMD_TIM); + spin_unlock_irqrestore(&cs->lock, flags); + break; + case (HW_INFO3 | REQUEST): + spin_lock_irqsave(&cs->lock, flags); + ph_command(cs, ISAC_CMD_AR8); + spin_unlock_irqrestore(&cs->lock, flags); + break; + case (HW_TESTLOOP | REQUEST): + spin_lock_irqsave(&cs->lock, flags); + val = 0; + if (1 & (long) arg) + val |= 0x0c; + if (2 & (long) arg) + val |= 0x3; + if (test_bit(HW_IOM1, &cs->HW_Flags)) { + /* IOM 1 Mode */ + if (!val) { + cs->writeisac(cs, ISAC_SPCR, 0xa); + cs->writeisac(cs, ISAC_ADF1, 0x2); + } else { + cs->writeisac(cs, ISAC_SPCR, val); + cs->writeisac(cs, ISAC_ADF1, 0xa); + } + } else { + /* IOM 2 Mode */ + cs->writeisac(cs, ISAC_SPCR, val); + if (val) + cs->writeisac(cs, ISAC_ADF1, 0x8); + else + cs->writeisac(cs, ISAC_ADF1, 0x0); + } + spin_unlock_irqrestore(&cs->lock, flags); + break; + case (HW_DEACTIVATE | RESPONSE): + skb_queue_purge(&cs->rq); + skb_queue_purge(&cs->sq); + if (cs->tx_skb) { + dev_kfree_skb_any(cs->tx_skb); + cs->tx_skb = NULL; + } + if (test_and_clear_bit(FLG_DBUSY_TIMER, &cs->HW_Flags)) + del_timer(&cs->dbusytimer); + if (test_and_clear_bit(FLG_L1_DBUSY, &cs->HW_Flags)) + schedule_event(cs, D_CLEARBUSY); + break; + default: + if (cs->debug & L1_DEB_WARN) + debugl1(cs, "isac_l1hw unknown %04x", pr); + break; + } +} + +static void +setstack_isac(struct PStack *st, struct IsdnCardState *cs) +{ + st->l1.l1hw = ISAC_l1hw; +} + +static void +DC_Close_isac(struct IsdnCardState *cs) +{ + kfree(cs->dc.isac.mon_rx); + cs->dc.isac.mon_rx = NULL; + kfree(cs->dc.isac.mon_tx); + cs->dc.isac.mon_tx = NULL; +} + +static void +dbusy_timer_handler(struct timer_list *t) +{ + struct IsdnCardState *cs = from_timer(cs, t, dbusytimer); + struct PStack *stptr; + int rbch, star; + + if (test_bit(FLG_DBUSY_TIMER, &cs->HW_Flags)) { + rbch = cs->readisac(cs, ISAC_RBCH); + star = cs->readisac(cs, ISAC_STAR); + if (cs->debug) + debugl1(cs, "D-Channel Busy RBCH %02x STAR %02x", + rbch, star); + if (rbch & ISAC_RBCH_XAC) { /* D-Channel Busy */ + test_and_set_bit(FLG_L1_DBUSY, &cs->HW_Flags); + stptr = cs->stlist; + while (stptr != NULL) { + stptr->l1.l1l2(stptr, PH_PAUSE | INDICATION, NULL); + stptr = stptr->next; + } + } else { + /* discard frame; reset transceiver */ + test_and_clear_bit(FLG_DBUSY_TIMER, &cs->HW_Flags); + if (cs->tx_skb) { + dev_kfree_skb_any(cs->tx_skb); + cs->tx_cnt = 0; + cs->tx_skb = NULL; + } else { + printk(KERN_WARNING "HiSax: ISAC D-Channel Busy no skb\n"); + debugl1(cs, "D-Channel Busy no skb"); + } + cs->writeisac(cs, ISAC_CMDR, 0x01); /* Transmitter reset */ + cs->irq_func(cs->irq, cs); + } + } +} + +void initisac(struct IsdnCardState *cs) +{ + cs->setstack_d = setstack_isac; + cs->DC_Close = DC_Close_isac; + cs->dc.isac.mon_tx = NULL; + cs->dc.isac.mon_rx = NULL; + cs->writeisac(cs, ISAC_MASK, 0xff); + cs->dc.isac.mocr = 0xaa; + if (test_bit(HW_IOM1, &cs->HW_Flags)) { + /* IOM 1 Mode */ + cs->writeisac(cs, ISAC_ADF2, 0x0); + cs->writeisac(cs, ISAC_SPCR, 0xa); + cs->writeisac(cs, ISAC_ADF1, 0x2); + cs->writeisac(cs, ISAC_STCR, 0x70); + cs->writeisac(cs, ISAC_MODE, 0xc9); + } else { + /* IOM 2 Mode */ + if (!cs->dc.isac.adf2) + cs->dc.isac.adf2 = 0x80; + cs->writeisac(cs, ISAC_ADF2, cs->dc.isac.adf2); + cs->writeisac(cs, ISAC_SQXR, 0x2f); + cs->writeisac(cs, ISAC_SPCR, 0x00); + cs->writeisac(cs, ISAC_STCR, 0x70); + cs->writeisac(cs, ISAC_MODE, 0xc9); + cs->writeisac(cs, ISAC_TIMR, 0x00); + cs->writeisac(cs, ISAC_ADF1, 0x00); + } + ph_command(cs, ISAC_CMD_RS); + cs->writeisac(cs, ISAC_MASK, 0x0); +} + +void clear_pending_isac_ints(struct IsdnCardState *cs) +{ + int val, eval; + + val = cs->readisac(cs, ISAC_STAR); + debugl1(cs, "ISAC STAR %x", val); + val = cs->readisac(cs, ISAC_MODE); + debugl1(cs, "ISAC MODE %x", val); + val = cs->readisac(cs, ISAC_ADF2); + debugl1(cs, "ISAC ADF2 %x", val); + val = cs->readisac(cs, ISAC_ISTA); + debugl1(cs, "ISAC ISTA %x", val); + if (val & 0x01) { + eval = cs->readisac(cs, ISAC_EXIR); + debugl1(cs, "ISAC EXIR %x", eval); + } + val = cs->readisac(cs, ISAC_CIR0); + debugl1(cs, "ISAC CIR0 %x", val); + cs->dc.isac.ph_state = (val >> 2) & 0xf; + schedule_event(cs, D_L1STATECHANGE); + /* Disable all IRQ */ + cs->writeisac(cs, ISAC_MASK, 0xFF); +} + +void setup_isac(struct IsdnCardState *cs) +{ + INIT_WORK(&cs->tqueue, isac_bh); + timer_setup(&cs->dbusytimer, dbusy_timer_handler, 0); +} diff --git a/drivers/isdn/hisax/isac.h b/drivers/isdn/hisax/isac.h new file mode 100644 index 000000000..04f16b91b --- /dev/null +++ b/drivers/isdn/hisax/isac.h @@ -0,0 +1,70 @@ +/* $Id: isac.h,v 1.9.2.2 2004/01/12 22:52:27 keil Exp $ + * + * ISAC specific defines + * + * Author Karsten Keil + * Copyright by Karsten Keil <keil@isdn4linux.de> + * + * This software may be used and distributed according to the terms + * of the GNU General Public License, incorporated herein by reference. + * + */ + +/* All Registers original Siemens Spec */ + +#define ISAC_MASK 0x20 +#define ISAC_ISTA 0x20 +#define ISAC_STAR 0x21 +#define ISAC_CMDR 0x21 +#define ISAC_EXIR 0x24 +#define ISAC_ADF2 0x39 +#define ISAC_SPCR 0x30 +#define ISAC_ADF1 0x38 +#define ISAC_CIR0 0x31 +#define ISAC_CIX0 0x31 +#define ISAC_CIR1 0x33 +#define ISAC_CIX1 0x33 +#define ISAC_STCR 0x37 +#define ISAC_MODE 0x22 +#define ISAC_RSTA 0x27 +#define ISAC_RBCL 0x25 +#define ISAC_RBCH 0x2A +#define ISAC_TIMR 0x23 +#define ISAC_SQXR 0x3b +#define ISAC_MOSR 0x3a +#define ISAC_MOCR 0x3a +#define ISAC_MOR0 0x32 +#define ISAC_MOX0 0x32 +#define ISAC_MOR1 0x34 +#define ISAC_MOX1 0x34 + +#define ISAC_RBCH_XAC 0x80 + +#define ISAC_CMD_TIM 0x0 +#define ISAC_CMD_RS 0x1 +#define ISAC_CMD_SCZ 0x4 +#define ISAC_CMD_SSZ 0x2 +#define ISAC_CMD_AR8 0x8 +#define ISAC_CMD_AR10 0x9 +#define ISAC_CMD_ARL 0xA +#define ISAC_CMD_DUI 0xF + +#define ISAC_IND_RS 0x1 +#define ISAC_IND_PU 0x7 +#define ISAC_IND_DR 0x0 +#define ISAC_IND_SD 0x2 +#define ISAC_IND_DIS 0x3 +#define ISAC_IND_EI 0x6 +#define ISAC_IND_RSY 0x4 +#define ISAC_IND_ARD 0x8 +#define ISAC_IND_TI 0xA +#define ISAC_IND_ATI 0xB +#define ISAC_IND_AI8 0xC +#define ISAC_IND_AI10 0xD +#define ISAC_IND_DID 0xF + +extern void ISACVersion(struct IsdnCardState *, char *); +extern void setup_isac(struct IsdnCardState *); +extern void initisac(struct IsdnCardState *); +extern void isac_interrupt(struct IsdnCardState *, u_char); +extern void clear_pending_isac_ints(struct IsdnCardState *); diff --git a/drivers/isdn/hisax/isar.c b/drivers/isdn/hisax/isar.c new file mode 100644 index 000000000..82c1879f5 --- /dev/null +++ b/drivers/isdn/hisax/isar.c @@ -0,0 +1,1910 @@ +/* $Id: isar.c,v 1.22.2.6 2004/02/11 13:21:34 keil Exp $ + * + * isar.c ISAR (Siemens PSB 7110) specific routines + * + * Author Karsten Keil (keil@isdn4linux.de) + * + * This file is (c) under GNU General Public License + * + */ + +#include <linux/init.h> +#include "hisax.h" +#include "isar.h" +#include "isdnl1.h" +#include <linux/interrupt.h> +#include <linux/slab.h> + +#define DBG_LOADFIRM 0 +#define DUMP_MBOXFRAME 2 + +#define DLE 0x10 +#define ETX 0x03 + +#define FAXMODCNT 13 +static const u_char faxmodulation[] = {3, 24, 48, 72, 73, 74, 96, 97, 98, 121, 122, 145, 146}; +static u_int modmask = 0x1fff; +static int frm_extra_delay = 2; +static int para_TOA = 6; +static const u_char *FC1_CMD[] = {"FAE", "FTS", "FRS", "FTM", "FRM", "FTH", "FRH", "CTRL"}; + +static void isar_setup(struct IsdnCardState *cs); +static void isar_pump_cmd(struct BCState *bcs, u_char cmd, u_char para); +static void ll_deliver_faxstat(struct BCState *bcs, u_char status); + +static inline int +waitforHIA(struct IsdnCardState *cs, int timeout) +{ + + while ((cs->BC_Read_Reg(cs, 0, ISAR_HIA) & 1) && timeout) { + udelay(1); + timeout--; + } + if (!timeout) + printk(KERN_WARNING "HiSax: ISAR waitforHIA timeout\n"); + return (timeout); +} + + +static int +sendmsg(struct IsdnCardState *cs, u_char his, u_char creg, u_char len, + u_char *msg) +{ + int i; + + if (!waitforHIA(cs, 4000)) + return (0); +#if DUMP_MBOXFRAME + if (cs->debug & L1_DEB_HSCX) + debugl1(cs, "sendmsg(%02x,%02x,%d)", his, creg, len); +#endif + cs->BC_Write_Reg(cs, 0, ISAR_CTRL_H, creg); + cs->BC_Write_Reg(cs, 0, ISAR_CTRL_L, len); + cs->BC_Write_Reg(cs, 0, ISAR_WADR, 0); + if (msg && len) { + cs->BC_Write_Reg(cs, 1, ISAR_MBOX, msg[0]); + for (i = 1; i < len; i++) + cs->BC_Write_Reg(cs, 2, ISAR_MBOX, msg[i]); +#if DUMP_MBOXFRAME > 1 + if (cs->debug & L1_DEB_HSCX_FIFO) { + char tmp[256], *t; + + i = len; + while (i > 0) { + t = tmp; + t += sprintf(t, "sendmbox cnt %d", len); + QuickHex(t, &msg[len-i], (i > 64) ? 64 : i); + debugl1(cs, "%s", tmp); + i -= 64; + } + } +#endif + } + cs->BC_Write_Reg(cs, 1, ISAR_HIS, his); + waitforHIA(cs, 10000); + return (1); +} + +/* Call only with IRQ disabled !!! */ +static inline void +rcv_mbox(struct IsdnCardState *cs, struct isar_reg *ireg, u_char *msg) +{ + int i; + + cs->BC_Write_Reg(cs, 1, ISAR_RADR, 0); + if (msg && ireg->clsb) { + msg[0] = cs->BC_Read_Reg(cs, 1, ISAR_MBOX); + for (i = 1; i < ireg->clsb; i++) + msg[i] = cs->BC_Read_Reg(cs, 2, ISAR_MBOX); +#if DUMP_MBOXFRAME > 1 + if (cs->debug & L1_DEB_HSCX_FIFO) { + char tmp[256], *t; + + i = ireg->clsb; + while (i > 0) { + t = tmp; + t += sprintf(t, "rcv_mbox cnt %d", ireg->clsb); + QuickHex(t, &msg[ireg->clsb - i], (i > 64) ? 64 : i); + debugl1(cs, "%s", tmp); + i -= 64; + } + } +#endif + } + cs->BC_Write_Reg(cs, 1, ISAR_IIA, 0); +} + +/* Call only with IRQ disabled !!! */ +static inline void +get_irq_infos(struct IsdnCardState *cs, struct isar_reg *ireg) +{ + ireg->iis = cs->BC_Read_Reg(cs, 1, ISAR_IIS); + ireg->cmsb = cs->BC_Read_Reg(cs, 1, ISAR_CTRL_H); + ireg->clsb = cs->BC_Read_Reg(cs, 1, ISAR_CTRL_L); +#if DUMP_MBOXFRAME + if (cs->debug & L1_DEB_HSCX) + debugl1(cs, "irq_stat(%02x,%02x,%d)", ireg->iis, ireg->cmsb, + ireg->clsb); +#endif +} + +static int +waitrecmsg(struct IsdnCardState *cs, u_char *len, + u_char *msg, int maxdelay) +{ + int timeout = 0; + struct isar_reg *ir = cs->bcs[0].hw.isar.reg; + + + while ((!(cs->BC_Read_Reg(cs, 0, ISAR_IRQBIT) & ISAR_IRQSTA)) && + (timeout++ < maxdelay)) + udelay(1); + if (timeout > maxdelay) { + printk(KERN_WARNING"isar recmsg IRQSTA timeout\n"); + return (0); + } + get_irq_infos(cs, ir); + rcv_mbox(cs, ir, msg); + *len = ir->clsb; + return (1); +} + +int +ISARVersion(struct IsdnCardState *cs, char *s) +{ + int ver; + u_char msg[] = ISAR_MSG_HWVER; + u_char tmp[64]; + u_char len; + u_long flags; + int debug; + + cs->cardmsg(cs, CARD_RESET, NULL); + spin_lock_irqsave(&cs->lock, flags); + /* disable ISAR IRQ */ + cs->BC_Write_Reg(cs, 0, ISAR_IRQBIT, 0); + debug = cs->debug; + cs->debug &= ~(L1_DEB_HSCX | L1_DEB_HSCX_FIFO); + if (!sendmsg(cs, ISAR_HIS_VNR, 0, 3, msg)) { + spin_unlock_irqrestore(&cs->lock, flags); + return (-1); + } + if (!waitrecmsg(cs, &len, tmp, 100000)) { + spin_unlock_irqrestore(&cs->lock, flags); + return (-2); + } + cs->debug = debug; + if (cs->bcs[0].hw.isar.reg->iis == ISAR_IIS_VNR) { + if (len == 1) { + ver = tmp[0] & 0xf; + printk(KERN_INFO "%s ISAR version %d\n", s, ver); + } else + ver = -3; + } else + ver = -4; + spin_unlock_irqrestore(&cs->lock, flags); + return (ver); +} + +static int +isar_load_firmware(struct IsdnCardState *cs, u_char __user *buf) +{ + int cfu_ret, ret, size, cnt, debug; + u_char len, nom, noc; + u_short sadr, left, *sp; + u_char __user *p = buf; + u_char *msg, *tmpmsg, *mp, tmp[64]; + u_long flags; + struct isar_reg *ireg = cs->bcs[0].hw.isar.reg; + + struct {u_short sadr; + u_short len; + u_short d_key; + } blk_head; + +#define BLK_HEAD_SIZE 6 + if (1 != (ret = ISARVersion(cs, "Testing"))) { + printk(KERN_ERR"isar_load_firmware wrong isar version %d\n", ret); + return (1); + } + debug = cs->debug; +#if DBG_LOADFIRM < 2 + cs->debug &= ~(L1_DEB_HSCX | L1_DEB_HSCX_FIFO); +#endif + + cfu_ret = copy_from_user(&size, p, sizeof(int)); + if (cfu_ret) { + printk(KERN_ERR "isar_load_firmware copy_from_user ret %d\n", cfu_ret); + return -EFAULT; + } + p += sizeof(int); + printk(KERN_DEBUG"isar_load_firmware size: %d\n", size); + cnt = 0; + /* disable ISAR IRQ */ + cs->BC_Write_Reg(cs, 0, ISAR_IRQBIT, 0); + if (!(msg = kmalloc(256, GFP_KERNEL))) { + printk(KERN_ERR"isar_load_firmware no buffer\n"); + return (1); + } + if (!(tmpmsg = kmalloc(256, GFP_KERNEL))) { + printk(KERN_ERR"isar_load_firmware no tmp buffer\n"); + kfree(msg); + return (1); + } + spin_lock_irqsave(&cs->lock, flags); + /* disable ISAR IRQ */ + cs->BC_Write_Reg(cs, 0, ISAR_IRQBIT, 0); + spin_unlock_irqrestore(&cs->lock, flags); + while (cnt < size) { + if ((ret = copy_from_user(&blk_head, p, BLK_HEAD_SIZE))) { + printk(KERN_ERR"isar_load_firmware copy_from_user ret %d\n", ret); + goto reterror; + } +#ifdef __BIG_ENDIAN + sadr = (blk_head.sadr & 0xff) * 256 + blk_head.sadr / 256; + blk_head.sadr = sadr; + sadr = (blk_head.len & 0xff) * 256 + blk_head.len / 256; + blk_head.len = sadr; + sadr = (blk_head.d_key & 0xff) * 256 + blk_head.d_key / 256; + blk_head.d_key = sadr; +#endif /* __BIG_ENDIAN */ + cnt += BLK_HEAD_SIZE; + p += BLK_HEAD_SIZE; + printk(KERN_DEBUG"isar firmware block (%#x,%5d,%#x)\n", + blk_head.sadr, blk_head.len, blk_head.d_key & 0xff); + sadr = blk_head.sadr; + left = blk_head.len; + spin_lock_irqsave(&cs->lock, flags); + if (!sendmsg(cs, ISAR_HIS_DKEY, blk_head.d_key & 0xff, 0, NULL)) { + printk(KERN_ERR"isar sendmsg dkey failed\n"); + ret = 1; goto reterr_unlock; + } + if (!waitrecmsg(cs, &len, tmp, 100000)) { + printk(KERN_ERR"isar waitrecmsg dkey failed\n"); + ret = 1; goto reterr_unlock; + } + if ((ireg->iis != ISAR_IIS_DKEY) || ireg->cmsb || len) { + printk(KERN_ERR"isar wrong dkey response (%x,%x,%x)\n", + ireg->iis, ireg->cmsb, len); + ret = 1; goto reterr_unlock; + } + spin_unlock_irqrestore(&cs->lock, flags); + while (left > 0) { + if (left > 126) + noc = 126; + else + noc = left; + nom = 2 * noc; + mp = msg; + *mp++ = sadr / 256; + *mp++ = sadr % 256; + left -= noc; + *mp++ = noc; + if ((ret = copy_from_user(tmpmsg, p, nom))) { + printk(KERN_ERR"isar_load_firmware copy_from_user ret %d\n", ret); + goto reterror; + } + p += nom; + cnt += nom; + nom += 3; + sp = (u_short *)tmpmsg; +#if DBG_LOADFIRM + printk(KERN_DEBUG"isar: load %3d words at %04x left %d\n", + noc, sadr, left); +#endif + sadr += noc; + while (noc) { +#ifdef __BIG_ENDIAN + *mp++ = *sp % 256; + *mp++ = *sp / 256; +#else + *mp++ = *sp / 256; + *mp++ = *sp % 256; +#endif /* __BIG_ENDIAN */ + sp++; + noc--; + } + spin_lock_irqsave(&cs->lock, flags); + if (!sendmsg(cs, ISAR_HIS_FIRM, 0, nom, msg)) { + printk(KERN_ERR"isar sendmsg prog failed\n"); + ret = 1; goto reterr_unlock; + } + if (!waitrecmsg(cs, &len, tmp, 100000)) { + printk(KERN_ERR"isar waitrecmsg prog failed\n"); + ret = 1; goto reterr_unlock; + } + if ((ireg->iis != ISAR_IIS_FIRM) || ireg->cmsb || len) { + printk(KERN_ERR"isar wrong prog response (%x,%x,%x)\n", + ireg->iis, ireg->cmsb, len); + ret = 1; goto reterr_unlock; + } + spin_unlock_irqrestore(&cs->lock, flags); + } + printk(KERN_DEBUG"isar firmware block %5d words loaded\n", + blk_head.len); + } + /* 10ms delay */ + cnt = 10; + while (cnt--) + udelay(1000); + msg[0] = 0xff; + msg[1] = 0xfe; + ireg->bstat = 0; + spin_lock_irqsave(&cs->lock, flags); + if (!sendmsg(cs, ISAR_HIS_STDSP, 0, 2, msg)) { + printk(KERN_ERR"isar sendmsg start dsp failed\n"); + ret = 1; goto reterr_unlock; + } + if (!waitrecmsg(cs, &len, tmp, 100000)) { + printk(KERN_ERR"isar waitrecmsg start dsp failed\n"); + ret = 1; goto reterr_unlock; + } + if ((ireg->iis != ISAR_IIS_STDSP) || ireg->cmsb || len) { + printk(KERN_ERR"isar wrong start dsp response (%x,%x,%x)\n", + ireg->iis, ireg->cmsb, len); + ret = 1; goto reterr_unlock; + } else + printk(KERN_DEBUG"isar start dsp success\n"); + /* NORMAL mode entered */ + /* Enable IRQs of ISAR */ + cs->BC_Write_Reg(cs, 0, ISAR_IRQBIT, ISAR_IRQSTA); + spin_unlock_irqrestore(&cs->lock, flags); + cnt = 1000; /* max 1s */ + while ((!ireg->bstat) && cnt) { + udelay(1000); + cnt--; + } + if (!cnt) { + printk(KERN_ERR"isar no general status event received\n"); + ret = 1; goto reterror; + } else { + printk(KERN_DEBUG"isar general status event %x\n", + ireg->bstat); + } + /* 10ms delay */ + cnt = 10; + while (cnt--) + udelay(1000); + spin_lock_irqsave(&cs->lock, flags); + ireg->iis = 0; + if (!sendmsg(cs, ISAR_HIS_DIAG, ISAR_CTRL_STST, 0, NULL)) { + printk(KERN_ERR"isar sendmsg self tst failed\n"); + ret = 1; goto reterr_unlock; + } + cnt = 10000; /* max 100 ms */ + spin_unlock_irqrestore(&cs->lock, flags); + while ((ireg->iis != ISAR_IIS_DIAG) && cnt) { + udelay(10); + cnt--; + } + udelay(1000); + if (!cnt) { + printk(KERN_ERR"isar no self tst response\n"); + ret = 1; goto reterror; + } + if ((ireg->cmsb == ISAR_CTRL_STST) && (ireg->clsb == 1) + && (ireg->par[0] == 0)) { + printk(KERN_DEBUG"isar selftest OK\n"); + } else { + printk(KERN_DEBUG"isar selftest not OK %x/%x/%x\n", + ireg->cmsb, ireg->clsb, ireg->par[0]); + ret = 1; goto reterror; + } + spin_lock_irqsave(&cs->lock, flags); + ireg->iis = 0; + if (!sendmsg(cs, ISAR_HIS_DIAG, ISAR_CTRL_SWVER, 0, NULL)) { + printk(KERN_ERR"isar RQST SVN failed\n"); + ret = 1; goto reterr_unlock; + } + spin_unlock_irqrestore(&cs->lock, flags); + cnt = 30000; /* max 300 ms */ + while ((ireg->iis != ISAR_IIS_DIAG) && cnt) { + udelay(10); + cnt--; + } + udelay(1000); + if (!cnt) { + printk(KERN_ERR"isar no SVN response\n"); + ret = 1; goto reterror; + } else { + if ((ireg->cmsb == ISAR_CTRL_SWVER) && (ireg->clsb == 1)) + printk(KERN_DEBUG"isar software version %#x\n", + ireg->par[0]); + else { + printk(KERN_ERR"isar wrong swver response (%x,%x) cnt(%d)\n", + ireg->cmsb, ireg->clsb, cnt); + ret = 1; goto reterror; + } + } + spin_lock_irqsave(&cs->lock, flags); + cs->debug = debug; + isar_setup(cs); + + ret = 0; +reterr_unlock: + spin_unlock_irqrestore(&cs->lock, flags); +reterror: + cs->debug = debug; + if (ret) + /* disable ISAR IRQ */ + cs->BC_Write_Reg(cs, 0, ISAR_IRQBIT, 0); + kfree(msg); + kfree(tmpmsg); + return (ret); +} + +#define B_LL_NOCARRIER 8 +#define B_LL_CONNECT 9 +#define B_LL_OK 10 + +static void +isar_bh(struct work_struct *work) +{ + struct BCState *bcs = container_of(work, struct BCState, tqueue); + + BChannel_bh(work); + if (test_and_clear_bit(B_LL_NOCARRIER, &bcs->event)) + ll_deliver_faxstat(bcs, ISDN_FAX_CLASS1_NOCARR); + if (test_and_clear_bit(B_LL_CONNECT, &bcs->event)) + ll_deliver_faxstat(bcs, ISDN_FAX_CLASS1_CONNECT); + if (test_and_clear_bit(B_LL_OK, &bcs->event)) + ll_deliver_faxstat(bcs, ISDN_FAX_CLASS1_OK); +} + +static void +send_DLE_ETX(struct BCState *bcs) +{ + u_char dleetx[2] = {DLE, ETX}; + struct sk_buff *skb; + + if ((skb = dev_alloc_skb(2))) { + skb_put_data(skb, dleetx, 2); + skb_queue_tail(&bcs->rqueue, skb); + schedule_event(bcs, B_RCVBUFREADY); + } else { + printk(KERN_WARNING "HiSax: skb out of memory\n"); + } +} + +static inline int +dle_count(unsigned char *buf, int len) +{ + int count = 0; + + while (len--) + if (*buf++ == DLE) + count++; + return count; +} + +static inline void +insert_dle(unsigned char *dest, unsigned char *src, int count) { + /* <DLE> in input stream have to be flagged as <DLE><DLE> */ + while (count--) { + *dest++ = *src; + if (*src++ == DLE) + *dest++ = DLE; + } +} + +static void +isar_rcv_frame(struct IsdnCardState *cs, struct BCState *bcs) +{ + u_char *ptr; + struct sk_buff *skb; + struct isar_reg *ireg = bcs->hw.isar.reg; + + if (!ireg->clsb) { + debugl1(cs, "isar zero len frame"); + cs->BC_Write_Reg(cs, 1, ISAR_IIA, 0); + return; + } + switch (bcs->mode) { + case L1_MODE_NULL: + debugl1(cs, "isar mode 0 spurious IIS_RDATA %x/%x/%x", + ireg->iis, ireg->cmsb, ireg->clsb); + printk(KERN_WARNING"isar mode 0 spurious IIS_RDATA %x/%x/%x\n", + ireg->iis, ireg->cmsb, ireg->clsb); + cs->BC_Write_Reg(cs, 1, ISAR_IIA, 0); + break; + case L1_MODE_TRANS: + case L1_MODE_V32: + if ((skb = dev_alloc_skb(ireg->clsb))) { + rcv_mbox(cs, ireg, (u_char *)skb_put(skb, ireg->clsb)); + skb_queue_tail(&bcs->rqueue, skb); + schedule_event(bcs, B_RCVBUFREADY); + } else { + printk(KERN_WARNING "HiSax: skb out of memory\n"); + cs->BC_Write_Reg(cs, 1, ISAR_IIA, 0); + } + break; + case L1_MODE_HDLC: + if ((bcs->hw.isar.rcvidx + ireg->clsb) > HSCX_BUFMAX) { + if (cs->debug & L1_DEB_WARN) + debugl1(cs, "isar_rcv_frame: incoming packet too large"); + cs->BC_Write_Reg(cs, 1, ISAR_IIA, 0); + bcs->hw.isar.rcvidx = 0; + } else if (ireg->cmsb & HDLC_ERROR) { + if (cs->debug & L1_DEB_WARN) + debugl1(cs, "isar frame error %x len %d", + ireg->cmsb, ireg->clsb); +#ifdef ERROR_STATISTIC + if (ireg->cmsb & HDLC_ERR_RER) + bcs->err_inv++; + if (ireg->cmsb & HDLC_ERR_CER) + bcs->err_crc++; +#endif + bcs->hw.isar.rcvidx = 0; + cs->BC_Write_Reg(cs, 1, ISAR_IIA, 0); + } else { + if (ireg->cmsb & HDLC_FSD) + bcs->hw.isar.rcvidx = 0; + ptr = bcs->hw.isar.rcvbuf + bcs->hw.isar.rcvidx; + bcs->hw.isar.rcvidx += ireg->clsb; + rcv_mbox(cs, ireg, ptr); + if (ireg->cmsb & HDLC_FED) { + if (bcs->hw.isar.rcvidx < 3) { /* last 2 bytes are the FCS */ + if (cs->debug & L1_DEB_WARN) + debugl1(cs, "isar frame to short %d", + bcs->hw.isar.rcvidx); + } else if (!(skb = dev_alloc_skb(bcs->hw.isar.rcvidx - 2))) { + printk(KERN_WARNING "ISAR: receive out of memory\n"); + } else { + skb_put_data(skb, bcs->hw.isar.rcvbuf, + bcs->hw.isar.rcvidx - 2); + skb_queue_tail(&bcs->rqueue, skb); + schedule_event(bcs, B_RCVBUFREADY); + } + bcs->hw.isar.rcvidx = 0; + } + } + break; + case L1_MODE_FAX: + if (bcs->hw.isar.state != STFAX_ACTIV) { + if (cs->debug & L1_DEB_WARN) + debugl1(cs, "isar_rcv_frame: not ACTIV"); + cs->BC_Write_Reg(cs, 1, ISAR_IIA, 0); + bcs->hw.isar.rcvidx = 0; + break; + } + if (bcs->hw.isar.cmd == PCTRL_CMD_FRM) { + rcv_mbox(cs, ireg, bcs->hw.isar.rcvbuf); + bcs->hw.isar.rcvidx = ireg->clsb + + dle_count(bcs->hw.isar.rcvbuf, ireg->clsb); + if (cs->debug & L1_DEB_HSCX) + debugl1(cs, "isar_rcv_frame: raw(%d) dle(%d)", + ireg->clsb, bcs->hw.isar.rcvidx); + if ((skb = dev_alloc_skb(bcs->hw.isar.rcvidx))) { + insert_dle((u_char *)skb_put(skb, bcs->hw.isar.rcvidx), + bcs->hw.isar.rcvbuf, ireg->clsb); + skb_queue_tail(&bcs->rqueue, skb); + schedule_event(bcs, B_RCVBUFREADY); + if (ireg->cmsb & SART_NMD) { /* ABORT */ + if (cs->debug & L1_DEB_WARN) + debugl1(cs, "isar_rcv_frame: no more data"); + bcs->hw.isar.rcvidx = 0; + send_DLE_ETX(bcs); + sendmsg(cs, SET_DPS(bcs->hw.isar.dpath) | + ISAR_HIS_PUMPCTRL, PCTRL_CMD_ESC, + 0, NULL); + bcs->hw.isar.state = STFAX_ESCAPE; + schedule_event(bcs, B_LL_NOCARRIER); + } + } else { + printk(KERN_WARNING "HiSax: skb out of memory\n"); + } + break; + } + if (bcs->hw.isar.cmd != PCTRL_CMD_FRH) { + if (cs->debug & L1_DEB_WARN) + debugl1(cs, "isar_rcv_frame: unknown fax mode %x", + bcs->hw.isar.cmd); + cs->BC_Write_Reg(cs, 1, ISAR_IIA, 0); + bcs->hw.isar.rcvidx = 0; + break; + } + /* PCTRL_CMD_FRH */ + if ((bcs->hw.isar.rcvidx + ireg->clsb) > HSCX_BUFMAX) { + if (cs->debug & L1_DEB_WARN) + debugl1(cs, "isar_rcv_frame: incoming packet too large"); + cs->BC_Write_Reg(cs, 1, ISAR_IIA, 0); + bcs->hw.isar.rcvidx = 0; + } else if (ireg->cmsb & HDLC_ERROR) { + if (cs->debug & L1_DEB_WARN) + debugl1(cs, "isar frame error %x len %d", + ireg->cmsb, ireg->clsb); + bcs->hw.isar.rcvidx = 0; + cs->BC_Write_Reg(cs, 1, ISAR_IIA, 0); + } else { + if (ireg->cmsb & HDLC_FSD) { + bcs->hw.isar.rcvidx = 0; + } + ptr = bcs->hw.isar.rcvbuf + bcs->hw.isar.rcvidx; + bcs->hw.isar.rcvidx += ireg->clsb; + rcv_mbox(cs, ireg, ptr); + if (ireg->cmsb & HDLC_FED) { + int len = bcs->hw.isar.rcvidx + + dle_count(bcs->hw.isar.rcvbuf, bcs->hw.isar.rcvidx); + if (bcs->hw.isar.rcvidx < 3) { /* last 2 bytes are the FCS */ + if (cs->debug & L1_DEB_WARN) + debugl1(cs, "isar frame to short %d", + bcs->hw.isar.rcvidx); + printk(KERN_WARNING "ISAR: frame to short %d\n", + bcs->hw.isar.rcvidx); + } else if (!(skb = dev_alloc_skb(len))) { + printk(KERN_WARNING "ISAR: receive out of memory\n"); + } else { + insert_dle((u_char *)skb_put(skb, len), + bcs->hw.isar.rcvbuf, + bcs->hw.isar.rcvidx); + skb_queue_tail(&bcs->rqueue, skb); + schedule_event(bcs, B_RCVBUFREADY); + send_DLE_ETX(bcs); + schedule_event(bcs, B_LL_OK); + test_and_clear_bit(BC_FLG_FRH_WAIT, &bcs->Flag); + } + bcs->hw.isar.rcvidx = 0; + } + } + if (ireg->cmsb & SART_NMD) { /* ABORT */ + if (cs->debug & L1_DEB_WARN) + debugl1(cs, "isar_rcv_frame: no more data"); + bcs->hw.isar.rcvidx = 0; + sendmsg(cs, SET_DPS(bcs->hw.isar.dpath) | + ISAR_HIS_PUMPCTRL, PCTRL_CMD_ESC, 0, NULL); + bcs->hw.isar.state = STFAX_ESCAPE; + if (test_and_clear_bit(BC_FLG_FRH_WAIT, &bcs->Flag)) { + send_DLE_ETX(bcs); + schedule_event(bcs, B_LL_NOCARRIER); + } + } + break; + default: + printk(KERN_ERR"isar_rcv_frame mode (%x)error\n", bcs->mode); + cs->BC_Write_Reg(cs, 1, ISAR_IIA, 0); + break; + } +} + +void +isar_fill_fifo(struct BCState *bcs) +{ + struct IsdnCardState *cs = bcs->cs; + int count; + u_char msb; + u_char *ptr; + + if ((cs->debug & L1_DEB_HSCX) && !(cs->debug & L1_DEB_HSCX_FIFO)) + debugl1(cs, "isar_fill_fifo"); + if (!bcs->tx_skb) + return; + if (bcs->tx_skb->len <= 0) + return; + if (!(bcs->hw.isar.reg->bstat & + (bcs->hw.isar.dpath == 1 ? BSTAT_RDM1 : BSTAT_RDM2))) + return; + if (bcs->tx_skb->len > bcs->hw.isar.mml) { + msb = 0; + count = bcs->hw.isar.mml; + } else { + count = bcs->tx_skb->len; + msb = HDLC_FED; + } + ptr = bcs->tx_skb->data; + if (!bcs->hw.isar.txcnt) { + msb |= HDLC_FST; + if ((bcs->mode == L1_MODE_FAX) && + (bcs->hw.isar.cmd == PCTRL_CMD_FTH)) { + if (bcs->tx_skb->len > 1) { + if ((ptr[0] == 0xff) && (ptr[1] == 0x13)) + /* last frame */ + test_and_set_bit(BC_FLG_LASTDATA, + &bcs->Flag); + } + } + } + skb_pull(bcs->tx_skb, count); + bcs->tx_cnt -= count; + bcs->hw.isar.txcnt += count; + switch (bcs->mode) { + case L1_MODE_NULL: + printk(KERN_ERR"isar_fill_fifo wrong mode 0\n"); + break; + case L1_MODE_TRANS: + case L1_MODE_V32: + sendmsg(cs, SET_DPS(bcs->hw.isar.dpath) | ISAR_HIS_SDATA, + 0, count, ptr); + break; + case L1_MODE_HDLC: + sendmsg(cs, SET_DPS(bcs->hw.isar.dpath) | ISAR_HIS_SDATA, + msb, count, ptr); + break; + case L1_MODE_FAX: + if (bcs->hw.isar.state != STFAX_ACTIV) { + if (cs->debug & L1_DEB_WARN) + debugl1(cs, "isar_fill_fifo: not ACTIV"); + } else if (bcs->hw.isar.cmd == PCTRL_CMD_FTH) { + sendmsg(cs, SET_DPS(bcs->hw.isar.dpath) | ISAR_HIS_SDATA, + msb, count, ptr); + } else if (bcs->hw.isar.cmd == PCTRL_CMD_FTM) { + sendmsg(cs, SET_DPS(bcs->hw.isar.dpath) | ISAR_HIS_SDATA, + 0, count, ptr); + } else { + if (cs->debug & L1_DEB_WARN) + debugl1(cs, "isar_fill_fifo: not FTH/FTM"); + } + break; + default: + if (cs->debug) + debugl1(cs, "isar_fill_fifo mode(%x) error", bcs->mode); + printk(KERN_ERR"isar_fill_fifo mode(%x) error\n", bcs->mode); + break; + } +} + +static inline +struct BCState *sel_bcs_isar(struct IsdnCardState *cs, u_char dpath) +{ + if ((!dpath) || (dpath == 3)) + return (NULL); + if (cs->bcs[0].hw.isar.dpath == dpath) + return (&cs->bcs[0]); + if (cs->bcs[1].hw.isar.dpath == dpath) + return (&cs->bcs[1]); + return (NULL); +} + +static void +send_frames(struct BCState *bcs) +{ + if (bcs->tx_skb) { + if (bcs->tx_skb->len) { + isar_fill_fifo(bcs); + return; + } else { + if (test_bit(FLG_LLI_L1WAKEUP, &bcs->st->lli.flag) && + (PACKET_NOACK != bcs->tx_skb->pkt_type)) { + u_long flags; + spin_lock_irqsave(&bcs->aclock, flags); + bcs->ackcnt += bcs->hw.isar.txcnt; + spin_unlock_irqrestore(&bcs->aclock, flags); + schedule_event(bcs, B_ACKPENDING); + } + if (bcs->mode == L1_MODE_FAX) { + if (bcs->hw.isar.cmd == PCTRL_CMD_FTH) { + if (test_bit(BC_FLG_LASTDATA, &bcs->Flag)) { + test_and_set_bit(BC_FLG_NMD_DATA, &bcs->Flag); + } + } else if (bcs->hw.isar.cmd == PCTRL_CMD_FTM) { + if (test_bit(BC_FLG_DLEETX, &bcs->Flag)) { + test_and_set_bit(BC_FLG_LASTDATA, &bcs->Flag); + test_and_set_bit(BC_FLG_NMD_DATA, &bcs->Flag); + } + } + } + dev_kfree_skb_any(bcs->tx_skb); + bcs->hw.isar.txcnt = 0; + bcs->tx_skb = NULL; + } + } + if ((bcs->tx_skb = skb_dequeue(&bcs->squeue))) { + bcs->hw.isar.txcnt = 0; + test_and_set_bit(BC_FLG_BUSY, &bcs->Flag); + isar_fill_fifo(bcs); + } else { + if (test_and_clear_bit(BC_FLG_DLEETX, &bcs->Flag)) { + if (test_and_clear_bit(BC_FLG_LASTDATA, &bcs->Flag)) { + if (test_and_clear_bit(BC_FLG_NMD_DATA, &bcs->Flag)) { + u_char dummy = 0; + sendmsg(bcs->cs, SET_DPS(bcs->hw.isar.dpath) | + ISAR_HIS_SDATA, 0x01, 1, &dummy); + } + test_and_set_bit(BC_FLG_LL_OK, &bcs->Flag); + } else { + schedule_event(bcs, B_LL_CONNECT); + } + } + test_and_clear_bit(BC_FLG_BUSY, &bcs->Flag); + schedule_event(bcs, B_XMTBUFREADY); + } +} + +static inline void +check_send(struct IsdnCardState *cs, u_char rdm) +{ + struct BCState *bcs; + + if (rdm & BSTAT_RDM1) { + if ((bcs = sel_bcs_isar(cs, 1))) { + if (bcs->mode) { + send_frames(bcs); + } + } + } + if (rdm & BSTAT_RDM2) { + if ((bcs = sel_bcs_isar(cs, 2))) { + if (bcs->mode) { + send_frames(bcs); + } + } + } + +} + +static const char *dmril[] = {"NO SPEED", "1200/75", "NODEF2", "75/1200", + "NODEF4", "300", "600", "1200", "2400", + "4800", "7200", "9600nt", "9600t", "12000", + "14400", "WRONG"}; +static const char *dmrim[] = {"NO MOD", "NO DEF", "V32/V32b", "V22", "V21", + "Bell103", "V23", "Bell202", "V17", "V29", + "V27ter"}; + +static void +isar_pump_status_rsp(struct BCState *bcs, struct isar_reg *ireg) { + struct IsdnCardState *cs = bcs->cs; + u_char ril = ireg->par[0]; + u_char rim; + + if (!test_and_clear_bit(ISAR_RATE_REQ, &bcs->hw.isar.reg->Flags)) + return; + if (ril > 14) { + if (cs->debug & L1_DEB_WARN) + debugl1(cs, "wrong pstrsp ril=%d", ril); + ril = 15; + } + switch (ireg->par[1]) { + case 0: + rim = 0; + break; + case 0x20: + rim = 2; + break; + case 0x40: + rim = 3; + break; + case 0x41: + rim = 4; + break; + case 0x51: + rim = 5; + break; + case 0x61: + rim = 6; + break; + case 0x71: + rim = 7; + break; + case 0x82: + rim = 8; + break; + case 0x92: + rim = 9; + break; + case 0xa2: + rim = 10; + break; + default: + rim = 1; + break; + } + sprintf(bcs->hw.isar.conmsg, "%s %s", dmril[ril], dmrim[rim]); + bcs->conmsg = bcs->hw.isar.conmsg; + if (cs->debug & L1_DEB_HSCX) + debugl1(cs, "pump strsp %s", bcs->conmsg); +} + +static void +isar_pump_statev_modem(struct BCState *bcs, u_char devt) { + struct IsdnCardState *cs = bcs->cs; + u_char dps = SET_DPS(bcs->hw.isar.dpath); + + switch (devt) { + case PSEV_10MS_TIMER: + if (cs->debug & L1_DEB_HSCX) + debugl1(cs, "pump stev TIMER"); + break; + case PSEV_CON_ON: + if (cs->debug & L1_DEB_HSCX) + debugl1(cs, "pump stev CONNECT"); + l1_msg_b(bcs->st, PH_ACTIVATE | REQUEST, NULL); + break; + case PSEV_CON_OFF: + if (cs->debug & L1_DEB_HSCX) + debugl1(cs, "pump stev NO CONNECT"); + sendmsg(cs, dps | ISAR_HIS_PSTREQ, 0, 0, NULL); + l1_msg_b(bcs->st, PH_DEACTIVATE | REQUEST, NULL); + break; + case PSEV_V24_OFF: + if (cs->debug & L1_DEB_HSCX) + debugl1(cs, "pump stev V24 OFF"); + break; + case PSEV_CTS_ON: + if (cs->debug & L1_DEB_HSCX) + debugl1(cs, "pump stev CTS ON"); + break; + case PSEV_CTS_OFF: + if (cs->debug & L1_DEB_HSCX) + debugl1(cs, "pump stev CTS OFF"); + break; + case PSEV_DCD_ON: + if (cs->debug & L1_DEB_HSCX) + debugl1(cs, "pump stev CARRIER ON"); + test_and_set_bit(ISAR_RATE_REQ, &bcs->hw.isar.reg->Flags); + sendmsg(cs, dps | ISAR_HIS_PSTREQ, 0, 0, NULL); + break; + case PSEV_DCD_OFF: + if (cs->debug & L1_DEB_HSCX) + debugl1(cs, "pump stev CARRIER OFF"); + break; + case PSEV_DSR_ON: + if (cs->debug & L1_DEB_HSCX) + debugl1(cs, "pump stev DSR ON"); + break; + case PSEV_DSR_OFF: + if (cs->debug & L1_DEB_HSCX) + debugl1(cs, "pump stev DSR_OFF"); + break; + case PSEV_REM_RET: + if (cs->debug & L1_DEB_HSCX) + debugl1(cs, "pump stev REMOTE RETRAIN"); + break; + case PSEV_REM_REN: + if (cs->debug & L1_DEB_HSCX) + debugl1(cs, "pump stev REMOTE RENEGOTIATE"); + break; + case PSEV_GSTN_CLR: + if (cs->debug & L1_DEB_HSCX) + debugl1(cs, "pump stev GSTN CLEAR"); + break; + default: + if (cs->debug & L1_DEB_HSCX) + debugl1(cs, "unknown pump stev %x", devt); + break; + } +} + +static void +ll_deliver_faxstat(struct BCState *bcs, u_char status) +{ + isdn_ctrl ic; + struct Channel *chanp = (struct Channel *) bcs->st->lli.userdata; + + if (bcs->cs->debug & L1_DEB_HSCX) + debugl1(bcs->cs, "HL->LL FAXIND %x", status); + ic.driver = bcs->cs->myid; + ic.command = ISDN_STAT_FAXIND; + ic.arg = chanp->chan; + ic.parm.aux.cmd = status; + bcs->cs->iif.statcallb(&ic); +} + +static void +isar_pump_statev_fax(struct BCState *bcs, u_char devt) { + struct IsdnCardState *cs = bcs->cs; + u_char dps = SET_DPS(bcs->hw.isar.dpath); + u_char p1; + + switch (devt) { + case PSEV_10MS_TIMER: + if (cs->debug & L1_DEB_HSCX) + debugl1(cs, "pump stev TIMER"); + break; + case PSEV_RSP_READY: + if (cs->debug & L1_DEB_HSCX) + debugl1(cs, "pump stev RSP_READY"); + bcs->hw.isar.state = STFAX_READY; + l1_msg_b(bcs->st, PH_ACTIVATE | REQUEST, NULL); + if (test_bit(BC_FLG_ORIG, &bcs->Flag)) { + isar_pump_cmd(bcs, ISDN_FAX_CLASS1_FRH, 3); + } else { + isar_pump_cmd(bcs, ISDN_FAX_CLASS1_FTH, 3); + } + break; + case PSEV_LINE_TX_H: + if (bcs->hw.isar.state == STFAX_LINE) { + if (cs->debug & L1_DEB_HSCX) + debugl1(cs, "pump stev LINE_TX_H"); + bcs->hw.isar.state = STFAX_CONT; + sendmsg(cs, dps | ISAR_HIS_PUMPCTRL, PCTRL_CMD_CONT, 0, NULL); + } else { + if (cs->debug & L1_DEB_WARN) + debugl1(cs, "pump stev LINE_TX_H wrong st %x", + bcs->hw.isar.state); + } + break; + case PSEV_LINE_RX_H: + if (bcs->hw.isar.state == STFAX_LINE) { + if (cs->debug & L1_DEB_HSCX) + debugl1(cs, "pump stev LINE_RX_H"); + bcs->hw.isar.state = STFAX_CONT; + sendmsg(cs, dps | ISAR_HIS_PUMPCTRL, PCTRL_CMD_CONT, 0, NULL); + } else { + if (cs->debug & L1_DEB_WARN) + debugl1(cs, "pump stev LINE_RX_H wrong st %x", + bcs->hw.isar.state); + } + break; + case PSEV_LINE_TX_B: + if (bcs->hw.isar.state == STFAX_LINE) { + if (cs->debug & L1_DEB_HSCX) + debugl1(cs, "pump stev LINE_TX_B"); + bcs->hw.isar.state = STFAX_CONT; + sendmsg(cs, dps | ISAR_HIS_PUMPCTRL, PCTRL_CMD_CONT, 0, NULL); + } else { + if (cs->debug & L1_DEB_WARN) + debugl1(cs, "pump stev LINE_TX_B wrong st %x", + bcs->hw.isar.state); + } + break; + case PSEV_LINE_RX_B: + if (bcs->hw.isar.state == STFAX_LINE) { + if (cs->debug & L1_DEB_HSCX) + debugl1(cs, "pump stev LINE_RX_B"); + bcs->hw.isar.state = STFAX_CONT; + sendmsg(cs, dps | ISAR_HIS_PUMPCTRL, PCTRL_CMD_CONT, 0, NULL); + } else { + if (cs->debug & L1_DEB_WARN) + debugl1(cs, "pump stev LINE_RX_B wrong st %x", + bcs->hw.isar.state); + } + break; + case PSEV_RSP_CONN: + if (bcs->hw.isar.state == STFAX_CONT) { + if (cs->debug & L1_DEB_HSCX) + debugl1(cs, "pump stev RSP_CONN"); + bcs->hw.isar.state = STFAX_ACTIV; + test_and_set_bit(ISAR_RATE_REQ, &bcs->hw.isar.reg->Flags); + sendmsg(cs, dps | ISAR_HIS_PSTREQ, 0, 0, NULL); + if (bcs->hw.isar.cmd == PCTRL_CMD_FTH) { + /* 1s Flags before data */ + if (test_and_set_bit(BC_FLG_FTI_RUN, &bcs->Flag)) + del_timer(&bcs->hw.isar.ftimer); + /* 1000 ms */ + bcs->hw.isar.ftimer.expires = + jiffies + ((1000 * HZ) / 1000); + test_and_set_bit(BC_FLG_LL_CONN, + &bcs->Flag); + add_timer(&bcs->hw.isar.ftimer); + } else { + schedule_event(bcs, B_LL_CONNECT); + } + } else { + if (cs->debug & L1_DEB_WARN) + debugl1(cs, "pump stev RSP_CONN wrong st %x", + bcs->hw.isar.state); + } + break; + case PSEV_FLAGS_DET: + if (cs->debug & L1_DEB_HSCX) + debugl1(cs, "pump stev FLAGS_DET"); + break; + case PSEV_RSP_DISC: + if (cs->debug & L1_DEB_HSCX) + debugl1(cs, "pump stev RSP_DISC"); + if (bcs->hw.isar.state == STFAX_ESCAPE) { + p1 = 5; + switch (bcs->hw.isar.newcmd) { + case 0: + bcs->hw.isar.state = STFAX_READY; + break; + case PCTRL_CMD_FTM: + p1 = 2; + /* fall through */ + case PCTRL_CMD_FTH: + sendmsg(cs, dps | ISAR_HIS_PUMPCTRL, + PCTRL_CMD_SILON, 1, &p1); + bcs->hw.isar.state = STFAX_SILDET; + break; + case PCTRL_CMD_FRM: + if (frm_extra_delay) + mdelay(frm_extra_delay); + /* fall through */ + case PCTRL_CMD_FRH: + p1 = bcs->hw.isar.mod = bcs->hw.isar.newmod; + bcs->hw.isar.newmod = 0; + bcs->hw.isar.cmd = bcs->hw.isar.newcmd; + bcs->hw.isar.newcmd = 0; + sendmsg(cs, dps | ISAR_HIS_PUMPCTRL, + bcs->hw.isar.cmd, 1, &p1); + bcs->hw.isar.state = STFAX_LINE; + bcs->hw.isar.try_mod = 3; + break; + default: + if (cs->debug & L1_DEB_HSCX) + debugl1(cs, "RSP_DISC unknown newcmd %x", bcs->hw.isar.newcmd); + break; + } + } else if (bcs->hw.isar.state == STFAX_ACTIV) { + if (test_and_clear_bit(BC_FLG_LL_OK, &bcs->Flag)) { + schedule_event(bcs, B_LL_OK); + } else if (bcs->hw.isar.cmd == PCTRL_CMD_FRM) { + send_DLE_ETX(bcs); + schedule_event(bcs, B_LL_NOCARRIER); + } else { + ll_deliver_faxstat(bcs, ISDN_FAX_CLASS1_FCERROR); + } + bcs->hw.isar.state = STFAX_READY; + } else { + bcs->hw.isar.state = STFAX_READY; + ll_deliver_faxstat(bcs, ISDN_FAX_CLASS1_FCERROR); + } + break; + case PSEV_RSP_SILDET: + if (cs->debug & L1_DEB_HSCX) + debugl1(cs, "pump stev RSP_SILDET"); + if (bcs->hw.isar.state == STFAX_SILDET) { + p1 = bcs->hw.isar.mod = bcs->hw.isar.newmod; + bcs->hw.isar.newmod = 0; + bcs->hw.isar.cmd = bcs->hw.isar.newcmd; + bcs->hw.isar.newcmd = 0; + sendmsg(cs, dps | ISAR_HIS_PUMPCTRL, + bcs->hw.isar.cmd, 1, &p1); + bcs->hw.isar.state = STFAX_LINE; + bcs->hw.isar.try_mod = 3; + } + break; + case PSEV_RSP_SILOFF: + if (cs->debug & L1_DEB_HSCX) + debugl1(cs, "pump stev RSP_SILOFF"); + break; + case PSEV_RSP_FCERR: + if (bcs->hw.isar.state == STFAX_LINE) { + if (cs->debug & L1_DEB_HSCX) + debugl1(cs, "pump stev RSP_FCERR try %d", + bcs->hw.isar.try_mod); + if (bcs->hw.isar.try_mod--) { + sendmsg(cs, dps | ISAR_HIS_PUMPCTRL, + bcs->hw.isar.cmd, 1, + &bcs->hw.isar.mod); + break; + } + } + if (cs->debug & L1_DEB_HSCX) + debugl1(cs, "pump stev RSP_FCERR"); + bcs->hw.isar.state = STFAX_ESCAPE; + sendmsg(cs, dps | ISAR_HIS_PUMPCTRL, PCTRL_CMD_ESC, 0, NULL); + ll_deliver_faxstat(bcs, ISDN_FAX_CLASS1_FCERROR); + break; + default: + break; + } +} + +static char debbuf[128]; + +void +isar_int_main(struct IsdnCardState *cs) +{ + struct isar_reg *ireg = cs->bcs[0].hw.isar.reg; + struct BCState *bcs; + + get_irq_infos(cs, ireg); + switch (ireg->iis & ISAR_IIS_MSCMSD) { + case ISAR_IIS_RDATA: + if ((bcs = sel_bcs_isar(cs, ireg->iis >> 6))) { + isar_rcv_frame(cs, bcs); + } else { + debugl1(cs, "isar spurious IIS_RDATA %x/%x/%x", + ireg->iis, ireg->cmsb, ireg->clsb); + cs->BC_Write_Reg(cs, 1, ISAR_IIA, 0); + } + break; + case ISAR_IIS_GSTEV: + cs->BC_Write_Reg(cs, 1, ISAR_IIA, 0); + ireg->bstat |= ireg->cmsb; + check_send(cs, ireg->cmsb); + break; + case ISAR_IIS_BSTEV: +#ifdef ERROR_STATISTIC + if ((bcs = sel_bcs_isar(cs, ireg->iis >> 6))) { + if (ireg->cmsb == BSTEV_TBO) + bcs->err_tx++; + if (ireg->cmsb == BSTEV_RBO) + bcs->err_rdo++; + } +#endif + if (cs->debug & L1_DEB_WARN) + debugl1(cs, "Buffer STEV dpath%d msb(%x)", + ireg->iis >> 6, ireg->cmsb); + cs->BC_Write_Reg(cs, 1, ISAR_IIA, 0); + break; + case ISAR_IIS_PSTEV: + if ((bcs = sel_bcs_isar(cs, ireg->iis >> 6))) { + rcv_mbox(cs, ireg, (u_char *)ireg->par); + if (bcs->mode == L1_MODE_V32) { + isar_pump_statev_modem(bcs, ireg->cmsb); + } else if (bcs->mode == L1_MODE_FAX) { + isar_pump_statev_fax(bcs, ireg->cmsb); + } else if (ireg->cmsb == PSEV_10MS_TIMER) { + if (cs->debug & L1_DEB_HSCX) + debugl1(cs, "pump stev TIMER"); + } else { + if (cs->debug & L1_DEB_WARN) + debugl1(cs, "isar IIS_PSTEV pmode %d stat %x", + bcs->mode, ireg->cmsb); + } + } else { + debugl1(cs, "isar spurious IIS_PSTEV %x/%x/%x", + ireg->iis, ireg->cmsb, ireg->clsb); + cs->BC_Write_Reg(cs, 1, ISAR_IIA, 0); + } + break; + case ISAR_IIS_PSTRSP: + if ((bcs = sel_bcs_isar(cs, ireg->iis >> 6))) { + rcv_mbox(cs, ireg, (u_char *)ireg->par); + isar_pump_status_rsp(bcs, ireg); + } else { + debugl1(cs, "isar spurious IIS_PSTRSP %x/%x/%x", + ireg->iis, ireg->cmsb, ireg->clsb); + cs->BC_Write_Reg(cs, 1, ISAR_IIA, 0); + } + break; + case ISAR_IIS_DIAG: + case ISAR_IIS_BSTRSP: + case ISAR_IIS_IOM2RSP: + rcv_mbox(cs, ireg, (u_char *)ireg->par); + if ((cs->debug & (L1_DEB_HSCX | L1_DEB_HSCX_FIFO)) + == L1_DEB_HSCX) { + u_char *tp = debbuf; + + tp += sprintf(debbuf, "msg iis(%x) msb(%x)", + ireg->iis, ireg->cmsb); + QuickHex(tp, (u_char *)ireg->par, ireg->clsb); + debugl1(cs, "%s", debbuf); + } + break; + case ISAR_IIS_INVMSG: + rcv_mbox(cs, ireg, debbuf); + if (cs->debug & L1_DEB_WARN) + debugl1(cs, "invalid msg his:%x", + ireg->cmsb); + break; + default: + rcv_mbox(cs, ireg, debbuf); + if (cs->debug & L1_DEB_WARN) + debugl1(cs, "unhandled msg iis(%x) ctrl(%x/%x)", + ireg->iis, ireg->cmsb, ireg->clsb); + break; + } +} + +static void +ftimer_handler(struct timer_list *t) { + struct BCState *bcs = from_timer(bcs, t, hw.isar.ftimer); + if (bcs->cs->debug) + debugl1(bcs->cs, "ftimer flags %04lx", + bcs->Flag); + test_and_clear_bit(BC_FLG_FTI_RUN, &bcs->Flag); + if (test_and_clear_bit(BC_FLG_LL_CONN, &bcs->Flag)) { + schedule_event(bcs, B_LL_CONNECT); + } + if (test_and_clear_bit(BC_FLG_FTI_FTS, &bcs->Flag)) { + schedule_event(bcs, B_LL_OK); + } +} + +static void +setup_pump(struct BCState *bcs) { + struct IsdnCardState *cs = bcs->cs; + u_char dps = SET_DPS(bcs->hw.isar.dpath); + u_char ctrl, param[6]; + + switch (bcs->mode) { + case L1_MODE_NULL: + case L1_MODE_TRANS: + case L1_MODE_HDLC: + sendmsg(cs, dps | ISAR_HIS_PUMPCFG, PMOD_BYPASS, 0, NULL); + break; + case L1_MODE_V32: + ctrl = PMOD_DATAMODEM; + if (test_bit(BC_FLG_ORIG, &bcs->Flag)) { + ctrl |= PCTRL_ORIG; + param[5] = PV32P6_CTN; + } else { + param[5] = PV32P6_ATN; + } + param[0] = para_TOA; /* 6 db */ + param[1] = PV32P2_V23R | PV32P2_V22A | PV32P2_V22B | + PV32P2_V22C | PV32P2_V21 | PV32P2_BEL; + param[2] = PV32P3_AMOD | PV32P3_V32B | PV32P3_V23B; + param[3] = PV32P4_UT144; + param[4] = PV32P5_UT144; + sendmsg(cs, dps | ISAR_HIS_PUMPCFG, ctrl, 6, param); + break; + case L1_MODE_FAX: + ctrl = PMOD_FAX; + if (test_bit(BC_FLG_ORIG, &bcs->Flag)) { + ctrl |= PCTRL_ORIG; + param[1] = PFAXP2_CTN; + } else { + param[1] = PFAXP2_ATN; + } + param[0] = para_TOA; /* 6 db */ + sendmsg(cs, dps | ISAR_HIS_PUMPCFG, ctrl, 2, param); + bcs->hw.isar.state = STFAX_NULL; + bcs->hw.isar.newcmd = 0; + bcs->hw.isar.newmod = 0; + test_and_set_bit(BC_FLG_FTI_RUN, &bcs->Flag); + break; + } + udelay(1000); + sendmsg(cs, dps | ISAR_HIS_PSTREQ, 0, 0, NULL); + udelay(1000); +} + +static void +setup_sart(struct BCState *bcs) { + struct IsdnCardState *cs = bcs->cs; + u_char dps = SET_DPS(bcs->hw.isar.dpath); + u_char ctrl, param[2]; + + switch (bcs->mode) { + case L1_MODE_NULL: + sendmsg(cs, dps | ISAR_HIS_SARTCFG, SMODE_DISABLE, 0, + NULL); + break; + case L1_MODE_TRANS: + sendmsg(cs, dps | ISAR_HIS_SARTCFG, SMODE_BINARY, 2, + "\0\0"); + break; + case L1_MODE_HDLC: + param[0] = 0; + sendmsg(cs, dps | ISAR_HIS_SARTCFG, SMODE_HDLC, 1, + param); + break; + case L1_MODE_V32: + ctrl = SMODE_V14 | SCTRL_HDMC_BOTH; + param[0] = S_P1_CHS_8; + param[1] = S_P2_BFT_DEF; + sendmsg(cs, dps | ISAR_HIS_SARTCFG, ctrl, 2, + param); + break; + case L1_MODE_FAX: + /* SART must not configured with FAX */ + break; + } + udelay(1000); + sendmsg(cs, dps | ISAR_HIS_BSTREQ, 0, 0, NULL); + udelay(1000); +} + +static void +setup_iom2(struct BCState *bcs) { + struct IsdnCardState *cs = bcs->cs; + u_char dps = SET_DPS(bcs->hw.isar.dpath); + u_char cmsb = IOM_CTRL_ENA, msg[5] = {IOM_P1_TXD, 0, 0, 0, 0}; + + if (bcs->channel) + msg[1] = msg[3] = 1; + switch (bcs->mode) { + case L1_MODE_NULL: + cmsb = 0; + /* dummy slot */ + msg[1] = msg[3] = bcs->hw.isar.dpath + 2; + break; + case L1_MODE_TRANS: + case L1_MODE_HDLC: + break; + case L1_MODE_V32: + case L1_MODE_FAX: + cmsb |= IOM_CTRL_ALAW | IOM_CTRL_RCV; + break; + } + sendmsg(cs, dps | ISAR_HIS_IOM2CFG, cmsb, 5, msg); + udelay(1000); + sendmsg(cs, dps | ISAR_HIS_IOM2REQ, 0, 0, NULL); + udelay(1000); +} + +static int +modeisar(struct BCState *bcs, int mode, int bc) +{ + struct IsdnCardState *cs = bcs->cs; + + /* Here we are selecting the best datapath for requested mode */ + if (bcs->mode == L1_MODE_NULL) { /* New Setup */ + bcs->channel = bc; + switch (mode) { + case L1_MODE_NULL: /* init */ + if (!bcs->hw.isar.dpath) + /* no init for dpath 0 */ + return (0); + break; + case L1_MODE_TRANS: + case L1_MODE_HDLC: + /* best is datapath 2 */ + if (!test_and_set_bit(ISAR_DP2_USE, + &bcs->hw.isar.reg->Flags)) + bcs->hw.isar.dpath = 2; + else if (!test_and_set_bit(ISAR_DP1_USE, + &bcs->hw.isar.reg->Flags)) + bcs->hw.isar.dpath = 1; + else { + printk(KERN_WARNING"isar modeisar both paths in use\n"); + return (1); + } + break; + case L1_MODE_V32: + case L1_MODE_FAX: + /* only datapath 1 */ + if (!test_and_set_bit(ISAR_DP1_USE, + &bcs->hw.isar.reg->Flags)) + bcs->hw.isar.dpath = 1; + else { + printk(KERN_WARNING"isar modeisar analog functions only with DP1\n"); + debugl1(cs, "isar modeisar analog functions only with DP1"); + return (1); + } + break; + } + } + if (cs->debug & L1_DEB_HSCX) + debugl1(cs, "isar dp%d mode %d->%d ichan %d", + bcs->hw.isar.dpath, bcs->mode, mode, bc); + bcs->mode = mode; + setup_pump(bcs); + setup_iom2(bcs); + setup_sart(bcs); + if (bcs->mode == L1_MODE_NULL) { + /* Clear resources */ + if (bcs->hw.isar.dpath == 1) + test_and_clear_bit(ISAR_DP1_USE, &bcs->hw.isar.reg->Flags); + else if (bcs->hw.isar.dpath == 2) + test_and_clear_bit(ISAR_DP2_USE, &bcs->hw.isar.reg->Flags); + bcs->hw.isar.dpath = 0; + } + return (0); +} + +static void +isar_pump_cmd(struct BCState *bcs, u_char cmd, u_char para) +{ + struct IsdnCardState *cs = bcs->cs; + u_char dps = SET_DPS(bcs->hw.isar.dpath); + u_char ctrl = 0, nom = 0, p1 = 0; + + switch (cmd) { + case ISDN_FAX_CLASS1_FTM: + test_and_clear_bit(BC_FLG_FRH_WAIT, &bcs->Flag); + if (bcs->hw.isar.state == STFAX_READY) { + p1 = para; + ctrl = PCTRL_CMD_FTM; + nom = 1; + bcs->hw.isar.state = STFAX_LINE; + bcs->hw.isar.cmd = ctrl; + bcs->hw.isar.mod = para; + bcs->hw.isar.newmod = 0; + bcs->hw.isar.newcmd = 0; + bcs->hw.isar.try_mod = 3; + } else if ((bcs->hw.isar.state == STFAX_ACTIV) && + (bcs->hw.isar.cmd == PCTRL_CMD_FTM) && + (bcs->hw.isar.mod == para)) { + ll_deliver_faxstat(bcs, ISDN_FAX_CLASS1_CONNECT); + } else { + bcs->hw.isar.newmod = para; + bcs->hw.isar.newcmd = PCTRL_CMD_FTM; + nom = 0; + ctrl = PCTRL_CMD_ESC; + bcs->hw.isar.state = STFAX_ESCAPE; + } + break; + case ISDN_FAX_CLASS1_FTH: + test_and_clear_bit(BC_FLG_FRH_WAIT, &bcs->Flag); + if (bcs->hw.isar.state == STFAX_READY) { + p1 = para; + ctrl = PCTRL_CMD_FTH; + nom = 1; + bcs->hw.isar.state = STFAX_LINE; + bcs->hw.isar.cmd = ctrl; + bcs->hw.isar.mod = para; + bcs->hw.isar.newmod = 0; + bcs->hw.isar.newcmd = 0; + bcs->hw.isar.try_mod = 3; + } else if ((bcs->hw.isar.state == STFAX_ACTIV) && + (bcs->hw.isar.cmd == PCTRL_CMD_FTH) && + (bcs->hw.isar.mod == para)) { + ll_deliver_faxstat(bcs, ISDN_FAX_CLASS1_CONNECT); + } else { + bcs->hw.isar.newmod = para; + bcs->hw.isar.newcmd = PCTRL_CMD_FTH; + nom = 0; + ctrl = PCTRL_CMD_ESC; + bcs->hw.isar.state = STFAX_ESCAPE; + } + break; + case ISDN_FAX_CLASS1_FRM: + test_and_clear_bit(BC_FLG_FRH_WAIT, &bcs->Flag); + if (bcs->hw.isar.state == STFAX_READY) { + p1 = para; + ctrl = PCTRL_CMD_FRM; + nom = 1; + bcs->hw.isar.state = STFAX_LINE; + bcs->hw.isar.cmd = ctrl; + bcs->hw.isar.mod = para; + bcs->hw.isar.newmod = 0; + bcs->hw.isar.newcmd = 0; + bcs->hw.isar.try_mod = 3; + } else if ((bcs->hw.isar.state == STFAX_ACTIV) && + (bcs->hw.isar.cmd == PCTRL_CMD_FRM) && + (bcs->hw.isar.mod == para)) { + ll_deliver_faxstat(bcs, ISDN_FAX_CLASS1_CONNECT); + } else { + bcs->hw.isar.newmod = para; + bcs->hw.isar.newcmd = PCTRL_CMD_FRM; + nom = 0; + ctrl = PCTRL_CMD_ESC; + bcs->hw.isar.state = STFAX_ESCAPE; + } + break; + case ISDN_FAX_CLASS1_FRH: + test_and_set_bit(BC_FLG_FRH_WAIT, &bcs->Flag); + if (bcs->hw.isar.state == STFAX_READY) { + p1 = para; + ctrl = PCTRL_CMD_FRH; + nom = 1; + bcs->hw.isar.state = STFAX_LINE; + bcs->hw.isar.cmd = ctrl; + bcs->hw.isar.mod = para; + bcs->hw.isar.newmod = 0; + bcs->hw.isar.newcmd = 0; + bcs->hw.isar.try_mod = 3; + } else if ((bcs->hw.isar.state == STFAX_ACTIV) && + (bcs->hw.isar.cmd == PCTRL_CMD_FRH) && + (bcs->hw.isar.mod == para)) { + ll_deliver_faxstat(bcs, ISDN_FAX_CLASS1_CONNECT); + } else { + bcs->hw.isar.newmod = para; + bcs->hw.isar.newcmd = PCTRL_CMD_FRH; + nom = 0; + ctrl = PCTRL_CMD_ESC; + bcs->hw.isar.state = STFAX_ESCAPE; + } + break; + case ISDN_FAXPUMP_HALT: + bcs->hw.isar.state = STFAX_NULL; + nom = 0; + ctrl = PCTRL_CMD_HALT; + break; + } + if (ctrl) + sendmsg(cs, dps | ISAR_HIS_PUMPCTRL, ctrl, nom, &p1); +} + +static void +isar_setup(struct IsdnCardState *cs) +{ + u_char msg; + int i; + + /* Dpath 1, 2 */ + msg = 61; + for (i = 0; i < 2; i++) { + /* Buffer Config */ + sendmsg(cs, (i ? ISAR_HIS_DPS2 : ISAR_HIS_DPS1) | + ISAR_HIS_P12CFG, 4, 1, &msg); + cs->bcs[i].hw.isar.mml = msg; + cs->bcs[i].mode = 0; + cs->bcs[i].hw.isar.dpath = i + 1; + modeisar(&cs->bcs[i], 0, 0); + INIT_WORK(&cs->bcs[i].tqueue, isar_bh); + } +} + +static void +isar_l2l1(struct PStack *st, int pr, void *arg) +{ + struct BCState *bcs = st->l1.bcs; + struct sk_buff *skb = arg; + int ret; + u_long flags; + + switch (pr) { + case (PH_DATA | REQUEST): + spin_lock_irqsave(&bcs->cs->lock, flags); + if (bcs->tx_skb) { + skb_queue_tail(&bcs->squeue, skb); + } else { + bcs->tx_skb = skb; + test_and_set_bit(BC_FLG_BUSY, &bcs->Flag); + if (bcs->cs->debug & L1_DEB_HSCX) + debugl1(bcs->cs, "DRQ set BC_FLG_BUSY"); + bcs->hw.isar.txcnt = 0; + bcs->cs->BC_Send_Data(bcs); + } + spin_unlock_irqrestore(&bcs->cs->lock, flags); + break; + case (PH_PULL | INDICATION): + spin_lock_irqsave(&bcs->cs->lock, flags); + if (bcs->tx_skb) { + printk(KERN_WARNING "isar_l2l1: this shouldn't happen\n"); + } else { + test_and_set_bit(BC_FLG_BUSY, &bcs->Flag); + if (bcs->cs->debug & L1_DEB_HSCX) + debugl1(bcs->cs, "PUI set BC_FLG_BUSY"); + bcs->tx_skb = skb; + bcs->hw.isar.txcnt = 0; + bcs->cs->BC_Send_Data(bcs); + } + spin_unlock_irqrestore(&bcs->cs->lock, flags); + break; + case (PH_PULL | REQUEST): + if (!bcs->tx_skb) { + test_and_clear_bit(FLG_L1_PULL_REQ, &st->l1.Flags); + st->l1.l1l2(st, PH_PULL | CONFIRM, NULL); + } else + test_and_set_bit(FLG_L1_PULL_REQ, &st->l1.Flags); + break; + case (PH_ACTIVATE | REQUEST): + spin_lock_irqsave(&bcs->cs->lock, flags); + test_and_set_bit(BC_FLG_ACTIV, &bcs->Flag); + bcs->hw.isar.conmsg[0] = 0; + if (test_bit(FLG_ORIG, &st->l2.flag)) + test_and_set_bit(BC_FLG_ORIG, &bcs->Flag); + else + test_and_clear_bit(BC_FLG_ORIG, &bcs->Flag); + switch (st->l1.mode) { + case L1_MODE_TRANS: + case L1_MODE_HDLC: + ret = modeisar(bcs, st->l1.mode, st->l1.bc); + spin_unlock_irqrestore(&bcs->cs->lock, flags); + if (ret) + l1_msg_b(st, PH_DEACTIVATE | REQUEST, arg); + else + l1_msg_b(st, PH_ACTIVATE | REQUEST, arg); + break; + case L1_MODE_V32: + case L1_MODE_FAX: + ret = modeisar(bcs, st->l1.mode, st->l1.bc); + spin_unlock_irqrestore(&bcs->cs->lock, flags); + if (ret) + l1_msg_b(st, PH_DEACTIVATE | REQUEST, arg); + break; + default: + spin_unlock_irqrestore(&bcs->cs->lock, flags); + break; + } + break; + case (PH_DEACTIVATE | REQUEST): + l1_msg_b(st, pr, arg); + break; + case (PH_DEACTIVATE | CONFIRM): + spin_lock_irqsave(&bcs->cs->lock, flags); + switch (st->l1.mode) { + case L1_MODE_TRANS: + case L1_MODE_HDLC: + case L1_MODE_V32: + break; + case L1_MODE_FAX: + isar_pump_cmd(bcs, ISDN_FAXPUMP_HALT, 0); + break; + } + test_and_clear_bit(BC_FLG_ACTIV, &bcs->Flag); + test_and_clear_bit(BC_FLG_BUSY, &bcs->Flag); + if (bcs->cs->debug & L1_DEB_HSCX) + debugl1(bcs->cs, "PDAC clear BC_FLG_BUSY"); + modeisar(bcs, 0, st->l1.bc); + spin_unlock_irqrestore(&bcs->cs->lock, flags); + st->l1.l1l2(st, PH_DEACTIVATE | CONFIRM, NULL); + break; + } +} + +static void +close_isarstate(struct BCState *bcs) +{ + modeisar(bcs, 0, bcs->channel); + if (test_and_clear_bit(BC_FLG_INIT, &bcs->Flag)) { + kfree(bcs->hw.isar.rcvbuf); + bcs->hw.isar.rcvbuf = NULL; + skb_queue_purge(&bcs->rqueue); + skb_queue_purge(&bcs->squeue); + if (bcs->tx_skb) { + dev_kfree_skb_any(bcs->tx_skb); + bcs->tx_skb = NULL; + test_and_clear_bit(BC_FLG_BUSY, &bcs->Flag); + if (bcs->cs->debug & L1_DEB_HSCX) + debugl1(bcs->cs, "closeisar clear BC_FLG_BUSY"); + } + } + del_timer(&bcs->hw.isar.ftimer); +} + +static int +open_isarstate(struct IsdnCardState *cs, struct BCState *bcs) +{ + if (!test_and_set_bit(BC_FLG_INIT, &bcs->Flag)) { + if (!(bcs->hw.isar.rcvbuf = kmalloc(HSCX_BUFMAX, GFP_ATOMIC))) { + printk(KERN_WARNING + "HiSax: No memory for isar.rcvbuf\n"); + return (1); + } + skb_queue_head_init(&bcs->rqueue); + skb_queue_head_init(&bcs->squeue); + } + bcs->tx_skb = NULL; + test_and_clear_bit(BC_FLG_BUSY, &bcs->Flag); + if (cs->debug & L1_DEB_HSCX) + debugl1(cs, "openisar clear BC_FLG_BUSY"); + bcs->event = 0; + bcs->hw.isar.rcvidx = 0; + bcs->tx_cnt = 0; + return (0); +} + +static int +setstack_isar(struct PStack *st, struct BCState *bcs) +{ + bcs->channel = st->l1.bc; + if (open_isarstate(st->l1.hardware, bcs)) + return (-1); + st->l1.bcs = bcs; + st->l2.l2l1 = isar_l2l1; + setstack_manager(st); + bcs->st = st; + setstack_l1_B(st); + return (0); +} + +int +isar_auxcmd(struct IsdnCardState *cs, isdn_ctrl *ic) { + u_long adr; + int features, i; + struct BCState *bcs; + + if (cs->debug & L1_DEB_HSCX) + debugl1(cs, "isar_auxcmd cmd/ch %x/%ld", ic->command, ic->arg); + switch (ic->command) { + case (ISDN_CMD_FAXCMD): + bcs = cs->channel[ic->arg].bcs; + if (cs->debug & L1_DEB_HSCX) + debugl1(cs, "isar_auxcmd cmd/subcmd %d/%d", + ic->parm.aux.cmd, ic->parm.aux.subcmd); + switch (ic->parm.aux.cmd) { + case ISDN_FAX_CLASS1_CTRL: + if (ic->parm.aux.subcmd == ETX) + test_and_set_bit(BC_FLG_DLEETX, + &bcs->Flag); + break; + case ISDN_FAX_CLASS1_FTS: + if (ic->parm.aux.subcmd == AT_QUERY) { + ic->command = ISDN_STAT_FAXIND; + ic->parm.aux.cmd = ISDN_FAX_CLASS1_OK; + cs->iif.statcallb(ic); + return (0); + } else if (ic->parm.aux.subcmd == AT_EQ_QUERY) { + strcpy(ic->parm.aux.para, "0-255"); + ic->command = ISDN_STAT_FAXIND; + ic->parm.aux.cmd = ISDN_FAX_CLASS1_QUERY; + cs->iif.statcallb(ic); + return (0); + } else if (ic->parm.aux.subcmd == AT_EQ_VALUE) { + if (cs->debug & L1_DEB_HSCX) + debugl1(cs, "isar_auxcmd %s=%d", + FC1_CMD[ic->parm.aux.cmd], ic->parm.aux.para[0]); + if (bcs->hw.isar.state == STFAX_READY) { + if (!ic->parm.aux.para[0]) { + ic->command = ISDN_STAT_FAXIND; + ic->parm.aux.cmd = ISDN_FAX_CLASS1_OK; + cs->iif.statcallb(ic); + return (0); + } + if (!test_and_set_bit(BC_FLG_FTI_RUN, &bcs->Flag)) { + /* n*10 ms */ + bcs->hw.isar.ftimer.expires = + jiffies + ((ic->parm.aux.para[0] * 10 * HZ) / 1000); + test_and_set_bit(BC_FLG_FTI_FTS, &bcs->Flag); + add_timer(&bcs->hw.isar.ftimer); + return (0); + } else { + if (cs->debug) + debugl1(cs, "isar FTS=%d and FTI busy", + ic->parm.aux.para[0]); + } + } else { + if (cs->debug) + debugl1(cs, "isar FTS=%d and isar.state not ready(%x)", + ic->parm.aux.para[0], bcs->hw.isar.state); + } + ic->command = ISDN_STAT_FAXIND; + ic->parm.aux.cmd = ISDN_FAX_CLASS1_ERROR; + cs->iif.statcallb(ic); + } + break; + case ISDN_FAX_CLASS1_FRM: + case ISDN_FAX_CLASS1_FRH: + case ISDN_FAX_CLASS1_FTM: + case ISDN_FAX_CLASS1_FTH: + if (ic->parm.aux.subcmd == AT_QUERY) { + sprintf(ic->parm.aux.para, + "%d", bcs->hw.isar.mod); + ic->command = ISDN_STAT_FAXIND; + ic->parm.aux.cmd = ISDN_FAX_CLASS1_QUERY; + cs->iif.statcallb(ic); + return (0); + } else if (ic->parm.aux.subcmd == AT_EQ_QUERY) { + char *p = ic->parm.aux.para; + for (i = 0; i < FAXMODCNT; i++) + if ((1 << i) & modmask) + p += sprintf(p, "%d,", faxmodulation[i]); + p--; + *p = 0; + ic->command = ISDN_STAT_FAXIND; + ic->parm.aux.cmd = ISDN_FAX_CLASS1_QUERY; + cs->iif.statcallb(ic); + return (0); + } else if (ic->parm.aux.subcmd == AT_EQ_VALUE) { + if (cs->debug & L1_DEB_HSCX) + debugl1(cs, "isar_auxcmd %s=%d", + FC1_CMD[ic->parm.aux.cmd], ic->parm.aux.para[0]); + for (i = 0; i < FAXMODCNT; i++) + if (faxmodulation[i] == ic->parm.aux.para[0]) + break; + if ((i < FAXMODCNT) && ((1 << i) & modmask) && + test_bit(BC_FLG_INIT, &bcs->Flag)) { + isar_pump_cmd(bcs, + ic->parm.aux.cmd, + ic->parm.aux.para[0]); + return (0); + } + } + /* wrong modulation or not activ */ + /* fall through */ + default: + ic->command = ISDN_STAT_FAXIND; + ic->parm.aux.cmd = ISDN_FAX_CLASS1_ERROR; + cs->iif.statcallb(ic); + } + break; + case (ISDN_CMD_IOCTL): + switch (ic->arg) { + case 9: /* load firmware */ + features = ISDN_FEATURE_L2_MODEM | + ISDN_FEATURE_L2_FAX | + ISDN_FEATURE_L3_FCLASS1; + memcpy(&adr, ic->parm.num, sizeof(ulong)); + if (isar_load_firmware(cs, (u_char __user *)adr)) + return (1); + else + ll_run(cs, features); + break; + case 20: + features = *(unsigned int *) ic->parm.num; + printk(KERN_DEBUG "HiSax: max modulation old(%04x) new(%04x)\n", + modmask, features); + modmask = features; + break; + case 21: + features = *(unsigned int *) ic->parm.num; + printk(KERN_DEBUG "HiSax: FRM extra delay old(%d) new(%d) ms\n", + frm_extra_delay, features); + if (features >= 0) + frm_extra_delay = features; + break; + case 22: + features = *(unsigned int *) ic->parm.num; + printk(KERN_DEBUG "HiSax: TOA old(%d) new(%d) db\n", + para_TOA, features); + if (features >= 0 && features < 32) + para_TOA = features; + break; + default: + printk(KERN_DEBUG "HiSax: invalid ioctl %d\n", + (int) ic->arg); + return (-EINVAL); + } + break; + default: + return (-EINVAL); + } + return (0); +} + +void initisar(struct IsdnCardState *cs) +{ + cs->bcs[0].BC_SetStack = setstack_isar; + cs->bcs[1].BC_SetStack = setstack_isar; + cs->bcs[0].BC_Close = close_isarstate; + cs->bcs[1].BC_Close = close_isarstate; + timer_setup(&cs->bcs[0].hw.isar.ftimer, ftimer_handler, 0); + timer_setup(&cs->bcs[1].hw.isar.ftimer, ftimer_handler, 0); +} diff --git a/drivers/isdn/hisax/isar.h b/drivers/isdn/hisax/isar.h new file mode 100644 index 000000000..0f4d101fa --- /dev/null +++ b/drivers/isdn/hisax/isar.h @@ -0,0 +1,222 @@ +/* $Id: isar.h,v 1.11.2.2 2004/01/12 22:52:27 keil Exp $ + * + * ISAR (Siemens PSB 7110) specific defines + * + * Author Karsten Keil + * Copyright by Karsten Keil <keil@isdn4linux.de> + * + * This software may be used and distributed according to the terms + * of the GNU General Public License, incorporated herein by reference. + * + */ + +#define ISAR_IRQMSK 0x04 +#define ISAR_IRQSTA 0x04 +#define ISAR_IRQBIT 0x75 +#define ISAR_CTRL_H 0x61 +#define ISAR_CTRL_L 0x60 +#define ISAR_IIS 0x58 +#define ISAR_IIA 0x58 +#define ISAR_HIS 0x50 +#define ISAR_HIA 0x50 +#define ISAR_MBOX 0x4c +#define ISAR_WADR 0x4a +#define ISAR_RADR 0x48 + +#define ISAR_HIS_VNR 0x14 +#define ISAR_HIS_DKEY 0x02 +#define ISAR_HIS_FIRM 0x1e +#define ISAR_HIS_STDSP 0x08 +#define ISAR_HIS_DIAG 0x05 +#define ISAR_HIS_WAITSTATE 0x27 +#define ISAR_HIS_TIMERIRQ 0x25 +#define ISAR_HIS_P0CFG 0x3c +#define ISAR_HIS_P12CFG 0x24 +#define ISAR_HIS_SARTCFG 0x25 +#define ISAR_HIS_PUMPCFG 0x26 +#define ISAR_HIS_PUMPCTRL 0x2a +#define ISAR_HIS_IOM2CFG 0x27 +#define ISAR_HIS_IOM2REQ 0x07 +#define ISAR_HIS_IOM2CTRL 0x2b +#define ISAR_HIS_BSTREQ 0x0c +#define ISAR_HIS_PSTREQ 0x0e +#define ISAR_HIS_SDATA 0x20 +#define ISAR_HIS_DPS1 0x40 +#define ISAR_HIS_DPS2 0x80 +#define SET_DPS(x) ((x << 6) & 0xc0) + +#define ISAR_CMD_TIMERIRQ_OFF 0x20 +#define ISAR_CMD_TIMERIRQ_ON 0x21 + + +#define ISAR_IIS_MSCMSD 0x3f +#define ISAR_IIS_VNR 0x15 +#define ISAR_IIS_DKEY 0x03 +#define ISAR_IIS_FIRM 0x1f +#define ISAR_IIS_STDSP 0x09 +#define ISAR_IIS_DIAG 0x25 +#define ISAR_IIS_GSTEV 0x00 +#define ISAR_IIS_BSTEV 0x28 +#define ISAR_IIS_BSTRSP 0x2c +#define ISAR_IIS_PSTRSP 0x2e +#define ISAR_IIS_PSTEV 0x2a +#define ISAR_IIS_IOM2RSP 0x27 +#define ISAR_IIS_RDATA 0x20 +#define ISAR_IIS_INVMSG 0x3f + +#define ISAR_CTRL_SWVER 0x10 +#define ISAR_CTRL_STST 0x40 + +#define ISAR_MSG_HWVER {0x20, 0, 1} + +#define ISAR_DP1_USE 1 +#define ISAR_DP2_USE 2 +#define ISAR_RATE_REQ 3 + +#define PMOD_DISABLE 0 +#define PMOD_FAX 1 +#define PMOD_DATAMODEM 2 +#define PMOD_HALFDUPLEX 3 +#define PMOD_V110 4 +#define PMOD_DTMF 5 +#define PMOD_DTMF_TRANS 6 +#define PMOD_BYPASS 7 + +#define PCTRL_ORIG 0x80 +#define PV32P2_V23R 0x40 +#define PV32P2_V22A 0x20 +#define PV32P2_V22B 0x10 +#define PV32P2_V22C 0x08 +#define PV32P2_V21 0x02 +#define PV32P2_BEL 0x01 + +// LSB MSB in ISAR doc wrong !!! Arghhh +#define PV32P3_AMOD 0x80 +#define PV32P3_V32B 0x02 +#define PV32P3_V23B 0x01 +#define PV32P4_48 0x11 +#define PV32P5_48 0x05 +#define PV32P4_UT48 0x11 +#define PV32P5_UT48 0x0d +#define PV32P4_96 0x11 +#define PV32P5_96 0x03 +#define PV32P4_UT96 0x11 +#define PV32P5_UT96 0x0f +#define PV32P4_B96 0x91 +#define PV32P5_B96 0x0b +#define PV32P4_UTB96 0xd1 +#define PV32P5_UTB96 0x0f +#define PV32P4_120 0xb1 +#define PV32P5_120 0x09 +#define PV32P4_UT120 0xf1 +#define PV32P5_UT120 0x0f +#define PV32P4_144 0x99 +#define PV32P5_144 0x09 +#define PV32P4_UT144 0xf9 +#define PV32P5_UT144 0x0f +#define PV32P6_CTN 0x01 +#define PV32P6_ATN 0x02 + +#define PFAXP2_CTN 0x01 +#define PFAXP2_ATN 0x04 + +#define PSEV_10MS_TIMER 0x02 +#define PSEV_CON_ON 0x18 +#define PSEV_CON_OFF 0x19 +#define PSEV_V24_OFF 0x20 +#define PSEV_CTS_ON 0x21 +#define PSEV_CTS_OFF 0x22 +#define PSEV_DCD_ON 0x23 +#define PSEV_DCD_OFF 0x24 +#define PSEV_DSR_ON 0x25 +#define PSEV_DSR_OFF 0x26 +#define PSEV_REM_RET 0xcc +#define PSEV_REM_REN 0xcd +#define PSEV_GSTN_CLR 0xd4 + +#define PSEV_RSP_READY 0xbc +#define PSEV_LINE_TX_H 0xb3 +#define PSEV_LINE_TX_B 0xb2 +#define PSEV_LINE_RX_H 0xb1 +#define PSEV_LINE_RX_B 0xb0 +#define PSEV_RSP_CONN 0xb5 +#define PSEV_RSP_DISC 0xb7 +#define PSEV_RSP_FCERR 0xb9 +#define PSEV_RSP_SILDET 0xbe +#define PSEV_RSP_SILOFF 0xab +#define PSEV_FLAGS_DET 0xba + +#define PCTRL_CMD_FTH 0xa7 +#define PCTRL_CMD_FRH 0xa5 +#define PCTRL_CMD_FTM 0xa8 +#define PCTRL_CMD_FRM 0xa6 +#define PCTRL_CMD_SILON 0xac +#define PCTRL_CMD_CONT 0xa2 +#define PCTRL_CMD_ESC 0xa4 +#define PCTRL_CMD_SILOFF 0xab +#define PCTRL_CMD_HALT 0xa9 + +#define PCTRL_LOC_RET 0xcf +#define PCTRL_LOC_REN 0xce + +#define SMODE_DISABLE 0 +#define SMODE_V14 2 +#define SMODE_HDLC 3 +#define SMODE_BINARY 4 +#define SMODE_FSK_V14 5 + +#define SCTRL_HDMC_BOTH 0x00 +#define SCTRL_HDMC_DTX 0x80 +#define SCTRL_HDMC_DRX 0x40 +#define S_P1_OVSP 0x40 +#define S_P1_SNP 0x20 +#define S_P1_EOP 0x10 +#define S_P1_EDP 0x08 +#define S_P1_NSB 0x04 +#define S_P1_CHS_8 0x03 +#define S_P1_CHS_7 0x02 +#define S_P1_CHS_6 0x01 +#define S_P1_CHS_5 0x00 + +#define S_P2_BFT_DEF 0x10 + +#define IOM_CTRL_ENA 0x80 +#define IOM_CTRL_NOPCM 0x00 +#define IOM_CTRL_ALAW 0x02 +#define IOM_CTRL_ULAW 0x04 +#define IOM_CTRL_RCV 0x01 + +#define IOM_P1_TXD 0x10 + +#define HDLC_FED 0x40 +#define HDLC_FSD 0x20 +#define HDLC_FST 0x20 +#define HDLC_ERROR 0x1c +#define HDLC_ERR_FAD 0x10 +#define HDLC_ERR_RER 0x08 +#define HDLC_ERR_CER 0x04 +#define SART_NMD 0x01 + +#define BSTAT_RDM0 0x1 +#define BSTAT_RDM1 0x2 +#define BSTAT_RDM2 0x4 +#define BSTAT_RDM3 0x8 +#define BSTEV_TBO 0x1f +#define BSTEV_RBO 0x2f + +/* FAX State Machine */ +#define STFAX_NULL 0 +#define STFAX_READY 1 +#define STFAX_LINE 2 +#define STFAX_CONT 3 +#define STFAX_ACTIV 4 +#define STFAX_ESCAPE 5 +#define STFAX_SILDET 6 + +#define ISDN_FAXPUMP_HALT 100 + +extern int ISARVersion(struct IsdnCardState *cs, char *s); +extern void isar_int_main(struct IsdnCardState *cs); +extern void initisar(struct IsdnCardState *cs); +extern void isar_fill_fifo(struct BCState *bcs); +extern int isar_auxcmd(struct IsdnCardState *cs, isdn_ctrl *ic); diff --git a/drivers/isdn/hisax/isdnl1.c b/drivers/isdn/hisax/isdnl1.c new file mode 100644 index 000000000..a560842c0 --- /dev/null +++ b/drivers/isdn/hisax/isdnl1.c @@ -0,0 +1,930 @@ +/* $Id: isdnl1.c,v 2.46.2.5 2004/02/11 13:21:34 keil Exp $ + * + * common low level stuff for Siemens Chipsetbased isdn cards + * + * Author Karsten Keil + * based on the teles driver from Jan den Ouden + * Copyright by Karsten Keil <keil@isdn4linux.de> + * + * This software may be used and distributed according to the terms + * of the GNU General Public License, incorporated herein by reference. + * + * For changes and modifications please read + * Documentation/isdn/HiSax.cert + * + * Thanks to Jan den Ouden + * Fritz Elfert + * Beat Doebeli + * + */ + +#include <linux/init.h> +#include <linux/gfp.h> +#include "hisax.h" +#include "isdnl1.h" + +const char *l1_revision = "$Revision: 2.46.2.5 $"; + +#define TIMER3_VALUE 7000 + +static struct Fsm l1fsm_b; +static struct Fsm l1fsm_s; + +enum { + ST_L1_F2, + ST_L1_F3, + ST_L1_F4, + ST_L1_F5, + ST_L1_F6, + ST_L1_F7, + ST_L1_F8, +}; + +#define L1S_STATE_COUNT (ST_L1_F8 + 1) + +static char *strL1SState[] = +{ + "ST_L1_F2", + "ST_L1_F3", + "ST_L1_F4", + "ST_L1_F5", + "ST_L1_F6", + "ST_L1_F7", + "ST_L1_F8", +}; + +#ifdef HISAX_UINTERFACE +static +struct Fsm l1fsm_u = +{NULL, 0, 0, NULL, NULL}; + +enum { + ST_L1_RESET, + ST_L1_DEACT, + ST_L1_SYNC2, + ST_L1_TRANS, +}; + +#define L1U_STATE_COUNT (ST_L1_TRANS + 1) + +static char *strL1UState[] = +{ + "ST_L1_RESET", + "ST_L1_DEACT", + "ST_L1_SYNC2", + "ST_L1_TRANS", +}; +#endif + +enum { + ST_L1_NULL, + ST_L1_WAIT_ACT, + ST_L1_WAIT_DEACT, + ST_L1_ACTIV, +}; + +#define L1B_STATE_COUNT (ST_L1_ACTIV + 1) + +static char *strL1BState[] = +{ + "ST_L1_NULL", + "ST_L1_WAIT_ACT", + "ST_L1_WAIT_DEACT", + "ST_L1_ACTIV", +}; + +enum { + EV_PH_ACTIVATE, + EV_PH_DEACTIVATE, + EV_RESET_IND, + EV_DEACT_CNF, + EV_DEACT_IND, + EV_POWER_UP, + EV_RSYNC_IND, + EV_INFO2_IND, + EV_INFO4_IND, + EV_TIMER_DEACT, + EV_TIMER_ACT, + EV_TIMER3, +}; + +#define L1_EVENT_COUNT (EV_TIMER3 + 1) + +static char *strL1Event[] = +{ + "EV_PH_ACTIVATE", + "EV_PH_DEACTIVATE", + "EV_RESET_IND", + "EV_DEACT_CNF", + "EV_DEACT_IND", + "EV_POWER_UP", + "EV_RSYNC_IND", + "EV_INFO2_IND", + "EV_INFO4_IND", + "EV_TIMER_DEACT", + "EV_TIMER_ACT", + "EV_TIMER3", +}; + +void +debugl1(struct IsdnCardState *cs, char *fmt, ...) +{ + va_list args; + char tmp[8]; + + va_start(args, fmt); + sprintf(tmp, "Card%d ", cs->cardnr + 1); + VHiSax_putstatus(cs, tmp, fmt, args); + va_end(args); +} + +static void +l1m_debug(struct FsmInst *fi, char *fmt, ...) +{ + va_list args; + struct PStack *st = fi->userdata; + struct IsdnCardState *cs = st->l1.hardware; + char tmp[8]; + + va_start(args, fmt); + sprintf(tmp, "Card%d ", cs->cardnr + 1); + VHiSax_putstatus(cs, tmp, fmt, args); + va_end(args); +} + +static void +L1activated(struct IsdnCardState *cs) +{ + struct PStack *st; + + st = cs->stlist; + while (st) { + if (test_and_clear_bit(FLG_L1_ACTIVATING, &st->l1.Flags)) + st->l1.l1l2(st, PH_ACTIVATE | CONFIRM, NULL); + else + st->l1.l1l2(st, PH_ACTIVATE | INDICATION, NULL); + st = st->next; + } +} + +static void +L1deactivated(struct IsdnCardState *cs) +{ + struct PStack *st; + + st = cs->stlist; + while (st) { + if (test_bit(FLG_L1_DBUSY, &cs->HW_Flags)) + st->l1.l1l2(st, PH_PAUSE | CONFIRM, NULL); + st->l1.l1l2(st, PH_DEACTIVATE | INDICATION, NULL); + st = st->next; + } + test_and_clear_bit(FLG_L1_DBUSY, &cs->HW_Flags); +} + +void +DChannel_proc_xmt(struct IsdnCardState *cs) +{ + struct PStack *stptr; + + if (cs->tx_skb) + return; + + stptr = cs->stlist; + while (stptr != NULL) { + if (test_and_clear_bit(FLG_L1_PULL_REQ, &stptr->l1.Flags)) { + stptr->l1.l1l2(stptr, PH_PULL | CONFIRM, NULL); + break; + } else + stptr = stptr->next; + } +} + +void +DChannel_proc_rcv(struct IsdnCardState *cs) +{ + struct sk_buff *skb, *nskb; + struct PStack *stptr = cs->stlist; + int found, tei, sapi; + + if (stptr) + if (test_bit(FLG_L1_ACTTIMER, &stptr->l1.Flags)) + FsmEvent(&stptr->l1.l1m, EV_TIMER_ACT, NULL); + while ((skb = skb_dequeue(&cs->rq))) { +#ifdef L2FRAME_DEBUG /* psa */ + if (cs->debug & L1_DEB_LAPD) + Logl2Frame(cs, skb, "PH_DATA", 1); +#endif + stptr = cs->stlist; + if (skb->len < 3) { + debugl1(cs, "D-channel frame too short(%d)", skb->len); + dev_kfree_skb(skb); + return; + } + if ((skb->data[0] & 1) || !(skb->data[1] & 1)) { + debugl1(cs, "D-channel frame wrong EA0/EA1"); + dev_kfree_skb(skb); + return; + } + sapi = skb->data[0] >> 2; + tei = skb->data[1] >> 1; + if (cs->debug & DEB_DLOG_HEX) + LogFrame(cs, skb->data, skb->len); + if (cs->debug & DEB_DLOG_VERBOSE) + dlogframe(cs, skb, 1); + if (tei == GROUP_TEI) { + if (sapi == CTRL_SAPI) { /* sapi 0 */ + while (stptr != NULL) { + if ((nskb = skb_clone(skb, GFP_ATOMIC))) + stptr->l1.l1l2(stptr, PH_DATA | INDICATION, nskb); + else + printk(KERN_WARNING "HiSax: isdn broadcast buffer shortage\n"); + stptr = stptr->next; + } + } else if (sapi == TEI_SAPI) { + while (stptr != NULL) { + if ((nskb = skb_clone(skb, GFP_ATOMIC))) + stptr->l1.l1tei(stptr, PH_DATA | INDICATION, nskb); + else + printk(KERN_WARNING "HiSax: tei broadcast buffer shortage\n"); + stptr = stptr->next; + } + } + dev_kfree_skb(skb); + } else if (sapi == CTRL_SAPI) { /* sapi 0 */ + found = 0; + while (stptr != NULL) + if (tei == stptr->l2.tei) { + stptr->l1.l1l2(stptr, PH_DATA | INDICATION, skb); + found = !0; + break; + } else + stptr = stptr->next; + if (!found) + dev_kfree_skb(skb); + } else + dev_kfree_skb(skb); + } +} + +static void +BChannel_proc_xmt(struct BCState *bcs) +{ + struct PStack *st = bcs->st; + + if (test_bit(BC_FLG_BUSY, &bcs->Flag)) { + debugl1(bcs->cs, "BC_BUSY Error"); + return; + } + + if (test_and_clear_bit(FLG_L1_PULL_REQ, &st->l1.Flags)) + st->l1.l1l2(st, PH_PULL | CONFIRM, NULL); + if (!test_bit(BC_FLG_ACTIV, &bcs->Flag)) { + if (!test_bit(BC_FLG_BUSY, &bcs->Flag) && + skb_queue_empty(&bcs->squeue)) { + st->l2.l2l1(st, PH_DEACTIVATE | CONFIRM, NULL); + } + } +} + +static void +BChannel_proc_rcv(struct BCState *bcs) +{ + struct sk_buff *skb; + + if (bcs->st->l1.l1m.state == ST_L1_WAIT_ACT) { + FsmDelTimer(&bcs->st->l1.timer, 4); + FsmEvent(&bcs->st->l1.l1m, EV_TIMER_ACT, NULL); + } + while ((skb = skb_dequeue(&bcs->rqueue))) { + bcs->st->l1.l1l2(bcs->st, PH_DATA | INDICATION, skb); + } +} + +static void +BChannel_proc_ack(struct BCState *bcs) +{ + u_long flags; + int ack; + + spin_lock_irqsave(&bcs->aclock, flags); + ack = bcs->ackcnt; + bcs->ackcnt = 0; + spin_unlock_irqrestore(&bcs->aclock, flags); + if (ack) + lli_writewakeup(bcs->st, ack); +} + +void +BChannel_bh(struct work_struct *work) +{ + struct BCState *bcs = container_of(work, struct BCState, tqueue); + + if (!bcs) + return; + if (test_and_clear_bit(B_RCVBUFREADY, &bcs->event)) + BChannel_proc_rcv(bcs); + if (test_and_clear_bit(B_XMTBUFREADY, &bcs->event)) + BChannel_proc_xmt(bcs); + if (test_and_clear_bit(B_ACKPENDING, &bcs->event)) + BChannel_proc_ack(bcs); +} + +void +HiSax_addlist(struct IsdnCardState *cs, + struct PStack *st) +{ + st->next = cs->stlist; + cs->stlist = st; +} + +void +HiSax_rmlist(struct IsdnCardState *cs, + struct PStack *st) +{ + struct PStack *p; + + FsmDelTimer(&st->l1.timer, 0); + if (cs->stlist == st) + cs->stlist = st->next; + else { + p = cs->stlist; + while (p) + if (p->next == st) { + p->next = st->next; + return; + } else + p = p->next; + } +} + +void +init_bcstate(struct IsdnCardState *cs, int bc) +{ + struct BCState *bcs = cs->bcs + bc; + + bcs->cs = cs; + bcs->channel = bc; + INIT_WORK(&bcs->tqueue, BChannel_bh); + spin_lock_init(&bcs->aclock); + bcs->BC_SetStack = NULL; + bcs->BC_Close = NULL; + bcs->Flag = 0; +} + +#ifdef L2FRAME_DEBUG /* psa */ + +static char * +l2cmd(u_char cmd) +{ + switch (cmd & ~0x10) { + case 1: + return "RR"; + case 5: + return "RNR"; + case 9: + return "REJ"; + case 0x6f: + return "SABME"; + case 0x0f: + return "DM"; + case 3: + return "UI"; + case 0x43: + return "DISC"; + case 0x63: + return "UA"; + case 0x87: + return "FRMR"; + case 0xaf: + return "XID"; + default: + if (!(cmd & 1)) + return "I"; + else + return "invalid command"; + } +} + +static char tmpdeb[32]; + +static char * +l2frames(u_char *ptr) +{ + switch (ptr[2] & ~0x10) { + case 1: + case 5: + case 9: + sprintf(tmpdeb, "%s[%d](nr %d)", l2cmd(ptr[2]), ptr[3] & 1, ptr[3] >> 1); + break; + case 0x6f: + case 0x0f: + case 3: + case 0x43: + case 0x63: + case 0x87: + case 0xaf: + sprintf(tmpdeb, "%s[%d]", l2cmd(ptr[2]), (ptr[2] & 0x10) >> 4); + break; + default: + if (!(ptr[2] & 1)) { + sprintf(tmpdeb, "I[%d](ns %d, nr %d)", ptr[3] & 1, ptr[2] >> 1, ptr[3] >> 1); + break; + } else + return "invalid command"; + } + + + return tmpdeb; +} + +void +Logl2Frame(struct IsdnCardState *cs, struct sk_buff *skb, char *buf, int dir) +{ + u_char *ptr; + + ptr = skb->data; + + if (ptr[0] & 1 || !(ptr[1] & 1)) + debugl1(cs, "Address not LAPD"); + else + debugl1(cs, "%s %s: %s%c (sapi %d, tei %d)", + (dir ? "<-" : "->"), buf, l2frames(ptr), + ((ptr[0] & 2) >> 1) == dir ? 'C' : 'R', ptr[0] >> 2, ptr[1] >> 1); +} +#endif + +static void +l1_reset(struct FsmInst *fi, int event, void *arg) +{ + FsmChangeState(fi, ST_L1_F3); +} + +static void +l1_deact_cnf(struct FsmInst *fi, int event, void *arg) +{ + struct PStack *st = fi->userdata; + + FsmChangeState(fi, ST_L1_F3); + if (test_bit(FLG_L1_ACTIVATING, &st->l1.Flags)) + st->l1.l1hw(st, HW_ENABLE | REQUEST, NULL); +} + +static void +l1_deact_req_s(struct FsmInst *fi, int event, void *arg) +{ + struct PStack *st = fi->userdata; + + FsmChangeState(fi, ST_L1_F3); + FsmRestartTimer(&st->l1.timer, 550, EV_TIMER_DEACT, NULL, 2); + test_and_set_bit(FLG_L1_DEACTTIMER, &st->l1.Flags); +} + +static void +l1_power_up_s(struct FsmInst *fi, int event, void *arg) +{ + struct PStack *st = fi->userdata; + + if (test_bit(FLG_L1_ACTIVATING, &st->l1.Flags)) { + FsmChangeState(fi, ST_L1_F4); + st->l1.l1hw(st, HW_INFO3 | REQUEST, NULL); + FsmRestartTimer(&st->l1.timer, TIMER3_VALUE, EV_TIMER3, NULL, 2); + test_and_set_bit(FLG_L1_T3RUN, &st->l1.Flags); + } else + FsmChangeState(fi, ST_L1_F3); +} + +static void +l1_go_F5(struct FsmInst *fi, int event, void *arg) +{ + FsmChangeState(fi, ST_L1_F5); +} + +static void +l1_go_F8(struct FsmInst *fi, int event, void *arg) +{ + FsmChangeState(fi, ST_L1_F8); +} + +static void +l1_info2_ind(struct FsmInst *fi, int event, void *arg) +{ + struct PStack *st = fi->userdata; + +#ifdef HISAX_UINTERFACE + if (test_bit(FLG_L1_UINT, &st->l1.Flags)) + FsmChangeState(fi, ST_L1_SYNC2); + else +#endif + FsmChangeState(fi, ST_L1_F6); + st->l1.l1hw(st, HW_INFO3 | REQUEST, NULL); +} + +static void +l1_info4_ind(struct FsmInst *fi, int event, void *arg) +{ + struct PStack *st = fi->userdata; + +#ifdef HISAX_UINTERFACE + if (test_bit(FLG_L1_UINT, &st->l1.Flags)) + FsmChangeState(fi, ST_L1_TRANS); + else +#endif + FsmChangeState(fi, ST_L1_F7); + st->l1.l1hw(st, HW_INFO3 | REQUEST, NULL); + if (test_and_clear_bit(FLG_L1_DEACTTIMER, &st->l1.Flags)) + FsmDelTimer(&st->l1.timer, 4); + if (!test_bit(FLG_L1_ACTIVATED, &st->l1.Flags)) { + if (test_and_clear_bit(FLG_L1_T3RUN, &st->l1.Flags)) + FsmDelTimer(&st->l1.timer, 3); + FsmRestartTimer(&st->l1.timer, 110, EV_TIMER_ACT, NULL, 2); + test_and_set_bit(FLG_L1_ACTTIMER, &st->l1.Flags); + } +} + +static void +l1_timer3(struct FsmInst *fi, int event, void *arg) +{ + struct PStack *st = fi->userdata; + + test_and_clear_bit(FLG_L1_T3RUN, &st->l1.Flags); + if (test_and_clear_bit(FLG_L1_ACTIVATING, &st->l1.Flags)) + L1deactivated(st->l1.hardware); + +#ifdef HISAX_UINTERFACE + if (!test_bit(FLG_L1_UINT, &st->l1.Flags)) +#endif + if (st->l1.l1m.state != ST_L1_F6) { + FsmChangeState(fi, ST_L1_F3); + st->l1.l1hw(st, HW_ENABLE | REQUEST, NULL); + } +} + +static void +l1_timer_act(struct FsmInst *fi, int event, void *arg) +{ + struct PStack *st = fi->userdata; + + test_and_clear_bit(FLG_L1_ACTTIMER, &st->l1.Flags); + test_and_set_bit(FLG_L1_ACTIVATED, &st->l1.Flags); + L1activated(st->l1.hardware); +} + +static void +l1_timer_deact(struct FsmInst *fi, int event, void *arg) +{ + struct PStack *st = fi->userdata; + + test_and_clear_bit(FLG_L1_DEACTTIMER, &st->l1.Flags); + test_and_clear_bit(FLG_L1_ACTIVATED, &st->l1.Flags); + L1deactivated(st->l1.hardware); + st->l1.l1hw(st, HW_DEACTIVATE | RESPONSE, NULL); +} + +static void +l1_activate_s(struct FsmInst *fi, int event, void *arg) +{ + struct PStack *st = fi->userdata; + + st->l1.l1hw(st, HW_RESET | REQUEST, NULL); +} + +static void +l1_activate_no(struct FsmInst *fi, int event, void *arg) +{ + struct PStack *st = fi->userdata; + + if ((!test_bit(FLG_L1_DEACTTIMER, &st->l1.Flags)) && (!test_bit(FLG_L1_T3RUN, &st->l1.Flags))) { + test_and_clear_bit(FLG_L1_ACTIVATING, &st->l1.Flags); + L1deactivated(st->l1.hardware); + } +} + +static struct FsmNode L1SFnList[] __initdata = +{ + {ST_L1_F3, EV_PH_ACTIVATE, l1_activate_s}, + {ST_L1_F6, EV_PH_ACTIVATE, l1_activate_no}, + {ST_L1_F8, EV_PH_ACTIVATE, l1_activate_no}, + {ST_L1_F3, EV_RESET_IND, l1_reset}, + {ST_L1_F4, EV_RESET_IND, l1_reset}, + {ST_L1_F5, EV_RESET_IND, l1_reset}, + {ST_L1_F6, EV_RESET_IND, l1_reset}, + {ST_L1_F7, EV_RESET_IND, l1_reset}, + {ST_L1_F8, EV_RESET_IND, l1_reset}, + {ST_L1_F3, EV_DEACT_CNF, l1_deact_cnf}, + {ST_L1_F4, EV_DEACT_CNF, l1_deact_cnf}, + {ST_L1_F5, EV_DEACT_CNF, l1_deact_cnf}, + {ST_L1_F6, EV_DEACT_CNF, l1_deact_cnf}, + {ST_L1_F7, EV_DEACT_CNF, l1_deact_cnf}, + {ST_L1_F8, EV_DEACT_CNF, l1_deact_cnf}, + {ST_L1_F6, EV_DEACT_IND, l1_deact_req_s}, + {ST_L1_F7, EV_DEACT_IND, l1_deact_req_s}, + {ST_L1_F8, EV_DEACT_IND, l1_deact_req_s}, + {ST_L1_F3, EV_POWER_UP, l1_power_up_s}, + {ST_L1_F4, EV_RSYNC_IND, l1_go_F5}, + {ST_L1_F6, EV_RSYNC_IND, l1_go_F8}, + {ST_L1_F7, EV_RSYNC_IND, l1_go_F8}, + {ST_L1_F3, EV_INFO2_IND, l1_info2_ind}, + {ST_L1_F4, EV_INFO2_IND, l1_info2_ind}, + {ST_L1_F5, EV_INFO2_IND, l1_info2_ind}, + {ST_L1_F7, EV_INFO2_IND, l1_info2_ind}, + {ST_L1_F8, EV_INFO2_IND, l1_info2_ind}, + {ST_L1_F3, EV_INFO4_IND, l1_info4_ind}, + {ST_L1_F4, EV_INFO4_IND, l1_info4_ind}, + {ST_L1_F5, EV_INFO4_IND, l1_info4_ind}, + {ST_L1_F6, EV_INFO4_IND, l1_info4_ind}, + {ST_L1_F8, EV_INFO4_IND, l1_info4_ind}, + {ST_L1_F3, EV_TIMER3, l1_timer3}, + {ST_L1_F4, EV_TIMER3, l1_timer3}, + {ST_L1_F5, EV_TIMER3, l1_timer3}, + {ST_L1_F6, EV_TIMER3, l1_timer3}, + {ST_L1_F8, EV_TIMER3, l1_timer3}, + {ST_L1_F7, EV_TIMER_ACT, l1_timer_act}, + {ST_L1_F3, EV_TIMER_DEACT, l1_timer_deact}, + {ST_L1_F4, EV_TIMER_DEACT, l1_timer_deact}, + {ST_L1_F5, EV_TIMER_DEACT, l1_timer_deact}, + {ST_L1_F6, EV_TIMER_DEACT, l1_timer_deact}, + {ST_L1_F7, EV_TIMER_DEACT, l1_timer_deact}, + {ST_L1_F8, EV_TIMER_DEACT, l1_timer_deact}, +}; + +#ifdef HISAX_UINTERFACE +static void +l1_deact_req_u(struct FsmInst *fi, int event, void *arg) +{ + struct PStack *st = fi->userdata; + + FsmChangeState(fi, ST_L1_RESET); + FsmRestartTimer(&st->l1.timer, 550, EV_TIMER_DEACT, NULL, 2); + test_and_set_bit(FLG_L1_DEACTTIMER, &st->l1.Flags); + st->l1.l1hw(st, HW_ENABLE | REQUEST, NULL); +} + +static void +l1_power_up_u(struct FsmInst *fi, int event, void *arg) +{ + struct PStack *st = fi->userdata; + + FsmRestartTimer(&st->l1.timer, TIMER3_VALUE, EV_TIMER3, NULL, 2); + test_and_set_bit(FLG_L1_T3RUN, &st->l1.Flags); +} + +static void +l1_info0_ind(struct FsmInst *fi, int event, void *arg) +{ + FsmChangeState(fi, ST_L1_DEACT); +} + +static void +l1_activate_u(struct FsmInst *fi, int event, void *arg) +{ + struct PStack *st = fi->userdata; + + st->l1.l1hw(st, HW_INFO1 | REQUEST, NULL); +} + +static struct FsmNode L1UFnList[] __initdata = +{ + {ST_L1_RESET, EV_DEACT_IND, l1_deact_req_u}, + {ST_L1_DEACT, EV_DEACT_IND, l1_deact_req_u}, + {ST_L1_SYNC2, EV_DEACT_IND, l1_deact_req_u}, + {ST_L1_TRANS, EV_DEACT_IND, l1_deact_req_u}, + {ST_L1_DEACT, EV_PH_ACTIVATE, l1_activate_u}, + {ST_L1_DEACT, EV_POWER_UP, l1_power_up_u}, + {ST_L1_DEACT, EV_INFO2_IND, l1_info2_ind}, + {ST_L1_TRANS, EV_INFO2_IND, l1_info2_ind}, + {ST_L1_RESET, EV_DEACT_CNF, l1_info0_ind}, + {ST_L1_DEACT, EV_INFO4_IND, l1_info4_ind}, + {ST_L1_SYNC2, EV_INFO4_IND, l1_info4_ind}, + {ST_L1_RESET, EV_INFO4_IND, l1_info4_ind}, + {ST_L1_DEACT, EV_TIMER3, l1_timer3}, + {ST_L1_SYNC2, EV_TIMER3, l1_timer3}, + {ST_L1_TRANS, EV_TIMER_ACT, l1_timer_act}, + {ST_L1_DEACT, EV_TIMER_DEACT, l1_timer_deact}, + {ST_L1_SYNC2, EV_TIMER_DEACT, l1_timer_deact}, + {ST_L1_RESET, EV_TIMER_DEACT, l1_timer_deact}, +}; + +#endif + +static void +l1b_activate(struct FsmInst *fi, int event, void *arg) +{ + struct PStack *st = fi->userdata; + + FsmChangeState(fi, ST_L1_WAIT_ACT); + FsmRestartTimer(&st->l1.timer, st->l1.delay, EV_TIMER_ACT, NULL, 2); +} + +static void +l1b_deactivate(struct FsmInst *fi, int event, void *arg) +{ + struct PStack *st = fi->userdata; + + FsmChangeState(fi, ST_L1_WAIT_DEACT); + FsmRestartTimer(&st->l1.timer, 10, EV_TIMER_DEACT, NULL, 2); +} + +static void +l1b_timer_act(struct FsmInst *fi, int event, void *arg) +{ + struct PStack *st = fi->userdata; + + FsmChangeState(fi, ST_L1_ACTIV); + st->l1.l1l2(st, PH_ACTIVATE | CONFIRM, NULL); +} + +static void +l1b_timer_deact(struct FsmInst *fi, int event, void *arg) +{ + struct PStack *st = fi->userdata; + + FsmChangeState(fi, ST_L1_NULL); + st->l2.l2l1(st, PH_DEACTIVATE | CONFIRM, NULL); +} + +static struct FsmNode L1BFnList[] __initdata = +{ + {ST_L1_NULL, EV_PH_ACTIVATE, l1b_activate}, + {ST_L1_WAIT_ACT, EV_TIMER_ACT, l1b_timer_act}, + {ST_L1_ACTIV, EV_PH_DEACTIVATE, l1b_deactivate}, + {ST_L1_WAIT_DEACT, EV_TIMER_DEACT, l1b_timer_deact}, +}; + +int __init +Isdnl1New(void) +{ + int retval; + + l1fsm_s.state_count = L1S_STATE_COUNT; + l1fsm_s.event_count = L1_EVENT_COUNT; + l1fsm_s.strEvent = strL1Event; + l1fsm_s.strState = strL1SState; + retval = FsmNew(&l1fsm_s, L1SFnList, ARRAY_SIZE(L1SFnList)); + if (retval) + return retval; + + l1fsm_b.state_count = L1B_STATE_COUNT; + l1fsm_b.event_count = L1_EVENT_COUNT; + l1fsm_b.strEvent = strL1Event; + l1fsm_b.strState = strL1BState; + retval = FsmNew(&l1fsm_b, L1BFnList, ARRAY_SIZE(L1BFnList)); + if (retval) { + FsmFree(&l1fsm_s); + return retval; + } +#ifdef HISAX_UINTERFACE + l1fsm_u.state_count = L1U_STATE_COUNT; + l1fsm_u.event_count = L1_EVENT_COUNT; + l1fsm_u.strEvent = strL1Event; + l1fsm_u.strState = strL1UState; + retval = FsmNew(&l1fsm_u, L1UFnList, ARRAY_SIZE(L1UFnList)); + if (retval) { + FsmFree(&l1fsm_s); + FsmFree(&l1fsm_b); + return retval; + } +#endif + return 0; +} + +void Isdnl1Free(void) +{ +#ifdef HISAX_UINTERFACE + FsmFree(&l1fsm_u); +#endif + FsmFree(&l1fsm_s); + FsmFree(&l1fsm_b); +} + +static void +dch_l2l1(struct PStack *st, int pr, void *arg) +{ + struct IsdnCardState *cs = (struct IsdnCardState *) st->l1.hardware; + + switch (pr) { + case (PH_DATA | REQUEST): + case (PH_PULL | REQUEST): + case (PH_PULL | INDICATION): + st->l1.l1hw(st, pr, arg); + break; + case (PH_ACTIVATE | REQUEST): + if (cs->debug) + debugl1(cs, "PH_ACTIVATE_REQ %s", + st->l1.l1m.fsm->strState[st->l1.l1m.state]); + if (test_bit(FLG_L1_ACTIVATED, &st->l1.Flags)) + st->l1.l1l2(st, PH_ACTIVATE | CONFIRM, NULL); + else { + test_and_set_bit(FLG_L1_ACTIVATING, &st->l1.Flags); + FsmEvent(&st->l1.l1m, EV_PH_ACTIVATE, arg); + } + break; + case (PH_TESTLOOP | REQUEST): + if (1 & (long) arg) + debugl1(cs, "PH_TEST_LOOP B1"); + if (2 & (long) arg) + debugl1(cs, "PH_TEST_LOOP B2"); + if (!(3 & (long) arg)) + debugl1(cs, "PH_TEST_LOOP DISABLED"); + st->l1.l1hw(st, HW_TESTLOOP | REQUEST, arg); + break; + default: + if (cs->debug) + debugl1(cs, "dch_l2l1 msg %04X unhandled", pr); + break; + } +} + +void +l1_msg(struct IsdnCardState *cs, int pr, void *arg) { + struct PStack *st; + + st = cs->stlist; + + while (st) { + switch (pr) { + case (HW_RESET | INDICATION): + FsmEvent(&st->l1.l1m, EV_RESET_IND, arg); + break; + case (HW_DEACTIVATE | CONFIRM): + FsmEvent(&st->l1.l1m, EV_DEACT_CNF, arg); + break; + case (HW_DEACTIVATE | INDICATION): + FsmEvent(&st->l1.l1m, EV_DEACT_IND, arg); + break; + case (HW_POWERUP | CONFIRM): + FsmEvent(&st->l1.l1m, EV_POWER_UP, arg); + break; + case (HW_RSYNC | INDICATION): + FsmEvent(&st->l1.l1m, EV_RSYNC_IND, arg); + break; + case (HW_INFO2 | INDICATION): + FsmEvent(&st->l1.l1m, EV_INFO2_IND, arg); + break; + case (HW_INFO4_P8 | INDICATION): + case (HW_INFO4_P10 | INDICATION): + FsmEvent(&st->l1.l1m, EV_INFO4_IND, arg); + break; + default: + if (cs->debug) + debugl1(cs, "%s %04X unhandled", __func__, pr); + break; + } + st = st->next; + } +} + +void +l1_msg_b(struct PStack *st, int pr, void *arg) { + switch (pr) { + case (PH_ACTIVATE | REQUEST): + FsmEvent(&st->l1.l1m, EV_PH_ACTIVATE, NULL); + break; + case (PH_DEACTIVATE | REQUEST): + FsmEvent(&st->l1.l1m, EV_PH_DEACTIVATE, NULL); + break; + } +} + +void +setstack_HiSax(struct PStack *st, struct IsdnCardState *cs) +{ + st->l1.hardware = cs; + st->protocol = cs->protocol; + st->l1.l1m.fsm = &l1fsm_s; + st->l1.l1m.state = ST_L1_F3; + st->l1.Flags = 0; +#ifdef HISAX_UINTERFACE + if (test_bit(FLG_HW_L1_UINT, &cs->HW_Flags)) { + st->l1.l1m.fsm = &l1fsm_u; + st->l1.l1m.state = ST_L1_RESET; + st->l1.Flags = FLG_L1_UINT; + } +#endif + st->l1.l1m.debug = cs->debug; + st->l1.l1m.userdata = st; + st->l1.l1m.userint = 0; + st->l1.l1m.printdebug = l1m_debug; + FsmInitTimer(&st->l1.l1m, &st->l1.timer); + setstack_tei(st); + setstack_manager(st); + st->l1.stlistp = &(cs->stlist); + st->l2.l2l1 = dch_l2l1; + if (cs->setstack_d) + cs->setstack_d(st, cs); +} + +void +setstack_l1_B(struct PStack *st) +{ + struct IsdnCardState *cs = st->l1.hardware; + + st->l1.l1m.fsm = &l1fsm_b; + st->l1.l1m.state = ST_L1_NULL; + st->l1.l1m.debug = cs->debug; + st->l1.l1m.userdata = st; + st->l1.l1m.userint = 0; + st->l1.l1m.printdebug = l1m_debug; + st->l1.Flags = 0; + FsmInitTimer(&st->l1.l1m, &st->l1.timer); +} diff --git a/drivers/isdn/hisax/isdnl1.h b/drivers/isdn/hisax/isdnl1.h new file mode 100644 index 000000000..66ddcab19 --- /dev/null +++ b/drivers/isdn/hisax/isdnl1.h @@ -0,0 +1,32 @@ +/* $Id: isdnl1.h,v 2.12.2.3 2004/02/11 13:21:34 keil Exp $ + * + * Layer 1 defines + * + * This software may be used and distributed according to the terms + * of the GNU General Public License, incorporated herein by reference. + * + */ + +#define D_RCVBUFREADY 0 +#define D_XMTBUFREADY 1 +#define D_L1STATECHANGE 2 +#define D_CLEARBUSY 3 +#define D_RX_MON0 4 +#define D_RX_MON1 5 +#define D_TX_MON0 6 +#define D_TX_MON1 7 +#define E_RCVBUFREADY 8 + +#define B_RCVBUFREADY 0 +#define B_XMTBUFREADY 1 +#define B_ACKPENDING 2 + +__printf(2, 3) +void debugl1(struct IsdnCardState *cs, char *fmt, ...); +void DChannel_proc_xmt(struct IsdnCardState *cs); +void DChannel_proc_rcv(struct IsdnCardState *cs); +void l1_msg(struct IsdnCardState *cs, int pr, void *arg); +void l1_msg_b(struct PStack *st, int pr, void *arg); +void Logl2Frame(struct IsdnCardState *cs, struct sk_buff *skb, char *buf, + int dir); +void BChannel_bh(struct work_struct *work); diff --git a/drivers/isdn/hisax/isdnl2.c b/drivers/isdn/hisax/isdnl2.c new file mode 100644 index 000000000..1a40ed04c --- /dev/null +++ b/drivers/isdn/hisax/isdnl2.c @@ -0,0 +1,1839 @@ +/* $Id: isdnl2.c,v 2.30.2.4 2004/02/11 13:21:34 keil Exp $ + * + * Author Karsten Keil + * based on the teles driver from Jan den Ouden + * Copyright by Karsten Keil <keil@isdn4linux.de> + * + * This software may be used and distributed according to the terms + * of the GNU General Public License, incorporated herein by reference. + * + * For changes and modifications please read + * Documentation/isdn/HiSax.cert + * + * Thanks to Jan den Ouden + * Fritz Elfert + * + */ + +#include <linux/init.h> +#include <linux/gfp.h> +#include "hisax.h" +#include "isdnl2.h" + +const char *l2_revision = "$Revision: 2.30.2.4 $"; + +static void l2m_debug(struct FsmInst *fi, char *fmt, ...); + +static struct Fsm l2fsm; + +enum { + ST_L2_1, + ST_L2_2, + ST_L2_3, + ST_L2_4, + ST_L2_5, + ST_L2_6, + ST_L2_7, + ST_L2_8, +}; + +#define L2_STATE_COUNT (ST_L2_8 + 1) + +static char *strL2State[] = +{ + "ST_L2_1", + "ST_L2_2", + "ST_L2_3", + "ST_L2_4", + "ST_L2_5", + "ST_L2_6", + "ST_L2_7", + "ST_L2_8", +}; + +enum { + EV_L2_UI, + EV_L2_SABME, + EV_L2_DISC, + EV_L2_DM, + EV_L2_UA, + EV_L2_FRMR, + EV_L2_SUPER, + EV_L2_I, + EV_L2_DL_DATA, + EV_L2_ACK_PULL, + EV_L2_DL_UNIT_DATA, + EV_L2_DL_ESTABLISH_REQ, + EV_L2_DL_RELEASE_REQ, + EV_L2_MDL_ASSIGN, + EV_L2_MDL_REMOVE, + EV_L2_MDL_ERROR, + EV_L1_DEACTIVATE, + EV_L2_T200, + EV_L2_T203, + EV_L2_SET_OWN_BUSY, + EV_L2_CLEAR_OWN_BUSY, + EV_L2_FRAME_ERROR, +}; + +#define L2_EVENT_COUNT (EV_L2_FRAME_ERROR + 1) + +static char *strL2Event[] = +{ + "EV_L2_UI", + "EV_L2_SABME", + "EV_L2_DISC", + "EV_L2_DM", + "EV_L2_UA", + "EV_L2_FRMR", + "EV_L2_SUPER", + "EV_L2_I", + "EV_L2_DL_DATA", + "EV_L2_ACK_PULL", + "EV_L2_DL_UNIT_DATA", + "EV_L2_DL_ESTABLISH_REQ", + "EV_L2_DL_RELEASE_REQ", + "EV_L2_MDL_ASSIGN", + "EV_L2_MDL_REMOVE", + "EV_L2_MDL_ERROR", + "EV_L1_DEACTIVATE", + "EV_L2_T200", + "EV_L2_T203", + "EV_L2_SET_OWN_BUSY", + "EV_L2_CLEAR_OWN_BUSY", + "EV_L2_FRAME_ERROR", +}; + +static int l2addrsize(struct Layer2 *l2); + +static void +set_peer_busy(struct Layer2 *l2) { + test_and_set_bit(FLG_PEER_BUSY, &l2->flag); + if (!skb_queue_empty(&l2->i_queue) || + !skb_queue_empty(&l2->ui_queue)) + test_and_set_bit(FLG_L2BLOCK, &l2->flag); +} + +static void +clear_peer_busy(struct Layer2 *l2) { + if (test_and_clear_bit(FLG_PEER_BUSY, &l2->flag)) + test_and_clear_bit(FLG_L2BLOCK, &l2->flag); +} + +static void +InitWin(struct Layer2 *l2) +{ + int i; + + for (i = 0; i < MAX_WINDOW; i++) + l2->windowar[i] = NULL; +} + +static int +freewin1(struct Layer2 *l2) +{ + int i, cnt = 0; + + for (i = 0; i < MAX_WINDOW; i++) { + if (l2->windowar[i]) { + cnt++; + dev_kfree_skb(l2->windowar[i]); + l2->windowar[i] = NULL; + } + } + return cnt; +} + +static inline void +freewin(struct PStack *st) +{ + freewin1(&st->l2); +} + +static void +ReleaseWin(struct Layer2 *l2) +{ + int cnt; + + if ((cnt = freewin1(l2))) + printk(KERN_WARNING "isdl2 freed %d skbuffs in release\n", cnt); +} + +static inline unsigned int +cansend(struct PStack *st) +{ + unsigned int p1; + + if (test_bit(FLG_MOD128, &st->l2.flag)) + p1 = (st->l2.vs - st->l2.va) % 128; + else + p1 = (st->l2.vs - st->l2.va) % 8; + return ((p1 < st->l2.window) && !test_bit(FLG_PEER_BUSY, &st->l2.flag)); +} + +static inline void +clear_exception(struct Layer2 *l2) +{ + test_and_clear_bit(FLG_ACK_PEND, &l2->flag); + test_and_clear_bit(FLG_REJEXC, &l2->flag); + test_and_clear_bit(FLG_OWN_BUSY, &l2->flag); + clear_peer_busy(l2); +} + +static inline int +l2headersize(struct Layer2 *l2, int ui) +{ + return (((test_bit(FLG_MOD128, &l2->flag) && (!ui)) ? 2 : 1) + + (test_bit(FLG_LAPD, &l2->flag) ? 2 : 1)); +} + +inline int +l2addrsize(struct Layer2 *l2) +{ + return (test_bit(FLG_LAPD, &l2->flag) ? 2 : 1); +} + +static int +sethdraddr(struct Layer2 *l2, u_char *header, int rsp) +{ + u_char *ptr = header; + int crbit = rsp; + + if (test_bit(FLG_LAPD, &l2->flag)) { + *ptr++ = (l2->sap << 2) | (rsp ? 2 : 0); + *ptr++ = (l2->tei << 1) | 1; + return (2); + } else { + if (test_bit(FLG_ORIG, &l2->flag)) + crbit = !crbit; + if (crbit) + *ptr++ = 1; + else + *ptr++ = 3; + return (1); + } +} + +static inline void +enqueue_super(struct PStack *st, + struct sk_buff *skb) +{ + if (test_bit(FLG_LAPB, &st->l2.flag)) + st->l1.bcs->tx_cnt += skb->len; + st->l2.l2l1(st, PH_DATA | REQUEST, skb); +} + +#define enqueue_ui(a, b) enqueue_super(a, b) + +static inline int +IsUI(u_char *data) +{ + return ((data[0] & 0xef) == UI); +} + +static inline int +IsUA(u_char *data) +{ + return ((data[0] & 0xef) == UA); +} + +static inline int +IsDM(u_char *data) +{ + return ((data[0] & 0xef) == DM); +} + +static inline int +IsDISC(u_char *data) +{ + return ((data[0] & 0xef) == DISC); +} + +static inline int +IsSFrame(u_char *data, struct PStack *st) +{ + register u_char d = *data; + + if (!test_bit(FLG_MOD128, &st->l2.flag)) + d &= 0xf; + return (((d & 0xf3) == 1) && ((d & 0x0c) != 0x0c)); +} + +static inline int +IsSABME(u_char *data, struct PStack *st) +{ + u_char d = data[0] & ~0x10; + + return (test_bit(FLG_MOD128, &st->l2.flag) ? d == SABME : d == SABM); +} + +static inline int +IsREJ(u_char *data, struct PStack *st) +{ + return (test_bit(FLG_MOD128, &st->l2.flag) ? data[0] == REJ : (data[0] & 0xf) == REJ); +} + +static inline int +IsFRMR(u_char *data) +{ + return ((data[0] & 0xef) == FRMR); +} + +static inline int +IsRNR(u_char *data, struct PStack *st) +{ + return (test_bit(FLG_MOD128, &st->l2.flag) ? data[0] == RNR : (data[0] & 0xf) == RNR); +} + +static int +iframe_error(struct PStack *st, struct sk_buff *skb) +{ + int i = l2addrsize(&st->l2) + (test_bit(FLG_MOD128, &st->l2.flag) ? 2 : 1); + int rsp = *skb->data & 0x2; + + if (test_bit(FLG_ORIG, &st->l2.flag)) + rsp = !rsp; + + if (rsp) + return 'L'; + + + if (skb->len < i) + return 'N'; + + if ((skb->len - i) > st->l2.maxlen) + return 'O'; + + + return 0; +} + +static int +super_error(struct PStack *st, struct sk_buff *skb) +{ + if (skb->len != l2addrsize(&st->l2) + + (test_bit(FLG_MOD128, &st->l2.flag) ? 2 : 1)) + return 'N'; + + return 0; +} + +static int +unnum_error(struct PStack *st, struct sk_buff *skb, int wantrsp) +{ + int rsp = (*skb->data & 0x2) >> 1; + if (test_bit(FLG_ORIG, &st->l2.flag)) + rsp = !rsp; + + if (rsp != wantrsp) + return 'L'; + + if (skb->len != l2addrsize(&st->l2) + 1) + return 'N'; + + return 0; +} + +static int +UI_error(struct PStack *st, struct sk_buff *skb) +{ + int rsp = *skb->data & 0x2; + if (test_bit(FLG_ORIG, &st->l2.flag)) + rsp = !rsp; + + if (rsp) + return 'L'; + + if (skb->len > st->l2.maxlen + l2addrsize(&st->l2) + 1) + return 'O'; + + return 0; +} + +static int +FRMR_error(struct PStack *st, struct sk_buff *skb) +{ + int headers = l2addrsize(&st->l2) + 1; + u_char *datap = skb->data + headers; + int rsp = *skb->data & 0x2; + + if (test_bit(FLG_ORIG, &st->l2.flag)) + rsp = !rsp; + + if (!rsp) + return 'L'; + + if (test_bit(FLG_MOD128, &st->l2.flag)) { + if (skb->len < headers + 5) + return 'N'; + else + l2m_debug(&st->l2.l2m, "FRMR information %2x %2x %2x %2x %2x", + datap[0], datap[1], datap[2], + datap[3], datap[4]); + } else { + if (skb->len < headers + 3) + return 'N'; + else + l2m_debug(&st->l2.l2m, "FRMR information %2x %2x %2x", + datap[0], datap[1], datap[2]); + } + + return 0; +} + +static unsigned int +legalnr(struct PStack *st, unsigned int nr) +{ + struct Layer2 *l2 = &st->l2; + + if (test_bit(FLG_MOD128, &l2->flag)) + return ((nr - l2->va) % 128) <= ((l2->vs - l2->va) % 128); + else + return ((nr - l2->va) % 8) <= ((l2->vs - l2->va) % 8); +} + +static void +setva(struct PStack *st, unsigned int nr) +{ + struct Layer2 *l2 = &st->l2; + int len; + u_long flags; + + spin_lock_irqsave(&l2->lock, flags); + while (l2->va != nr) { + (l2->va)++; + if (test_bit(FLG_MOD128, &l2->flag)) + l2->va %= 128; + else + l2->va %= 8; + len = l2->windowar[l2->sow]->len; + if (PACKET_NOACK == l2->windowar[l2->sow]->pkt_type) + len = -1; + dev_kfree_skb(l2->windowar[l2->sow]); + l2->windowar[l2->sow] = NULL; + l2->sow = (l2->sow + 1) % l2->window; + spin_unlock_irqrestore(&l2->lock, flags); + if (test_bit(FLG_LLI_L2WAKEUP, &st->lli.flag) && (len >= 0)) + lli_writewakeup(st, len); + spin_lock_irqsave(&l2->lock, flags); + } + spin_unlock_irqrestore(&l2->lock, flags); +} + +static void +send_uframe(struct PStack *st, u_char cmd, u_char cr) +{ + struct sk_buff *skb; + u_char tmp[MAX_HEADER_LEN]; + int i; + + i = sethdraddr(&st->l2, tmp, cr); + tmp[i++] = cmd; + if (!(skb = alloc_skb(i, GFP_ATOMIC))) { + printk(KERN_WARNING "isdl2 can't alloc sbbuff for send_uframe\n"); + return; + } + skb_put_data(skb, tmp, i); + enqueue_super(st, skb); +} + +static inline u_char +get_PollFlag(struct PStack *st, struct sk_buff *skb) +{ + return (skb->data[l2addrsize(&(st->l2))] & 0x10); +} + +static inline u_char +get_PollFlagFree(struct PStack *st, struct sk_buff *skb) +{ + u_char PF; + + PF = get_PollFlag(st, skb); + dev_kfree_skb(skb); + return (PF); +} + +static inline void +start_t200(struct PStack *st, int i) +{ + FsmAddTimer(&st->l2.t200, st->l2.T200, EV_L2_T200, NULL, i); + test_and_set_bit(FLG_T200_RUN, &st->l2.flag); +} + +static inline void +restart_t200(struct PStack *st, int i) +{ + FsmRestartTimer(&st->l2.t200, st->l2.T200, EV_L2_T200, NULL, i); + test_and_set_bit(FLG_T200_RUN, &st->l2.flag); +} + +static inline void +stop_t200(struct PStack *st, int i) +{ + if (test_and_clear_bit(FLG_T200_RUN, &st->l2.flag)) + FsmDelTimer(&st->l2.t200, i); +} + +static inline void +st5_dl_release_l2l3(struct PStack *st) +{ + int pr; + + if (test_and_clear_bit(FLG_PEND_REL, &st->l2.flag)) + pr = DL_RELEASE | CONFIRM; + else + pr = DL_RELEASE | INDICATION; + + st->l2.l2l3(st, pr, NULL); +} + +static inline void +lapb_dl_release_l2l3(struct PStack *st, int f) +{ + if (test_bit(FLG_LAPB, &st->l2.flag)) + st->l2.l2l1(st, PH_DEACTIVATE | REQUEST, NULL); + st->l2.l2l3(st, DL_RELEASE | f, NULL); +} + +static void +establishlink(struct FsmInst *fi) +{ + struct PStack *st = fi->userdata; + u_char cmd; + + clear_exception(&st->l2); + st->l2.rc = 0; + cmd = (test_bit(FLG_MOD128, &st->l2.flag) ? SABME : SABM) | 0x10; + send_uframe(st, cmd, CMD); + FsmDelTimer(&st->l2.t203, 1); + restart_t200(st, 1); + test_and_clear_bit(FLG_PEND_REL, &st->l2.flag); + freewin(st); + FsmChangeState(fi, ST_L2_5); +} + +static void +l2_mdl_error_ua(struct FsmInst *fi, int event, void *arg) +{ + struct sk_buff *skb = arg; + struct PStack *st = fi->userdata; + + if (get_PollFlagFree(st, skb)) + st->ma.layer(st, MDL_ERROR | INDICATION, (void *) 'C'); + else + st->ma.layer(st, MDL_ERROR | INDICATION, (void *) 'D'); +} + +static void +l2_mdl_error_dm(struct FsmInst *fi, int event, void *arg) +{ + struct sk_buff *skb = arg; + struct PStack *st = fi->userdata; + + if (get_PollFlagFree(st, skb)) + st->ma.layer(st, MDL_ERROR | INDICATION, (void *) 'B'); + else { + st->ma.layer(st, MDL_ERROR | INDICATION, (void *) 'E'); + establishlink(fi); + test_and_clear_bit(FLG_L3_INIT, &st->l2.flag); + } +} + +static void +l2_st8_mdl_error_dm(struct FsmInst *fi, int event, void *arg) +{ + struct sk_buff *skb = arg; + struct PStack *st = fi->userdata; + + if (get_PollFlagFree(st, skb)) + st->ma.layer(st, MDL_ERROR | INDICATION, (void *) 'B'); + else { + st->ma.layer(st, MDL_ERROR | INDICATION, (void *) 'E'); + } + establishlink(fi); + test_and_clear_bit(FLG_L3_INIT, &st->l2.flag); +} + +static void +l2_go_st3(struct FsmInst *fi, int event, void *arg) +{ + FsmChangeState(fi, ST_L2_3); +} + +static void +l2_mdl_assign(struct FsmInst *fi, int event, void *arg) +{ + struct PStack *st = fi->userdata; + + FsmChangeState(fi, ST_L2_3); + st->l2.l2tei(st, MDL_ASSIGN | INDICATION, NULL); +} + +static void +l2_queue_ui_assign(struct FsmInst *fi, int event, void *arg) +{ + struct PStack *st = fi->userdata; + struct sk_buff *skb = arg; + + skb_queue_tail(&st->l2.ui_queue, skb); + FsmChangeState(fi, ST_L2_2); + st->l2.l2tei(st, MDL_ASSIGN | INDICATION, NULL); +} + +static void +l2_queue_ui(struct FsmInst *fi, int event, void *arg) +{ + struct PStack *st = fi->userdata; + struct sk_buff *skb = arg; + + skb_queue_tail(&st->l2.ui_queue, skb); +} + +static void +tx_ui(struct PStack *st) +{ + struct sk_buff *skb; + u_char header[MAX_HEADER_LEN]; + int i; + + i = sethdraddr(&(st->l2), header, CMD); + header[i++] = UI; + while ((skb = skb_dequeue(&st->l2.ui_queue))) { + memcpy(skb_push(skb, i), header, i); + enqueue_ui(st, skb); + } +} + +static void +l2_send_ui(struct FsmInst *fi, int event, void *arg) +{ + struct PStack *st = fi->userdata; + struct sk_buff *skb = arg; + + skb_queue_tail(&st->l2.ui_queue, skb); + tx_ui(st); +} + +static void +l2_got_ui(struct FsmInst *fi, int event, void *arg) +{ + struct PStack *st = fi->userdata; + struct sk_buff *skb = arg; + + skb_pull(skb, l2headersize(&st->l2, 1)); + st->l2.l2l3(st, DL_UNIT_DATA | INDICATION, skb); +/* ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + * in states 1-3 for broadcast + */ + + +} + +static void +l2_establish(struct FsmInst *fi, int event, void *arg) +{ + struct PStack *st = fi->userdata; + + establishlink(fi); + test_and_set_bit(FLG_L3_INIT, &st->l2.flag); +} + +static void +l2_discard_i_setl3(struct FsmInst *fi, int event, void *arg) +{ + struct PStack *st = fi->userdata; + + skb_queue_purge(&st->l2.i_queue); + test_and_set_bit(FLG_L3_INIT, &st->l2.flag); + test_and_clear_bit(FLG_PEND_REL, &st->l2.flag); +} + +static void +l2_l3_reestablish(struct FsmInst *fi, int event, void *arg) +{ + struct PStack *st = fi->userdata; + + skb_queue_purge(&st->l2.i_queue); + establishlink(fi); + test_and_set_bit(FLG_L3_INIT, &st->l2.flag); +} + +static void +l2_release(struct FsmInst *fi, int event, void *arg) +{ + struct PStack *st = fi->userdata; + + st->l2.l2l3(st, DL_RELEASE | CONFIRM, NULL); +} + +static void +l2_pend_rel(struct FsmInst *fi, int event, void *arg) +{ + struct PStack *st = fi->userdata; + + test_and_set_bit(FLG_PEND_REL, &st->l2.flag); +} + +static void +l2_disconnect(struct FsmInst *fi, int event, void *arg) +{ + struct PStack *st = fi->userdata; + + skb_queue_purge(&st->l2.i_queue); + freewin(st); + FsmChangeState(fi, ST_L2_6); + st->l2.rc = 0; + send_uframe(st, DISC | 0x10, CMD); + FsmDelTimer(&st->l2.t203, 1); + restart_t200(st, 2); +} + +static void +l2_start_multi(struct FsmInst *fi, int event, void *arg) +{ + struct PStack *st = fi->userdata; + struct sk_buff *skb = arg; + + send_uframe(st, UA | get_PollFlagFree(st, skb), RSP); + + clear_exception(&st->l2); + st->l2.vs = 0; + st->l2.va = 0; + st->l2.vr = 0; + st->l2.sow = 0; + FsmChangeState(fi, ST_L2_7); + FsmAddTimer(&st->l2.t203, st->l2.T203, EV_L2_T203, NULL, 3); + + st->l2.l2l3(st, DL_ESTABLISH | INDICATION, NULL); +} + +static void +l2_send_UA(struct FsmInst *fi, int event, void *arg) +{ + struct PStack *st = fi->userdata; + struct sk_buff *skb = arg; + + send_uframe(st, UA | get_PollFlagFree(st, skb), RSP); +} + +static void +l2_send_DM(struct FsmInst *fi, int event, void *arg) +{ + struct PStack *st = fi->userdata; + struct sk_buff *skb = arg; + + send_uframe(st, DM | get_PollFlagFree(st, skb), RSP); +} + +static void +l2_restart_multi(struct FsmInst *fi, int event, void *arg) +{ + struct PStack *st = fi->userdata; + struct sk_buff *skb = arg; + int est = 0, state; + + state = fi->state; + + send_uframe(st, UA | get_PollFlagFree(st, skb), RSP); + + st->ma.layer(st, MDL_ERROR | INDICATION, (void *) 'F'); + + if (st->l2.vs != st->l2.va) { + skb_queue_purge(&st->l2.i_queue); + est = 1; + } + + clear_exception(&st->l2); + st->l2.vs = 0; + st->l2.va = 0; + st->l2.vr = 0; + st->l2.sow = 0; + FsmChangeState(fi, ST_L2_7); + stop_t200(st, 3); + FsmRestartTimer(&st->l2.t203, st->l2.T203, EV_L2_T203, NULL, 3); + + if (est) + st->l2.l2l3(st, DL_ESTABLISH | INDICATION, NULL); + + if ((ST_L2_7 == state) || (ST_L2_8 == state)) + if (!skb_queue_empty(&st->l2.i_queue) && cansend(st)) + st->l2.l2l1(st, PH_PULL | REQUEST, NULL); +} + +static void +l2_stop_multi(struct FsmInst *fi, int event, void *arg) +{ + struct PStack *st = fi->userdata; + struct sk_buff *skb = arg; + + FsmChangeState(fi, ST_L2_4); + FsmDelTimer(&st->l2.t203, 3); + stop_t200(st, 4); + + send_uframe(st, UA | get_PollFlagFree(st, skb), RSP); + + skb_queue_purge(&st->l2.i_queue); + freewin(st); + lapb_dl_release_l2l3(st, INDICATION); +} + +static void +l2_connected(struct FsmInst *fi, int event, void *arg) +{ + struct PStack *st = fi->userdata; + struct sk_buff *skb = arg; + int pr = -1; + + if (!get_PollFlag(st, skb)) { + l2_mdl_error_ua(fi, event, arg); + return; + } + dev_kfree_skb(skb); + + if (test_and_clear_bit(FLG_PEND_REL, &st->l2.flag)) + l2_disconnect(fi, event, arg); + + if (test_and_clear_bit(FLG_L3_INIT, &st->l2.flag)) { + pr = DL_ESTABLISH | CONFIRM; + } else if (st->l2.vs != st->l2.va) { + skb_queue_purge(&st->l2.i_queue); + pr = DL_ESTABLISH | INDICATION; + } + + stop_t200(st, 5); + + st->l2.vr = 0; + st->l2.vs = 0; + st->l2.va = 0; + st->l2.sow = 0; + FsmChangeState(fi, ST_L2_7); + FsmAddTimer(&st->l2.t203, st->l2.T203, EV_L2_T203, NULL, 4); + + if (pr != -1) + st->l2.l2l3(st, pr, NULL); + + if (!skb_queue_empty(&st->l2.i_queue) && cansend(st)) + st->l2.l2l1(st, PH_PULL | REQUEST, NULL); +} + +static void +l2_released(struct FsmInst *fi, int event, void *arg) +{ + struct PStack *st = fi->userdata; + struct sk_buff *skb = arg; + + if (!get_PollFlag(st, skb)) { + l2_mdl_error_ua(fi, event, arg); + return; + } + dev_kfree_skb(skb); + + stop_t200(st, 6); + lapb_dl_release_l2l3(st, CONFIRM); + FsmChangeState(fi, ST_L2_4); +} + +static void +l2_reestablish(struct FsmInst *fi, int event, void *arg) +{ + struct PStack *st = fi->userdata; + struct sk_buff *skb = arg; + + if (!get_PollFlagFree(st, skb)) { + establishlink(fi); + test_and_set_bit(FLG_L3_INIT, &st->l2.flag); + } +} + +static void +l2_st5_dm_release(struct FsmInst *fi, int event, void *arg) +{ + struct PStack *st = fi->userdata; + struct sk_buff *skb = arg; + + if (get_PollFlagFree(st, skb)) { + stop_t200(st, 7); + if (!test_bit(FLG_L3_INIT, &st->l2.flag)) + skb_queue_purge(&st->l2.i_queue); + if (test_bit(FLG_LAPB, &st->l2.flag)) + st->l2.l2l1(st, PH_DEACTIVATE | REQUEST, NULL); + st5_dl_release_l2l3(st); + FsmChangeState(fi, ST_L2_4); + } +} + +static void +l2_st6_dm_release(struct FsmInst *fi, int event, void *arg) +{ + struct PStack *st = fi->userdata; + struct sk_buff *skb = arg; + + if (get_PollFlagFree(st, skb)) { + stop_t200(st, 8); + lapb_dl_release_l2l3(st, CONFIRM); + FsmChangeState(fi, ST_L2_4); + } +} + +static inline void +enquiry_cr(struct PStack *st, u_char typ, u_char cr, u_char pf) +{ + struct sk_buff *skb; + struct Layer2 *l2; + u_char tmp[MAX_HEADER_LEN]; + int i; + + l2 = &st->l2; + i = sethdraddr(l2, tmp, cr); + if (test_bit(FLG_MOD128, &l2->flag)) { + tmp[i++] = typ; + tmp[i++] = (l2->vr << 1) | (pf ? 1 : 0); + } else + tmp[i++] = (l2->vr << 5) | typ | (pf ? 0x10 : 0); + if (!(skb = alloc_skb(i, GFP_ATOMIC))) { + printk(KERN_WARNING "isdl2 can't alloc sbbuff for enquiry_cr\n"); + return; + } + skb_put_data(skb, tmp, i); + enqueue_super(st, skb); +} + +static inline void +enquiry_response(struct PStack *st) +{ + if (test_bit(FLG_OWN_BUSY, &st->l2.flag)) + enquiry_cr(st, RNR, RSP, 1); + else + enquiry_cr(st, RR, RSP, 1); + test_and_clear_bit(FLG_ACK_PEND, &st->l2.flag); +} + +static inline void +transmit_enquiry(struct PStack *st) +{ + if (test_bit(FLG_OWN_BUSY, &st->l2.flag)) + enquiry_cr(st, RNR, CMD, 1); + else + enquiry_cr(st, RR, CMD, 1); + test_and_clear_bit(FLG_ACK_PEND, &st->l2.flag); + start_t200(st, 9); +} + + +static void +nrerrorrecovery(struct FsmInst *fi) +{ + struct PStack *st = fi->userdata; + + st->ma.layer(st, MDL_ERROR | INDICATION, (void *) 'J'); + establishlink(fi); + test_and_clear_bit(FLG_L3_INIT, &st->l2.flag); +} + +static void +invoke_retransmission(struct PStack *st, unsigned int nr) +{ + struct Layer2 *l2 = &st->l2; + u_int p1; + u_long flags; + + spin_lock_irqsave(&l2->lock, flags); + if (l2->vs != nr) { + while (l2->vs != nr) { + (l2->vs)--; + if (test_bit(FLG_MOD128, &l2->flag)) { + l2->vs %= 128; + p1 = (l2->vs - l2->va) % 128; + } else { + l2->vs %= 8; + p1 = (l2->vs - l2->va) % 8; + } + p1 = (p1 + l2->sow) % l2->window; + if (test_bit(FLG_LAPB, &l2->flag)) + st->l1.bcs->tx_cnt += l2->windowar[p1]->len + l2headersize(l2, 0); + skb_queue_head(&l2->i_queue, l2->windowar[p1]); + l2->windowar[p1] = NULL; + } + spin_unlock_irqrestore(&l2->lock, flags); + st->l2.l2l1(st, PH_PULL | REQUEST, NULL); + return; + } + spin_unlock_irqrestore(&l2->lock, flags); +} + +static void +l2_st7_got_super(struct FsmInst *fi, int event, void *arg) +{ + struct PStack *st = fi->userdata; + struct sk_buff *skb = arg; + int PollFlag, rsp, typ = RR; + unsigned int nr; + struct Layer2 *l2 = &st->l2; + + rsp = *skb->data & 0x2; + if (test_bit(FLG_ORIG, &l2->flag)) + rsp = !rsp; + + skb_pull(skb, l2addrsize(l2)); + if (IsRNR(skb->data, st)) { + set_peer_busy(l2); + typ = RNR; + } else + clear_peer_busy(l2); + if (IsREJ(skb->data, st)) + typ = REJ; + + if (test_bit(FLG_MOD128, &l2->flag)) { + PollFlag = (skb->data[1] & 0x1) == 0x1; + nr = skb->data[1] >> 1; + } else { + PollFlag = (skb->data[0] & 0x10); + nr = (skb->data[0] >> 5) & 0x7; + } + dev_kfree_skb(skb); + + if (PollFlag) { + if (rsp) + st->ma.layer(st, MDL_ERROR | INDICATION, (void *) 'A'); + else + enquiry_response(st); + } + if (legalnr(st, nr)) { + if (typ == REJ) { + setva(st, nr); + invoke_retransmission(st, nr); + stop_t200(st, 10); + if (FsmAddTimer(&st->l2.t203, st->l2.T203, + EV_L2_T203, NULL, 6)) + l2m_debug(&st->l2.l2m, "Restart T203 ST7 REJ"); + } else if ((nr == l2->vs) && (typ == RR)) { + setva(st, nr); + stop_t200(st, 11); + FsmRestartTimer(&st->l2.t203, st->l2.T203, + EV_L2_T203, NULL, 7); + } else if ((l2->va != nr) || (typ == RNR)) { + setva(st, nr); + if (typ != RR) FsmDelTimer(&st->l2.t203, 9); + restart_t200(st, 12); + } + if (!skb_queue_empty(&st->l2.i_queue) && (typ == RR)) + st->l2.l2l1(st, PH_PULL | REQUEST, NULL); + } else + nrerrorrecovery(fi); +} + +static void +l2_feed_i_if_reest(struct FsmInst *fi, int event, void *arg) +{ + struct PStack *st = fi->userdata; + struct sk_buff *skb = arg; + + if (test_bit(FLG_LAPB, &st->l2.flag)) + st->l1.bcs->tx_cnt += skb->len + l2headersize(&st->l2, 0); + if (!test_bit(FLG_L3_INIT, &st->l2.flag)) + skb_queue_tail(&st->l2.i_queue, skb); + else + dev_kfree_skb(skb); +} + +static void +l2_feed_i_pull(struct FsmInst *fi, int event, void *arg) +{ + struct PStack *st = fi->userdata; + struct sk_buff *skb = arg; + + if (test_bit(FLG_LAPB, &st->l2.flag)) + st->l1.bcs->tx_cnt += skb->len + l2headersize(&st->l2, 0); + skb_queue_tail(&st->l2.i_queue, skb); + st->l2.l2l1(st, PH_PULL | REQUEST, NULL); +} + +static void +l2_feed_iqueue(struct FsmInst *fi, int event, void *arg) +{ + struct PStack *st = fi->userdata; + struct sk_buff *skb = arg; + + if (test_bit(FLG_LAPB, &st->l2.flag)) + st->l1.bcs->tx_cnt += skb->len + l2headersize(&st->l2, 0); + skb_queue_tail(&st->l2.i_queue, skb); +} + +static void +l2_got_iframe(struct FsmInst *fi, int event, void *arg) +{ + struct PStack *st = fi->userdata; + struct sk_buff *skb = arg; + struct Layer2 *l2 = &(st->l2); + int PollFlag, ns, i; + unsigned int nr; + + i = l2addrsize(l2); + if (test_bit(FLG_MOD128, &l2->flag)) { + PollFlag = ((skb->data[i + 1] & 0x1) == 0x1); + ns = skb->data[i] >> 1; + nr = (skb->data[i + 1] >> 1) & 0x7f; + } else { + PollFlag = (skb->data[i] & 0x10); + ns = (skb->data[i] >> 1) & 0x7; + nr = (skb->data[i] >> 5) & 0x7; + } + if (test_bit(FLG_OWN_BUSY, &l2->flag)) { + dev_kfree_skb(skb); + if (PollFlag) enquiry_response(st); + } else if (l2->vr == ns) { + (l2->vr)++; + if (test_bit(FLG_MOD128, &l2->flag)) + l2->vr %= 128; + else + l2->vr %= 8; + test_and_clear_bit(FLG_REJEXC, &l2->flag); + + if (PollFlag) + enquiry_response(st); + else + test_and_set_bit(FLG_ACK_PEND, &l2->flag); + skb_pull(skb, l2headersize(l2, 0)); + st->l2.l2l3(st, DL_DATA | INDICATION, skb); + } else { + /* n(s)!=v(r) */ + dev_kfree_skb(skb); + if (test_and_set_bit(FLG_REJEXC, &l2->flag)) { + if (PollFlag) + enquiry_response(st); + } else { + enquiry_cr(st, REJ, RSP, PollFlag); + test_and_clear_bit(FLG_ACK_PEND, &l2->flag); + } + } + + if (legalnr(st, nr)) { + if (!test_bit(FLG_PEER_BUSY, &st->l2.flag) && (fi->state == ST_L2_7)) { + if (nr == st->l2.vs) { + stop_t200(st, 13); + FsmRestartTimer(&st->l2.t203, st->l2.T203, + EV_L2_T203, NULL, 7); + } else if (nr != st->l2.va) + restart_t200(st, 14); + } + setva(st, nr); + } else { + nrerrorrecovery(fi); + return; + } + + if (!skb_queue_empty(&st->l2.i_queue) && (fi->state == ST_L2_7)) + st->l2.l2l1(st, PH_PULL | REQUEST, NULL); + if (test_and_clear_bit(FLG_ACK_PEND, &st->l2.flag)) + enquiry_cr(st, RR, RSP, 0); +} + +static void +l2_got_tei(struct FsmInst *fi, int event, void *arg) +{ + struct PStack *st = fi->userdata; + + st->l2.tei = (long) arg; + + if (fi->state == ST_L2_3) { + establishlink(fi); + test_and_set_bit(FLG_L3_INIT, &st->l2.flag); + } else + FsmChangeState(fi, ST_L2_4); + if (!skb_queue_empty(&st->l2.ui_queue)) + tx_ui(st); +} + +static void +l2_st5_tout_200(struct FsmInst *fi, int event, void *arg) +{ + struct PStack *st = fi->userdata; + + if (test_bit(FLG_LAPD, &st->l2.flag) && + test_bit(FLG_DCHAN_BUSY, &st->l2.flag)) { + FsmAddTimer(&st->l2.t200, st->l2.T200, EV_L2_T200, NULL, 9); + } else if (st->l2.rc == st->l2.N200) { + FsmChangeState(fi, ST_L2_4); + test_and_clear_bit(FLG_T200_RUN, &st->l2.flag); + skb_queue_purge(&st->l2.i_queue); + st->ma.layer(st, MDL_ERROR | INDICATION, (void *) 'G'); + if (test_bit(FLG_LAPB, &st->l2.flag)) + st->l2.l2l1(st, PH_DEACTIVATE | REQUEST, NULL); + st5_dl_release_l2l3(st); + } else { + st->l2.rc++; + FsmAddTimer(&st->l2.t200, st->l2.T200, EV_L2_T200, NULL, 9); + send_uframe(st, (test_bit(FLG_MOD128, &st->l2.flag) ? SABME : SABM) + | 0x10, CMD); + } +} + +static void +l2_st6_tout_200(struct FsmInst *fi, int event, void *arg) +{ + struct PStack *st = fi->userdata; + + if (test_bit(FLG_LAPD, &st->l2.flag) && + test_bit(FLG_DCHAN_BUSY, &st->l2.flag)) { + FsmAddTimer(&st->l2.t200, st->l2.T200, EV_L2_T200, NULL, 9); + } else if (st->l2.rc == st->l2.N200) { + FsmChangeState(fi, ST_L2_4); + test_and_clear_bit(FLG_T200_RUN, &st->l2.flag); + st->ma.layer(st, MDL_ERROR | INDICATION, (void *) 'H'); + lapb_dl_release_l2l3(st, CONFIRM); + } else { + st->l2.rc++; + FsmAddTimer(&st->l2.t200, st->l2.T200, EV_L2_T200, + NULL, 9); + send_uframe(st, DISC | 0x10, CMD); + } +} + +static void +l2_st7_tout_200(struct FsmInst *fi, int event, void *arg) +{ + struct PStack *st = fi->userdata; + + if (test_bit(FLG_LAPD, &st->l2.flag) && + test_bit(FLG_DCHAN_BUSY, &st->l2.flag)) { + FsmAddTimer(&st->l2.t200, st->l2.T200, EV_L2_T200, NULL, 9); + return; + } + test_and_clear_bit(FLG_T200_RUN, &st->l2.flag); + st->l2.rc = 0; + FsmChangeState(fi, ST_L2_8); + + transmit_enquiry(st); + st->l2.rc++; +} + +static void +l2_st8_tout_200(struct FsmInst *fi, int event, void *arg) +{ + struct PStack *st = fi->userdata; + + if (test_bit(FLG_LAPD, &st->l2.flag) && + test_bit(FLG_DCHAN_BUSY, &st->l2.flag)) { + FsmAddTimer(&st->l2.t200, st->l2.T200, EV_L2_T200, NULL, 9); + return; + } + test_and_clear_bit(FLG_T200_RUN, &st->l2.flag); + if (st->l2.rc == st->l2.N200) { + st->ma.layer(st, MDL_ERROR | INDICATION, (void *) 'I'); + establishlink(fi); + test_and_clear_bit(FLG_L3_INIT, &st->l2.flag); + } else { + transmit_enquiry(st); + st->l2.rc++; + } +} + +static void +l2_st7_tout_203(struct FsmInst *fi, int event, void *arg) +{ + struct PStack *st = fi->userdata; + + if (test_bit(FLG_LAPD, &st->l2.flag) && + test_bit(FLG_DCHAN_BUSY, &st->l2.flag)) { + FsmAddTimer(&st->l2.t203, st->l2.T203, EV_L2_T203, NULL, 9); + return; + } + FsmChangeState(fi, ST_L2_8); + transmit_enquiry(st); + st->l2.rc = 0; +} + +static void +l2_pull_iqueue(struct FsmInst *fi, int event, void *arg) +{ + struct PStack *st = fi->userdata; + struct sk_buff *skb, *nskb; + struct Layer2 *l2 = &st->l2; + u_char header[MAX_HEADER_LEN]; + int i, hdr_space_needed; + int unsigned p1; + u_long flags; + + if (!cansend(st)) + return; + + skb = skb_dequeue(&l2->i_queue); + if (!skb) + return; + + hdr_space_needed = l2headersize(l2, 0); + nskb = skb_realloc_headroom(skb, hdr_space_needed); + if (!nskb) { + skb_queue_head(&l2->i_queue, skb); + return; + } + spin_lock_irqsave(&l2->lock, flags); + if (test_bit(FLG_MOD128, &l2->flag)) + p1 = (l2->vs - l2->va) % 128; + else + p1 = (l2->vs - l2->va) % 8; + p1 = (p1 + l2->sow) % l2->window; + if (l2->windowar[p1]) { + printk(KERN_WARNING "isdnl2 try overwrite ack queue entry %d\n", + p1); + dev_kfree_skb(l2->windowar[p1]); + } + l2->windowar[p1] = skb; + + i = sethdraddr(&st->l2, header, CMD); + + if (test_bit(FLG_MOD128, &l2->flag)) { + header[i++] = l2->vs << 1; + header[i++] = l2->vr << 1; + l2->vs = (l2->vs + 1) % 128; + } else { + header[i++] = (l2->vr << 5) | (l2->vs << 1); + l2->vs = (l2->vs + 1) % 8; + } + spin_unlock_irqrestore(&l2->lock, flags); + memcpy(skb_push(nskb, i), header, i); + st->l2.l2l1(st, PH_PULL | INDICATION, nskb); + test_and_clear_bit(FLG_ACK_PEND, &st->l2.flag); + if (!test_and_set_bit(FLG_T200_RUN, &st->l2.flag)) { + FsmDelTimer(&st->l2.t203, 13); + FsmAddTimer(&st->l2.t200, st->l2.T200, EV_L2_T200, NULL, 11); + } + if (!skb_queue_empty(&l2->i_queue) && cansend(st)) + st->l2.l2l1(st, PH_PULL | REQUEST, NULL); +} + +static void +l2_st8_got_super(struct FsmInst *fi, int event, void *arg) +{ + struct PStack *st = fi->userdata; + struct sk_buff *skb = arg; + int PollFlag, rsp, rnr = 0; + unsigned int nr; + struct Layer2 *l2 = &st->l2; + + rsp = *skb->data & 0x2; + if (test_bit(FLG_ORIG, &l2->flag)) + rsp = !rsp; + + skb_pull(skb, l2addrsize(l2)); + + if (IsRNR(skb->data, st)) { + set_peer_busy(l2); + rnr = 1; + } else + clear_peer_busy(l2); + + if (test_bit(FLG_MOD128, &l2->flag)) { + PollFlag = (skb->data[1] & 0x1) == 0x1; + nr = skb->data[1] >> 1; + } else { + PollFlag = (skb->data[0] & 0x10); + nr = (skb->data[0] >> 5) & 0x7; + } + dev_kfree_skb(skb); + + if (rsp && PollFlag) { + if (legalnr(st, nr)) { + if (rnr) { + restart_t200(st, 15); + } else { + stop_t200(st, 16); + FsmAddTimer(&l2->t203, l2->T203, + EV_L2_T203, NULL, 5); + setva(st, nr); + } + invoke_retransmission(st, nr); + FsmChangeState(fi, ST_L2_7); + if (!skb_queue_empty(&l2->i_queue) && cansend(st)) + st->l2.l2l1(st, PH_PULL | REQUEST, NULL); + } else + nrerrorrecovery(fi); + } else { + if (!rsp && PollFlag) + enquiry_response(st); + if (legalnr(st, nr)) { + setva(st, nr); + } else + nrerrorrecovery(fi); + } +} + +static void +l2_got_FRMR(struct FsmInst *fi, int event, void *arg) +{ + struct PStack *st = fi->userdata; + struct sk_buff *skb = arg; + + skb_pull(skb, l2addrsize(&st->l2) + 1); + + if (!(skb->data[0] & 1) || ((skb->data[0] & 3) == 1) || /* I or S */ + (IsUA(skb->data) && (fi->state == ST_L2_7))) { + st->ma.layer(st, MDL_ERROR | INDICATION, (void *) 'K'); + establishlink(fi); + test_and_clear_bit(FLG_L3_INIT, &st->l2.flag); + } + dev_kfree_skb(skb); +} + +static void +l2_st24_tei_remove(struct FsmInst *fi, int event, void *arg) +{ + struct PStack *st = fi->userdata; + + skb_queue_purge(&st->l2.ui_queue); + st->l2.tei = -1; + FsmChangeState(fi, ST_L2_1); +} + +static void +l2_st3_tei_remove(struct FsmInst *fi, int event, void *arg) +{ + struct PStack *st = fi->userdata; + + skb_queue_purge(&st->l2.ui_queue); + st->l2.tei = -1; + st->l2.l2l3(st, DL_RELEASE | INDICATION, NULL); + FsmChangeState(fi, ST_L2_1); +} + +static void +l2_st5_tei_remove(struct FsmInst *fi, int event, void *arg) +{ + struct PStack *st = fi->userdata; + + skb_queue_purge(&st->l2.i_queue); + skb_queue_purge(&st->l2.ui_queue); + freewin(st); + st->l2.tei = -1; + stop_t200(st, 17); + st5_dl_release_l2l3(st); + FsmChangeState(fi, ST_L2_1); +} + +static void +l2_st6_tei_remove(struct FsmInst *fi, int event, void *arg) +{ + struct PStack *st = fi->userdata; + + skb_queue_purge(&st->l2.ui_queue); + st->l2.tei = -1; + stop_t200(st, 18); + st->l2.l2l3(st, DL_RELEASE | CONFIRM, NULL); + FsmChangeState(fi, ST_L2_1); +} + +static void +l2_tei_remove(struct FsmInst *fi, int event, void *arg) +{ + struct PStack *st = fi->userdata; + + skb_queue_purge(&st->l2.i_queue); + skb_queue_purge(&st->l2.ui_queue); + freewin(st); + st->l2.tei = -1; + stop_t200(st, 17); + FsmDelTimer(&st->l2.t203, 19); + st->l2.l2l3(st, DL_RELEASE | INDICATION, NULL); + FsmChangeState(fi, ST_L2_1); +} + +static void +l2_st14_persistent_da(struct FsmInst *fi, int event, void *arg) +{ + struct PStack *st = fi->userdata; + + skb_queue_purge(&st->l2.i_queue); + skb_queue_purge(&st->l2.ui_queue); + if (test_and_clear_bit(FLG_ESTAB_PEND, &st->l2.flag)) + st->l2.l2l3(st, DL_RELEASE | INDICATION, NULL); +} + +static void +l2_st5_persistent_da(struct FsmInst *fi, int event, void *arg) +{ + struct PStack *st = fi->userdata; + + skb_queue_purge(&st->l2.i_queue); + skb_queue_purge(&st->l2.ui_queue); + freewin(st); + stop_t200(st, 19); + st5_dl_release_l2l3(st); + FsmChangeState(fi, ST_L2_4); +} + +static void +l2_st6_persistent_da(struct FsmInst *fi, int event, void *arg) +{ + struct PStack *st = fi->userdata; + + skb_queue_purge(&st->l2.ui_queue); + stop_t200(st, 20); + st->l2.l2l3(st, DL_RELEASE | CONFIRM, NULL); + FsmChangeState(fi, ST_L2_4); +} + +static void +l2_persistent_da(struct FsmInst *fi, int event, void *arg) +{ + struct PStack *st = fi->userdata; + + skb_queue_purge(&st->l2.i_queue); + skb_queue_purge(&st->l2.ui_queue); + freewin(st); + stop_t200(st, 19); + FsmDelTimer(&st->l2.t203, 19); + st->l2.l2l3(st, DL_RELEASE | INDICATION, NULL); + FsmChangeState(fi, ST_L2_4); +} + +static void +l2_set_own_busy(struct FsmInst *fi, int event, void *arg) +{ + struct PStack *st = fi->userdata; + + if (!test_and_set_bit(FLG_OWN_BUSY, &st->l2.flag)) { + enquiry_cr(st, RNR, RSP, 0); + test_and_clear_bit(FLG_ACK_PEND, &st->l2.flag); + } +} + +static void +l2_clear_own_busy(struct FsmInst *fi, int event, void *arg) +{ + struct PStack *st = fi->userdata; + + if (!test_and_clear_bit(FLG_OWN_BUSY, &st->l2.flag)) { + enquiry_cr(st, RR, RSP, 0); + test_and_clear_bit(FLG_ACK_PEND, &st->l2.flag); + } +} + +static void +l2_frame_error(struct FsmInst *fi, int event, void *arg) +{ + struct PStack *st = fi->userdata; + + st->ma.layer(st, MDL_ERROR | INDICATION, arg); +} + +static void +l2_frame_error_reest(struct FsmInst *fi, int event, void *arg) +{ + struct PStack *st = fi->userdata; + + st->ma.layer(st, MDL_ERROR | INDICATION, arg); + establishlink(fi); + test_and_clear_bit(FLG_L3_INIT, &st->l2.flag); +} + +static struct FsmNode L2FnList[] __initdata = +{ + {ST_L2_1, EV_L2_DL_ESTABLISH_REQ, l2_mdl_assign}, + {ST_L2_2, EV_L2_DL_ESTABLISH_REQ, l2_go_st3}, + {ST_L2_4, EV_L2_DL_ESTABLISH_REQ, l2_establish}, + {ST_L2_5, EV_L2_DL_ESTABLISH_REQ, l2_discard_i_setl3}, + {ST_L2_7, EV_L2_DL_ESTABLISH_REQ, l2_l3_reestablish}, + {ST_L2_8, EV_L2_DL_ESTABLISH_REQ, l2_l3_reestablish}, + {ST_L2_4, EV_L2_DL_RELEASE_REQ, l2_release}, + {ST_L2_5, EV_L2_DL_RELEASE_REQ, l2_pend_rel}, + {ST_L2_7, EV_L2_DL_RELEASE_REQ, l2_disconnect}, + {ST_L2_8, EV_L2_DL_RELEASE_REQ, l2_disconnect}, + {ST_L2_5, EV_L2_DL_DATA, l2_feed_i_if_reest}, + {ST_L2_7, EV_L2_DL_DATA, l2_feed_i_pull}, + {ST_L2_8, EV_L2_DL_DATA, l2_feed_iqueue}, + {ST_L2_1, EV_L2_DL_UNIT_DATA, l2_queue_ui_assign}, + {ST_L2_2, EV_L2_DL_UNIT_DATA, l2_queue_ui}, + {ST_L2_3, EV_L2_DL_UNIT_DATA, l2_queue_ui}, + {ST_L2_4, EV_L2_DL_UNIT_DATA, l2_send_ui}, + {ST_L2_5, EV_L2_DL_UNIT_DATA, l2_send_ui}, + {ST_L2_6, EV_L2_DL_UNIT_DATA, l2_send_ui}, + {ST_L2_7, EV_L2_DL_UNIT_DATA, l2_send_ui}, + {ST_L2_8, EV_L2_DL_UNIT_DATA, l2_send_ui}, + {ST_L2_1, EV_L2_MDL_ASSIGN, l2_got_tei}, + {ST_L2_2, EV_L2_MDL_ASSIGN, l2_got_tei}, + {ST_L2_3, EV_L2_MDL_ASSIGN, l2_got_tei}, + {ST_L2_2, EV_L2_MDL_ERROR, l2_st24_tei_remove}, + {ST_L2_3, EV_L2_MDL_ERROR, l2_st3_tei_remove}, + {ST_L2_4, EV_L2_MDL_REMOVE, l2_st24_tei_remove}, + {ST_L2_5, EV_L2_MDL_REMOVE, l2_st5_tei_remove}, + {ST_L2_6, EV_L2_MDL_REMOVE, l2_st6_tei_remove}, + {ST_L2_7, EV_L2_MDL_REMOVE, l2_tei_remove}, + {ST_L2_8, EV_L2_MDL_REMOVE, l2_tei_remove}, + {ST_L2_4, EV_L2_SABME, l2_start_multi}, + {ST_L2_5, EV_L2_SABME, l2_send_UA}, + {ST_L2_6, EV_L2_SABME, l2_send_DM}, + {ST_L2_7, EV_L2_SABME, l2_restart_multi}, + {ST_L2_8, EV_L2_SABME, l2_restart_multi}, + {ST_L2_4, EV_L2_DISC, l2_send_DM}, + {ST_L2_5, EV_L2_DISC, l2_send_DM}, + {ST_L2_6, EV_L2_DISC, l2_send_UA}, + {ST_L2_7, EV_L2_DISC, l2_stop_multi}, + {ST_L2_8, EV_L2_DISC, l2_stop_multi}, + {ST_L2_4, EV_L2_UA, l2_mdl_error_ua}, + {ST_L2_5, EV_L2_UA, l2_connected}, + {ST_L2_6, EV_L2_UA, l2_released}, + {ST_L2_7, EV_L2_UA, l2_mdl_error_ua}, + {ST_L2_8, EV_L2_UA, l2_mdl_error_ua}, + {ST_L2_4, EV_L2_DM, l2_reestablish}, + {ST_L2_5, EV_L2_DM, l2_st5_dm_release}, + {ST_L2_6, EV_L2_DM, l2_st6_dm_release}, + {ST_L2_7, EV_L2_DM, l2_mdl_error_dm}, + {ST_L2_8, EV_L2_DM, l2_st8_mdl_error_dm}, + {ST_L2_1, EV_L2_UI, l2_got_ui}, + {ST_L2_2, EV_L2_UI, l2_got_ui}, + {ST_L2_3, EV_L2_UI, l2_got_ui}, + {ST_L2_4, EV_L2_UI, l2_got_ui}, + {ST_L2_5, EV_L2_UI, l2_got_ui}, + {ST_L2_6, EV_L2_UI, l2_got_ui}, + {ST_L2_7, EV_L2_UI, l2_got_ui}, + {ST_L2_8, EV_L2_UI, l2_got_ui}, + {ST_L2_7, EV_L2_FRMR, l2_got_FRMR}, + {ST_L2_8, EV_L2_FRMR, l2_got_FRMR}, + {ST_L2_7, EV_L2_SUPER, l2_st7_got_super}, + {ST_L2_8, EV_L2_SUPER, l2_st8_got_super}, + {ST_L2_7, EV_L2_I, l2_got_iframe}, + {ST_L2_8, EV_L2_I, l2_got_iframe}, + {ST_L2_5, EV_L2_T200, l2_st5_tout_200}, + {ST_L2_6, EV_L2_T200, l2_st6_tout_200}, + {ST_L2_7, EV_L2_T200, l2_st7_tout_200}, + {ST_L2_8, EV_L2_T200, l2_st8_tout_200}, + {ST_L2_7, EV_L2_T203, l2_st7_tout_203}, + {ST_L2_7, EV_L2_ACK_PULL, l2_pull_iqueue}, + {ST_L2_7, EV_L2_SET_OWN_BUSY, l2_set_own_busy}, + {ST_L2_8, EV_L2_SET_OWN_BUSY, l2_set_own_busy}, + {ST_L2_7, EV_L2_CLEAR_OWN_BUSY, l2_clear_own_busy}, + {ST_L2_8, EV_L2_CLEAR_OWN_BUSY, l2_clear_own_busy}, + {ST_L2_4, EV_L2_FRAME_ERROR, l2_frame_error}, + {ST_L2_5, EV_L2_FRAME_ERROR, l2_frame_error}, + {ST_L2_6, EV_L2_FRAME_ERROR, l2_frame_error}, + {ST_L2_7, EV_L2_FRAME_ERROR, l2_frame_error_reest}, + {ST_L2_8, EV_L2_FRAME_ERROR, l2_frame_error_reest}, + {ST_L2_1, EV_L1_DEACTIVATE, l2_st14_persistent_da}, + {ST_L2_2, EV_L1_DEACTIVATE, l2_st24_tei_remove}, + {ST_L2_3, EV_L1_DEACTIVATE, l2_st3_tei_remove}, + {ST_L2_4, EV_L1_DEACTIVATE, l2_st14_persistent_da}, + {ST_L2_5, EV_L1_DEACTIVATE, l2_st5_persistent_da}, + {ST_L2_6, EV_L1_DEACTIVATE, l2_st6_persistent_da}, + {ST_L2_7, EV_L1_DEACTIVATE, l2_persistent_da}, + {ST_L2_8, EV_L1_DEACTIVATE, l2_persistent_da}, +}; + +static void +isdnl2_l1l2(struct PStack *st, int pr, void *arg) +{ + struct sk_buff *skb = arg; + u_char *datap; + int ret = 1, len; + int c = 0; + + switch (pr) { + case (PH_DATA | INDICATION): + datap = skb->data; + len = l2addrsize(&st->l2); + if (skb->len > len) + datap += len; + else { + FsmEvent(&st->l2.l2m, EV_L2_FRAME_ERROR, (void *) 'N'); + dev_kfree_skb(skb); + return; + } + if (!(*datap & 1)) { /* I-Frame */ + if (!(c = iframe_error(st, skb))) + ret = FsmEvent(&st->l2.l2m, EV_L2_I, skb); + } else if (IsSFrame(datap, st)) { /* S-Frame */ + if (!(c = super_error(st, skb))) + ret = FsmEvent(&st->l2.l2m, EV_L2_SUPER, skb); + } else if (IsUI(datap)) { + if (!(c = UI_error(st, skb))) + ret = FsmEvent(&st->l2.l2m, EV_L2_UI, skb); + } else if (IsSABME(datap, st)) { + if (!(c = unnum_error(st, skb, CMD))) + ret = FsmEvent(&st->l2.l2m, EV_L2_SABME, skb); + } else if (IsUA(datap)) { + if (!(c = unnum_error(st, skb, RSP))) + ret = FsmEvent(&st->l2.l2m, EV_L2_UA, skb); + } else if (IsDISC(datap)) { + if (!(c = unnum_error(st, skb, CMD))) + ret = FsmEvent(&st->l2.l2m, EV_L2_DISC, skb); + } else if (IsDM(datap)) { + if (!(c = unnum_error(st, skb, RSP))) + ret = FsmEvent(&st->l2.l2m, EV_L2_DM, skb); + } else if (IsFRMR(datap)) { + if (!(c = FRMR_error(st, skb))) + ret = FsmEvent(&st->l2.l2m, EV_L2_FRMR, skb); + } else { + FsmEvent(&st->l2.l2m, EV_L2_FRAME_ERROR, (void *) 'L'); + dev_kfree_skb(skb); + ret = 0; + } + if (c) { + dev_kfree_skb(skb); + FsmEvent(&st->l2.l2m, EV_L2_FRAME_ERROR, (void *)(long)c); + ret = 0; + } + if (ret) + dev_kfree_skb(skb); + break; + case (PH_PULL | CONFIRM): + FsmEvent(&st->l2.l2m, EV_L2_ACK_PULL, arg); + break; + case (PH_PAUSE | INDICATION): + test_and_set_bit(FLG_DCHAN_BUSY, &st->l2.flag); + break; + case (PH_PAUSE | CONFIRM): + test_and_clear_bit(FLG_DCHAN_BUSY, &st->l2.flag); + break; + case (PH_ACTIVATE | CONFIRM): + case (PH_ACTIVATE | INDICATION): + test_and_set_bit(FLG_L1_ACTIV, &st->l2.flag); + if (test_and_clear_bit(FLG_ESTAB_PEND, &st->l2.flag)) + FsmEvent(&st->l2.l2m, EV_L2_DL_ESTABLISH_REQ, arg); + break; + case (PH_DEACTIVATE | INDICATION): + case (PH_DEACTIVATE | CONFIRM): + test_and_clear_bit(FLG_L1_ACTIV, &st->l2.flag); + FsmEvent(&st->l2.l2m, EV_L1_DEACTIVATE, arg); + break; + default: + l2m_debug(&st->l2.l2m, "l2 unknown pr %04x", pr); + break; + } +} + +static void +isdnl2_l3l2(struct PStack *st, int pr, void *arg) +{ + switch (pr) { + case (DL_DATA | REQUEST): + if (FsmEvent(&st->l2.l2m, EV_L2_DL_DATA, arg)) { + dev_kfree_skb((struct sk_buff *) arg); + } + break; + case (DL_UNIT_DATA | REQUEST): + if (FsmEvent(&st->l2.l2m, EV_L2_DL_UNIT_DATA, arg)) { + dev_kfree_skb((struct sk_buff *) arg); + } + break; + case (DL_ESTABLISH | REQUEST): + if (test_bit(FLG_L1_ACTIV, &st->l2.flag)) { + if (test_bit(FLG_LAPD, &st->l2.flag) || + test_bit(FLG_ORIG, &st->l2.flag)) { + FsmEvent(&st->l2.l2m, EV_L2_DL_ESTABLISH_REQ, arg); + } + } else { + if (test_bit(FLG_LAPD, &st->l2.flag) || + test_bit(FLG_ORIG, &st->l2.flag)) { + test_and_set_bit(FLG_ESTAB_PEND, &st->l2.flag); + } + st->l2.l2l1(st, PH_ACTIVATE, NULL); + } + break; + case (DL_RELEASE | REQUEST): + if (test_bit(FLG_LAPB, &st->l2.flag)) { + st->l2.l2l1(st, PH_DEACTIVATE, NULL); + } + FsmEvent(&st->l2.l2m, EV_L2_DL_RELEASE_REQ, arg); + break; + case (MDL_ASSIGN | REQUEST): + FsmEvent(&st->l2.l2m, EV_L2_MDL_ASSIGN, arg); + break; + case (MDL_REMOVE | REQUEST): + FsmEvent(&st->l2.l2m, EV_L2_MDL_REMOVE, arg); + break; + case (MDL_ERROR | RESPONSE): + FsmEvent(&st->l2.l2m, EV_L2_MDL_ERROR, arg); + break; + } +} + +void +releasestack_isdnl2(struct PStack *st) +{ + FsmDelTimer(&st->l2.t200, 21); + FsmDelTimer(&st->l2.t203, 16); + skb_queue_purge(&st->l2.i_queue); + skb_queue_purge(&st->l2.ui_queue); + ReleaseWin(&st->l2); +} + +static void +l2m_debug(struct FsmInst *fi, char *fmt, ...) +{ + va_list args; + struct PStack *st = fi->userdata; + + va_start(args, fmt); + VHiSax_putstatus(st->l1.hardware, st->l2.debug_id, fmt, args); + va_end(args); +} + +void +setstack_isdnl2(struct PStack *st, char *debug_id) +{ + spin_lock_init(&st->l2.lock); + st->l1.l1l2 = isdnl2_l1l2; + st->l3.l3l2 = isdnl2_l3l2; + + skb_queue_head_init(&st->l2.i_queue); + skb_queue_head_init(&st->l2.ui_queue); + InitWin(&st->l2); + st->l2.debug = 0; + + st->l2.l2m.fsm = &l2fsm; + if (test_bit(FLG_LAPB, &st->l2.flag)) + st->l2.l2m.state = ST_L2_4; + else + st->l2.l2m.state = ST_L2_1; + st->l2.l2m.debug = 0; + st->l2.l2m.userdata = st; + st->l2.l2m.userint = 0; + st->l2.l2m.printdebug = l2m_debug; + strcpy(st->l2.debug_id, debug_id); + + FsmInitTimer(&st->l2.l2m, &st->l2.t200); + FsmInitTimer(&st->l2.l2m, &st->l2.t203); +} + +static void +transl2_l3l2(struct PStack *st, int pr, void *arg) +{ + switch (pr) { + case (DL_DATA | REQUEST): + case (DL_UNIT_DATA | REQUEST): + st->l2.l2l1(st, PH_DATA | REQUEST, arg); + break; + case (DL_ESTABLISH | REQUEST): + st->l2.l2l1(st, PH_ACTIVATE | REQUEST, NULL); + break; + case (DL_RELEASE | REQUEST): + st->l2.l2l1(st, PH_DEACTIVATE | REQUEST, NULL); + break; + } +} + +void +setstack_transl2(struct PStack *st) +{ + st->l3.l3l2 = transl2_l3l2; +} + +void +releasestack_transl2(struct PStack *st) +{ +} + +int __init +Isdnl2New(void) +{ + l2fsm.state_count = L2_STATE_COUNT; + l2fsm.event_count = L2_EVENT_COUNT; + l2fsm.strEvent = strL2Event; + l2fsm.strState = strL2State; + return FsmNew(&l2fsm, L2FnList, ARRAY_SIZE(L2FnList)); +} + +void +Isdnl2Free(void) +{ + FsmFree(&l2fsm); +} diff --git a/drivers/isdn/hisax/isdnl2.h b/drivers/isdn/hisax/isdnl2.h new file mode 100644 index 000000000..7e447fb8e --- /dev/null +++ b/drivers/isdn/hisax/isdnl2.h @@ -0,0 +1,25 @@ +/* $Id: isdnl2.h,v 1.3.6.2 2001/09/23 22:24:49 kai Exp $ + * + * Layer 2 defines + * + * This software may be used and distributed according to the terms + * of the GNU General Public License, incorporated herein by reference. + * + */ + +#define RR 0x01 +#define RNR 0x05 +#define REJ 0x09 +#define SABME 0x6f +#define SABM 0x2f +#define DM 0x0f +#define UI 0x03 +#define DISC 0x43 +#define UA 0x63 +#define FRMR 0x87 +#define XID 0xaf + +#define CMD 0 +#define RSP 1 + +#define LC_FLUSH_WAIT 1 diff --git a/drivers/isdn/hisax/isdnl3.c b/drivers/isdn/hisax/isdnl3.c new file mode 100644 index 000000000..bb3f9ec62 --- /dev/null +++ b/drivers/isdn/hisax/isdnl3.c @@ -0,0 +1,594 @@ +/* $Id: isdnl3.c,v 2.22.2.3 2004/01/13 14:31:25 keil Exp $ + * + * Author Karsten Keil + * based on the teles driver from Jan den Ouden + * Copyright by Karsten Keil <keil@isdn4linux.de> + * + * This software may be used and distributed according to the terms + * of the GNU General Public License, incorporated herein by reference. + * + * For changes and modifications please read + * Documentation/isdn/HiSax.cert + * + * Thanks to Jan den Ouden + * Fritz Elfert + * + */ + +#include <linux/init.h> +#include <linux/slab.h> +#include "hisax.h" +#include "isdnl3.h" + +const char *l3_revision = "$Revision: 2.22.2.3 $"; + +static struct Fsm l3fsm; + +enum { + ST_L3_LC_REL, + ST_L3_LC_ESTAB_WAIT, + ST_L3_LC_REL_DELAY, + ST_L3_LC_REL_WAIT, + ST_L3_LC_ESTAB, +}; + +#define L3_STATE_COUNT (ST_L3_LC_ESTAB + 1) + +static char *strL3State[] = +{ + "ST_L3_LC_REL", + "ST_L3_LC_ESTAB_WAIT", + "ST_L3_LC_REL_DELAY", + "ST_L3_LC_REL_WAIT", + "ST_L3_LC_ESTAB", +}; + +enum { + EV_ESTABLISH_REQ, + EV_ESTABLISH_IND, + EV_ESTABLISH_CNF, + EV_RELEASE_REQ, + EV_RELEASE_CNF, + EV_RELEASE_IND, + EV_TIMEOUT, +}; + +#define L3_EVENT_COUNT (EV_TIMEOUT + 1) + +static char *strL3Event[] = +{ + "EV_ESTABLISH_REQ", + "EV_ESTABLISH_IND", + "EV_ESTABLISH_CNF", + "EV_RELEASE_REQ", + "EV_RELEASE_CNF", + "EV_RELEASE_IND", + "EV_TIMEOUT", +}; + +static __printf(2, 3) void + l3m_debug(struct FsmInst *fi, char *fmt, ...) +{ + va_list args; + struct PStack *st = fi->userdata; + + va_start(args, fmt); + VHiSax_putstatus(st->l1.hardware, st->l3.debug_id, fmt, args); + va_end(args); +} + +u_char * +findie(u_char *p, int size, u_char ie, int wanted_set) +{ + int l, codeset, maincodeset; + u_char *pend = p + size; + + /* skip protocol discriminator, callref and message type */ + p++; + l = (*p++) & 0xf; + p += l; + p++; + codeset = 0; + maincodeset = 0; + /* while there are bytes left... */ + while (p < pend) { + if ((*p & 0xf0) == 0x90) { + codeset = *p & 0x07; + if (!(*p & 0x08)) + maincodeset = codeset; + } + if (*p & 0x80) + p++; + else { + if (codeset == wanted_set) { + if (*p == ie) + { /* improved length check (Werner Cornelius) */ + if ((pend - p) < 2) + return (NULL); + if (*(p + 1) > (pend - (p + 2))) + return (NULL); + return (p); + } + + if (*p > ie) + return (NULL); + } + p++; + l = *p++; + p += l; + codeset = maincodeset; + } + } + return (NULL); +} + +int +getcallref(u_char *p) +{ + int l, cr = 0; + + p++; /* prot discr */ + if (*p & 0xfe) /* wrong callref BRI only 1 octet*/ + return (-2); + l = 0xf & *p++; /* callref length */ + if (!l) /* dummy CallRef */ + return (-1); + cr = *p++; + return (cr); +} + +static int OrigCallRef = 0; + +int +newcallref(void) +{ + if (OrigCallRef == 127) + OrigCallRef = 1; + else + OrigCallRef++; + return (OrigCallRef); +} + +void +newl3state(struct l3_process *pc, int state) +{ + if (pc->debug & L3_DEB_STATE) + l3_debug(pc->st, "%s cr %d %d --> %d", __func__, + pc->callref & 0x7F, + pc->state, state); + pc->state = state; +} + +static void +L3ExpireTimer(struct timer_list *timer) +{ + struct L3Timer *t = from_timer(t, timer, tl); + t->pc->st->lli.l4l3(t->pc->st, t->event, t->pc); +} + +void +L3InitTimer(struct l3_process *pc, struct L3Timer *t) +{ + t->pc = pc; + timer_setup(&t->tl, L3ExpireTimer, 0); +} + +void +L3DelTimer(struct L3Timer *t) +{ + del_timer(&t->tl); +} + +int +L3AddTimer(struct L3Timer *t, + int millisec, int event) +{ + if (timer_pending(&t->tl)) { + printk(KERN_WARNING "L3AddTimer: timer already active!\n"); + return -1; + } + t->event = event; + t->tl.expires = jiffies + (millisec * HZ) / 1000; + add_timer(&t->tl); + return 0; +} + +void +StopAllL3Timer(struct l3_process *pc) +{ + L3DelTimer(&pc->timer); +} + +struct sk_buff * +l3_alloc_skb(int len) +{ + struct sk_buff *skb; + + if (!(skb = alloc_skb(len + MAX_HEADER_LEN, GFP_ATOMIC))) { + printk(KERN_WARNING "HiSax: No skb for D-channel\n"); + return (NULL); + } + skb_reserve(skb, MAX_HEADER_LEN); + return (skb); +} + +static void +no_l3_proto(struct PStack *st, int pr, void *arg) +{ + struct sk_buff *skb = arg; + + HiSax_putstatus(st->l1.hardware, "L3", "no D protocol"); + if (skb) { + dev_kfree_skb(skb); + } +} + +static int +no_l3_proto_spec(struct PStack *st, isdn_ctrl *ic) +{ + printk(KERN_WARNING "HiSax: no specific protocol handler for proto %lu\n", ic->arg & 0xFF); + return (-1); +} + +struct l3_process +*getl3proc(struct PStack *st, int cr) +{ + struct l3_process *p = st->l3.proc; + + while (p) + if (p->callref == cr) + return (p); + else + p = p->next; + return (NULL); +} + +struct l3_process +*new_l3_process(struct PStack *st, int cr) +{ + struct l3_process *p, *np; + + if (!(p = kmalloc(sizeof(struct l3_process), GFP_ATOMIC))) { + printk(KERN_ERR "HiSax can't get memory for cr %d\n", cr); + return (NULL); + } + if (!st->l3.proc) + st->l3.proc = p; + else { + np = st->l3.proc; + while (np->next) + np = np->next; + np->next = p; + } + p->next = NULL; + p->debug = st->l3.debug; + p->callref = cr; + p->state = 0; + p->chan = NULL; + p->st = st; + p->N303 = st->l3.N303; + L3InitTimer(p, &p->timer); + return (p); +}; + +void +release_l3_process(struct l3_process *p) +{ + struct l3_process *np, *pp = NULL; + + if (!p) + return; + np = p->st->l3.proc; + while (np) { + if (np == p) { + StopAllL3Timer(p); + if (pp) + pp->next = np->next; + else if (!(p->st->l3.proc = np->next) && + !test_bit(FLG_PTP, &p->st->l2.flag)) { + if (p->debug) + l3_debug(p->st, "release_l3_process: last process"); + if (skb_queue_empty(&p->st->l3.squeue)) { + if (p->debug) + l3_debug(p->st, "release_l3_process: release link"); + if (p->st->protocol != ISDN_PTYPE_NI1) + FsmEvent(&p->st->l3.l3m, EV_RELEASE_REQ, NULL); + else + FsmEvent(&p->st->l3.l3m, EV_RELEASE_IND, NULL); + } else { + if (p->debug) + l3_debug(p->st, "release_l3_process: not release link"); + } + } + kfree(p); + return; + } + pp = np; + np = np->next; + } + printk(KERN_ERR "HiSax internal L3 error CR(%d) not in list\n", p->callref); + l3_debug(p->st, "HiSax internal L3 error CR(%d) not in list", p->callref); +}; + +static void +l3ml3p(struct PStack *st, int pr) +{ + struct l3_process *p = st->l3.proc; + struct l3_process *np; + + while (p) { + /* p might be kfreed under us, so we need to save where we want to go on */ + np = p->next; + st->l3.l3ml3(st, pr, p); + p = np; + } +} + +void +setstack_l3dc(struct PStack *st, struct Channel *chanp) +{ + char tmp[64]; + + st->l3.proc = NULL; + st->l3.global = NULL; + skb_queue_head_init(&st->l3.squeue); + st->l3.l3m.fsm = &l3fsm; + st->l3.l3m.state = ST_L3_LC_REL; + st->l3.l3m.debug = 1; + st->l3.l3m.userdata = st; + st->l3.l3m.userint = 0; + st->l3.l3m.printdebug = l3m_debug; + FsmInitTimer(&st->l3.l3m, &st->l3.l3m_timer); + strcpy(st->l3.debug_id, "L3DC "); + st->lli.l4l3_proto = no_l3_proto_spec; + +#ifdef CONFIG_HISAX_EURO + if (st->protocol == ISDN_PTYPE_EURO) { + setstack_dss1(st); + } else +#endif +#ifdef CONFIG_HISAX_NI1 + if (st->protocol == ISDN_PTYPE_NI1) { + setstack_ni1(st); + } else +#endif +#ifdef CONFIG_HISAX_1TR6 + if (st->protocol == ISDN_PTYPE_1TR6) { + setstack_1tr6(st); + } else +#endif + if (st->protocol == ISDN_PTYPE_LEASED) { + st->lli.l4l3 = no_l3_proto; + st->l2.l2l3 = no_l3_proto; + st->l3.l3ml3 = no_l3_proto; + printk(KERN_INFO "HiSax: Leased line mode\n"); + } else { + st->lli.l4l3 = no_l3_proto; + st->l2.l2l3 = no_l3_proto; + st->l3.l3ml3 = no_l3_proto; + sprintf(tmp, "protocol %s not supported", + (st->protocol == ISDN_PTYPE_1TR6) ? "1tr6" : + (st->protocol == ISDN_PTYPE_EURO) ? "euro" : + (st->protocol == ISDN_PTYPE_NI1) ? "ni1" : + "unknown"); + printk(KERN_WARNING "HiSax: %s\n", tmp); + st->protocol = -1; + } +} + +static void +isdnl3_trans(struct PStack *st, int pr, void *arg) { + st->l3.l3l2(st, pr, arg); +} + +void +releasestack_isdnl3(struct PStack *st) +{ + while (st->l3.proc) + release_l3_process(st->l3.proc); + if (st->l3.global) { + StopAllL3Timer(st->l3.global); + kfree(st->l3.global); + st->l3.global = NULL; + } + FsmDelTimer(&st->l3.l3m_timer, 54); + skb_queue_purge(&st->l3.squeue); +} + +void +setstack_l3bc(struct PStack *st, struct Channel *chanp) +{ + + st->l3.proc = NULL; + st->l3.global = NULL; + skb_queue_head_init(&st->l3.squeue); + st->l3.l3m.fsm = &l3fsm; + st->l3.l3m.state = ST_L3_LC_REL; + st->l3.l3m.debug = 1; + st->l3.l3m.userdata = st; + st->l3.l3m.userint = 0; + st->l3.l3m.printdebug = l3m_debug; + strcpy(st->l3.debug_id, "L3BC "); + st->lli.l4l3 = isdnl3_trans; +} + +#define DREL_TIMER_VALUE 40000 + +static void +lc_activate(struct FsmInst *fi, int event, void *arg) +{ + struct PStack *st = fi->userdata; + + FsmChangeState(fi, ST_L3_LC_ESTAB_WAIT); + st->l3.l3l2(st, DL_ESTABLISH | REQUEST, NULL); +} + +static void +lc_connect(struct FsmInst *fi, int event, void *arg) +{ + struct PStack *st = fi->userdata; + struct sk_buff *skb = arg; + int dequeued = 0; + + FsmChangeState(fi, ST_L3_LC_ESTAB); + while ((skb = skb_dequeue(&st->l3.squeue))) { + st->l3.l3l2(st, DL_DATA | REQUEST, skb); + dequeued++; + } + if ((!st->l3.proc) && dequeued) { + if (st->l3.debug) + l3_debug(st, "lc_connect: release link"); + FsmEvent(&st->l3.l3m, EV_RELEASE_REQ, NULL); + } else + l3ml3p(st, DL_ESTABLISH | INDICATION); +} + +static void +lc_connected(struct FsmInst *fi, int event, void *arg) +{ + struct PStack *st = fi->userdata; + struct sk_buff *skb = arg; + int dequeued = 0; + + FsmDelTimer(&st->l3.l3m_timer, 51); + FsmChangeState(fi, ST_L3_LC_ESTAB); + while ((skb = skb_dequeue(&st->l3.squeue))) { + st->l3.l3l2(st, DL_DATA | REQUEST, skb); + dequeued++; + } + if ((!st->l3.proc) && dequeued) { + if (st->l3.debug) + l3_debug(st, "lc_connected: release link"); + FsmEvent(&st->l3.l3m, EV_RELEASE_REQ, NULL); + } else + l3ml3p(st, DL_ESTABLISH | CONFIRM); +} + +static void +lc_start_delay(struct FsmInst *fi, int event, void *arg) +{ + struct PStack *st = fi->userdata; + + FsmChangeState(fi, ST_L3_LC_REL_DELAY); + FsmAddTimer(&st->l3.l3m_timer, DREL_TIMER_VALUE, EV_TIMEOUT, NULL, 50); +} + +static void +lc_start_delay_check(struct FsmInst *fi, int event, void *arg) +/* 20/09/00 - GE timer not user for NI-1 as layer 2 should stay up */ +{ + struct PStack *st = fi->userdata; + + FsmChangeState(fi, ST_L3_LC_REL_DELAY); + /* 19/09/00 - GE timer not user for NI-1 */ + if (st->protocol != ISDN_PTYPE_NI1) + FsmAddTimer(&st->l3.l3m_timer, DREL_TIMER_VALUE, EV_TIMEOUT, NULL, 50); +} + +static void +lc_release_req(struct FsmInst *fi, int event, void *arg) +{ + struct PStack *st = fi->userdata; + + if (test_bit(FLG_L2BLOCK, &st->l2.flag)) { + if (st->l3.debug) + l3_debug(st, "lc_release_req: l2 blocked"); + /* restart release timer */ + FsmAddTimer(&st->l3.l3m_timer, DREL_TIMER_VALUE, EV_TIMEOUT, NULL, 51); + } else { + FsmChangeState(fi, ST_L3_LC_REL_WAIT); + st->l3.l3l2(st, DL_RELEASE | REQUEST, NULL); + } +} + +static void +lc_release_ind(struct FsmInst *fi, int event, void *arg) +{ + struct PStack *st = fi->userdata; + + FsmDelTimer(&st->l3.l3m_timer, 52); + FsmChangeState(fi, ST_L3_LC_REL); + skb_queue_purge(&st->l3.squeue); + l3ml3p(st, DL_RELEASE | INDICATION); +} + +static void +lc_release_cnf(struct FsmInst *fi, int event, void *arg) +{ + struct PStack *st = fi->userdata; + + FsmChangeState(fi, ST_L3_LC_REL); + skb_queue_purge(&st->l3.squeue); + l3ml3p(st, DL_RELEASE | CONFIRM); +} + + +/* *INDENT-OFF* */ +static struct FsmNode L3FnList[] __initdata = +{ + {ST_L3_LC_REL, EV_ESTABLISH_REQ, lc_activate}, + {ST_L3_LC_REL, EV_ESTABLISH_IND, lc_connect}, + {ST_L3_LC_REL, EV_ESTABLISH_CNF, lc_connect}, + {ST_L3_LC_ESTAB_WAIT, EV_ESTABLISH_CNF, lc_connected}, + {ST_L3_LC_ESTAB_WAIT, EV_RELEASE_REQ, lc_start_delay}, + {ST_L3_LC_ESTAB_WAIT, EV_RELEASE_IND, lc_release_ind}, + {ST_L3_LC_ESTAB, EV_RELEASE_IND, lc_release_ind}, + {ST_L3_LC_ESTAB, EV_RELEASE_REQ, lc_start_delay_check}, + {ST_L3_LC_REL_DELAY, EV_RELEASE_IND, lc_release_ind}, + {ST_L3_LC_REL_DELAY, EV_ESTABLISH_REQ, lc_connected}, + {ST_L3_LC_REL_DELAY, EV_TIMEOUT, lc_release_req}, + {ST_L3_LC_REL_WAIT, EV_RELEASE_CNF, lc_release_cnf}, + {ST_L3_LC_REL_WAIT, EV_ESTABLISH_REQ, lc_activate}, +}; +/* *INDENT-ON* */ + +void +l3_msg(struct PStack *st, int pr, void *arg) +{ + switch (pr) { + case (DL_DATA | REQUEST): + if (st->l3.l3m.state == ST_L3_LC_ESTAB) { + st->l3.l3l2(st, pr, arg); + } else { + struct sk_buff *skb = arg; + + skb_queue_tail(&st->l3.squeue, skb); + FsmEvent(&st->l3.l3m, EV_ESTABLISH_REQ, NULL); + } + break; + case (DL_ESTABLISH | REQUEST): + FsmEvent(&st->l3.l3m, EV_ESTABLISH_REQ, NULL); + break; + case (DL_ESTABLISH | CONFIRM): + FsmEvent(&st->l3.l3m, EV_ESTABLISH_CNF, NULL); + break; + case (DL_ESTABLISH | INDICATION): + FsmEvent(&st->l3.l3m, EV_ESTABLISH_IND, NULL); + break; + case (DL_RELEASE | INDICATION): + FsmEvent(&st->l3.l3m, EV_RELEASE_IND, NULL); + break; + case (DL_RELEASE | CONFIRM): + FsmEvent(&st->l3.l3m, EV_RELEASE_CNF, NULL); + break; + case (DL_RELEASE | REQUEST): + FsmEvent(&st->l3.l3m, EV_RELEASE_REQ, NULL); + break; + } +} + +int __init +Isdnl3New(void) +{ + l3fsm.state_count = L3_STATE_COUNT; + l3fsm.event_count = L3_EVENT_COUNT; + l3fsm.strEvent = strL3Event; + l3fsm.strState = strL3State; + return FsmNew(&l3fsm, L3FnList, ARRAY_SIZE(L3FnList)); +} + +void +Isdnl3Free(void) +{ + FsmFree(&l3fsm); +} diff --git a/drivers/isdn/hisax/isdnl3.h b/drivers/isdn/hisax/isdnl3.h new file mode 100644 index 000000000..0edc99d40 --- /dev/null +++ b/drivers/isdn/hisax/isdnl3.h @@ -0,0 +1,42 @@ +/* $Id: isdnl3.h,v 2.6.6.2 2001/09/23 22:24:49 kai Exp $ + * + * This software may be used and distributed according to the terms + * of the GNU General Public License, incorporated herein by reference. + * + */ + +#define SBIT(state) (1 << state) +#define ALL_STATES 0x03ffffff + +#define PROTO_DIS_EURO 0x08 + +#define L3_DEB_WARN 0x01 +#define L3_DEB_PROTERR 0x02 +#define L3_DEB_STATE 0x04 +#define L3_DEB_CHARGE 0x08 +#define L3_DEB_CHECK 0x10 +#define L3_DEB_SI 0x20 + +struct stateentry { + int state; + int primitive; + void (*rout) (struct l3_process *, u8, void *); +}; + +#define l3_debug(st, fmt, args...) HiSax_putstatus(st->l1.hardware, "l3 ", fmt, ## args) + +struct PStack; + +void newl3state(struct l3_process *pc, int state); +void L3InitTimer(struct l3_process *pc, struct L3Timer *t); +void L3DelTimer(struct L3Timer *t); +int L3AddTimer(struct L3Timer *t, int millisec, int event); +void StopAllL3Timer(struct l3_process *pc); +struct sk_buff *l3_alloc_skb(int len); +struct l3_process *new_l3_process(struct PStack *st, int cr); +void release_l3_process(struct l3_process *p); +struct l3_process *getl3proc(struct PStack *st, int cr); +void l3_msg(struct PStack *st, int pr, void *arg); +void setstack_dss1(struct PStack *st); +void setstack_ni1(struct PStack *st); +void setstack_1tr6(struct PStack *st); diff --git a/drivers/isdn/hisax/isurf.c b/drivers/isdn/hisax/isurf.c new file mode 100644 index 000000000..53e299be4 --- /dev/null +++ b/drivers/isdn/hisax/isurf.c @@ -0,0 +1,305 @@ +/* $Id: isurf.c,v 1.12.2.4 2004/01/13 21:46:03 keil Exp $ + * + * low level stuff for Siemens I-Surf/I-Talk cards + * + * Author Karsten Keil + * Copyright by Karsten Keil <keil@isdn4linux.de> + * + * This software may be used and distributed according to the terms + * of the GNU General Public License, incorporated herein by reference. + * + */ + +#include <linux/init.h> +#include "hisax.h" +#include "isac.h" +#include "isar.h" +#include "isdnl1.h" +#include <linux/isapnp.h> + +static const char *ISurf_revision = "$Revision: 1.12.2.4 $"; + +#define byteout(addr, val) outb(val, addr) +#define bytein(addr) inb(addr) + +#define ISURF_ISAR_RESET 1 +#define ISURF_ISAC_RESET 2 +#define ISURF_ISAR_EA 4 +#define ISURF_ARCOFI_RESET 8 +#define ISURF_RESET (ISURF_ISAR_RESET | ISURF_ISAC_RESET | ISURF_ARCOFI_RESET) + +#define ISURF_ISAR_OFFSET 0 +#define ISURF_ISAC_OFFSET 0x100 +#define ISURF_IOMEM_SIZE 0x400 +/* Interface functions */ + +static u_char +ReadISAC(struct IsdnCardState *cs, u_char offset) +{ + return (readb(cs->hw.isurf.isac + offset)); +} + +static void +WriteISAC(struct IsdnCardState *cs, u_char offset, u_char value) +{ + writeb(value, cs->hw.isurf.isac + offset); mb(); +} + +static void +ReadISACfifo(struct IsdnCardState *cs, u_char *data, int size) +{ + register int i; + for (i = 0; i < size; i++) + data[i] = readb(cs->hw.isurf.isac); +} + +static void +WriteISACfifo(struct IsdnCardState *cs, u_char *data, int size) +{ + register int i; + for (i = 0; i < size; i++) { + writeb(data[i], cs->hw.isurf.isac); mb(); + } +} + +/* ISAR access routines + * mode = 0 access with IRQ on + * mode = 1 access with IRQ off + * mode = 2 access with IRQ off and using last offset + */ + +static u_char +ReadISAR(struct IsdnCardState *cs, int mode, u_char offset) +{ + return (readb(cs->hw.isurf.isar + offset)); +} + +static void +WriteISAR(struct IsdnCardState *cs, int mode, u_char offset, u_char value) +{ + writeb(value, cs->hw.isurf.isar + offset); mb(); +} + +static irqreturn_t +isurf_interrupt(int intno, void *dev_id) +{ + struct IsdnCardState *cs = dev_id; + u_char val; + int cnt = 5; + u_long flags; + + spin_lock_irqsave(&cs->lock, flags); + val = readb(cs->hw.isurf.isar + ISAR_IRQBIT); +Start_ISAR: + if (val & ISAR_IRQSTA) + isar_int_main(cs); + val = readb(cs->hw.isurf.isac + ISAC_ISTA); +Start_ISAC: + if (val) + isac_interrupt(cs, val); + val = readb(cs->hw.isurf.isar + ISAR_IRQBIT); + if ((val & ISAR_IRQSTA) && --cnt) { + if (cs->debug & L1_DEB_HSCX) + debugl1(cs, "ISAR IntStat after IntRoutine"); + goto Start_ISAR; + } + val = readb(cs->hw.isurf.isac + ISAC_ISTA); + if (val && --cnt) { + if (cs->debug & L1_DEB_ISAC) + debugl1(cs, "ISAC IntStat after IntRoutine"); + goto Start_ISAC; + } + if (!cnt) + printk(KERN_WARNING "ISurf IRQ LOOP\n"); + + writeb(0, cs->hw.isurf.isar + ISAR_IRQBIT); mb(); + writeb(0xFF, cs->hw.isurf.isac + ISAC_MASK); mb(); + writeb(0, cs->hw.isurf.isac + ISAC_MASK); mb(); + writeb(ISAR_IRQMSK, cs->hw.isurf.isar + ISAR_IRQBIT); mb(); + spin_unlock_irqrestore(&cs->lock, flags); + return IRQ_HANDLED; +} + +static void +release_io_isurf(struct IsdnCardState *cs) +{ + release_region(cs->hw.isurf.reset, 1); + iounmap(cs->hw.isurf.isar); + release_mem_region(cs->hw.isurf.phymem, ISURF_IOMEM_SIZE); +} + +static void +reset_isurf(struct IsdnCardState *cs, u_char chips) +{ + printk(KERN_INFO "ISurf: resetting card\n"); + + byteout(cs->hw.isurf.reset, chips); /* Reset On */ + mdelay(10); + byteout(cs->hw.isurf.reset, ISURF_ISAR_EA); /* Reset Off */ + mdelay(10); +} + +static int +ISurf_card_msg(struct IsdnCardState *cs, int mt, void *arg) +{ + u_long flags; + + switch (mt) { + case CARD_RESET: + spin_lock_irqsave(&cs->lock, flags); + reset_isurf(cs, ISURF_RESET); + spin_unlock_irqrestore(&cs->lock, flags); + return (0); + case CARD_RELEASE: + release_io_isurf(cs); + return (0); + case CARD_INIT: + spin_lock_irqsave(&cs->lock, flags); + reset_isurf(cs, ISURF_RESET); + clear_pending_isac_ints(cs); + writeb(0, cs->hw.isurf.isar + ISAR_IRQBIT); mb(); + initisac(cs); + initisar(cs); + /* Reenable ISAC IRQ */ + cs->writeisac(cs, ISAC_MASK, 0); + /* RESET Receiver and Transmitter */ + cs->writeisac(cs, ISAC_CMDR, 0x41); + spin_unlock_irqrestore(&cs->lock, flags); + return (0); + case CARD_TEST: + return (0); + } + return (0); +} + +static int +isurf_auxcmd(struct IsdnCardState *cs, isdn_ctrl *ic) { + int ret; + u_long flags; + + if ((ic->command == ISDN_CMD_IOCTL) && (ic->arg == 9)) { + ret = isar_auxcmd(cs, ic); + spin_lock_irqsave(&cs->lock, flags); + if (!ret) { + reset_isurf(cs, ISURF_ISAR_EA | ISURF_ISAC_RESET | + ISURF_ARCOFI_RESET); + initisac(cs); + cs->writeisac(cs, ISAC_MASK, 0); + cs->writeisac(cs, ISAC_CMDR, 0x41); + } + spin_unlock_irqrestore(&cs->lock, flags); + return (ret); + } + return (isar_auxcmd(cs, ic)); +} + +#ifdef __ISAPNP__ +static struct pnp_card *pnp_c = NULL; +#endif + +int setup_isurf(struct IsdnCard *card) +{ + int ver; + struct IsdnCardState *cs = card->cs; + char tmp[64]; + + strcpy(tmp, ISurf_revision); + printk(KERN_INFO "HiSax: ISurf driver Rev. %s\n", HiSax_getrev(tmp)); + + if (cs->typ != ISDN_CTYPE_ISURF) + return (0); + if (card->para[1] && card->para[2]) { + cs->hw.isurf.reset = card->para[1]; + cs->hw.isurf.phymem = card->para[2]; + cs->irq = card->para[0]; + } else { +#ifdef __ISAPNP__ + if (isapnp_present()) { + struct pnp_dev *pnp_d = NULL; + int err; + + cs->subtyp = 0; + if ((pnp_c = pnp_find_card( + ISAPNP_VENDOR('S', 'I', 'E'), + ISAPNP_FUNCTION(0x0010), pnp_c))) { + if (!(pnp_d = pnp_find_dev(pnp_c, + ISAPNP_VENDOR('S', 'I', 'E'), + ISAPNP_FUNCTION(0x0010), pnp_d))) { + printk(KERN_ERR "ISurfPnP: PnP error card found, no device\n"); + return (0); + } + pnp_disable_dev(pnp_d); + err = pnp_activate_dev(pnp_d); + if (err < 0) { + pr_warn("%s: pnp_activate_dev ret=%d\n", + __func__, err); + return 0; + } + cs->hw.isurf.reset = pnp_port_start(pnp_d, 0); + cs->hw.isurf.phymem = pnp_mem_start(pnp_d, 1); + cs->irq = pnp_irq(pnp_d, 0); + if (cs->irq == -1 || !cs->hw.isurf.reset || !cs->hw.isurf.phymem) { + printk(KERN_ERR "ISurfPnP:some resources are missing %d/%x/%lx\n", + cs->irq, cs->hw.isurf.reset, cs->hw.isurf.phymem); + pnp_disable_dev(pnp_d); + return (0); + } + } else { + printk(KERN_INFO "ISurfPnP: no ISAPnP card found\n"); + return (0); + } + } else { + printk(KERN_INFO "ISurfPnP: no ISAPnP bus found\n"); + return (0); + } +#else + printk(KERN_WARNING "HiSax: Siemens I-Surf port/mem not set\n"); + return (0); +#endif + } + if (!request_region(cs->hw.isurf.reset, 1, "isurf isdn")) { + printk(KERN_WARNING + "HiSax: Siemens I-Surf config port %x already in use\n", + cs->hw.isurf.reset); + return (0); + } + if (!request_region(cs->hw.isurf.phymem, ISURF_IOMEM_SIZE, "isurf iomem")) { + printk(KERN_WARNING "HiSax: Siemens I-Surf memory region " + "%lx-%lx already in use\n", + cs->hw.isurf.phymem, + cs->hw.isurf.phymem + ISURF_IOMEM_SIZE); + release_region(cs->hw.isurf.reset, 1); + return (0); + } + cs->hw.isurf.isar = ioremap(cs->hw.isurf.phymem, ISURF_IOMEM_SIZE); + cs->hw.isurf.isac = cs->hw.isurf.isar + ISURF_ISAC_OFFSET; + printk(KERN_INFO + "ISurf: defined at 0x%x 0x%lx IRQ %d\n", + cs->hw.isurf.reset, + cs->hw.isurf.phymem, + cs->irq); + + setup_isac(cs); + cs->cardmsg = &ISurf_card_msg; + cs->irq_func = &isurf_interrupt; + cs->auxcmd = &isurf_auxcmd; + cs->readisac = &ReadISAC; + cs->writeisac = &WriteISAC; + cs->readisacfifo = &ReadISACfifo; + cs->writeisacfifo = &WriteISACfifo; + cs->bcs[0].hw.isar.reg = &cs->hw.isurf.isar_r; + cs->bcs[1].hw.isar.reg = &cs->hw.isurf.isar_r; + test_and_set_bit(HW_ISAR, &cs->HW_Flags); + ISACVersion(cs, "ISurf:"); + cs->BC_Read_Reg = &ReadISAR; + cs->BC_Write_Reg = &WriteISAR; + cs->BC_Send_Data = &isar_fill_fifo; + ver = ISARVersion(cs, "ISurf:"); + if (ver < 0) { + printk(KERN_WARNING + "ISurf: wrong ISAR version (ret = %d)\n", ver); + release_io_isurf(cs); + return (0); + } + return (1); +} diff --git a/drivers/isdn/hisax/ix1_micro.c b/drivers/isdn/hisax/ix1_micro.c new file mode 100644 index 000000000..bfb79f3f0 --- /dev/null +++ b/drivers/isdn/hisax/ix1_micro.c @@ -0,0 +1,316 @@ +/* $Id: ix1_micro.c,v 2.12.2.4 2004/01/13 23:48:39 keil Exp $ + * + * low level stuff for ITK ix1-micro Rev.2 isdn cards + * derived from the original file teles3.c from Karsten Keil + * + * Author Klaus-Peter Nischke + * Copyright by Klaus-Peter Nischke, ITK AG + * <klaus@nischke.do.eunet.de> + * by Karsten Keil <keil@isdn4linux.de> + * + * This software may be used and distributed according to the terms + * of the GNU General Public License, incorporated herein by reference. + * + * Klaus-Peter Nischke + * Deusener Str. 287 + * 44369 Dortmund + * Germany + */ + +#include <linux/init.h> +#include <linux/isapnp.h> +#include "hisax.h" +#include "isac.h" +#include "hscx.h" +#include "isdnl1.h" + +static const char *ix1_revision = "$Revision: 2.12.2.4 $"; + +#define byteout(addr, val) outb(val, addr) +#define bytein(addr) inb(addr) + +#define SPECIAL_PORT_OFFSET 3 + +#define ISAC_COMMAND_OFFSET 2 +#define ISAC_DATA_OFFSET 0 +#define HSCX_COMMAND_OFFSET 2 +#define HSCX_DATA_OFFSET 1 + +#define TIMEOUT 50 + +static inline u_char +readreg(unsigned int ale, unsigned int adr, u_char off) +{ + register u_char ret; + + byteout(ale, off); + ret = bytein(adr); + return (ret); +} + +static inline void +readfifo(unsigned int ale, unsigned int adr, u_char off, u_char *data, int size) +{ + byteout(ale, off); + insb(adr, data, size); +} + + +static inline void +writereg(unsigned int ale, unsigned int adr, u_char off, u_char data) +{ + byteout(ale, off); + byteout(adr, data); +} + +static inline void +writefifo(unsigned int ale, unsigned int adr, u_char off, u_char *data, int size) +{ + byteout(ale, off); + outsb(adr, data, size); +} + +/* Interface functions */ + +static u_char +ReadISAC(struct IsdnCardState *cs, u_char offset) +{ + return (readreg(cs->hw.ix1.isac_ale, cs->hw.ix1.isac, offset)); +} + +static void +WriteISAC(struct IsdnCardState *cs, u_char offset, u_char value) +{ + writereg(cs->hw.ix1.isac_ale, cs->hw.ix1.isac, offset, value); +} + +static void +ReadISACfifo(struct IsdnCardState *cs, u_char *data, int size) +{ + readfifo(cs->hw.ix1.isac_ale, cs->hw.ix1.isac, 0, data, size); +} + +static void +WriteISACfifo(struct IsdnCardState *cs, u_char *data, int size) +{ + writefifo(cs->hw.ix1.isac_ale, cs->hw.ix1.isac, 0, data, size); +} + +static u_char +ReadHSCX(struct IsdnCardState *cs, int hscx, u_char offset) +{ + return (readreg(cs->hw.ix1.hscx_ale, + cs->hw.ix1.hscx, offset + (hscx ? 0x40 : 0))); +} + +static void +WriteHSCX(struct IsdnCardState *cs, int hscx, u_char offset, u_char value) +{ + writereg(cs->hw.ix1.hscx_ale, + cs->hw.ix1.hscx, offset + (hscx ? 0x40 : 0), value); +} + +#define READHSCX(cs, nr, reg) readreg(cs->hw.ix1.hscx_ale, \ + cs->hw.ix1.hscx, reg + (nr ? 0x40 : 0)) +#define WRITEHSCX(cs, nr, reg, data) writereg(cs->hw.ix1.hscx_ale, \ + cs->hw.ix1.hscx, reg + (nr ? 0x40 : 0), data) + +#define READHSCXFIFO(cs, nr, ptr, cnt) readfifo(cs->hw.ix1.hscx_ale, \ + cs->hw.ix1.hscx, (nr ? 0x40 : 0), ptr, cnt) + +#define WRITEHSCXFIFO(cs, nr, ptr, cnt) writefifo(cs->hw.ix1.hscx_ale, \ + cs->hw.ix1.hscx, (nr ? 0x40 : 0), ptr, cnt) + +#include "hscx_irq.c" + +static irqreturn_t +ix1micro_interrupt(int intno, void *dev_id) +{ + struct IsdnCardState *cs = dev_id; + u_char val; + u_long flags; + + spin_lock_irqsave(&cs->lock, flags); + val = readreg(cs->hw.ix1.hscx_ale, cs->hw.ix1.hscx, HSCX_ISTA + 0x40); +Start_HSCX: + if (val) + hscx_int_main(cs, val); + val = readreg(cs->hw.ix1.isac_ale, cs->hw.ix1.isac, ISAC_ISTA); +Start_ISAC: + if (val) + isac_interrupt(cs, val); + val = readreg(cs->hw.ix1.hscx_ale, cs->hw.ix1.hscx, HSCX_ISTA + 0x40); + if (val) { + if (cs->debug & L1_DEB_HSCX) + debugl1(cs, "HSCX IntStat after IntRoutine"); + goto Start_HSCX; + } + val = readreg(cs->hw.ix1.isac_ale, cs->hw.ix1.isac, ISAC_ISTA); + if (val) { + if (cs->debug & L1_DEB_ISAC) + debugl1(cs, "ISAC IntStat after IntRoutine"); + goto Start_ISAC; + } + writereg(cs->hw.ix1.hscx_ale, cs->hw.ix1.hscx, HSCX_MASK, 0xFF); + writereg(cs->hw.ix1.hscx_ale, cs->hw.ix1.hscx, HSCX_MASK + 0x40, 0xFF); + writereg(cs->hw.ix1.isac_ale, cs->hw.ix1.isac, ISAC_MASK, 0xFF); + writereg(cs->hw.ix1.isac_ale, cs->hw.ix1.isac, ISAC_MASK, 0); + writereg(cs->hw.ix1.hscx_ale, cs->hw.ix1.hscx, HSCX_MASK, 0); + writereg(cs->hw.ix1.hscx_ale, cs->hw.ix1.hscx, HSCX_MASK + 0x40, 0); + spin_unlock_irqrestore(&cs->lock, flags); + return IRQ_HANDLED; +} + +static void +release_io_ix1micro(struct IsdnCardState *cs) +{ + if (cs->hw.ix1.cfg_reg) + release_region(cs->hw.ix1.cfg_reg, 4); +} + +static void +ix1_reset(struct IsdnCardState *cs) +{ + int cnt; + + /* reset isac */ + cnt = 3 * (HZ / 10) + 1; + while (cnt--) { + byteout(cs->hw.ix1.cfg_reg + SPECIAL_PORT_OFFSET, 1); + HZDELAY(1); /* wait >=10 ms */ + } + byteout(cs->hw.ix1.cfg_reg + SPECIAL_PORT_OFFSET, 0); +} + +static int +ix1_card_msg(struct IsdnCardState *cs, int mt, void *arg) +{ + u_long flags; + + switch (mt) { + case CARD_RESET: + spin_lock_irqsave(&cs->lock, flags); + ix1_reset(cs); + spin_unlock_irqrestore(&cs->lock, flags); + return (0); + case CARD_RELEASE: + release_io_ix1micro(cs); + return (0); + case CARD_INIT: + spin_lock_irqsave(&cs->lock, flags); + ix1_reset(cs); + inithscxisac(cs, 3); + spin_unlock_irqrestore(&cs->lock, flags); + return (0); + case CARD_TEST: + return (0); + } + return (0); +} + +#ifdef __ISAPNP__ +static struct isapnp_device_id itk_ids[] = { + { ISAPNP_VENDOR('I', 'T', 'K'), ISAPNP_FUNCTION(0x25), + ISAPNP_VENDOR('I', 'T', 'K'), ISAPNP_FUNCTION(0x25), + (unsigned long) "ITK micro 2" }, + { ISAPNP_VENDOR('I', 'T', 'K'), ISAPNP_FUNCTION(0x29), + ISAPNP_VENDOR('I', 'T', 'K'), ISAPNP_FUNCTION(0x29), + (unsigned long) "ITK micro 2." }, + { 0, } +}; + +static struct isapnp_device_id *ipid = &itk_ids[0]; +static struct pnp_card *pnp_c = NULL; +#endif + + +int setup_ix1micro(struct IsdnCard *card) +{ + struct IsdnCardState *cs = card->cs; + char tmp[64]; + + strcpy(tmp, ix1_revision); + printk(KERN_INFO "HiSax: ITK IX1 driver Rev. %s\n", HiSax_getrev(tmp)); + if (cs->typ != ISDN_CTYPE_IX1MICROR2) + return (0); + +#ifdef __ISAPNP__ + if (!card->para[1] && isapnp_present()) { + struct pnp_dev *pnp_d; + while (ipid->card_vendor) { + if ((pnp_c = pnp_find_card(ipid->card_vendor, + ipid->card_device, pnp_c))) { + pnp_d = NULL; + if ((pnp_d = pnp_find_dev(pnp_c, + ipid->vendor, ipid->function, pnp_d))) { + int err; + + printk(KERN_INFO "HiSax: %s detected\n", + (char *)ipid->driver_data); + pnp_disable_dev(pnp_d); + err = pnp_activate_dev(pnp_d); + if (err < 0) { + printk(KERN_WARNING "%s: pnp_activate_dev ret(%d)\n", + __func__, err); + return (0); + } + card->para[1] = pnp_port_start(pnp_d, 0); + card->para[0] = pnp_irq(pnp_d, 0); + if (card->para[0] == -1 || !card->para[1]) { + printk(KERN_ERR "ITK PnP:some resources are missing %ld/%lx\n", + card->para[0], card->para[1]); + pnp_disable_dev(pnp_d); + return (0); + } + break; + } else { + printk(KERN_ERR "ITK PnP: PnP error card found, no device\n"); + } + } + ipid++; + pnp_c = NULL; + } + if (!ipid->card_vendor) { + printk(KERN_INFO "ITK PnP: no ISAPnP card found\n"); + return (0); + } + } +#endif + /* IO-Ports */ + cs->hw.ix1.isac_ale = card->para[1] + ISAC_COMMAND_OFFSET; + cs->hw.ix1.hscx_ale = card->para[1] + HSCX_COMMAND_OFFSET; + cs->hw.ix1.isac = card->para[1] + ISAC_DATA_OFFSET; + cs->hw.ix1.hscx = card->para[1] + HSCX_DATA_OFFSET; + cs->hw.ix1.cfg_reg = card->para[1]; + cs->irq = card->para[0]; + if (cs->hw.ix1.cfg_reg) { + if (!request_region(cs->hw.ix1.cfg_reg, 4, "ix1micro cfg")) { + printk(KERN_WARNING + "HiSax: ITK ix1-micro Rev.2 config port " + "%x-%x already in use\n", + cs->hw.ix1.cfg_reg, + cs->hw.ix1.cfg_reg + 4); + return (0); + } + } + printk(KERN_INFO "HiSax: ITK ix1-micro Rev.2 config irq:%d io:0x%X\n", + cs->irq, cs->hw.ix1.cfg_reg); + setup_isac(cs); + cs->readisac = &ReadISAC; + cs->writeisac = &WriteISAC; + cs->readisacfifo = &ReadISACfifo; + cs->writeisacfifo = &WriteISACfifo; + cs->BC_Read_Reg = &ReadHSCX; + cs->BC_Write_Reg = &WriteHSCX; + cs->BC_Send_Data = &hscx_fill_fifo; + cs->cardmsg = &ix1_card_msg; + cs->irq_func = &ix1micro_interrupt; + ISACVersion(cs, "ix1-Micro:"); + if (HscxVersion(cs, "ix1-Micro:")) { + printk(KERN_WARNING + "ix1-Micro: wrong HSCX versions check IO address\n"); + release_io_ix1micro(cs); + return (0); + } + return (1); +} diff --git a/drivers/isdn/hisax/jade.c b/drivers/isdn/hisax/jade.c new file mode 100644 index 000000000..e2ae7871a --- /dev/null +++ b/drivers/isdn/hisax/jade.c @@ -0,0 +1,305 @@ +/* $Id: jade.c,v 1.9.2.4 2004/01/14 16:04:48 keil Exp $ + * + * JADE stuff (derived from original hscx.c) + * + * Author Roland Klabunde + * Copyright by Roland Klabunde <R.Klabunde@Berkom.de> + * + * This software may be used and distributed according to the terms + * of the GNU General Public License, incorporated herein by reference. + * + */ + + +#include <linux/init.h> +#include "hisax.h" +#include "hscx.h" +#include "jade.h" +#include "isdnl1.h" +#include <linux/interrupt.h> +#include <linux/slab.h> + + +int +JadeVersion(struct IsdnCardState *cs, char *s) +{ + int ver; + int to = 50; + cs->BC_Write_Reg(cs, -1, 0x50, 0x19); + while (to) { + udelay(1); + ver = cs->BC_Read_Reg(cs, -1, 0x60); + to--; + if (ver) + break; + if (!to) { + printk(KERN_INFO "%s JADE version not obtainable\n", s); + return (0); + } + } + /* Wait for the JADE */ + udelay(10); + /* Read version */ + ver = cs->BC_Read_Reg(cs, -1, 0x60); + printk(KERN_INFO "%s JADE version: %d\n", s, ver); + return (1); +} + +/* Write to indirect accessible jade register set */ +static void +jade_write_indirect(struct IsdnCardState *cs, u_char reg, u_char value) +{ + int to = 50; + u_char ret; + + /* Write the data */ + cs->BC_Write_Reg(cs, -1, COMM_JADE + 1, value); + /* Say JADE we wanna write indirect reg 'reg' */ + cs->BC_Write_Reg(cs, -1, COMM_JADE, reg); + to = 50; + /* Wait for RDY goes high */ + while (to) { + udelay(1); + ret = cs->BC_Read_Reg(cs, -1, COMM_JADE); + to--; + if (ret & 1) + /* Got acknowledge */ + break; + if (!to) { + printk(KERN_INFO "Can not see ready bit from JADE DSP (reg=0x%X, value=0x%X)\n", reg, value); + return; + } + } +} + + + +static void +modejade(struct BCState *bcs, int mode, int bc) +{ + struct IsdnCardState *cs = bcs->cs; + int jade = bcs->hw.hscx.hscx; + + if (cs->debug & L1_DEB_HSCX) { + debugl1(cs, "jade %c mode %d ichan %d", 'A' + jade, mode, bc); + } + bcs->mode = mode; + bcs->channel = bc; + + cs->BC_Write_Reg(cs, jade, jade_HDLC_MODE, (mode == L1_MODE_TRANS ? jadeMODE_TMO : 0x00)); + cs->BC_Write_Reg(cs, jade, jade_HDLC_CCR0, (jadeCCR0_PU | jadeCCR0_ITF)); + cs->BC_Write_Reg(cs, jade, jade_HDLC_CCR1, 0x00); + + jade_write_indirect(cs, jade_HDLC1SERRXPATH, 0x08); + jade_write_indirect(cs, jade_HDLC2SERRXPATH, 0x08); + jade_write_indirect(cs, jade_HDLC1SERTXPATH, 0x00); + jade_write_indirect(cs, jade_HDLC2SERTXPATH, 0x00); + + cs->BC_Write_Reg(cs, jade, jade_HDLC_XCCR, 0x07); + cs->BC_Write_Reg(cs, jade, jade_HDLC_RCCR, 0x07); + + if (bc == 0) { + cs->BC_Write_Reg(cs, jade, jade_HDLC_TSAX, 0x00); + cs->BC_Write_Reg(cs, jade, jade_HDLC_TSAR, 0x00); + } else { + cs->BC_Write_Reg(cs, jade, jade_HDLC_TSAX, 0x04); + cs->BC_Write_Reg(cs, jade, jade_HDLC_TSAR, 0x04); + } + switch (mode) { + case (L1_MODE_NULL): + cs->BC_Write_Reg(cs, jade, jade_HDLC_MODE, jadeMODE_TMO); + break; + case (L1_MODE_TRANS): + cs->BC_Write_Reg(cs, jade, jade_HDLC_MODE, (jadeMODE_TMO | jadeMODE_RAC | jadeMODE_XAC)); + break; + case (L1_MODE_HDLC): + cs->BC_Write_Reg(cs, jade, jade_HDLC_MODE, (jadeMODE_RAC | jadeMODE_XAC)); + break; + } + if (mode) { + cs->BC_Write_Reg(cs, jade, jade_HDLC_RCMD, (jadeRCMD_RRES | jadeRCMD_RMC)); + cs->BC_Write_Reg(cs, jade, jade_HDLC_XCMD, jadeXCMD_XRES); + /* Unmask ints */ + cs->BC_Write_Reg(cs, jade, jade_HDLC_IMR, 0xF8); + } + else + /* Mask ints */ + cs->BC_Write_Reg(cs, jade, jade_HDLC_IMR, 0x00); +} + +static void +jade_l2l1(struct PStack *st, int pr, void *arg) +{ + struct BCState *bcs = st->l1.bcs; + struct sk_buff *skb = arg; + u_long flags; + + switch (pr) { + case (PH_DATA | REQUEST): + spin_lock_irqsave(&bcs->cs->lock, flags); + if (bcs->tx_skb) { + skb_queue_tail(&bcs->squeue, skb); + } else { + bcs->tx_skb = skb; + test_and_set_bit(BC_FLG_BUSY, &bcs->Flag); + bcs->hw.hscx.count = 0; + bcs->cs->BC_Send_Data(bcs); + } + spin_unlock_irqrestore(&bcs->cs->lock, flags); + break; + case (PH_PULL | INDICATION): + spin_lock_irqsave(&bcs->cs->lock, flags); + if (bcs->tx_skb) { + printk(KERN_WARNING "jade_l2l1: this shouldn't happen\n"); + } else { + test_and_set_bit(BC_FLG_BUSY, &bcs->Flag); + bcs->tx_skb = skb; + bcs->hw.hscx.count = 0; + bcs->cs->BC_Send_Data(bcs); + } + spin_unlock_irqrestore(&bcs->cs->lock, flags); + break; + case (PH_PULL | REQUEST): + if (!bcs->tx_skb) { + test_and_clear_bit(FLG_L1_PULL_REQ, &st->l1.Flags); + st->l1.l1l2(st, PH_PULL | CONFIRM, NULL); + } else + test_and_set_bit(FLG_L1_PULL_REQ, &st->l1.Flags); + break; + case (PH_ACTIVATE | REQUEST): + spin_lock_irqsave(&bcs->cs->lock, flags); + test_and_set_bit(BC_FLG_ACTIV, &bcs->Flag); + modejade(bcs, st->l1.mode, st->l1.bc); + spin_unlock_irqrestore(&bcs->cs->lock, flags); + l1_msg_b(st, pr, arg); + break; + case (PH_DEACTIVATE | REQUEST): + l1_msg_b(st, pr, arg); + break; + case (PH_DEACTIVATE | CONFIRM): + spin_lock_irqsave(&bcs->cs->lock, flags); + test_and_clear_bit(BC_FLG_ACTIV, &bcs->Flag); + test_and_clear_bit(BC_FLG_BUSY, &bcs->Flag); + modejade(bcs, 0, st->l1.bc); + spin_unlock_irqrestore(&bcs->cs->lock, flags); + st->l1.l1l2(st, PH_DEACTIVATE | CONFIRM, NULL); + break; + } +} + +static void +close_jadestate(struct BCState *bcs) +{ + modejade(bcs, 0, bcs->channel); + if (test_and_clear_bit(BC_FLG_INIT, &bcs->Flag)) { + kfree(bcs->hw.hscx.rcvbuf); + bcs->hw.hscx.rcvbuf = NULL; + kfree(bcs->blog); + bcs->blog = NULL; + skb_queue_purge(&bcs->rqueue); + skb_queue_purge(&bcs->squeue); + if (bcs->tx_skb) { + dev_kfree_skb_any(bcs->tx_skb); + bcs->tx_skb = NULL; + test_and_clear_bit(BC_FLG_BUSY, &bcs->Flag); + } + } +} + +static int +open_jadestate(struct IsdnCardState *cs, struct BCState *bcs) +{ + if (!test_and_set_bit(BC_FLG_INIT, &bcs->Flag)) { + if (!(bcs->hw.hscx.rcvbuf = kmalloc(HSCX_BUFMAX, GFP_ATOMIC))) { + printk(KERN_WARNING + "HiSax: No memory for hscx.rcvbuf\n"); + test_and_clear_bit(BC_FLG_INIT, &bcs->Flag); + return (1); + } + if (!(bcs->blog = kmalloc(MAX_BLOG_SPACE, GFP_ATOMIC))) { + printk(KERN_WARNING + "HiSax: No memory for bcs->blog\n"); + test_and_clear_bit(BC_FLG_INIT, &bcs->Flag); + kfree(bcs->hw.hscx.rcvbuf); + bcs->hw.hscx.rcvbuf = NULL; + return (2); + } + skb_queue_head_init(&bcs->rqueue); + skb_queue_head_init(&bcs->squeue); + } + bcs->tx_skb = NULL; + test_and_clear_bit(BC_FLG_BUSY, &bcs->Flag); + bcs->event = 0; + bcs->hw.hscx.rcvidx = 0; + bcs->tx_cnt = 0; + return (0); +} + + +static int +setstack_jade(struct PStack *st, struct BCState *bcs) +{ + bcs->channel = st->l1.bc; + if (open_jadestate(st->l1.hardware, bcs)) + return (-1); + st->l1.bcs = bcs; + st->l2.l2l1 = jade_l2l1; + setstack_manager(st); + bcs->st = st; + setstack_l1_B(st); + return (0); +} + +void +clear_pending_jade_ints(struct IsdnCardState *cs) +{ + int val; + + cs->BC_Write_Reg(cs, 0, jade_HDLC_IMR, 0x00); + cs->BC_Write_Reg(cs, 1, jade_HDLC_IMR, 0x00); + + val = cs->BC_Read_Reg(cs, 1, jade_HDLC_ISR); + debugl1(cs, "jade B ISTA %x", val); + val = cs->BC_Read_Reg(cs, 0, jade_HDLC_ISR); + debugl1(cs, "jade A ISTA %x", val); + val = cs->BC_Read_Reg(cs, 1, jade_HDLC_STAR); + debugl1(cs, "jade B STAR %x", val); + val = cs->BC_Read_Reg(cs, 0, jade_HDLC_STAR); + debugl1(cs, "jade A STAR %x", val); + /* Unmask ints */ + cs->BC_Write_Reg(cs, 0, jade_HDLC_IMR, 0xF8); + cs->BC_Write_Reg(cs, 1, jade_HDLC_IMR, 0xF8); +} + +void +initjade(struct IsdnCardState *cs) +{ + cs->bcs[0].BC_SetStack = setstack_jade; + cs->bcs[1].BC_SetStack = setstack_jade; + cs->bcs[0].BC_Close = close_jadestate; + cs->bcs[1].BC_Close = close_jadestate; + cs->bcs[0].hw.hscx.hscx = 0; + cs->bcs[1].hw.hscx.hscx = 1; + + /* Stop DSP audio tx/rx */ + jade_write_indirect(cs, 0x11, 0x0f); + jade_write_indirect(cs, 0x17, 0x2f); + + /* Transparent Mode, RxTx inactive, No Test, No RFS/TFS */ + cs->BC_Write_Reg(cs, 0, jade_HDLC_MODE, jadeMODE_TMO); + cs->BC_Write_Reg(cs, 1, jade_HDLC_MODE, jadeMODE_TMO); + /* Power down, 1-Idle, RxTx least significant bit first */ + cs->BC_Write_Reg(cs, 0, jade_HDLC_CCR0, 0x00); + cs->BC_Write_Reg(cs, 1, jade_HDLC_CCR0, 0x00); + /* Mask all interrupts */ + cs->BC_Write_Reg(cs, 0, jade_HDLC_IMR, 0x00); + cs->BC_Write_Reg(cs, 1, jade_HDLC_IMR, 0x00); + /* Setup host access to hdlc controller */ + jade_write_indirect(cs, jade_HDLCCNTRACCESS, (jadeINDIRECT_HAH1 | jadeINDIRECT_HAH2)); + /* Unmask HDLC int (don't forget DSP int later on)*/ + cs->BC_Write_Reg(cs, -1, jade_INT, (jadeINT_HDLC1 | jadeINT_HDLC2)); + + /* once again TRANSPARENT */ + modejade(cs->bcs, 0, 0); + modejade(cs->bcs + 1, 0, 0); +} diff --git a/drivers/isdn/hisax/jade.h b/drivers/isdn/hisax/jade.h new file mode 100644 index 000000000..4b98096a5 --- /dev/null +++ b/drivers/isdn/hisax/jade.h @@ -0,0 +1,134 @@ +/* $Id: jade.h,v 1.5.2.3 2004/01/14 16:04:48 keil Exp $ + * + * JADE specific defines + * + * Author Roland Klabunde + * Copyright by Roland Klabunde <R.Klabunde@Berkom.de> + * + * This software may be used and distributed according to the terms + * of the GNU General Public License, incorporated herein by reference. + * + */ + +/* All Registers original Siemens Spec */ +#ifndef __JADE_H__ +#define __JADE_H__ + +/* Special registers for access to indirect accessible JADE regs */ +#define DIRECT_IO_JADE 0x0000 /* Jade direct io access area */ +#define COMM_JADE 0x0040 /* Jade communication area */ + +/********************************************************************/ +/* JADE-HDLC registers */ +/********************************************************************/ +#define jade_HDLC_RFIFO 0x00 /* R */ +#define jade_HDLC_XFIFO 0x00 /* W */ + +#define jade_HDLC_STAR 0x20 /* R */ +#define jadeSTAR_XDOV 0x80 +#define jadeSTAR_XFW 0x40 /* Does not work*/ +#define jadeSTAR_XCEC 0x20 +#define jadeSTAR_RCEC 0x10 +#define jadeSTAR_BSY 0x08 +#define jadeSTAR_RNA 0x04 +#define jadeSTAR_STR 0x02 +#define jadeSTAR_STX 0x01 + +#define jade_HDLC_XCMD 0x20 /* W */ +#define jadeXCMD_XF 0x80 +#define jadeXCMD_XME 0x40 +#define jadeXCMD_XRES 0x20 +#define jadeXCMD_STX 0x01 + +#define jade_HDLC_RSTA 0x21 /* R */ +#define jadeRSTA_VFR 0x80 +#define jadeRSTA_RDO 0x40 +#define jadeRSTA_CRC 0x20 +#define jadeRSTA_RAB 0x10 +#define jadeRSTA_MASK 0xF0 + +#define jade_HDLC_MODE 0x22 /* RW*/ +#define jadeMODE_TMO 0x80 +#define jadeMODE_RAC 0x40 +#define jadeMODE_XAC 0x20 +#define jadeMODE_TLP 0x10 +#define jadeMODE_ERFS 0x02 +#define jadeMODE_ETFS 0x01 + +#define jade_HDLC_RBCH 0x24 /* R */ + +#define jade_HDLC_RBCL 0x25 /* R */ +#define jade_HDLC_RCMD 0x25 /* W */ +#define jadeRCMD_RMC 0x80 +#define jadeRCMD_RRES 0x40 +#define jadeRCMD_RMD 0x20 +#define jadeRCMD_STR 0x02 + +#define jade_HDLC_CCR0 0x26 /* RW*/ +#define jadeCCR0_PU 0x80 +#define jadeCCR0_ITF 0x40 +#define jadeCCR0_C32 0x20 +#define jadeCCR0_CRL 0x10 +#define jadeCCR0_RCRC 0x08 +#define jadeCCR0_XCRC 0x04 +#define jadeCCR0_RMSB 0x02 +#define jadeCCR0_XMSB 0x01 + +#define jade_HDLC_CCR1 0x27 /* RW*/ +#define jadeCCR1_RCS0 0x80 +#define jadeCCR1_RCONT 0x40 +#define jadeCCR1_RFDIS 0x20 +#define jadeCCR1_XCS0 0x10 +#define jadeCCR1_XCONT 0x08 +#define jadeCCR1_XFDIS 0x04 + +#define jade_HDLC_TSAR 0x28 /* RW*/ +#define jade_HDLC_TSAX 0x29 /* RW*/ +#define jade_HDLC_RCCR 0x2A /* RW*/ +#define jade_HDLC_XCCR 0x2B /* RW*/ + +#define jade_HDLC_ISR 0x2C /* R */ +#define jade_HDLC_IMR 0x2C /* W */ +#define jadeISR_RME 0x80 +#define jadeISR_RPF 0x40 +#define jadeISR_RFO 0x20 +#define jadeISR_XPR 0x10 +#define jadeISR_XDU 0x08 +#define jadeISR_ALLS 0x04 + +#define jade_INT 0x75 +#define jadeINT_HDLC1 0x02 +#define jadeINT_HDLC2 0x01 +#define jadeINT_DSP 0x04 +#define jade_INTR 0x70 + +/********************************************************************/ +/* Indirect accessible JADE registers of common interest */ +/********************************************************************/ +#define jade_CHIPVERSIONNR 0x00 /* Does not work*/ + +#define jade_HDLCCNTRACCESS 0x10 +#define jadeINDIRECT_HAH1 0x02 +#define jadeINDIRECT_HAH2 0x01 + +#define jade_HDLC1SERRXPATH 0x1D +#define jade_HDLC1SERTXPATH 0x1E +#define jade_HDLC2SERRXPATH 0x1F +#define jade_HDLC2SERTXPATH 0x20 +#define jadeINDIRECT_SLIN1 0x10 +#define jadeINDIRECT_SLIN0 0x08 +#define jadeINDIRECT_LMOD1 0x04 +#define jadeINDIRECT_LMOD0 0x02 +#define jadeINDIRECT_HHR 0x01 +#define jadeINDIRECT_HHX 0x01 + +#define jade_RXAUDIOCH1CFG 0x11 +#define jade_RXAUDIOCH2CFG 0x14 +#define jade_TXAUDIOCH1CFG 0x17 +#define jade_TXAUDIOCH2CFG 0x1A + +extern int JadeVersion(struct IsdnCardState *cs, char *s); +extern void clear_pending_jade_ints(struct IsdnCardState *cs); +extern void initjade(struct IsdnCardState *cs); + +#endif /* __JADE_H__ */ diff --git a/drivers/isdn/hisax/jade_irq.c b/drivers/isdn/hisax/jade_irq.c new file mode 100644 index 000000000..a89e2df91 --- /dev/null +++ b/drivers/isdn/hisax/jade_irq.c @@ -0,0 +1,238 @@ +/* $Id: jade_irq.c,v 1.7.2.4 2004/02/11 13:21:34 keil Exp $ + * + * Low level JADE IRQ stuff (derived from original hscx_irq.c) + * + * Author Roland Klabunde + * Copyright by Roland Klabunde <R.Klabunde@Berkom.de> + * + * This software may be used and distributed according to the terms + * of the GNU General Public License, incorporated herein by reference. + * + */ + +static inline void +waitforCEC(struct IsdnCardState *cs, int jade, int reg) +{ + int to = 50; + int mask = (reg == jade_HDLC_XCMD ? jadeSTAR_XCEC : jadeSTAR_RCEC); + while ((READJADE(cs, jade, jade_HDLC_STAR) & mask) && to) { + udelay(1); + to--; + } + if (!to) + printk(KERN_WARNING "HiSax: waitforCEC (jade) timeout\n"); +} + + +static inline void +waitforXFW(struct IsdnCardState *cs, int jade) +{ + /* Does not work on older jade versions, don't care */ +} + +static inline void +WriteJADECMDR(struct IsdnCardState *cs, int jade, int reg, u_char data) +{ + waitforCEC(cs, jade, reg); + WRITEJADE(cs, jade, reg, data); +} + + + +static void +jade_empty_fifo(struct BCState *bcs, int count) +{ + u_char *ptr; + struct IsdnCardState *cs = bcs->cs; + + if ((cs->debug & L1_DEB_HSCX) && !(cs->debug & L1_DEB_HSCX_FIFO)) + debugl1(cs, "jade_empty_fifo"); + + if (bcs->hw.hscx.rcvidx + count > HSCX_BUFMAX) { + if (cs->debug & L1_DEB_WARN) + debugl1(cs, "jade_empty_fifo: incoming packet too large"); + WriteJADECMDR(cs, bcs->hw.hscx.hscx, jade_HDLC_RCMD, jadeRCMD_RMC); + bcs->hw.hscx.rcvidx = 0; + return; + } + ptr = bcs->hw.hscx.rcvbuf + bcs->hw.hscx.rcvidx; + bcs->hw.hscx.rcvidx += count; + READJADEFIFO(cs, bcs->hw.hscx.hscx, ptr, count); + WriteJADECMDR(cs, bcs->hw.hscx.hscx, jade_HDLC_RCMD, jadeRCMD_RMC); + if (cs->debug & L1_DEB_HSCX_FIFO) { + char *t = bcs->blog; + + t += sprintf(t, "jade_empty_fifo %c cnt %d", + bcs->hw.hscx.hscx ? 'B' : 'A', count); + QuickHex(t, ptr, count); + debugl1(cs, "%s", bcs->blog); + } +} + +static void +jade_fill_fifo(struct BCState *bcs) +{ + struct IsdnCardState *cs = bcs->cs; + int more, count; + int fifo_size = 32; + u_char *ptr; + + if ((cs->debug & L1_DEB_HSCX) && !(cs->debug & L1_DEB_HSCX_FIFO)) + debugl1(cs, "jade_fill_fifo"); + + if (!bcs->tx_skb) + return; + if (bcs->tx_skb->len <= 0) + return; + + more = (bcs->mode == L1_MODE_TRANS) ? 1 : 0; + if (bcs->tx_skb->len > fifo_size) { + more = !0; + count = fifo_size; + } else + count = bcs->tx_skb->len; + + waitforXFW(cs, bcs->hw.hscx.hscx); + ptr = bcs->tx_skb->data; + skb_pull(bcs->tx_skb, count); + bcs->tx_cnt -= count; + bcs->hw.hscx.count += count; + WRITEJADEFIFO(cs, bcs->hw.hscx.hscx, ptr, count); + WriteJADECMDR(cs, bcs->hw.hscx.hscx, jade_HDLC_XCMD, more ? jadeXCMD_XF : (jadeXCMD_XF | jadeXCMD_XME)); + if (cs->debug & L1_DEB_HSCX_FIFO) { + char *t = bcs->blog; + + t += sprintf(t, "jade_fill_fifo %c cnt %d", + bcs->hw.hscx.hscx ? 'B' : 'A', count); + QuickHex(t, ptr, count); + debugl1(cs, "%s", bcs->blog); + } +} + + +static void +jade_interrupt(struct IsdnCardState *cs, u_char val, u_char jade) +{ + u_char r; + struct BCState *bcs = cs->bcs + jade; + struct sk_buff *skb; + int fifo_size = 32; + int count; + int i_jade = (int) jade; /* To satisfy the compiler */ + + if (!test_bit(BC_FLG_INIT, &bcs->Flag)) + return; + + if (val & 0x80) { /* RME */ + r = READJADE(cs, i_jade, jade_HDLC_RSTA); + if ((r & 0xf0) != 0xa0) { + if (!(r & 0x80)) + if (cs->debug & L1_DEB_WARN) + debugl1(cs, "JADE %s invalid frame", (jade ? "B" : "A")); + if ((r & 0x40) && bcs->mode) + if (cs->debug & L1_DEB_WARN) + debugl1(cs, "JADE %c RDO mode=%d", 'A' + jade, bcs->mode); + if (!(r & 0x20)) + if (cs->debug & L1_DEB_WARN) + debugl1(cs, "JADE %c CRC error", 'A' + jade); + WriteJADECMDR(cs, jade, jade_HDLC_RCMD, jadeRCMD_RMC); + } else { + count = READJADE(cs, i_jade, jade_HDLC_RBCL) & 0x1F; + if (count == 0) + count = fifo_size; + jade_empty_fifo(bcs, count); + if ((count = bcs->hw.hscx.rcvidx - 1) > 0) { + if (cs->debug & L1_DEB_HSCX_FIFO) + debugl1(cs, "HX Frame %d", count); + if (!(skb = dev_alloc_skb(count))) + printk(KERN_WARNING "JADE %s receive out of memory\n", (jade ? "B" : "A")); + else { + skb_put_data(skb, bcs->hw.hscx.rcvbuf, + count); + skb_queue_tail(&bcs->rqueue, skb); + } + } + } + bcs->hw.hscx.rcvidx = 0; + schedule_event(bcs, B_RCVBUFREADY); + } + if (val & 0x40) { /* RPF */ + jade_empty_fifo(bcs, fifo_size); + if (bcs->mode == L1_MODE_TRANS) { + /* receive audio data */ + if (!(skb = dev_alloc_skb(fifo_size))) + printk(KERN_WARNING "HiSax: receive out of memory\n"); + else { + skb_put_data(skb, bcs->hw.hscx.rcvbuf, + fifo_size); + skb_queue_tail(&bcs->rqueue, skb); + } + bcs->hw.hscx.rcvidx = 0; + schedule_event(bcs, B_RCVBUFREADY); + } + } + if (val & 0x10) { /* XPR */ + if (bcs->tx_skb) { + if (bcs->tx_skb->len) { + jade_fill_fifo(bcs); + return; + } else { + if (test_bit(FLG_LLI_L1WAKEUP, &bcs->st->lli.flag) && + (PACKET_NOACK != bcs->tx_skb->pkt_type)) { + u_long flags; + spin_lock_irqsave(&bcs->aclock, flags); + bcs->ackcnt += bcs->hw.hscx.count; + spin_unlock_irqrestore(&bcs->aclock, flags); + schedule_event(bcs, B_ACKPENDING); + } + dev_kfree_skb_irq(bcs->tx_skb); + bcs->hw.hscx.count = 0; + bcs->tx_skb = NULL; + } + } + if ((bcs->tx_skb = skb_dequeue(&bcs->squeue))) { + bcs->hw.hscx.count = 0; + test_and_set_bit(BC_FLG_BUSY, &bcs->Flag); + jade_fill_fifo(bcs); + } else { + test_and_clear_bit(BC_FLG_BUSY, &bcs->Flag); + schedule_event(bcs, B_XMTBUFREADY); + } + } +} + +static inline void +jade_int_main(struct IsdnCardState *cs, u_char val, int jade) +{ + struct BCState *bcs; + bcs = cs->bcs + jade; + + if (val & jadeISR_RFO) { + /* handled with RDO */ + val &= ~jadeISR_RFO; + } + if (val & jadeISR_XDU) { + /* relevant in HDLC mode only */ + /* don't reset XPR here */ + if (bcs->mode == 1) + jade_fill_fifo(bcs); + else { + /* Here we lost an TX interrupt, so + * restart transmitting the whole frame. + */ + if (bcs->tx_skb) { + skb_push(bcs->tx_skb, bcs->hw.hscx.count); + bcs->tx_cnt += bcs->hw.hscx.count; + bcs->hw.hscx.count = 0; + } + WriteJADECMDR(cs, bcs->hw.hscx.hscx, jade_HDLC_XCMD, jadeXCMD_XRES); + if (cs->debug & L1_DEB_WARN) + debugl1(cs, "JADE %c EXIR %x Lost TX", 'A' + jade, val); + } + } + if (val & (jadeISR_RME | jadeISR_RPF | jadeISR_XPR)) { + if (cs->debug & L1_DEB_HSCX) + debugl1(cs, "JADE %c interrupt %x", 'A' + jade, val); + jade_interrupt(cs, val, jade); + } +} diff --git a/drivers/isdn/hisax/l3_1tr6.c b/drivers/isdn/hisax/l3_1tr6.c new file mode 100644 index 000000000..98f60d152 --- /dev/null +++ b/drivers/isdn/hisax/l3_1tr6.c @@ -0,0 +1,932 @@ +/* $Id: l3_1tr6.c,v 2.15.2.3 2004/01/13 14:31:25 keil Exp $ + * + * German 1TR6 D-channel protocol + * + * Author Karsten Keil + * Copyright by Karsten Keil <keil@isdn4linux.de> + * + * This software may be used and distributed according to the terms + * of the GNU General Public License, incorporated herein by reference. + * + * For changes and modifications please read + * Documentation/isdn/HiSax.cert + * + */ + +#include "hisax.h" +#include "l3_1tr6.h" +#include "isdnl3.h" +#include <linux/ctype.h> + +extern char *HiSax_getrev(const char *revision); +static const char *l3_1tr6_revision = "$Revision: 2.15.2.3 $"; + +#define MsgHead(ptr, cref, mty, dis) \ + *ptr++ = dis; \ + *ptr++ = 0x1; \ + *ptr++ = cref ^ 0x80; \ + *ptr++ = mty + +static void +l3_1TR6_message(struct l3_process *pc, u_char mt, u_char pd) +{ + struct sk_buff *skb; + u_char *p; + + if (!(skb = l3_alloc_skb(4))) + return; + p = skb_put(skb, 4); + MsgHead(p, pc->callref, mt, pd); + l3_msg(pc->st, DL_DATA | REQUEST, skb); +} + +static void +l3_1tr6_release_req(struct l3_process *pc, u_char pr, void *arg) +{ + StopAllL3Timer(pc); + newl3state(pc, 19); + l3_1TR6_message(pc, MT_N1_REL, PROTO_DIS_N1); + L3AddTimer(&pc->timer, T308, CC_T308_1); +} + +static void +l3_1tr6_invalid(struct l3_process *pc, u_char pr, void *arg) +{ + struct sk_buff *skb = arg; + + dev_kfree_skb(skb); + l3_1tr6_release_req(pc, 0, NULL); +} + +static void +l3_1tr6_error(struct l3_process *pc, u_char *msg, struct sk_buff *skb) +{ + dev_kfree_skb(skb); + if (pc->st->l3.debug & L3_DEB_WARN) + l3_debug(pc->st, "%s", msg); + l3_1tr6_release_req(pc, 0, NULL); +} + +static void +l3_1tr6_setup_req(struct l3_process *pc, u_char pr, void *arg) +{ + struct sk_buff *skb; + u_char tmp[128]; + u_char *p = tmp; + u_char *teln; + u_char *eaz; + u_char channel = 0; + int l; + + MsgHead(p, pc->callref, MT_N1_SETUP, PROTO_DIS_N1); + teln = pc->para.setup.phone; + pc->para.spv = 0; + if (!isdigit(*teln)) { + switch (0x5f & *teln) { + case 'S': + pc->para.spv = 1; + break; + case 'C': + channel = 0x08; + /* fall through */ + case 'P': + channel |= 0x80; + teln++; + if (*teln == '1') + channel |= 0x01; + else + channel |= 0x02; + break; + default: + if (pc->st->l3.debug & L3_DEB_WARN) + l3_debug(pc->st, "Wrong MSN Code"); + break; + } + teln++; + } + if (channel) { + *p++ = 0x18; /* channel indicator */ + *p++ = 1; + *p++ = channel; + } + if (pc->para.spv) { /* SPV ? */ + /* NSF SPV */ + *p++ = WE0_netSpecFac; + *p++ = 4; /* Laenge */ + *p++ = 0; + *p++ = FAC_SPV; /* SPV */ + *p++ = pc->para.setup.si1; /* 0 for all Services */ + *p++ = pc->para.setup.si2; /* 0 for all Services */ + *p++ = WE0_netSpecFac; + *p++ = 4; /* Laenge */ + *p++ = 0; + *p++ = FAC_Activate; /* aktiviere SPV (default) */ + *p++ = pc->para.setup.si1; /* 0 for all Services */ + *p++ = pc->para.setup.si2; /* 0 for all Services */ + } + eaz = pc->para.setup.eazmsn; + if (*eaz) { + *p++ = WE0_origAddr; + *p++ = strlen(eaz) + 1; + /* Classify as AnyPref. */ + *p++ = 0x81; /* Ext = '1'B, Type = '000'B, Plan = '0001'B. */ + while (*eaz) + *p++ = *eaz++ & 0x7f; + } + *p++ = WE0_destAddr; + *p++ = strlen(teln) + 1; + /* Classify as AnyPref. */ + *p++ = 0x81; /* Ext = '1'B, Type = '000'B, Plan = '0001'B. */ + while (*teln) + *p++ = *teln++ & 0x7f; + + *p++ = WE_Shift_F6; + /* Codesatz 6 fuer Service */ + *p++ = WE6_serviceInd; + *p++ = 2; /* len=2 info,info2 */ + *p++ = pc->para.setup.si1; + *p++ = pc->para.setup.si2; + + l = p - tmp; + if (!(skb = l3_alloc_skb(l))) + return; + skb_put_data(skb, tmp, l); + L3DelTimer(&pc->timer); + L3AddTimer(&pc->timer, T303, CC_T303); + newl3state(pc, 1); + l3_msg(pc->st, DL_DATA | REQUEST, skb); +} + +static void +l3_1tr6_setup(struct l3_process *pc, u_char pr, void *arg) +{ + u_char *p; + int bcfound = 0; + struct sk_buff *skb = arg; + + /* Channel Identification */ + p = findie(skb->data, skb->len, WE0_chanID, 0); + if (p) { + if (p[1] != 1) { + l3_1tr6_error(pc, "setup wrong chanID len", skb); + return; + } + if ((p[2] & 0xf4) != 0x80) { + l3_1tr6_error(pc, "setup wrong WE0_chanID", skb); + return; + } + if ((pc->para.bchannel = p[2] & 0x3)) + bcfound++; + } else { + l3_1tr6_error(pc, "missing setup chanID", skb); + return; + } + + p = skb->data; + if ((p = findie(p, skb->len, WE6_serviceInd, 6))) { + pc->para.setup.si1 = p[2]; + pc->para.setup.si2 = p[3]; + } else { + l3_1tr6_error(pc, "missing setup SI", skb); + return; + } + + p = skb->data; + if ((p = findie(p, skb->len, WE0_destAddr, 0))) + iecpy(pc->para.setup.eazmsn, p, 1); + else + pc->para.setup.eazmsn[0] = 0; + + p = skb->data; + if ((p = findie(p, skb->len, WE0_origAddr, 0))) { + iecpy(pc->para.setup.phone, p, 1); + } else + pc->para.setup.phone[0] = 0; + + p = skb->data; + pc->para.spv = 0; + if ((p = findie(p, skb->len, WE0_netSpecFac, 0))) { + if ((FAC_SPV == p[3]) || (FAC_Activate == p[3])) + pc->para.spv = 1; + } + dev_kfree_skb(skb); + + /* Signal all services, linklevel takes care of Service-Indicator */ + if (bcfound) { + if ((pc->para.setup.si1 != 7) && (pc->st->l3.debug & L3_DEB_WARN)) { + l3_debug(pc->st, "non-digital call: %s -> %s", + pc->para.setup.phone, + pc->para.setup.eazmsn); + } + newl3state(pc, 6); + pc->st->l3.l3l4(pc->st, CC_SETUP | INDICATION, pc); + } else + release_l3_process(pc); +} + +static void +l3_1tr6_setup_ack(struct l3_process *pc, u_char pr, void *arg) +{ + u_char *p; + struct sk_buff *skb = arg; + + L3DelTimer(&pc->timer); + p = skb->data; + newl3state(pc, 2); + if ((p = findie(p, skb->len, WE0_chanID, 0))) { + if (p[1] != 1) { + l3_1tr6_error(pc, "setup_ack wrong chanID len", skb); + return; + } + if ((p[2] & 0xf4) != 0x80) { + l3_1tr6_error(pc, "setup_ack wrong WE0_chanID", skb); + return; + } + pc->para.bchannel = p[2] & 0x3; + } else { + l3_1tr6_error(pc, "missing setup_ack WE0_chanID", skb); + return; + } + dev_kfree_skb(skb); + L3AddTimer(&pc->timer, T304, CC_T304); + pc->st->l3.l3l4(pc->st, CC_MORE_INFO | INDICATION, pc); +} + +static void +l3_1tr6_call_sent(struct l3_process *pc, u_char pr, void *arg) +{ + u_char *p; + struct sk_buff *skb = arg; + + L3DelTimer(&pc->timer); + p = skb->data; + if ((p = findie(p, skb->len, WE0_chanID, 0))) { + if (p[1] != 1) { + l3_1tr6_error(pc, "call sent wrong chanID len", skb); + return; + } + if ((p[2] & 0xf4) != 0x80) { + l3_1tr6_error(pc, "call sent wrong WE0_chanID", skb); + return; + } + if ((pc->state == 2) && (pc->para.bchannel != (p[2] & 0x3))) { + l3_1tr6_error(pc, "call sent wrong chanID value", skb); + return; + } + pc->para.bchannel = p[2] & 0x3; + } else { + l3_1tr6_error(pc, "missing call sent WE0_chanID", skb); + return; + } + dev_kfree_skb(skb); + L3AddTimer(&pc->timer, T310, CC_T310); + newl3state(pc, 3); + pc->st->l3.l3l4(pc->st, CC_PROCEEDING | INDICATION, pc); +} + +static void +l3_1tr6_alert(struct l3_process *pc, u_char pr, void *arg) +{ + struct sk_buff *skb = arg; + + dev_kfree_skb(skb); + L3DelTimer(&pc->timer); /* T304 */ + newl3state(pc, 4); + pc->st->l3.l3l4(pc->st, CC_ALERTING | INDICATION, pc); +} + +static void +l3_1tr6_info(struct l3_process *pc, u_char pr, void *arg) +{ + u_char *p; + int i, tmpcharge = 0; + char a_charge[8]; + struct sk_buff *skb = arg; + + p = skb->data; + if ((p = findie(p, skb->len, WE6_chargingInfo, 6))) { + iecpy(a_charge, p, 1); + for (i = 0; i < strlen(a_charge); i++) { + tmpcharge *= 10; + tmpcharge += a_charge[i] & 0xf; + } + if (tmpcharge > pc->para.chargeinfo) { + pc->para.chargeinfo = tmpcharge; + pc->st->l3.l3l4(pc->st, CC_CHARGE | INDICATION, pc); + } + if (pc->st->l3.debug & L3_DEB_CHARGE) { + l3_debug(pc->st, "charging info %d", + pc->para.chargeinfo); + } + } else if (pc->st->l3.debug & L3_DEB_CHARGE) + l3_debug(pc->st, "charging info not found"); + dev_kfree_skb(skb); + +} + +static void +l3_1tr6_info_s2(struct l3_process *pc, u_char pr, void *arg) +{ + struct sk_buff *skb = arg; + + dev_kfree_skb(skb); +} + +static void +l3_1tr6_connect(struct l3_process *pc, u_char pr, void *arg) +{ + struct sk_buff *skb = arg; + + L3DelTimer(&pc->timer); /* T310 */ + if (!findie(skb->data, skb->len, WE6_date, 6)) { + l3_1tr6_error(pc, "missing connect date", skb); + return; + } + newl3state(pc, 10); + dev_kfree_skb(skb); + pc->para.chargeinfo = 0; + pc->st->l3.l3l4(pc->st, CC_SETUP | CONFIRM, pc); +} + +static void +l3_1tr6_rel(struct l3_process *pc, u_char pr, void *arg) +{ + struct sk_buff *skb = arg; + u_char *p; + + p = skb->data; + if ((p = findie(p, skb->len, WE0_cause, 0))) { + if (p[1] > 0) { + pc->para.cause = p[2]; + if (p[1] > 1) + pc->para.loc = p[3]; + else + pc->para.loc = 0; + } else { + pc->para.cause = 0; + pc->para.loc = 0; + } + } else { + pc->para.cause = NO_CAUSE; + l3_1tr6_error(pc, "missing REL cause", skb); + return; + } + dev_kfree_skb(skb); + StopAllL3Timer(pc); + newl3state(pc, 0); + l3_1TR6_message(pc, MT_N1_REL_ACK, PROTO_DIS_N1); + pc->st->l3.l3l4(pc->st, CC_RELEASE | INDICATION, pc); + release_l3_process(pc); +} + +static void +l3_1tr6_rel_ack(struct l3_process *pc, u_char pr, void *arg) +{ + struct sk_buff *skb = arg; + + dev_kfree_skb(skb); + StopAllL3Timer(pc); + newl3state(pc, 0); + pc->para.cause = NO_CAUSE; + pc->st->l3.l3l4(pc->st, CC_RELEASE | CONFIRM, pc); + release_l3_process(pc); +} + +static void +l3_1tr6_disc(struct l3_process *pc, u_char pr, void *arg) +{ + struct sk_buff *skb = arg; + u_char *p; + int i, tmpcharge = 0; + char a_charge[8]; + + StopAllL3Timer(pc); + p = skb->data; + if ((p = findie(p, skb->len, WE6_chargingInfo, 6))) { + iecpy(a_charge, p, 1); + for (i = 0; i < strlen(a_charge); i++) { + tmpcharge *= 10; + tmpcharge += a_charge[i] & 0xf; + } + if (tmpcharge > pc->para.chargeinfo) { + pc->para.chargeinfo = tmpcharge; + pc->st->l3.l3l4(pc->st, CC_CHARGE | INDICATION, pc); + } + if (pc->st->l3.debug & L3_DEB_CHARGE) { + l3_debug(pc->st, "charging info %d", + pc->para.chargeinfo); + } + } else if (pc->st->l3.debug & L3_DEB_CHARGE) + l3_debug(pc->st, "charging info not found"); + + + p = skb->data; + if ((p = findie(p, skb->len, WE0_cause, 0))) { + if (p[1] > 0) { + pc->para.cause = p[2]; + if (p[1] > 1) + pc->para.loc = p[3]; + else + pc->para.loc = 0; + } else { + pc->para.cause = 0; + pc->para.loc = 0; + } + } else { + if (pc->st->l3.debug & L3_DEB_WARN) + l3_debug(pc->st, "cause not found"); + pc->para.cause = NO_CAUSE; + } + if (!findie(skb->data, skb->len, WE6_date, 6)) { + l3_1tr6_error(pc, "missing connack date", skb); + return; + } + dev_kfree_skb(skb); + newl3state(pc, 12); + pc->st->l3.l3l4(pc->st, CC_DISCONNECT | INDICATION, pc); +} + + +static void +l3_1tr6_connect_ack(struct l3_process *pc, u_char pr, void *arg) +{ + struct sk_buff *skb = arg; + + if (!findie(skb->data, skb->len, WE6_date, 6)) { + l3_1tr6_error(pc, "missing connack date", skb); + return; + } + dev_kfree_skb(skb); + newl3state(pc, 10); + pc->para.chargeinfo = 0; + L3DelTimer(&pc->timer); + pc->st->l3.l3l4(pc->st, CC_SETUP_COMPL | INDICATION, pc); +} + +static void +l3_1tr6_alert_req(struct l3_process *pc, u_char pr, void *arg) +{ + newl3state(pc, 7); + l3_1TR6_message(pc, MT_N1_ALERT, PROTO_DIS_N1); +} + +static void +l3_1tr6_setup_rsp(struct l3_process *pc, u_char pr, void *arg) +{ + struct sk_buff *skb; + u_char tmp[24]; + u_char *p = tmp; + int l; + + MsgHead(p, pc->callref, MT_N1_CONN, PROTO_DIS_N1); + if (pc->para.spv) { /* SPV ? */ + /* NSF SPV */ + *p++ = WE0_netSpecFac; + *p++ = 4; /* Laenge */ + *p++ = 0; + *p++ = FAC_SPV; /* SPV */ + *p++ = pc->para.setup.si1; + *p++ = pc->para.setup.si2; + *p++ = WE0_netSpecFac; + *p++ = 4; /* Laenge */ + *p++ = 0; + *p++ = FAC_Activate; /* aktiviere SPV */ + *p++ = pc->para.setup.si1; + *p++ = pc->para.setup.si2; + } + newl3state(pc, 8); + l = p - tmp; + if (!(skb = l3_alloc_skb(l))) + return; + skb_put_data(skb, tmp, l); + l3_msg(pc->st, DL_DATA | REQUEST, skb); + L3DelTimer(&pc->timer); + L3AddTimer(&pc->timer, T313, CC_T313); +} + +static void +l3_1tr6_reset(struct l3_process *pc, u_char pr, void *arg) +{ + release_l3_process(pc); +} + +static void +l3_1tr6_disconnect_req(struct l3_process *pc, u_char pr, void *arg) +{ + struct sk_buff *skb; + u_char tmp[16]; + u_char *p = tmp; + int l; + u_char cause = 0x10; + u_char clen = 1; + + if (pc->para.cause > 0) + cause = pc->para.cause; + /* Map DSS1 causes */ + switch (cause & 0x7f) { + case 0x10: + clen = 0; + break; + case 0x11: + cause = CAUSE_UserBusy; + break; + case 0x15: + cause = CAUSE_CallRejected; + break; + } + StopAllL3Timer(pc); + MsgHead(p, pc->callref, MT_N1_DISC, PROTO_DIS_N1); + *p++ = WE0_cause; + *p++ = clen; /* Laenge */ + if (clen) + *p++ = cause | 0x80; + newl3state(pc, 11); + l = p - tmp; + if (!(skb = l3_alloc_skb(l))) + return; + skb_put_data(skb, tmp, l); + l3_msg(pc->st, DL_DATA | REQUEST, skb); + L3AddTimer(&pc->timer, T305, CC_T305); +} + +static void +l3_1tr6_t303(struct l3_process *pc, u_char pr, void *arg) +{ + if (pc->N303 > 0) { + pc->N303--; + L3DelTimer(&pc->timer); + l3_1tr6_setup_req(pc, pr, arg); + } else { + L3DelTimer(&pc->timer); + pc->para.cause = 0; + l3_1tr6_disconnect_req(pc, 0, NULL); + } +} + +static void +l3_1tr6_t304(struct l3_process *pc, u_char pr, void *arg) +{ + L3DelTimer(&pc->timer); + pc->para.cause = 0xE6; + l3_1tr6_disconnect_req(pc, pr, NULL); + pc->st->l3.l3l4(pc->st, CC_SETUP_ERR, pc); +} + +static void +l3_1tr6_t305(struct l3_process *pc, u_char pr, void *arg) +{ + struct sk_buff *skb; + u_char tmp[16]; + u_char *p = tmp; + int l; + u_char cause = 0x90; + u_char clen = 1; + + L3DelTimer(&pc->timer); + if (pc->para.cause != NO_CAUSE) + cause = pc->para.cause; + /* Map DSS1 causes */ + switch (cause & 0x7f) { + case 0x10: + clen = 0; + break; + case 0x15: + cause = CAUSE_CallRejected; + break; + } + MsgHead(p, pc->callref, MT_N1_REL, PROTO_DIS_N1); + *p++ = WE0_cause; + *p++ = clen; /* Laenge */ + if (clen) + *p++ = cause; + newl3state(pc, 19); + l = p - tmp; + if (!(skb = l3_alloc_skb(l))) + return; + skb_put_data(skb, tmp, l); + l3_msg(pc->st, DL_DATA | REQUEST, skb); + L3AddTimer(&pc->timer, T308, CC_T308_1); +} + +static void +l3_1tr6_t310(struct l3_process *pc, u_char pr, void *arg) +{ + L3DelTimer(&pc->timer); + pc->para.cause = 0xE6; + l3_1tr6_disconnect_req(pc, pr, NULL); + pc->st->l3.l3l4(pc->st, CC_SETUP_ERR, pc); +} + +static void +l3_1tr6_t313(struct l3_process *pc, u_char pr, void *arg) +{ + L3DelTimer(&pc->timer); + pc->para.cause = 0xE6; + l3_1tr6_disconnect_req(pc, pr, NULL); + pc->st->l3.l3l4(pc->st, CC_CONNECT_ERR, pc); +} + +static void +l3_1tr6_t308_1(struct l3_process *pc, u_char pr, void *arg) +{ + L3DelTimer(&pc->timer); + l3_1TR6_message(pc, MT_N1_REL, PROTO_DIS_N1); + L3AddTimer(&pc->timer, T308, CC_T308_2); + newl3state(pc, 19); +} + +static void +l3_1tr6_t308_2(struct l3_process *pc, u_char pr, void *arg) +{ + L3DelTimer(&pc->timer); + pc->st->l3.l3l4(pc->st, CC_RELEASE_ERR, pc); + release_l3_process(pc); +} + +static void +l3_1tr6_dl_reset(struct l3_process *pc, u_char pr, void *arg) +{ + pc->para.cause = CAUSE_LocalProcErr; + l3_1tr6_disconnect_req(pc, pr, NULL); + pc->st->l3.l3l4(pc->st, CC_SETUP_ERR, pc); +} + +static void +l3_1tr6_dl_release(struct l3_process *pc, u_char pr, void *arg) +{ + newl3state(pc, 0); + pc->para.cause = 0x1b; /* Destination out of order */ + pc->para.loc = 0; + pc->st->l3.l3l4(pc->st, CC_RELEASE | INDICATION, pc); + release_l3_process(pc); +} + +/* *INDENT-OFF* */ +static struct stateentry downstl[] = +{ + {SBIT(0), + CC_SETUP | REQUEST, l3_1tr6_setup_req}, + {SBIT(1) | SBIT(2) | SBIT(3) | SBIT(4) | SBIT(6) | SBIT(7) | SBIT(8) | + SBIT(10), + CC_DISCONNECT | REQUEST, l3_1tr6_disconnect_req}, + {SBIT(12), + CC_RELEASE | REQUEST, l3_1tr6_release_req}, + {SBIT(6), + CC_IGNORE | REQUEST, l3_1tr6_reset}, + {SBIT(6), + CC_REJECT | REQUEST, l3_1tr6_disconnect_req}, + {SBIT(6), + CC_ALERTING | REQUEST, l3_1tr6_alert_req}, + {SBIT(6) | SBIT(7), + CC_SETUP | RESPONSE, l3_1tr6_setup_rsp}, + {SBIT(1), + CC_T303, l3_1tr6_t303}, + {SBIT(2), + CC_T304, l3_1tr6_t304}, + {SBIT(3), + CC_T310, l3_1tr6_t310}, + {SBIT(8), + CC_T313, l3_1tr6_t313}, + {SBIT(11), + CC_T305, l3_1tr6_t305}, + {SBIT(19), + CC_T308_1, l3_1tr6_t308_1}, + {SBIT(19), + CC_T308_2, l3_1tr6_t308_2}, +}; + +static struct stateentry datastln1[] = +{ + {SBIT(0), + MT_N1_INVALID, l3_1tr6_invalid}, + {SBIT(0), + MT_N1_SETUP, l3_1tr6_setup}, + {SBIT(1), + MT_N1_SETUP_ACK, l3_1tr6_setup_ack}, + {SBIT(1) | SBIT(2), + MT_N1_CALL_SENT, l3_1tr6_call_sent}, + {SBIT(1) | SBIT(2) | SBIT(3) | SBIT(4) | SBIT(7) | SBIT(8) | SBIT(10), + MT_N1_DISC, l3_1tr6_disc}, + {SBIT(2) | SBIT(3) | SBIT(4), + MT_N1_ALERT, l3_1tr6_alert}, + {SBIT(2) | SBIT(3) | SBIT(4), + MT_N1_CONN, l3_1tr6_connect}, + {SBIT(2), + MT_N1_INFO, l3_1tr6_info_s2}, + {SBIT(8), + MT_N1_CONN_ACK, l3_1tr6_connect_ack}, + {SBIT(10), + MT_N1_INFO, l3_1tr6_info}, + {SBIT(0) | SBIT(1) | SBIT(2) | SBIT(3) | SBIT(4) | SBIT(7) | SBIT(8) | + SBIT(10) | SBIT(11) | SBIT(12) | SBIT(15) | SBIT(17), + MT_N1_REL, l3_1tr6_rel}, + {SBIT(19), + MT_N1_REL, l3_1tr6_rel_ack}, + {SBIT(0) | SBIT(1) | SBIT(2) | SBIT(3) | SBIT(4) | SBIT(7) | SBIT(8) | + SBIT(10) | SBIT(11) | SBIT(12) | SBIT(15) | SBIT(17), + MT_N1_REL_ACK, l3_1tr6_invalid}, + {SBIT(19), + MT_N1_REL_ACK, l3_1tr6_rel_ack} +}; + +static struct stateentry manstatelist[] = +{ + {SBIT(2), + DL_ESTABLISH | INDICATION, l3_1tr6_dl_reset}, + {ALL_STATES, + DL_RELEASE | INDICATION, l3_1tr6_dl_release}, +}; + +/* *INDENT-ON* */ + +static void +up1tr6(struct PStack *st, int pr, void *arg) +{ + int i, mt, cr; + struct l3_process *proc; + struct sk_buff *skb = arg; + + switch (pr) { + case (DL_DATA | INDICATION): + case (DL_UNIT_DATA | INDICATION): + break; + case (DL_ESTABLISH | CONFIRM): + case (DL_ESTABLISH | INDICATION): + case (DL_RELEASE | INDICATION): + case (DL_RELEASE | CONFIRM): + l3_msg(st, pr, arg); + return; + break; + } + if (skb->len < 4) { + if (st->l3.debug & L3_DEB_PROTERR) { + l3_debug(st, "up1tr6 len only %d", skb->len); + } + dev_kfree_skb(skb); + return; + } + if ((skb->data[0] & 0xfe) != PROTO_DIS_N0) { + if (st->l3.debug & L3_DEB_PROTERR) { + l3_debug(st, "up1tr6%sunexpected discriminator %x message len %d", + (pr == (DL_DATA | INDICATION)) ? " " : "(broadcast) ", + skb->data[0], skb->len); + } + dev_kfree_skb(skb); + return; + } + if (skb->data[1] != 1) { + if (st->l3.debug & L3_DEB_PROTERR) { + l3_debug(st, "up1tr6 CR len not 1"); + } + dev_kfree_skb(skb); + return; + } + cr = skb->data[2]; + mt = skb->data[3]; + if (skb->data[0] == PROTO_DIS_N0) { + dev_kfree_skb(skb); + if (st->l3.debug & L3_DEB_STATE) { + l3_debug(st, "up1tr6%s N0 mt %x unhandled", + (pr == (DL_DATA | INDICATION)) ? " " : "(broadcast) ", mt); + } + } else if (skb->data[0] == PROTO_DIS_N1) { + if (!(proc = getl3proc(st, cr))) { + if (mt == MT_N1_SETUP) { + if (cr < 128) { + if (!(proc = new_l3_process(st, cr))) { + if (st->l3.debug & L3_DEB_PROTERR) { + l3_debug(st, "up1tr6 no roc mem"); + } + dev_kfree_skb(skb); + return; + } + } else { + dev_kfree_skb(skb); + return; + } + } else if ((mt == MT_N1_REL) || (mt == MT_N1_REL_ACK) || + (mt == MT_N1_CANC_ACK) || (mt == MT_N1_CANC_REJ) || + (mt == MT_N1_REG_ACK) || (mt == MT_N1_REG_REJ) || + (mt == MT_N1_SUSP_ACK) || (mt == MT_N1_RES_REJ) || + (mt == MT_N1_INFO)) { + dev_kfree_skb(skb); + return; + } else { + if (!(proc = new_l3_process(st, cr))) { + if (st->l3.debug & L3_DEB_PROTERR) { + l3_debug(st, "up1tr6 no roc mem"); + } + dev_kfree_skb(skb); + return; + } + mt = MT_N1_INVALID; + } + } + for (i = 0; i < ARRAY_SIZE(datastln1); i++) + if ((mt == datastln1[i].primitive) && + ((1 << proc->state) & datastln1[i].state)) + break; + if (i == ARRAY_SIZE(datastln1)) { + dev_kfree_skb(skb); + if (st->l3.debug & L3_DEB_STATE) { + l3_debug(st, "up1tr6%sstate %d mt %x unhandled", + (pr == (DL_DATA | INDICATION)) ? " " : "(broadcast) ", + proc->state, mt); + } + return; + } else { + if (st->l3.debug & L3_DEB_STATE) { + l3_debug(st, "up1tr6%sstate %d mt %x", + (pr == (DL_DATA | INDICATION)) ? " " : "(broadcast) ", + proc->state, mt); + } + datastln1[i].rout(proc, pr, skb); + } + } +} + +static void +down1tr6(struct PStack *st, int pr, void *arg) +{ + int i, cr; + struct l3_process *proc; + struct Channel *chan; + + if ((DL_ESTABLISH | REQUEST) == pr) { + l3_msg(st, pr, NULL); + return; + } else if ((CC_SETUP | REQUEST) == pr) { + chan = arg; + cr = newcallref(); + cr |= 0x80; + if (!(proc = new_l3_process(st, cr))) { + return; + } else { + proc->chan = chan; + chan->proc = proc; + memcpy(&proc->para.setup, &chan->setup, sizeof(setup_parm)); + proc->callref = cr; + } + } else { + proc = arg; + } + + for (i = 0; i < ARRAY_SIZE(downstl); i++) + if ((pr == downstl[i].primitive) && + ((1 << proc->state) & downstl[i].state)) + break; + if (i == ARRAY_SIZE(downstl)) { + if (st->l3.debug & L3_DEB_STATE) { + l3_debug(st, "down1tr6 state %d prim %d unhandled", + proc->state, pr); + } + } else { + if (st->l3.debug & L3_DEB_STATE) { + l3_debug(st, "down1tr6 state %d prim %d", + proc->state, pr); + } + downstl[i].rout(proc, pr, arg); + } +} + +static void +man1tr6(struct PStack *st, int pr, void *arg) +{ + int i; + struct l3_process *proc = arg; + + if (!proc) { + printk(KERN_ERR "HiSax man1tr6 without proc pr=%04x\n", pr); + return; + } + for (i = 0; i < ARRAY_SIZE(manstatelist); i++) + if ((pr == manstatelist[i].primitive) && + ((1 << proc->state) & manstatelist[i].state)) + break; + if (i == ARRAY_SIZE(manstatelist)) { + if (st->l3.debug & L3_DEB_STATE) { + l3_debug(st, "cr %d man1tr6 state %d prim %d unhandled", + proc->callref & 0x7f, proc->state, pr); + } + } else { + if (st->l3.debug & L3_DEB_STATE) { + l3_debug(st, "cr %d man1tr6 state %d prim %d", + proc->callref & 0x7f, proc->state, pr); + } + manstatelist[i].rout(proc, pr, arg); + } +} + +void +setstack_1tr6(struct PStack *st) +{ + char tmp[64]; + + st->lli.l4l3 = down1tr6; + st->l2.l2l3 = up1tr6; + st->l3.l3ml3 = man1tr6; + st->l3.N303 = 0; + + strcpy(tmp, l3_1tr6_revision); + printk(KERN_INFO "HiSax: 1TR6 Rev. %s\n", HiSax_getrev(tmp)); +} diff --git a/drivers/isdn/hisax/l3_1tr6.h b/drivers/isdn/hisax/l3_1tr6.h new file mode 100644 index 000000000..43215c00c --- /dev/null +++ b/drivers/isdn/hisax/l3_1tr6.h @@ -0,0 +1,164 @@ +/* $Id: l3_1tr6.h,v 2.2.6.2 2001/09/23 22:24:49 kai Exp $ + * + * German 1TR6 D-channel protocol defines + * + * This software may be used and distributed according to the terms + * of the GNU General Public License, incorporated herein by reference. + * + */ + +#ifndef l3_1tr6 +#define l3_1tr6 + +#define PROTO_DIS_N0 0x40 +#define PROTO_DIS_N1 0x41 + +/* + * MsgType N0 + */ +#define MT_N0_REG_IND 0x61 +#define MT_N0_CANC_IND 0x62 +#define MT_N0_FAC_STA 0x63 +#define MT_N0_STA_ACK 0x64 +#define MT_N0_STA_REJ 0x65 +#define MT_N0_FAC_INF 0x66 +#define MT_N0_INF_ACK 0x67 +#define MT_N0_INF_REJ 0x68 +#define MT_N0_CLOSE 0x75 +#define MT_N0_CLO_ACK 0x77 + +/* + * MsgType N1 + */ + +#define MT_N1_ESC 0x00 +#define MT_N1_ALERT 0x01 +#define MT_N1_CALL_SENT 0x02 +#define MT_N1_CONN 0x07 +#define MT_N1_CONN_ACK 0x0F +#define MT_N1_SETUP 0x05 +#define MT_N1_SETUP_ACK 0x0D +#define MT_N1_RES 0x26 +#define MT_N1_RES_ACK 0x2E +#define MT_N1_RES_REJ 0x22 +#define MT_N1_SUSP 0x25 +#define MT_N1_SUSP_ACK 0x2D +#define MT_N1_SUSP_REJ 0x21 +#define MT_N1_USER_INFO 0x20 +#define MT_N1_DET 0x40 +#define MT_N1_DISC 0x45 +#define MT_N1_REL 0x4D +#define MT_N1_REL_ACK 0x5A +#define MT_N1_CANC_ACK 0x6E +#define MT_N1_CANC_REJ 0x67 +#define MT_N1_CON_CON 0x69 +#define MT_N1_FAC 0x60 +#define MT_N1_FAC_ACK 0x68 +#define MT_N1_FAC_CAN 0x66 +#define MT_N1_FAC_REG 0x64 +#define MT_N1_FAC_REJ 0x65 +#define MT_N1_INFO 0x6D +#define MT_N1_REG_ACK 0x6C +#define MT_N1_REG_REJ 0x6F +#define MT_N1_STAT 0x63 +#define MT_N1_INVALID 0 + +/* + * W Elemente + */ + +#define WE_Shift_F0 0x90 +#define WE_Shift_F6 0x96 +#define WE_Shift_OF0 0x98 +#define WE_Shift_OF6 0x9E + +#define WE0_cause 0x08 +#define WE0_connAddr 0x0C +#define WE0_callID 0x10 +#define WE0_chanID 0x18 +#define WE0_netSpecFac 0x20 +#define WE0_display 0x28 +#define WE0_keypad 0x2C +#define WE0_origAddr 0x6C +#define WE0_destAddr 0x70 +#define WE0_userInfo 0x7E + +#define WE0_moreData 0xA0 +#define WE0_congestLevel 0xB0 + +#define WE6_serviceInd 0x01 +#define WE6_chargingInfo 0x02 +#define WE6_date 0x03 +#define WE6_facSelect 0x05 +#define WE6_facStatus 0x06 +#define WE6_statusCalled 0x07 +#define WE6_addTransAttr 0x08 + +/* + * FacCodes + */ +#define FAC_Sperre 0x01 +#define FAC_Sperre_All 0x02 +#define FAC_Sperre_Fern 0x03 +#define FAC_Sperre_Intl 0x04 +#define FAC_Sperre_Interk 0x05 + +#define FAC_Forward1 0x02 +#define FAC_Forward2 0x03 +#define FAC_Konferenz 0x06 +#define FAC_GrabBchan 0x0F +#define FAC_Reactivate 0x10 +#define FAC_Konferenz3 0x11 +#define FAC_Dienstwechsel1 0x12 +#define FAC_Dienstwechsel2 0x13 +#define FAC_NummernIdent 0x14 +#define FAC_GBG 0x15 +#define FAC_DisplayUebergeben 0x17 +#define FAC_DisplayUmgeleitet 0x1A +#define FAC_Unterdruecke 0x1B +#define FAC_Deactivate 0x1E +#define FAC_Activate 0x1D +#define FAC_SPV 0x1F +#define FAC_Rueckwechsel 0x23 +#define FAC_Umleitung 0x24 + +/* + * Cause codes + */ +#define CAUSE_InvCRef 0x01 +#define CAUSE_BearerNotImpl 0x03 +#define CAUSE_CIDunknown 0x07 +#define CAUSE_CIDinUse 0x08 +#define CAUSE_NoChans 0x0A +#define CAUSE_FacNotImpl 0x10 +#define CAUSE_FacNotSubscr 0x11 +#define CAUSE_OutgoingBarred 0x20 +#define CAUSE_UserAccessBusy 0x21 +#define CAUSE_NegativeGBG 0x22 +#define CAUSE_UnknownGBG 0x23 +#define CAUSE_NoSPVknown 0x25 +#define CAUSE_DestNotObtain 0x35 +#define CAUSE_NumberChanged 0x38 +#define CAUSE_OutOfOrder 0x39 +#define CAUSE_NoUserResponse 0x3A +#define CAUSE_UserBusy 0x3B +#define CAUSE_IncomingBarred 0x3D +#define CAUSE_CallRejected 0x3E +#define CAUSE_NetworkCongestion 0x59 +#define CAUSE_RemoteUser 0x5A +#define CAUSE_LocalProcErr 0x70 +#define CAUSE_RemoteProcErr 0x71 +#define CAUSE_RemoteUserSuspend 0x72 +#define CAUSE_RemoteUserResumed 0x73 +#define CAUSE_UserInfoDiscarded 0x7F + +#define T303 4000 +#define T304 20000 +#define T305 4000 +#define T308 4000 +#define T310 120000 +#define T313 4000 +#define T318 4000 +#define T319 4000 + +#endif diff --git a/drivers/isdn/hisax/l3dss1.c b/drivers/isdn/hisax/l3dss1.c new file mode 100644 index 000000000..368d152a8 --- /dev/null +++ b/drivers/isdn/hisax/l3dss1.c @@ -0,0 +1,3227 @@ +/* $Id: l3dss1.c,v 2.32.2.3 2004/01/13 14:31:25 keil Exp $ + * + * EURO/DSS1 D-channel protocol + * + * German 1TR6 D-channel protocol + * + * Author Karsten Keil + * based on the teles driver from Jan den Ouden + * Copyright by Karsten Keil <keil@isdn4linux.de> + * + * This software may be used and distributed according to the terms + * of the GNU General Public License, incorporated herein by reference. + * + * For changes and modifications please read + * Documentation/isdn/HiSax.cert + * + * Thanks to Jan den Ouden + * Fritz Elfert + * + */ + +#include "hisax.h" +#include "isdnl3.h" +#include "l3dss1.h" +#include <linux/ctype.h> +#include <linux/slab.h> + +extern char *HiSax_getrev(const char *revision); +static const char *dss1_revision = "$Revision: 2.32.2.3 $"; + +#define EXT_BEARER_CAPS 1 + +#define MsgHead(ptr, cref, mty) \ + *ptr++ = 0x8; \ + if (cref == -1) { \ + *ptr++ = 0x0; \ + } else { \ + *ptr++ = 0x1; \ + *ptr++ = cref^0x80; \ + } \ + *ptr++ = mty + + +/**********************************************/ +/* get a new invoke id for remote operations. */ +/* Only a return value != 0 is valid */ +/**********************************************/ +static unsigned char new_invoke_id(struct PStack *p) +{ + unsigned char retval; + int i; + + i = 32; /* maximum search depth */ + + retval = p->prot.dss1.last_invoke_id + 1; /* try new id */ + while ((i) && (p->prot.dss1.invoke_used[retval >> 3] == 0xFF)) { + p->prot.dss1.last_invoke_id = (retval & 0xF8) + 8; + i--; + } + if (i) { + while (p->prot.dss1.invoke_used[retval >> 3] & (1 << (retval & 7))) + retval++; + } else + retval = 0; + p->prot.dss1.last_invoke_id = retval; + p->prot.dss1.invoke_used[retval >> 3] |= (1 << (retval & 7)); + return (retval); +} /* new_invoke_id */ + +/*************************/ +/* free a used invoke id */ +/*************************/ +static void free_invoke_id(struct PStack *p, unsigned char id) +{ + + if (!id) return; /* 0 = invalid value */ + + p->prot.dss1.invoke_used[id >> 3] &= ~(1 << (id & 7)); +} /* free_invoke_id */ + + +/**********************************************************/ +/* create a new l3 process and fill in dss1 specific data */ +/**********************************************************/ +static struct l3_process +*dss1_new_l3_process(struct PStack *st, int cr) +{ struct l3_process *proc; + + if (!(proc = new_l3_process(st, cr))) + return (NULL); + + proc->prot.dss1.invoke_id = 0; + proc->prot.dss1.remote_operation = 0; + proc->prot.dss1.uus1_data[0] = '\0'; + + return (proc); +} /* dss1_new_l3_process */ + +/************************************************/ +/* free a l3 process and all dss1 specific data */ +/************************************************/ +static void +dss1_release_l3_process(struct l3_process *p) +{ + free_invoke_id(p->st, p->prot.dss1.invoke_id); + release_l3_process(p); +} /* dss1_release_l3_process */ + +/********************************************************/ +/* search a process with invoke id id and dummy callref */ +/********************************************************/ +static struct l3_process * +l3dss1_search_dummy_proc(struct PStack *st, int id) +{ struct l3_process *pc = st->l3.proc; /* start of processes */ + + if (!id) return (NULL); + + while (pc) + { if ((pc->callref == -1) && (pc->prot.dss1.invoke_id == id)) + return (pc); + pc = pc->next; + } + return (NULL); +} /* l3dss1_search_dummy_proc */ + +/*******************************************************************/ +/* called when a facility message with a dummy callref is received */ +/* and a return result is delivered. id specifies the invoke id. */ +/*******************************************************************/ +static void +l3dss1_dummy_return_result(struct PStack *st, int id, u_char *p, u_char nlen) +{ isdn_ctrl ic; + struct IsdnCardState *cs; + struct l3_process *pc = NULL; + + if ((pc = l3dss1_search_dummy_proc(st, id))) + { L3DelTimer(&pc->timer); /* remove timer */ + + cs = pc->st->l1.hardware; + ic.driver = cs->myid; + ic.command = ISDN_STAT_PROT; + ic.arg = DSS1_STAT_INVOKE_RES; + ic.parm.dss1_io.hl_id = pc->prot.dss1.invoke_id; + ic.parm.dss1_io.ll_id = pc->prot.dss1.ll_id; + ic.parm.dss1_io.proc = pc->prot.dss1.proc; + ic.parm.dss1_io.timeout = 0; + ic.parm.dss1_io.datalen = nlen; + ic.parm.dss1_io.data = p; + free_invoke_id(pc->st, pc->prot.dss1.invoke_id); + pc->prot.dss1.invoke_id = 0; /* reset id */ + + cs->iif.statcallb(&ic); + dss1_release_l3_process(pc); + } + else + l3_debug(st, "dummy return result id=0x%x result len=%d", id, nlen); +} /* l3dss1_dummy_return_result */ + +/*******************************************************************/ +/* called when a facility message with a dummy callref is received */ +/* and a return error is delivered. id specifies the invoke id. */ +/*******************************************************************/ +static void +l3dss1_dummy_error_return(struct PStack *st, int id, ulong error) +{ isdn_ctrl ic; + struct IsdnCardState *cs; + struct l3_process *pc = NULL; + + if ((pc = l3dss1_search_dummy_proc(st, id))) + { L3DelTimer(&pc->timer); /* remove timer */ + + cs = pc->st->l1.hardware; + ic.driver = cs->myid; + ic.command = ISDN_STAT_PROT; + ic.arg = DSS1_STAT_INVOKE_ERR; + ic.parm.dss1_io.hl_id = pc->prot.dss1.invoke_id; + ic.parm.dss1_io.ll_id = pc->prot.dss1.ll_id; + ic.parm.dss1_io.proc = pc->prot.dss1.proc; + ic.parm.dss1_io.timeout = error; + ic.parm.dss1_io.datalen = 0; + ic.parm.dss1_io.data = NULL; + free_invoke_id(pc->st, pc->prot.dss1.invoke_id); + pc->prot.dss1.invoke_id = 0; /* reset id */ + + cs->iif.statcallb(&ic); + dss1_release_l3_process(pc); + } + else + l3_debug(st, "dummy return error id=0x%x error=0x%lx", id, error); +} /* l3dss1_error_return */ + +/*******************************************************************/ +/* called when a facility message with a dummy callref is received */ +/* and a invoke is delivered. id specifies the invoke id. */ +/*******************************************************************/ +static void +l3dss1_dummy_invoke(struct PStack *st, int cr, int id, + int ident, u_char *p, u_char nlen) +{ isdn_ctrl ic; + struct IsdnCardState *cs; + + l3_debug(st, "dummy invoke %s id=0x%x ident=0x%x datalen=%d", + (cr == -1) ? "local" : "broadcast", id, ident, nlen); + if (cr >= -1) return; /* ignore local data */ + + cs = st->l1.hardware; + ic.driver = cs->myid; + ic.command = ISDN_STAT_PROT; + ic.arg = DSS1_STAT_INVOKE_BRD; + ic.parm.dss1_io.hl_id = id; + ic.parm.dss1_io.ll_id = 0; + ic.parm.dss1_io.proc = ident; + ic.parm.dss1_io.timeout = 0; + ic.parm.dss1_io.datalen = nlen; + ic.parm.dss1_io.data = p; + + cs->iif.statcallb(&ic); +} /* l3dss1_dummy_invoke */ + +static void +l3dss1_parse_facility(struct PStack *st, struct l3_process *pc, + int cr, u_char *p) +{ + int qd_len = 0; + unsigned char nlen = 0, ilen, cp_tag; + int ident, id; + ulong err_ret; + + if (pc) + st = pc->st; /* valid Stack */ + else + if ((!st) || (cr >= 0)) return; /* neither pc nor st specified */ + + p++; + qd_len = *p++; + if (qd_len == 0) { + l3_debug(st, "qd_len == 0"); + return; + } + if ((*p & 0x1F) != 0x11) { /* Service discriminator, supplementary service */ + l3_debug(st, "supplementary service != 0x11"); + return; + } + while (qd_len > 0 && !(*p & 0x80)) { /* extension ? */ + p++; + qd_len--; + } + if (qd_len < 2) { + l3_debug(st, "qd_len < 2"); + return; + } + p++; + qd_len--; + if ((*p & 0xE0) != 0xA0) { /* class and form */ + l3_debug(st, "class and form != 0xA0"); + return; + } + + cp_tag = *p & 0x1F; /* remember tag value */ + + p++; + qd_len--; + if (qd_len < 1) + { l3_debug(st, "qd_len < 1"); + return; + } + if (*p & 0x80) + { /* length format indefinite or limited */ + nlen = *p++ & 0x7F; /* number of len bytes or indefinite */ + if ((qd_len-- < ((!nlen) ? 3 : (1 + nlen))) || + (nlen > 1)) + { l3_debug(st, "length format error or not implemented"); + return; + } + if (nlen == 1) + { nlen = *p++; /* complete length */ + qd_len--; + } + else + { qd_len -= 2; /* trailing null bytes */ + if ((*(p + qd_len)) || (*(p + qd_len + 1))) + { l3_debug(st, "length format indefinite error"); + return; + } + nlen = qd_len; + } + } + else + { nlen = *p++; + qd_len--; + } + if (qd_len < nlen) + { l3_debug(st, "qd_len < nlen"); + return; + } + qd_len -= nlen; + + if (nlen < 2) + { l3_debug(st, "nlen < 2"); + return; + } + if (*p != 0x02) + { /* invoke identifier tag */ + l3_debug(st, "invoke identifier tag !=0x02"); + return; + } + p++; + nlen--; + if (*p & 0x80) + { /* length format */ + l3_debug(st, "invoke id length format 2"); + return; + } + ilen = *p++; + nlen--; + if (ilen > nlen || ilen == 0) + { l3_debug(st, "ilen > nlen || ilen == 0"); + return; + } + nlen -= ilen; + id = 0; + while (ilen > 0) + { id = (id << 8) | (*p++ & 0xFF); /* invoke identifier */ + ilen--; + } + + switch (cp_tag) { /* component tag */ + case 1: /* invoke */ + if (nlen < 2) { + l3_debug(st, "nlen < 2 22"); + return; + } + if (*p != 0x02) { /* operation value */ + l3_debug(st, "operation value !=0x02"); + return; + } + p++; + nlen--; + ilen = *p++; + nlen--; + if (ilen > nlen || ilen == 0) { + l3_debug(st, "ilen > nlen || ilen == 0 22"); + return; + } + nlen -= ilen; + ident = 0; + while (ilen > 0) { + ident = (ident << 8) | (*p++ & 0xFF); + ilen--; + } + + if (!pc) + { l3dss1_dummy_invoke(st, cr, id, ident, p, nlen); + return; + } +#ifdef CONFIG_DE_AOC + { + +#define FOO1(s, a, b) \ + while (nlen > 1) { \ + int ilen = p[1]; \ + if (nlen < ilen + 2) { \ + l3_debug(st, "FOO1 nlen < ilen+2"); \ + return; \ + } \ + nlen -= ilen + 2; \ + if ((*p & 0xFF) == (a)) { \ + int nlen = ilen; \ + p += 2; \ + b; \ + } else { \ + p += ilen + 2; \ + } \ + } + + switch (ident) { + case 0x22: /* during */ + FOO1("1A", 0x30, FOO1("1C", 0xA1, FOO1("1D", 0x30, FOO1("1E", 0x02, ( { + ident = 0; + nlen = (nlen) ? nlen : 0; /* Make gcc happy */ + while (ilen > 0) { + ident = (ident << 8) | *p++; + ilen--; + } + if (ident > pc->para.chargeinfo) { + pc->para.chargeinfo = ident; + st->l3.l3l4(st, CC_CHARGE | INDICATION, pc); + } + if (st->l3.debug & L3_DEB_CHARGE) { + if (*(p + 2) == 0) { + l3_debug(st, "charging info during %d", pc->para.chargeinfo); + } + else { + l3_debug(st, "charging info final %d", pc->para.chargeinfo); + } + } + } + ))))) + break; + case 0x24: /* final */ + FOO1("2A", 0x30, FOO1("2B", 0x30, FOO1("2C", 0xA1, FOO1("2D", 0x30, FOO1("2E", 0x02, ( { + ident = 0; + nlen = (nlen) ? nlen : 0; /* Make gcc happy */ + while (ilen > 0) { + ident = (ident << 8) | *p++; + ilen--; + } + if (ident > pc->para.chargeinfo) { + pc->para.chargeinfo = ident; + st->l3.l3l4(st, CC_CHARGE | INDICATION, pc); + } + if (st->l3.debug & L3_DEB_CHARGE) { + l3_debug(st, "charging info final %d", pc->para.chargeinfo); + } + } + )))))) + break; + default: + l3_debug(st, "invoke break invalid ident %02x", ident); + break; + } +#undef FOO1 + + } +#else /* not CONFIG_DE_AOC */ + l3_debug(st, "invoke break"); +#endif /* not CONFIG_DE_AOC */ + break; + case 2: /* return result */ + /* if no process available handle separately */ + if (!pc) + { if (cr == -1) + l3dss1_dummy_return_result(st, id, p, nlen); + return; + } + if ((pc->prot.dss1.invoke_id) && (pc->prot.dss1.invoke_id == id)) + { /* Diversion successful */ + free_invoke_id(st, pc->prot.dss1.invoke_id); + pc->prot.dss1.remote_result = 0; /* success */ + pc->prot.dss1.invoke_id = 0; + pc->redir_result = pc->prot.dss1.remote_result; + st->l3.l3l4(st, CC_REDIR | INDICATION, pc); } /* Diversion successful */ + else + l3_debug(st, "return error unknown identifier"); + break; + case 3: /* return error */ + err_ret = 0; + if (nlen < 2) + { l3_debug(st, "return error nlen < 2"); + return; + } + if (*p != 0x02) + { /* result tag */ + l3_debug(st, "invoke error tag !=0x02"); + return; + } + p++; + nlen--; + if (*p > 4) + { /* length format */ + l3_debug(st, "invoke return errlen > 4 "); + return; + } + ilen = *p++; + nlen--; + if (ilen > nlen || ilen == 0) + { l3_debug(st, "error return ilen > nlen || ilen == 0"); + return; + } + nlen -= ilen; + while (ilen > 0) + { err_ret = (err_ret << 8) | (*p++ & 0xFF); /* error value */ + ilen--; + } + /* if no process available handle separately */ + if (!pc) + { if (cr == -1) + l3dss1_dummy_error_return(st, id, err_ret); + return; + } + if ((pc->prot.dss1.invoke_id) && (pc->prot.dss1.invoke_id == id)) + { /* Deflection error */ + free_invoke_id(st, pc->prot.dss1.invoke_id); + pc->prot.dss1.remote_result = err_ret; /* result */ + pc->prot.dss1.invoke_id = 0; + pc->redir_result = pc->prot.dss1.remote_result; + st->l3.l3l4(st, CC_REDIR | INDICATION, pc); + } /* Deflection error */ + else + l3_debug(st, "return result unknown identifier"); + break; + default: + l3_debug(st, "facility default break tag=0x%02x", cp_tag); + break; + } +} + +static void +l3dss1_message(struct l3_process *pc, u_char mt) +{ + struct sk_buff *skb; + u_char *p; + + if (!(skb = l3_alloc_skb(4))) + return; + p = skb_put(skb, 4); + MsgHead(p, pc->callref, mt); + l3_msg(pc->st, DL_DATA | REQUEST, skb); +} + +static void +l3dss1_message_cause(struct l3_process *pc, u_char mt, u_char cause) +{ + struct sk_buff *skb; + u_char tmp[16]; + u_char *p = tmp; + int l; + + MsgHead(p, pc->callref, mt); + *p++ = IE_CAUSE; + *p++ = 0x2; + *p++ = 0x80; + *p++ = cause | 0x80; + + l = p - tmp; + if (!(skb = l3_alloc_skb(l))) + return; + skb_put_data(skb, tmp, l); + l3_msg(pc->st, DL_DATA | REQUEST, skb); +} + +static void +l3dss1_status_send(struct l3_process *pc, u_char pr, void *arg) +{ + u_char tmp[16]; + u_char *p = tmp; + int l; + struct sk_buff *skb; + + MsgHead(p, pc->callref, MT_STATUS); + + *p++ = IE_CAUSE; + *p++ = 0x2; + *p++ = 0x80; + *p++ = pc->para.cause | 0x80; + + *p++ = IE_CALL_STATE; + *p++ = 0x1; + *p++ = pc->state & 0x3f; + + l = p - tmp; + if (!(skb = l3_alloc_skb(l))) + return; + skb_put_data(skb, tmp, l); + l3_msg(pc->st, DL_DATA | REQUEST, skb); +} + +static void +l3dss1_msg_without_setup(struct l3_process *pc, u_char pr, void *arg) +{ + /* This routine is called if here was no SETUP made (checks in dss1up and in + * l3dss1_setup) and a RELEASE_COMPLETE have to be sent with an error code + * MT_STATUS_ENQUIRE in the NULL state is handled too + */ + u_char tmp[16]; + u_char *p = tmp; + int l; + struct sk_buff *skb; + + switch (pc->para.cause) { + case 81: /* invalid callreference */ + case 88: /* incomp destination */ + case 96: /* mandory IE missing */ + case 100: /* invalid IE contents */ + case 101: /* incompatible Callstate */ + MsgHead(p, pc->callref, MT_RELEASE_COMPLETE); + *p++ = IE_CAUSE; + *p++ = 0x2; + *p++ = 0x80; + *p++ = pc->para.cause | 0x80; + break; + default: + printk(KERN_ERR "HiSax l3dss1_msg_without_setup wrong cause %d\n", + pc->para.cause); + return; + } + l = p - tmp; + if (!(skb = l3_alloc_skb(l))) + return; + skb_put_data(skb, tmp, l); + l3_msg(pc->st, DL_DATA | REQUEST, skb); + dss1_release_l3_process(pc); +} + +static int ie_ALERTING[] = {IE_BEARER, IE_CHANNEL_ID | IE_MANDATORY_1, + IE_FACILITY, IE_PROGRESS, IE_DISPLAY, IE_SIGNAL, IE_HLC, + IE_USER_USER, -1}; +static int ie_CALL_PROCEEDING[] = {IE_BEARER, IE_CHANNEL_ID | IE_MANDATORY_1, + IE_FACILITY, IE_PROGRESS, IE_DISPLAY, IE_HLC, -1}; +static int ie_CONNECT[] = {IE_BEARER, IE_CHANNEL_ID | IE_MANDATORY_1, + IE_FACILITY, IE_PROGRESS, IE_DISPLAY, IE_DATE, IE_SIGNAL, + IE_CONNECT_PN, IE_CONNECT_SUB, IE_LLC, IE_HLC, IE_USER_USER, -1}; +static int ie_CONNECT_ACKNOWLEDGE[] = {IE_CHANNEL_ID, IE_DISPLAY, IE_SIGNAL, -1}; +static int ie_DISCONNECT[] = {IE_CAUSE | IE_MANDATORY, IE_FACILITY, + IE_PROGRESS, IE_DISPLAY, IE_SIGNAL, IE_USER_USER, -1}; +static int ie_INFORMATION[] = {IE_COMPLETE, IE_DISPLAY, IE_KEYPAD, IE_SIGNAL, + IE_CALLED_PN, -1}; +static int ie_NOTIFY[] = {IE_BEARER, IE_NOTIFY | IE_MANDATORY, IE_DISPLAY, -1}; +static int ie_PROGRESS[] = {IE_BEARER, IE_CAUSE, IE_FACILITY, IE_PROGRESS | + IE_MANDATORY, IE_DISPLAY, IE_HLC, IE_USER_USER, -1}; +static int ie_RELEASE[] = {IE_CAUSE | IE_MANDATORY_1, IE_FACILITY, IE_DISPLAY, + IE_SIGNAL, IE_USER_USER, -1}; +/* a RELEASE_COMPLETE with errors don't require special actions + static int ie_RELEASE_COMPLETE[] = {IE_CAUSE | IE_MANDATORY_1, IE_DISPLAY, IE_SIGNAL, IE_USER_USER, -1}; +*/ +static int ie_RESUME_ACKNOWLEDGE[] = {IE_CHANNEL_ID | IE_MANDATORY, IE_FACILITY, + IE_DISPLAY, -1}; +static int ie_RESUME_REJECT[] = {IE_CAUSE | IE_MANDATORY, IE_DISPLAY, -1}; +static int ie_SETUP[] = {IE_COMPLETE, IE_BEARER | IE_MANDATORY, + IE_CHANNEL_ID | IE_MANDATORY, IE_FACILITY, IE_PROGRESS, + IE_NET_FAC, IE_DISPLAY, IE_KEYPAD, IE_SIGNAL, IE_CALLING_PN, + IE_CALLING_SUB, IE_CALLED_PN, IE_CALLED_SUB, IE_REDIR_NR, + IE_LLC, IE_HLC, IE_USER_USER, -1}; +static int ie_SETUP_ACKNOWLEDGE[] = {IE_CHANNEL_ID | IE_MANDATORY, IE_FACILITY, + IE_PROGRESS, IE_DISPLAY, IE_SIGNAL, -1}; +static int ie_STATUS[] = {IE_CAUSE | IE_MANDATORY, IE_CALL_STATE | + IE_MANDATORY, IE_DISPLAY, -1}; +static int ie_STATUS_ENQUIRY[] = {IE_DISPLAY, -1}; +static int ie_SUSPEND_ACKNOWLEDGE[] = {IE_DISPLAY, IE_FACILITY, -1}; +static int ie_SUSPEND_REJECT[] = {IE_CAUSE | IE_MANDATORY, IE_DISPLAY, -1}; +/* not used + * static int ie_CONGESTION_CONTROL[] = {IE_CONGESTION | IE_MANDATORY, + * IE_CAUSE | IE_MANDATORY, IE_DISPLAY, -1}; + * static int ie_USER_INFORMATION[] = {IE_MORE_DATA, IE_USER_USER | IE_MANDATORY, -1}; + * static int ie_RESTART[] = {IE_CHANNEL_ID, IE_DISPLAY, IE_RESTART_IND | + * IE_MANDATORY, -1}; + */ +static int ie_FACILITY[] = {IE_FACILITY | IE_MANDATORY, IE_DISPLAY, -1}; +static int comp_required[] = {1, 2, 3, 5, 6, 7, 9, 10, 11, 14, 15, -1}; +static int l3_valid_states[] = {0, 1, 2, 3, 4, 6, 7, 8, 9, 10, 11, 12, 15, 17, 19, 25, -1}; + +struct ie_len { + int ie; + int len; +}; + +static +struct ie_len max_ie_len[] = { + {IE_SEGMENT, 4}, + {IE_BEARER, 12}, + {IE_CAUSE, 32}, + {IE_CALL_ID, 10}, + {IE_CALL_STATE, 3}, + {IE_CHANNEL_ID, 34}, + {IE_FACILITY, 255}, + {IE_PROGRESS, 4}, + {IE_NET_FAC, 255}, + {IE_NOTIFY, 3}, + {IE_DISPLAY, 82}, + {IE_DATE, 8}, + {IE_KEYPAD, 34}, + {IE_SIGNAL, 3}, + {IE_INFORATE, 6}, + {IE_E2E_TDELAY, 11}, + {IE_TDELAY_SEL, 5}, + {IE_PACK_BINPARA, 3}, + {IE_PACK_WINSIZE, 4}, + {IE_PACK_SIZE, 4}, + {IE_CUG, 7}, + {IE_REV_CHARGE, 3}, + {IE_CALLING_PN, 24}, + {IE_CALLING_SUB, 23}, + {IE_CALLED_PN, 24}, + {IE_CALLED_SUB, 23}, + {IE_REDIR_NR, 255}, + {IE_TRANS_SEL, 255}, + {IE_RESTART_IND, 3}, + {IE_LLC, 18}, + {IE_HLC, 5}, + {IE_USER_USER, 131}, + {-1, 0}, +}; + +static int +getmax_ie_len(u_char ie) { + int i = 0; + while (max_ie_len[i].ie != -1) { + if (max_ie_len[i].ie == ie) + return (max_ie_len[i].len); + i++; + } + return (255); +} + +static int +ie_in_set(struct l3_process *pc, u_char ie, int *checklist) { + int ret = 1; + + while (*checklist != -1) { + if ((*checklist & 0xff) == ie) { + if (ie & 0x80) + return (-ret); + else + return (ret); + } + ret++; + checklist++; + } + return (0); +} + +static int +check_infoelements(struct l3_process *pc, struct sk_buff *skb, int *checklist) +{ + int *cl = checklist; + u_char mt; + u_char *p, ie; + int l, newpos, oldpos; + int err_seq = 0, err_len = 0, err_compr = 0, err_ureg = 0; + u_char codeset = 0; + u_char old_codeset = 0; + u_char codelock = 1; + + p = skb->data; + /* skip cr */ + p++; + l = (*p++) & 0xf; + p += l; + mt = *p++; + oldpos = 0; + while ((p - skb->data) < skb->len) { + if ((*p & 0xf0) == 0x90) { /* shift codeset */ + old_codeset = codeset; + codeset = *p & 7; + if (*p & 0x08) + codelock = 0; + else + codelock = 1; + if (pc->debug & L3_DEB_CHECK) + l3_debug(pc->st, "check IE shift%scodeset %d->%d", + codelock ? " locking " : " ", old_codeset, codeset); + p++; + continue; + } + if (!codeset) { /* only codeset 0 */ + if ((newpos = ie_in_set(pc, *p, cl))) { + if (newpos > 0) { + if (newpos < oldpos) + err_seq++; + else + oldpos = newpos; + } + } else { + if (ie_in_set(pc, *p, comp_required)) + err_compr++; + else + err_ureg++; + } + } + ie = *p++; + if (ie & 0x80) { + l = 1; + } else { + l = *p++; + p += l; + l += 2; + } + if (!codeset && (l > getmax_ie_len(ie))) + err_len++; + if (!codelock) { + if (pc->debug & L3_DEB_CHECK) + l3_debug(pc->st, "check IE shift back codeset %d->%d", + codeset, old_codeset); + codeset = old_codeset; + codelock = 1; + } + } + if (err_compr | err_ureg | err_len | err_seq) { + if (pc->debug & L3_DEB_CHECK) + l3_debug(pc->st, "check IE MT(%x) %d/%d/%d/%d", + mt, err_compr, err_ureg, err_len, err_seq); + if (err_compr) + return (ERR_IE_COMPREHENSION); + if (err_ureg) + return (ERR_IE_UNRECOGNIZED); + if (err_len) + return (ERR_IE_LENGTH); + if (err_seq) + return (ERR_IE_SEQUENCE); + } + return (0); +} + +/* verify if a message type exists and contain no IE error */ +static int +l3dss1_check_messagetype_validity(struct l3_process *pc, int mt, void *arg) +{ + switch (mt) { + case MT_ALERTING: + case MT_CALL_PROCEEDING: + case MT_CONNECT: + case MT_CONNECT_ACKNOWLEDGE: + case MT_DISCONNECT: + case MT_INFORMATION: + case MT_FACILITY: + case MT_NOTIFY: + case MT_PROGRESS: + case MT_RELEASE: + case MT_RELEASE_COMPLETE: + case MT_SETUP: + case MT_SETUP_ACKNOWLEDGE: + case MT_RESUME_ACKNOWLEDGE: + case MT_RESUME_REJECT: + case MT_SUSPEND_ACKNOWLEDGE: + case MT_SUSPEND_REJECT: + case MT_USER_INFORMATION: + case MT_RESTART: + case MT_RESTART_ACKNOWLEDGE: + case MT_CONGESTION_CONTROL: + case MT_STATUS: + case MT_STATUS_ENQUIRY: + if (pc->debug & L3_DEB_CHECK) + l3_debug(pc->st, "l3dss1_check_messagetype_validity mt(%x) OK", mt); + break; + case MT_RESUME: /* RESUME only in user->net */ + case MT_SUSPEND: /* SUSPEND only in user->net */ + default: + if (pc->debug & (L3_DEB_CHECK | L3_DEB_WARN)) + l3_debug(pc->st, "l3dss1_check_messagetype_validity mt(%x) fail", mt); + pc->para.cause = 97; + l3dss1_status_send(pc, 0, NULL); + return (1); + } + return (0); +} + +static void +l3dss1_std_ie_err(struct l3_process *pc, int ret) { + + if (pc->debug & L3_DEB_CHECK) + l3_debug(pc->st, "check_infoelements ret %d", ret); + switch (ret) { + case 0: + break; + case ERR_IE_COMPREHENSION: + pc->para.cause = 96; + l3dss1_status_send(pc, 0, NULL); + break; + case ERR_IE_UNRECOGNIZED: + pc->para.cause = 99; + l3dss1_status_send(pc, 0, NULL); + break; + case ERR_IE_LENGTH: + pc->para.cause = 100; + l3dss1_status_send(pc, 0, NULL); + break; + case ERR_IE_SEQUENCE: + default: + break; + } +} + +static int +l3dss1_get_channel_id(struct l3_process *pc, struct sk_buff *skb) { + u_char *p; + + p = skb->data; + if ((p = findie(p, skb->len, IE_CHANNEL_ID, 0))) { + p++; + if (*p != 1) { /* len for BRI = 1 */ + if (pc->debug & L3_DEB_WARN) + l3_debug(pc->st, "wrong chid len %d", *p); + return (-2); + } + p++; + if (*p & 0x60) { /* only base rate interface */ + if (pc->debug & L3_DEB_WARN) + l3_debug(pc->st, "wrong chid %x", *p); + return (-3); + } + return (*p & 0x3); + } else + return (-1); +} + +static int +l3dss1_get_cause(struct l3_process *pc, struct sk_buff *skb) { + u_char l, i = 0; + u_char *p; + + p = skb->data; + pc->para.cause = 31; + pc->para.loc = 0; + if ((p = findie(p, skb->len, IE_CAUSE, 0))) { + p++; + l = *p++; + if (l > 30) + return (1); + if (l) { + pc->para.loc = *p++; + l--; + } else { + return (2); + } + if (l && !(pc->para.loc & 0x80)) { + l--; + p++; /* skip recommendation */ + } + if (l) { + pc->para.cause = *p++; + l--; + if (!(pc->para.cause & 0x80)) + return (3); + } else + return (4); + while (l && (i < 6)) { + pc->para.diag[i++] = *p++; + l--; + } + } else + return (-1); + return (0); +} + +static void +l3dss1_msg_with_uus(struct l3_process *pc, u_char cmd) +{ + struct sk_buff *skb; + u_char tmp[16 + 40]; + u_char *p = tmp; + int l; + + MsgHead(p, pc->callref, cmd); + + if (pc->prot.dss1.uus1_data[0]) + { *p++ = IE_USER_USER; /* UUS info element */ + *p++ = strlen(pc->prot.dss1.uus1_data) + 1; + *p++ = 0x04; /* IA5 chars */ + strcpy(p, pc->prot.dss1.uus1_data); + p += strlen(pc->prot.dss1.uus1_data); + pc->prot.dss1.uus1_data[0] = '\0'; + } + + l = p - tmp; + if (!(skb = l3_alloc_skb(l))) + return; + skb_put_data(skb, tmp, l); + l3_msg(pc->st, DL_DATA | REQUEST, skb); +} /* l3dss1_msg_with_uus */ + +static void +l3dss1_release_req(struct l3_process *pc, u_char pr, void *arg) +{ + StopAllL3Timer(pc); + newl3state(pc, 19); + if (!pc->prot.dss1.uus1_data[0]) + l3dss1_message(pc, MT_RELEASE); + else + l3dss1_msg_with_uus(pc, MT_RELEASE); + L3AddTimer(&pc->timer, T308, CC_T308_1); +} + +static void +l3dss1_release_cmpl(struct l3_process *pc, u_char pr, void *arg) +{ + struct sk_buff *skb = arg; + int ret; + + if ((ret = l3dss1_get_cause(pc, skb)) > 0) { + if (pc->debug & L3_DEB_WARN) + l3_debug(pc->st, "RELCMPL get_cause ret(%d)", ret); + } else if (ret < 0) + pc->para.cause = NO_CAUSE; + StopAllL3Timer(pc); + newl3state(pc, 0); + pc->st->l3.l3l4(pc->st, CC_RELEASE | CONFIRM, pc); + dss1_release_l3_process(pc); +} + +#ifdef EXT_BEARER_CAPS + +static u_char * +EncodeASyncParams(u_char *p, u_char si2) +{ // 7c 06 88 90 21 42 00 bb + + p[0] = 0; + p[1] = 0x40; // Intermediate rate: 16 kbit/s jj 2000.02.19 + p[2] = 0x80; + if (si2 & 32) // 7 data bits + + p[2] += 16; + else // 8 data bits + + p[2] += 24; + + if (si2 & 16) // 2 stop bits + + p[2] += 96; + else // 1 stop bit + + p[2] += 32; + + if (si2 & 8) // even parity + + p[2] += 2; + else // no parity + + p[2] += 3; + + switch (si2 & 0x07) { + case 0: + p[0] = 66; // 1200 bit/s + + break; + case 1: + p[0] = 88; // 1200/75 bit/s + + break; + case 2: + p[0] = 87; // 75/1200 bit/s + + break; + case 3: + p[0] = 67; // 2400 bit/s + + break; + case 4: + p[0] = 69; // 4800 bit/s + + break; + case 5: + p[0] = 72; // 9600 bit/s + + break; + case 6: + p[0] = 73; // 14400 bit/s + + break; + case 7: + p[0] = 75; // 19200 bit/s + + break; + } + return p + 3; +} + +static u_char +EncodeSyncParams(u_char si2, u_char ai) +{ + + switch (si2) { + case 0: + return ai + 2; // 1200 bit/s + + case 1: + return ai + 24; // 1200/75 bit/s + + case 2: + return ai + 23; // 75/1200 bit/s + + case 3: + return ai + 3; // 2400 bit/s + + case 4: + return ai + 5; // 4800 bit/s + + case 5: + return ai + 8; // 9600 bit/s + + case 6: + return ai + 9; // 14400 bit/s + + case 7: + return ai + 11; // 19200 bit/s + + case 8: + return ai + 14; // 48000 bit/s + + case 9: + return ai + 15; // 56000 bit/s + + case 15: + return ai + 40; // negotiate bit/s + + default: + break; + } + return ai; +} + + +static u_char +DecodeASyncParams(u_char si2, u_char *p) +{ + u_char info; + + switch (p[5]) { + case 66: // 1200 bit/s + + break; // si2 don't change + + case 88: // 1200/75 bit/s + + si2 += 1; + break; + case 87: // 75/1200 bit/s + + si2 += 2; + break; + case 67: // 2400 bit/s + + si2 += 3; + break; + case 69: // 4800 bit/s + + si2 += 4; + break; + case 72: // 9600 bit/s + + si2 += 5; + break; + case 73: // 14400 bit/s + + si2 += 6; + break; + case 75: // 19200 bit/s + + si2 += 7; + break; + } + + info = p[7] & 0x7f; + if ((info & 16) && (!(info & 8))) // 7 data bits + + si2 += 32; // else 8 data bits + + if ((info & 96) == 96) // 2 stop bits + + si2 += 16; // else 1 stop bit + + if ((info & 2) && (!(info & 1))) // even parity + + si2 += 8; // else no parity + + return si2; +} + + +static u_char +DecodeSyncParams(u_char si2, u_char info) +{ + info &= 0x7f; + switch (info) { + case 40: // bit/s negotiation failed ai := 165 not 175! + + return si2 + 15; + case 15: // 56000 bit/s failed, ai := 0 not 169 ! + + return si2 + 9; + case 14: // 48000 bit/s + + return si2 + 8; + case 11: // 19200 bit/s + + return si2 + 7; + case 9: // 14400 bit/s + + return si2 + 6; + case 8: // 9600 bit/s + + return si2 + 5; + case 5: // 4800 bit/s + + return si2 + 4; + case 3: // 2400 bit/s + + return si2 + 3; + case 23: // 75/1200 bit/s + + return si2 + 2; + case 24: // 1200/75 bit/s + + return si2 + 1; + default: // 1200 bit/s + + return si2; + } +} + +static u_char +DecodeSI2(struct sk_buff *skb) +{ + u_char *p; //, *pend=skb->data + skb->len; + + if ((p = findie(skb->data, skb->len, 0x7c, 0))) { + switch (p[4] & 0x0f) { + case 0x01: + if (p[1] == 0x04) // sync. Bitratenadaption + + return DecodeSyncParams(160, p[5]); // V.110/X.30 + + else if (p[1] == 0x06) // async. Bitratenadaption + + return DecodeASyncParams(192, p); // V.110/X.30 + + break; + case 0x08: // if (p[5] == 0x02) // sync. Bitratenadaption + if (p[1] > 3) + return DecodeSyncParams(176, p[5]); // V.120 + break; + } + } + return 0; +} + +#endif + + +static void +l3dss1_setup_req(struct l3_process *pc, u_char pr, + void *arg) +{ + struct sk_buff *skb; + u_char tmp[128]; + u_char *p = tmp; + u_char channel = 0; + + u_char send_keypad; + u_char screen = 0x80; + u_char *teln; + u_char *msn; + u_char *sub; + u_char *sp; + int l; + + MsgHead(p, pc->callref, MT_SETUP); + + teln = pc->para.setup.phone; +#ifndef CONFIG_HISAX_NO_KEYPAD + send_keypad = (strchr(teln, '*') || strchr(teln, '#')) ? 1 : 0; +#else + send_keypad = 0; +#endif +#ifndef CONFIG_HISAX_NO_SENDCOMPLETE + if (!send_keypad) + *p++ = 0xa1; /* complete indicator */ +#endif + /* + * Set Bearer Capability, Map info from 1TR6-convention to EDSS1 + */ + switch (pc->para.setup.si1) { + case 1: /* Telephony */ + *p++ = IE_BEARER; + *p++ = 0x3; /* Length */ + *p++ = 0x90; /* Coding Std. CCITT, 3.1 kHz audio */ + *p++ = 0x90; /* Circuit-Mode 64kbps */ + *p++ = 0xa3; /* A-Law Audio */ + break; + case 5: /* Datatransmission 64k, BTX */ + case 7: /* Datatransmission 64k */ + default: + *p++ = IE_BEARER; + *p++ = 0x2; /* Length */ + *p++ = 0x88; /* Coding Std. CCITT, unrestr. dig. Inform. */ + *p++ = 0x90; /* Circuit-Mode 64kbps */ + break; + } + + if (send_keypad) { + *p++ = IE_KEYPAD; + *p++ = strlen(teln); + while (*teln) + *p++ = (*teln++) & 0x7F; + } + + /* + * What about info2? Mapping to High-Layer-Compatibility? + */ + if ((*teln) && (!send_keypad)) { + /* parse number for special things */ + if (!isdigit(*teln)) { + switch (0x5f & *teln) { + case 'C': + channel = 0x08; + /* fall through */ + case 'P': + channel |= 0x80; + teln++; + if (*teln == '1') + channel |= 0x01; + else + channel |= 0x02; + break; + case 'R': + screen = 0xA0; + break; + case 'D': + screen = 0x80; + break; + + default: + if (pc->debug & L3_DEB_WARN) + l3_debug(pc->st, "Wrong MSN Code"); + break; + } + teln++; + } + } + if (channel) { + *p++ = IE_CHANNEL_ID; + *p++ = 1; + *p++ = channel; + } + msn = pc->para.setup.eazmsn; + sub = NULL; + sp = msn; + while (*sp) { + if ('.' == *sp) { + sub = sp; + *sp = 0; + } else + sp++; + } + if (*msn) { + *p++ = IE_CALLING_PN; + *p++ = strlen(msn) + (screen ? 2 : 1); + /* Classify as AnyPref. */ + if (screen) { + *p++ = 0x01; /* Ext = '0'B, Type = '000'B, Plan = '0001'B. */ + *p++ = screen; + } else + *p++ = 0x81; /* Ext = '1'B, Type = '000'B, Plan = '0001'B. */ + while (*msn) + *p++ = *msn++ & 0x7f; + } + if (sub) { + *sub++ = '.'; + *p++ = IE_CALLING_SUB; + *p++ = strlen(sub) + 2; + *p++ = 0x80; /* NSAP coded */ + *p++ = 0x50; /* local IDI format */ + while (*sub) + *p++ = *sub++ & 0x7f; + } + sub = NULL; + sp = teln; + while (*sp) { + if ('.' == *sp) { + sub = sp; + *sp = 0; + } else + sp++; + } + + if (!send_keypad) { + *p++ = IE_CALLED_PN; + *p++ = strlen(teln) + 1; + /* Classify as AnyPref. */ + *p++ = 0x81; /* Ext = '1'B, Type = '000'B, Plan = '0001'B. */ + while (*teln) + *p++ = *teln++ & 0x7f; + + if (sub) { + *sub++ = '.'; + *p++ = IE_CALLED_SUB; + *p++ = strlen(sub) + 2; + *p++ = 0x80; /* NSAP coded */ + *p++ = 0x50; /* local IDI format */ + while (*sub) + *p++ = *sub++ & 0x7f; + } + } +#ifdef EXT_BEARER_CAPS + if ((pc->para.setup.si2 >= 160) && (pc->para.setup.si2 <= 175)) { // sync. Bitratenadaption, V.110/X.30 + + *p++ = IE_LLC; + *p++ = 0x04; + *p++ = 0x88; + *p++ = 0x90; + *p++ = 0x21; + *p++ = EncodeSyncParams(pc->para.setup.si2 - 160, 0x80); + } else if ((pc->para.setup.si2 >= 176) && (pc->para.setup.si2 <= 191)) { // sync. Bitratenadaption, V.120 + + *p++ = IE_LLC; + *p++ = 0x05; + *p++ = 0x88; + *p++ = 0x90; + *p++ = 0x28; + *p++ = EncodeSyncParams(pc->para.setup.si2 - 176, 0); + *p++ = 0x82; + } else if (pc->para.setup.si2 >= 192) { // async. Bitratenadaption, V.110/X.30 + + *p++ = IE_LLC; + *p++ = 0x06; + *p++ = 0x88; + *p++ = 0x90; + *p++ = 0x21; + p = EncodeASyncParams(p, pc->para.setup.si2 - 192); +#ifndef CONFIG_HISAX_NO_LLC + } else { + switch (pc->para.setup.si1) { + case 1: /* Telephony */ + *p++ = IE_LLC; + *p++ = 0x3; /* Length */ + *p++ = 0x90; /* Coding Std. CCITT, 3.1 kHz audio */ + *p++ = 0x90; /* Circuit-Mode 64kbps */ + *p++ = 0xa3; /* A-Law Audio */ + break; + case 5: /* Datatransmission 64k, BTX */ + case 7: /* Datatransmission 64k */ + default: + *p++ = IE_LLC; + *p++ = 0x2; /* Length */ + *p++ = 0x88; /* Coding Std. CCITT, unrestr. dig. Inform. */ + *p++ = 0x90; /* Circuit-Mode 64kbps */ + break; + } +#endif + } +#endif + l = p - tmp; + if (!(skb = l3_alloc_skb(l))) + return; + skb_put_data(skb, tmp, l); + L3DelTimer(&pc->timer); + L3AddTimer(&pc->timer, T303, CC_T303); + newl3state(pc, 1); + l3_msg(pc->st, DL_DATA | REQUEST, skb); +} + +static void +l3dss1_call_proc(struct l3_process *pc, u_char pr, void *arg) +{ + struct sk_buff *skb = arg; + int id, ret; + + if ((id = l3dss1_get_channel_id(pc, skb)) >= 0) { + if ((0 == id) || ((3 == id) && (0x10 == pc->para.moderate))) { + if (pc->debug & L3_DEB_WARN) + l3_debug(pc->st, "setup answer with wrong chid %x", id); + pc->para.cause = 100; + l3dss1_status_send(pc, pr, NULL); + return; + } + pc->para.bchannel = id; + } else if (1 == pc->state) { + if (pc->debug & L3_DEB_WARN) + l3_debug(pc->st, "setup answer wrong chid (ret %d)", id); + if (id == -1) + pc->para.cause = 96; + else + pc->para.cause = 100; + l3dss1_status_send(pc, pr, NULL); + return; + } + /* Now we are on none mandatory IEs */ + ret = check_infoelements(pc, skb, ie_CALL_PROCEEDING); + if (ERR_IE_COMPREHENSION == ret) { + l3dss1_std_ie_err(pc, ret); + return; + } + L3DelTimer(&pc->timer); + newl3state(pc, 3); + L3AddTimer(&pc->timer, T310, CC_T310); + if (ret) /* STATUS for none mandatory IE errors after actions are taken */ + l3dss1_std_ie_err(pc, ret); + pc->st->l3.l3l4(pc->st, CC_PROCEEDING | INDICATION, pc); +} + +static void +l3dss1_setup_ack(struct l3_process *pc, u_char pr, void *arg) +{ + struct sk_buff *skb = arg; + int id, ret; + + if ((id = l3dss1_get_channel_id(pc, skb)) >= 0) { + if ((0 == id) || ((3 == id) && (0x10 == pc->para.moderate))) { + if (pc->debug & L3_DEB_WARN) + l3_debug(pc->st, "setup answer with wrong chid %x", id); + pc->para.cause = 100; + l3dss1_status_send(pc, pr, NULL); + return; + } + pc->para.bchannel = id; + } else { + if (pc->debug & L3_DEB_WARN) + l3_debug(pc->st, "setup answer wrong chid (ret %d)", id); + if (id == -1) + pc->para.cause = 96; + else + pc->para.cause = 100; + l3dss1_status_send(pc, pr, NULL); + return; + } + /* Now we are on none mandatory IEs */ + ret = check_infoelements(pc, skb, ie_SETUP_ACKNOWLEDGE); + if (ERR_IE_COMPREHENSION == ret) { + l3dss1_std_ie_err(pc, ret); + return; + } + L3DelTimer(&pc->timer); + newl3state(pc, 2); + L3AddTimer(&pc->timer, T304, CC_T304); + if (ret) /* STATUS for none mandatory IE errors after actions are taken */ + l3dss1_std_ie_err(pc, ret); + pc->st->l3.l3l4(pc->st, CC_MORE_INFO | INDICATION, pc); +} + +static void +l3dss1_disconnect(struct l3_process *pc, u_char pr, void *arg) +{ + struct sk_buff *skb = arg; + u_char *p; + int ret; + u_char cause = 0; + + StopAllL3Timer(pc); + if ((ret = l3dss1_get_cause(pc, skb))) { + if (pc->debug & L3_DEB_WARN) + l3_debug(pc->st, "DISC get_cause ret(%d)", ret); + if (ret < 0) + cause = 96; + else if (ret > 0) + cause = 100; + } + if ((p = findie(skb->data, skb->len, IE_FACILITY, 0))) + l3dss1_parse_facility(pc->st, pc, pc->callref, p); + ret = check_infoelements(pc, skb, ie_DISCONNECT); + if (ERR_IE_COMPREHENSION == ret) + cause = 96; + else if ((!cause) && (ERR_IE_UNRECOGNIZED == ret)) + cause = 99; + ret = pc->state; + newl3state(pc, 12); + if (cause) + newl3state(pc, 19); + if (11 != ret) + pc->st->l3.l3l4(pc->st, CC_DISCONNECT | INDICATION, pc); + else if (!cause) + l3dss1_release_req(pc, pr, NULL); + if (cause) { + l3dss1_message_cause(pc, MT_RELEASE, cause); + L3AddTimer(&pc->timer, T308, CC_T308_1); + } +} + +static void +l3dss1_connect(struct l3_process *pc, u_char pr, void *arg) +{ + struct sk_buff *skb = arg; + int ret; + + ret = check_infoelements(pc, skb, ie_CONNECT); + if (ERR_IE_COMPREHENSION == ret) { + l3dss1_std_ie_err(pc, ret); + return; + } + L3DelTimer(&pc->timer); /* T310 */ + newl3state(pc, 10); + pc->para.chargeinfo = 0; + /* here should inserted COLP handling KKe */ + if (ret) + l3dss1_std_ie_err(pc, ret); + pc->st->l3.l3l4(pc->st, CC_SETUP | CONFIRM, pc); +} + +static void +l3dss1_alerting(struct l3_process *pc, u_char pr, void *arg) +{ + struct sk_buff *skb = arg; + int ret; + + ret = check_infoelements(pc, skb, ie_ALERTING); + if (ERR_IE_COMPREHENSION == ret) { + l3dss1_std_ie_err(pc, ret); + return; + } + L3DelTimer(&pc->timer); /* T304 */ + newl3state(pc, 4); + if (ret) + l3dss1_std_ie_err(pc, ret); + pc->st->l3.l3l4(pc->st, CC_ALERTING | INDICATION, pc); +} + +static void +l3dss1_setup(struct l3_process *pc, u_char pr, void *arg) +{ + u_char *p; + int bcfound = 0; + char tmp[80]; + struct sk_buff *skb = arg; + int id; + int err = 0; + + /* + * Bearer Capabilities + */ + p = skb->data; + /* only the first occurrence 'll be detected ! */ + if ((p = findie(p, skb->len, 0x04, 0))) { + if ((p[1] < 2) || (p[1] > 11)) + err = 1; + else { + pc->para.setup.si2 = 0; + switch (p[2] & 0x7f) { + case 0x00: /* Speech */ + case 0x10: /* 3.1 Khz audio */ + pc->para.setup.si1 = 1; + break; + case 0x08: /* Unrestricted digital information */ + pc->para.setup.si1 = 7; +/* JIM, 05.11.97 I wanna set service indicator 2 */ +#ifdef EXT_BEARER_CAPS + pc->para.setup.si2 = DecodeSI2(skb); +#endif + break; + case 0x09: /* Restricted digital information */ + pc->para.setup.si1 = 2; + break; + case 0x11: + /* Unrestr. digital information with + * tones/announcements ( or 7 kHz audio + */ + pc->para.setup.si1 = 3; + break; + case 0x18: /* Video */ + pc->para.setup.si1 = 4; + break; + default: + err = 2; + break; + } + switch (p[3] & 0x7f) { + case 0x40: /* packed mode */ + pc->para.setup.si1 = 8; + break; + case 0x10: /* 64 kbit */ + case 0x11: /* 2*64 kbit */ + case 0x13: /* 384 kbit */ + case 0x15: /* 1536 kbit */ + case 0x17: /* 1920 kbit */ + pc->para.moderate = p[3] & 0x7f; + break; + default: + err = 3; + break; + } + } + if (pc->debug & L3_DEB_SI) + l3_debug(pc->st, "SI=%d, AI=%d", + pc->para.setup.si1, pc->para.setup.si2); + if (err) { + if (pc->debug & L3_DEB_WARN) + l3_debug(pc->st, "setup with wrong bearer(l=%d:%x,%x)", + p[1], p[2], p[3]); + pc->para.cause = 100; + l3dss1_msg_without_setup(pc, pr, NULL); + return; + } + } else { + if (pc->debug & L3_DEB_WARN) + l3_debug(pc->st, "setup without bearer capabilities"); + /* ETS 300-104 1.3.3 */ + pc->para.cause = 96; + l3dss1_msg_without_setup(pc, pr, NULL); + return; + } + /* + * Channel Identification + */ + if ((id = l3dss1_get_channel_id(pc, skb)) >= 0) { + if ((pc->para.bchannel = id)) { + if ((3 == id) && (0x10 == pc->para.moderate)) { + if (pc->debug & L3_DEB_WARN) + l3_debug(pc->st, "setup with wrong chid %x", + id); + pc->para.cause = 100; + l3dss1_msg_without_setup(pc, pr, NULL); + return; + } + bcfound++; + } else + { if (pc->debug & L3_DEB_WARN) + l3_debug(pc->st, "setup without bchannel, call waiting"); + bcfound++; + } + } else { + if (pc->debug & L3_DEB_WARN) + l3_debug(pc->st, "setup with wrong chid ret %d", id); + if (id == -1) + pc->para.cause = 96; + else + pc->para.cause = 100; + l3dss1_msg_without_setup(pc, pr, NULL); + return; + } + /* Now we are on none mandatory IEs */ + err = check_infoelements(pc, skb, ie_SETUP); + if (ERR_IE_COMPREHENSION == err) { + pc->para.cause = 96; + l3dss1_msg_without_setup(pc, pr, NULL); + return; + } + p = skb->data; + if ((p = findie(p, skb->len, 0x70, 0))) + iecpy(pc->para.setup.eazmsn, p, 1); + else + pc->para.setup.eazmsn[0] = 0; + + p = skb->data; + if ((p = findie(p, skb->len, 0x71, 0))) { + /* Called party subaddress */ + if ((p[1] >= 2) && (p[2] == 0x80) && (p[3] == 0x50)) { + tmp[0] = '.'; + iecpy(&tmp[1], p, 2); + strcat(pc->para.setup.eazmsn, tmp); + } else if (pc->debug & L3_DEB_WARN) + l3_debug(pc->st, "wrong called subaddress"); + } + p = skb->data; + if ((p = findie(p, skb->len, 0x6c, 0))) { + pc->para.setup.plan = p[2]; + if (p[2] & 0x80) { + iecpy(pc->para.setup.phone, p, 1); + pc->para.setup.screen = 0; + } else { + iecpy(pc->para.setup.phone, p, 2); + pc->para.setup.screen = p[3]; + } + } else { + pc->para.setup.phone[0] = 0; + pc->para.setup.plan = 0; + pc->para.setup.screen = 0; + } + p = skb->data; + if ((p = findie(p, skb->len, 0x6d, 0))) { + /* Calling party subaddress */ + if ((p[1] >= 2) && (p[2] == 0x80) && (p[3] == 0x50)) { + tmp[0] = '.'; + iecpy(&tmp[1], p, 2); + strcat(pc->para.setup.phone, tmp); + } else if (pc->debug & L3_DEB_WARN) + l3_debug(pc->st, "wrong calling subaddress"); + } + newl3state(pc, 6); + if (err) /* STATUS for none mandatory IE errors after actions are taken */ + l3dss1_std_ie_err(pc, err); + pc->st->l3.l3l4(pc->st, CC_SETUP | INDICATION, pc); +} + +static void +l3dss1_reset(struct l3_process *pc, u_char pr, void *arg) +{ + dss1_release_l3_process(pc); +} + +static void +l3dss1_disconnect_req(struct l3_process *pc, u_char pr, void *arg) +{ + struct sk_buff *skb; + u_char tmp[16 + 40]; + u_char *p = tmp; + int l; + u_char cause = 16; + + if (pc->para.cause != NO_CAUSE) + cause = pc->para.cause; + + StopAllL3Timer(pc); + + MsgHead(p, pc->callref, MT_DISCONNECT); + + *p++ = IE_CAUSE; + *p++ = 0x2; + *p++ = 0x80; + *p++ = cause | 0x80; + + if (pc->prot.dss1.uus1_data[0]) + { *p++ = IE_USER_USER; /* UUS info element */ + *p++ = strlen(pc->prot.dss1.uus1_data) + 1; + *p++ = 0x04; /* IA5 chars */ + strcpy(p, pc->prot.dss1.uus1_data); + p += strlen(pc->prot.dss1.uus1_data); + pc->prot.dss1.uus1_data[0] = '\0'; + } + + l = p - tmp; + if (!(skb = l3_alloc_skb(l))) + return; + skb_put_data(skb, tmp, l); + newl3state(pc, 11); + l3_msg(pc->st, DL_DATA | REQUEST, skb); + L3AddTimer(&pc->timer, T305, CC_T305); +} + +static void +l3dss1_setup_rsp(struct l3_process *pc, u_char pr, + void *arg) +{ + if (!pc->para.bchannel) + { if (pc->debug & L3_DEB_WARN) + l3_debug(pc->st, "D-chan connect for waiting call"); + l3dss1_disconnect_req(pc, pr, arg); + return; + } + newl3state(pc, 8); + l3dss1_message(pc, MT_CONNECT); + L3DelTimer(&pc->timer); + L3AddTimer(&pc->timer, T313, CC_T313); +} + +static void +l3dss1_connect_ack(struct l3_process *pc, u_char pr, void *arg) +{ + struct sk_buff *skb = arg; + int ret; + + ret = check_infoelements(pc, skb, ie_CONNECT_ACKNOWLEDGE); + if (ERR_IE_COMPREHENSION == ret) { + l3dss1_std_ie_err(pc, ret); + return; + } + newl3state(pc, 10); + L3DelTimer(&pc->timer); + if (ret) + l3dss1_std_ie_err(pc, ret); + pc->st->l3.l3l4(pc->st, CC_SETUP_COMPL | INDICATION, pc); +} + +static void +l3dss1_reject_req(struct l3_process *pc, u_char pr, void *arg) +{ + struct sk_buff *skb; + u_char tmp[16]; + u_char *p = tmp; + int l; + u_char cause = 21; + + if (pc->para.cause != NO_CAUSE) + cause = pc->para.cause; + + MsgHead(p, pc->callref, MT_RELEASE_COMPLETE); + + *p++ = IE_CAUSE; + *p++ = 0x2; + *p++ = 0x80; + *p++ = cause | 0x80; + + l = p - tmp; + if (!(skb = l3_alloc_skb(l))) + return; + skb_put_data(skb, tmp, l); + l3_msg(pc->st, DL_DATA | REQUEST, skb); + pc->st->l3.l3l4(pc->st, CC_RELEASE | INDICATION, pc); + newl3state(pc, 0); + dss1_release_l3_process(pc); +} + +static void +l3dss1_release(struct l3_process *pc, u_char pr, void *arg) +{ + struct sk_buff *skb = arg; + u_char *p; + int ret, cause = 0; + + StopAllL3Timer(pc); + if ((ret = l3dss1_get_cause(pc, skb)) > 0) { + if (pc->debug & L3_DEB_WARN) + l3_debug(pc->st, "REL get_cause ret(%d)", ret); + } else if (ret < 0) + pc->para.cause = NO_CAUSE; + if ((p = findie(skb->data, skb->len, IE_FACILITY, 0))) { + l3dss1_parse_facility(pc->st, pc, pc->callref, p); + } + if ((ret < 0) && (pc->state != 11)) + cause = 96; + else if (ret > 0) + cause = 100; + ret = check_infoelements(pc, skb, ie_RELEASE); + if (ERR_IE_COMPREHENSION == ret) + cause = 96; + else if ((ERR_IE_UNRECOGNIZED == ret) && (!cause)) + cause = 99; + if (cause) + l3dss1_message_cause(pc, MT_RELEASE_COMPLETE, cause); + else + l3dss1_message(pc, MT_RELEASE_COMPLETE); + pc->st->l3.l3l4(pc->st, CC_RELEASE | INDICATION, pc); + newl3state(pc, 0); + dss1_release_l3_process(pc); +} + +static void +l3dss1_alert_req(struct l3_process *pc, u_char pr, + void *arg) +{ + newl3state(pc, 7); + if (!pc->prot.dss1.uus1_data[0]) + l3dss1_message(pc, MT_ALERTING); + else + l3dss1_msg_with_uus(pc, MT_ALERTING); +} + +static void +l3dss1_proceed_req(struct l3_process *pc, u_char pr, + void *arg) +{ + newl3state(pc, 9); + l3dss1_message(pc, MT_CALL_PROCEEDING); + pc->st->l3.l3l4(pc->st, CC_PROCEED_SEND | INDICATION, pc); +} + +static void +l3dss1_setup_ack_req(struct l3_process *pc, u_char pr, + void *arg) +{ + newl3state(pc, 25); + L3DelTimer(&pc->timer); + L3AddTimer(&pc->timer, T302, CC_T302); + l3dss1_message(pc, MT_SETUP_ACKNOWLEDGE); +} + +/********************************************/ +/* deliver a incoming display message to HL */ +/********************************************/ +static void +l3dss1_deliver_display(struct l3_process *pc, int pr, u_char *infp) +{ u_char len; + isdn_ctrl ic; + struct IsdnCardState *cs; + char *p; + + if (*infp++ != IE_DISPLAY) return; + if ((len = *infp++) > 80) return; /* total length <= 82 */ + if (!pc->chan) return; + + p = ic.parm.display; + while (len--) + *p++ = *infp++; + *p = '\0'; + ic.command = ISDN_STAT_DISPLAY; + cs = pc->st->l1.hardware; + ic.driver = cs->myid; + ic.arg = pc->chan->chan; + cs->iif.statcallb(&ic); +} /* l3dss1_deliver_display */ + + +static void +l3dss1_progress(struct l3_process *pc, u_char pr, void *arg) +{ + struct sk_buff *skb = arg; + int err = 0; + u_char *p; + + if ((p = findie(skb->data, skb->len, IE_PROGRESS, 0))) { + if (p[1] != 2) { + err = 1; + pc->para.cause = 100; + } else if (!(p[2] & 0x70)) { + switch (p[2]) { + case 0x80: + case 0x81: + case 0x82: + case 0x84: + case 0x85: + case 0x87: + case 0x8a: + switch (p[3]) { + case 0x81: + case 0x82: + case 0x83: + case 0x84: + case 0x88: + break; + default: + err = 2; + pc->para.cause = 100; + break; + } + break; + default: + err = 3; + pc->para.cause = 100; + break; + } + } + } else { + pc->para.cause = 96; + err = 4; + } + if (err) { + if (pc->debug & L3_DEB_WARN) + l3_debug(pc->st, "progress error %d", err); + l3dss1_status_send(pc, pr, NULL); + return; + } + /* Now we are on none mandatory IEs */ + err = check_infoelements(pc, skb, ie_PROGRESS); + if (err) + l3dss1_std_ie_err(pc, err); + if (ERR_IE_COMPREHENSION != err) + pc->st->l3.l3l4(pc->st, CC_PROGRESS | INDICATION, pc); +} + +static void +l3dss1_notify(struct l3_process *pc, u_char pr, void *arg) +{ + struct sk_buff *skb = arg; + int err = 0; + u_char *p; + + if ((p = findie(skb->data, skb->len, IE_NOTIFY, 0))) { + if (p[1] != 1) { + err = 1; + pc->para.cause = 100; + } else { + switch (p[2]) { + case 0x80: + case 0x81: + case 0x82: + break; + default: + pc->para.cause = 100; + err = 2; + break; + } + } + } else { + pc->para.cause = 96; + err = 3; + } + if (err) { + if (pc->debug & L3_DEB_WARN) + l3_debug(pc->st, "notify error %d", err); + l3dss1_status_send(pc, pr, NULL); + return; + } + /* Now we are on none mandatory IEs */ + err = check_infoelements(pc, skb, ie_NOTIFY); + if (err) + l3dss1_std_ie_err(pc, err); + if (ERR_IE_COMPREHENSION != err) + pc->st->l3.l3l4(pc->st, CC_NOTIFY | INDICATION, pc); +} + +static void +l3dss1_status_enq(struct l3_process *pc, u_char pr, void *arg) +{ + int ret; + struct sk_buff *skb = arg; + + ret = check_infoelements(pc, skb, ie_STATUS_ENQUIRY); + l3dss1_std_ie_err(pc, ret); + pc->para.cause = 30; /* response to STATUS_ENQUIRY */ + l3dss1_status_send(pc, pr, NULL); +} + +static void +l3dss1_information(struct l3_process *pc, u_char pr, void *arg) +{ + int ret; + struct sk_buff *skb = arg; + u_char *p; + char tmp[32]; + + ret = check_infoelements(pc, skb, ie_INFORMATION); + if (ret) + l3dss1_std_ie_err(pc, ret); + if (pc->state == 25) { /* overlap receiving */ + L3DelTimer(&pc->timer); + p = skb->data; + if ((p = findie(p, skb->len, 0x70, 0))) { + iecpy(tmp, p, 1); + strcat(pc->para.setup.eazmsn, tmp); + pc->st->l3.l3l4(pc->st, CC_MORE_INFO | INDICATION, pc); + } + L3AddTimer(&pc->timer, T302, CC_T302); + } +} + +/******************************/ +/* handle deflection requests */ +/******************************/ +static void l3dss1_redir_req(struct l3_process *pc, u_char pr, void *arg) +{ + struct sk_buff *skb; + u_char tmp[128]; + u_char *p = tmp; + u_char *subp; + u_char len_phone = 0; + u_char len_sub = 0; + int l; + + + strcpy(pc->prot.dss1.uus1_data, pc->chan->setup.eazmsn); /* copy uus element if available */ + if (!pc->chan->setup.phone[0]) + { pc->para.cause = -1; + l3dss1_disconnect_req(pc, pr, arg); /* disconnect immediately */ + return; + } /* only uus */ + + if (pc->prot.dss1.invoke_id) + free_invoke_id(pc->st, pc->prot.dss1.invoke_id); + + if (!(pc->prot.dss1.invoke_id = new_invoke_id(pc->st))) + return; + + MsgHead(p, pc->callref, MT_FACILITY); + + for (subp = pc->chan->setup.phone; (*subp) && (*subp != '.'); subp++) len_phone++; /* len of phone number */ + if (*subp++ == '.') len_sub = strlen(subp) + 2; /* length including info subaddress element */ + + *p++ = 0x1c; /* Facility info element */ + *p++ = len_phone + len_sub + 2 + 2 + 8 + 3 + 3; /* length of element */ + *p++ = 0x91; /* remote operations protocol */ + *p++ = 0xa1; /* invoke component */ + + *p++ = len_phone + len_sub + 2 + 2 + 8 + 3; /* length of data */ + *p++ = 0x02; /* invoke id tag, integer */ + *p++ = 0x01; /* length */ + *p++ = pc->prot.dss1.invoke_id; /* invoke id */ + *p++ = 0x02; /* operation value tag, integer */ + *p++ = 0x01; /* length */ + *p++ = 0x0D; /* Call Deflect */ + + *p++ = 0x30; /* sequence phone number */ + *p++ = len_phone + 2 + 2 + 3 + len_sub; /* length */ + + *p++ = 0x30; /* Deflected to UserNumber */ + *p++ = len_phone + 2 + len_sub; /* length */ + *p++ = 0x80; /* NumberDigits */ + *p++ = len_phone; /* length */ + for (l = 0; l < len_phone; l++) + *p++ = pc->chan->setup.phone[l]; + + if (len_sub) + { *p++ = 0x04; /* called party subaddress */ + *p++ = len_sub - 2; + while (*subp) *p++ = *subp++; + } + + *p++ = 0x01; /* screening identifier */ + *p++ = 0x01; + *p++ = pc->chan->setup.screen; + + l = p - tmp; + if (!(skb = l3_alloc_skb(l))) return; + skb_put_data(skb, tmp, l); + + l3_msg(pc->st, DL_DATA | REQUEST, skb); +} /* l3dss1_redir_req */ + +/********************************************/ +/* handle deflection request in early state */ +/********************************************/ +static void l3dss1_redir_req_early(struct l3_process *pc, u_char pr, void *arg) +{ + l3dss1_proceed_req(pc, pr, arg); + l3dss1_redir_req(pc, pr, arg); +} /* l3dss1_redir_req_early */ + +/***********************************************/ +/* handle special commands for this protocol. */ +/* Examples are call independent services like */ +/* remote operations with dummy callref. */ +/***********************************************/ +static int l3dss1_cmd_global(struct PStack *st, isdn_ctrl *ic) +{ u_char id; + u_char temp[265]; + u_char *p = temp; + int i, l, proc_len; + struct sk_buff *skb; + struct l3_process *pc = NULL; + + switch (ic->arg) + { case DSS1_CMD_INVOKE: + if (ic->parm.dss1_io.datalen < 0) return (-2); /* invalid parameter */ + + for (proc_len = 1, i = ic->parm.dss1_io.proc >> 8; i; i++) + i = i >> 8; /* add one byte */ + l = ic->parm.dss1_io.datalen + proc_len + 8; /* length excluding ie header */ + if (l > 255) + return (-2); /* too long */ + + if (!(id = new_invoke_id(st))) + return (0); /* first get a invoke id -> return if no available */ + + i = -1; + MsgHead(p, i, MT_FACILITY); /* build message head */ + *p++ = 0x1C; /* Facility IE */ + *p++ = l; /* length of ie */ + *p++ = 0x91; /* remote operations */ + *p++ = 0xA1; /* invoke */ + *p++ = l - 3; /* length of invoke */ + *p++ = 0x02; /* invoke id tag */ + *p++ = 0x01; /* length is 1 */ + *p++ = id; /* invoke id */ + *p++ = 0x02; /* operation */ + *p++ = proc_len; /* length of operation */ + + for (i = proc_len; i; i--) + *p++ = (ic->parm.dss1_io.proc >> (i - 1)) & 0xFF; + memcpy(p, ic->parm.dss1_io.data, ic->parm.dss1_io.datalen); /* copy data */ + l = (p - temp) + ic->parm.dss1_io.datalen; /* total length */ + + if (ic->parm.dss1_io.timeout > 0) + if (!(pc = dss1_new_l3_process(st, -1))) + { free_invoke_id(st, id); + return (-2); + } + pc->prot.dss1.ll_id = ic->parm.dss1_io.ll_id; /* remember id */ + pc->prot.dss1.proc = ic->parm.dss1_io.proc; /* and procedure */ + + if (!(skb = l3_alloc_skb(l))) + { free_invoke_id(st, id); + if (pc) dss1_release_l3_process(pc); + return (-2); + } + skb_put_data(skb, temp, l); + + if (pc) + { pc->prot.dss1.invoke_id = id; /* remember id */ + L3AddTimer(&pc->timer, ic->parm.dss1_io.timeout, CC_TDSS1_IO | REQUEST); + } + + l3_msg(st, DL_DATA | REQUEST, skb); + ic->parm.dss1_io.hl_id = id; /* return id */ + return (0); + + case DSS1_CMD_INVOKE_ABORT: + if ((pc = l3dss1_search_dummy_proc(st, ic->parm.dss1_io.hl_id))) + { L3DelTimer(&pc->timer); /* remove timer */ + dss1_release_l3_process(pc); + return (0); + } + else + { l3_debug(st, "l3dss1_cmd_global abort unknown id"); + return (-2); + } + break; + + default: + l3_debug(st, "l3dss1_cmd_global unknown cmd 0x%lx", ic->arg); + return (-1); + } /* switch ic-> arg */ + return (-1); +} /* l3dss1_cmd_global */ + +static void +l3dss1_io_timer(struct l3_process *pc) +{ isdn_ctrl ic; + struct IsdnCardState *cs = pc->st->l1.hardware; + + L3DelTimer(&pc->timer); /* remove timer */ + + ic.driver = cs->myid; + ic.command = ISDN_STAT_PROT; + ic.arg = DSS1_STAT_INVOKE_ERR; + ic.parm.dss1_io.hl_id = pc->prot.dss1.invoke_id; + ic.parm.dss1_io.ll_id = pc->prot.dss1.ll_id; + ic.parm.dss1_io.proc = pc->prot.dss1.proc; + ic.parm.dss1_io.timeout = -1; + ic.parm.dss1_io.datalen = 0; + ic.parm.dss1_io.data = NULL; + free_invoke_id(pc->st, pc->prot.dss1.invoke_id); + pc->prot.dss1.invoke_id = 0; /* reset id */ + + cs->iif.statcallb(&ic); + + dss1_release_l3_process(pc); +} /* l3dss1_io_timer */ + +static void +l3dss1_release_ind(struct l3_process *pc, u_char pr, void *arg) +{ + u_char *p; + struct sk_buff *skb = arg; + int callState = 0; + p = skb->data; + + if ((p = findie(p, skb->len, IE_CALL_STATE, 0))) { + p++; + if (1 == *p++) + callState = *p; + } + if (callState == 0) { + /* ETS 300-104 7.6.1, 8.6.1, 10.6.1... and 16.1 + * set down layer 3 without sending any message + */ + pc->st->l3.l3l4(pc->st, CC_RELEASE | INDICATION, pc); + newl3state(pc, 0); + dss1_release_l3_process(pc); + } else { + pc->st->l3.l3l4(pc->st, CC_IGNORE | INDICATION, pc); + } +} + +static void +l3dss1_dummy(struct l3_process *pc, u_char pr, void *arg) +{ +} + +static void +l3dss1_t302(struct l3_process *pc, u_char pr, void *arg) +{ + L3DelTimer(&pc->timer); + pc->para.loc = 0; + pc->para.cause = 28; /* invalid number */ + l3dss1_disconnect_req(pc, pr, NULL); + pc->st->l3.l3l4(pc->st, CC_SETUP_ERR, pc); +} + +static void +l3dss1_t303(struct l3_process *pc, u_char pr, void *arg) +{ + if (pc->N303 > 0) { + pc->N303--; + L3DelTimer(&pc->timer); + l3dss1_setup_req(pc, pr, arg); + } else { + L3DelTimer(&pc->timer); + l3dss1_message_cause(pc, MT_RELEASE_COMPLETE, 102); + pc->st->l3.l3l4(pc->st, CC_NOSETUP_RSP, pc); + dss1_release_l3_process(pc); + } +} + +static void +l3dss1_t304(struct l3_process *pc, u_char pr, void *arg) +{ + L3DelTimer(&pc->timer); + pc->para.loc = 0; + pc->para.cause = 102; + l3dss1_disconnect_req(pc, pr, NULL); + pc->st->l3.l3l4(pc->st, CC_SETUP_ERR, pc); + +} + +static void +l3dss1_t305(struct l3_process *pc, u_char pr, void *arg) +{ + u_char tmp[16]; + u_char *p = tmp; + int l; + struct sk_buff *skb; + u_char cause = 16; + + L3DelTimer(&pc->timer); + if (pc->para.cause != NO_CAUSE) + cause = pc->para.cause; + + MsgHead(p, pc->callref, MT_RELEASE); + + *p++ = IE_CAUSE; + *p++ = 0x2; + *p++ = 0x80; + *p++ = cause | 0x80; + + l = p - tmp; + if (!(skb = l3_alloc_skb(l))) + return; + skb_put_data(skb, tmp, l); + newl3state(pc, 19); + l3_msg(pc->st, DL_DATA | REQUEST, skb); + L3AddTimer(&pc->timer, T308, CC_T308_1); +} + +static void +l3dss1_t310(struct l3_process *pc, u_char pr, void *arg) +{ + L3DelTimer(&pc->timer); + pc->para.loc = 0; + pc->para.cause = 102; + l3dss1_disconnect_req(pc, pr, NULL); + pc->st->l3.l3l4(pc->st, CC_SETUP_ERR, pc); +} + +static void +l3dss1_t313(struct l3_process *pc, u_char pr, void *arg) +{ + L3DelTimer(&pc->timer); + pc->para.loc = 0; + pc->para.cause = 102; + l3dss1_disconnect_req(pc, pr, NULL); + pc->st->l3.l3l4(pc->st, CC_CONNECT_ERR, pc); +} + +static void +l3dss1_t308_1(struct l3_process *pc, u_char pr, void *arg) +{ + newl3state(pc, 19); + L3DelTimer(&pc->timer); + l3dss1_message(pc, MT_RELEASE); + L3AddTimer(&pc->timer, T308, CC_T308_2); +} + +static void +l3dss1_t308_2(struct l3_process *pc, u_char pr, void *arg) +{ + L3DelTimer(&pc->timer); + pc->st->l3.l3l4(pc->st, CC_RELEASE_ERR, pc); + dss1_release_l3_process(pc); +} + +static void +l3dss1_t318(struct l3_process *pc, u_char pr, void *arg) +{ + L3DelTimer(&pc->timer); + pc->para.cause = 102; /* Timer expiry */ + pc->para.loc = 0; /* local */ + pc->st->l3.l3l4(pc->st, CC_RESUME_ERR, pc); + newl3state(pc, 19); + l3dss1_message(pc, MT_RELEASE); + L3AddTimer(&pc->timer, T308, CC_T308_1); +} + +static void +l3dss1_t319(struct l3_process *pc, u_char pr, void *arg) +{ + L3DelTimer(&pc->timer); + pc->para.cause = 102; /* Timer expiry */ + pc->para.loc = 0; /* local */ + pc->st->l3.l3l4(pc->st, CC_SUSPEND_ERR, pc); + newl3state(pc, 10); +} + +static void +l3dss1_restart(struct l3_process *pc, u_char pr, void *arg) +{ + L3DelTimer(&pc->timer); + pc->st->l3.l3l4(pc->st, CC_RELEASE | INDICATION, pc); + dss1_release_l3_process(pc); +} + +static void +l3dss1_status(struct l3_process *pc, u_char pr, void *arg) +{ + u_char *p; + struct sk_buff *skb = arg; + int ret; + u_char cause = 0, callState = 0; + + if ((ret = l3dss1_get_cause(pc, skb))) { + if (pc->debug & L3_DEB_WARN) + l3_debug(pc->st, "STATUS get_cause ret(%d)", ret); + if (ret < 0) + cause = 96; + else if (ret > 0) + cause = 100; + } + if ((p = findie(skb->data, skb->len, IE_CALL_STATE, 0))) { + p++; + if (1 == *p++) { + callState = *p; + if (!ie_in_set(pc, *p, l3_valid_states)) + cause = 100; + } else + cause = 100; + } else + cause = 96; + if (!cause) { /* no error before */ + ret = check_infoelements(pc, skb, ie_STATUS); + if (ERR_IE_COMPREHENSION == ret) + cause = 96; + else if (ERR_IE_UNRECOGNIZED == ret) + cause = 99; + } + if (cause) { + u_char tmp; + + if (pc->debug & L3_DEB_WARN) + l3_debug(pc->st, "STATUS error(%d/%d)", ret, cause); + tmp = pc->para.cause; + pc->para.cause = cause; + l3dss1_status_send(pc, 0, NULL); + if (cause == 99) + pc->para.cause = tmp; + else + return; + } + cause = pc->para.cause; + if (((cause & 0x7f) == 111) && (callState == 0)) { + /* ETS 300-104 7.6.1, 8.6.1, 10.6.1... + * if received MT_STATUS with cause == 111 and call + * state == 0, then we must set down layer 3 + */ + pc->st->l3.l3l4(pc->st, CC_RELEASE | INDICATION, pc); + newl3state(pc, 0); + dss1_release_l3_process(pc); + } +} + +static void +l3dss1_facility(struct l3_process *pc, u_char pr, void *arg) +{ + struct sk_buff *skb = arg; + int ret; + + ret = check_infoelements(pc, skb, ie_FACILITY); + l3dss1_std_ie_err(pc, ret); + { + u_char *p; + if ((p = findie(skb->data, skb->len, IE_FACILITY, 0))) + l3dss1_parse_facility(pc->st, pc, pc->callref, p); + } +} + +static void +l3dss1_suspend_req(struct l3_process *pc, u_char pr, void *arg) +{ + struct sk_buff *skb; + u_char tmp[32]; + u_char *p = tmp; + u_char i, l; + u_char *msg = pc->chan->setup.phone; + + MsgHead(p, pc->callref, MT_SUSPEND); + l = *msg++; + if (l && (l <= 10)) { /* Max length 10 octets */ + *p++ = IE_CALL_ID; + *p++ = l; + for (i = 0; i < l; i++) + *p++ = *msg++; + } else if (l) { + l3_debug(pc->st, "SUS wrong CALL_ID len %d", l); + return; + } + l = p - tmp; + if (!(skb = l3_alloc_skb(l))) + return; + skb_put_data(skb, tmp, l); + l3_msg(pc->st, DL_DATA | REQUEST, skb); + newl3state(pc, 15); + L3AddTimer(&pc->timer, T319, CC_T319); +} + +static void +l3dss1_suspend_ack(struct l3_process *pc, u_char pr, void *arg) +{ + struct sk_buff *skb = arg; + int ret; + + L3DelTimer(&pc->timer); + newl3state(pc, 0); + pc->para.cause = NO_CAUSE; + pc->st->l3.l3l4(pc->st, CC_SUSPEND | CONFIRM, pc); + /* We don't handle suspend_ack for IE errors now */ + if ((ret = check_infoelements(pc, skb, ie_SUSPEND_ACKNOWLEDGE))) + if (pc->debug & L3_DEB_WARN) + l3_debug(pc->st, "SUSPACK check ie(%d)", ret); + dss1_release_l3_process(pc); +} + +static void +l3dss1_suspend_rej(struct l3_process *pc, u_char pr, void *arg) +{ + struct sk_buff *skb = arg; + int ret; + + if ((ret = l3dss1_get_cause(pc, skb))) { + if (pc->debug & L3_DEB_WARN) + l3_debug(pc->st, "SUSP_REJ get_cause ret(%d)", ret); + if (ret < 0) + pc->para.cause = 96; + else + pc->para.cause = 100; + l3dss1_status_send(pc, pr, NULL); + return; + } + ret = check_infoelements(pc, skb, ie_SUSPEND_REJECT); + if (ERR_IE_COMPREHENSION == ret) { + l3dss1_std_ie_err(pc, ret); + return; + } + L3DelTimer(&pc->timer); + pc->st->l3.l3l4(pc->st, CC_SUSPEND_ERR, pc); + newl3state(pc, 10); + if (ret) /* STATUS for none mandatory IE errors after actions are taken */ + l3dss1_std_ie_err(pc, ret); +} + +static void +l3dss1_resume_req(struct l3_process *pc, u_char pr, void *arg) +{ + struct sk_buff *skb; + u_char tmp[32]; + u_char *p = tmp; + u_char i, l; + u_char *msg = pc->para.setup.phone; + + MsgHead(p, pc->callref, MT_RESUME); + + l = *msg++; + if (l && (l <= 10)) { /* Max length 10 octets */ + *p++ = IE_CALL_ID; + *p++ = l; + for (i = 0; i < l; i++) + *p++ = *msg++; + } else if (l) { + l3_debug(pc->st, "RES wrong CALL_ID len %d", l); + return; + } + l = p - tmp; + if (!(skb = l3_alloc_skb(l))) + return; + skb_put_data(skb, tmp, l); + l3_msg(pc->st, DL_DATA | REQUEST, skb); + newl3state(pc, 17); + L3AddTimer(&pc->timer, T318, CC_T318); +} + +static void +l3dss1_resume_ack(struct l3_process *pc, u_char pr, void *arg) +{ + struct sk_buff *skb = arg; + int id, ret; + + if ((id = l3dss1_get_channel_id(pc, skb)) > 0) { + if ((0 == id) || ((3 == id) && (0x10 == pc->para.moderate))) { + if (pc->debug & L3_DEB_WARN) + l3_debug(pc->st, "resume ack with wrong chid %x", id); + pc->para.cause = 100; + l3dss1_status_send(pc, pr, NULL); + return; + } + pc->para.bchannel = id; + } else if (1 == pc->state) { + if (pc->debug & L3_DEB_WARN) + l3_debug(pc->st, "resume ack without chid (ret %d)", id); + pc->para.cause = 96; + l3dss1_status_send(pc, pr, NULL); + return; + } + ret = check_infoelements(pc, skb, ie_RESUME_ACKNOWLEDGE); + if (ERR_IE_COMPREHENSION == ret) { + l3dss1_std_ie_err(pc, ret); + return; + } + L3DelTimer(&pc->timer); + pc->st->l3.l3l4(pc->st, CC_RESUME | CONFIRM, pc); + newl3state(pc, 10); + if (ret) /* STATUS for none mandatory IE errors after actions are taken */ + l3dss1_std_ie_err(pc, ret); +} + +static void +l3dss1_resume_rej(struct l3_process *pc, u_char pr, void *arg) +{ + struct sk_buff *skb = arg; + int ret; + + if ((ret = l3dss1_get_cause(pc, skb))) { + if (pc->debug & L3_DEB_WARN) + l3_debug(pc->st, "RES_REJ get_cause ret(%d)", ret); + if (ret < 0) + pc->para.cause = 96; + else + pc->para.cause = 100; + l3dss1_status_send(pc, pr, NULL); + return; + } + ret = check_infoelements(pc, skb, ie_RESUME_REJECT); + if (ERR_IE_COMPREHENSION == ret) { + l3dss1_std_ie_err(pc, ret); + return; + } + L3DelTimer(&pc->timer); + pc->st->l3.l3l4(pc->st, CC_RESUME_ERR, pc); + newl3state(pc, 0); + if (ret) /* STATUS for none mandatory IE errors after actions are taken */ + l3dss1_std_ie_err(pc, ret); + dss1_release_l3_process(pc); +} + +static void +l3dss1_global_restart(struct l3_process *pc, u_char pr, void *arg) +{ + u_char tmp[32]; + u_char *p; + u_char ri, ch = 0, chan = 0; + int l; + struct sk_buff *skb = arg; + struct l3_process *up; + + newl3state(pc, 2); + L3DelTimer(&pc->timer); + p = skb->data; + if ((p = findie(p, skb->len, IE_RESTART_IND, 0))) { + ri = p[2]; + l3_debug(pc->st, "Restart %x", ri); + } else { + l3_debug(pc->st, "Restart without restart IE"); + ri = 0x86; + } + p = skb->data; + if ((p = findie(p, skb->len, IE_CHANNEL_ID, 0))) { + chan = p[2] & 3; + ch = p[2]; + if (pc->st->l3.debug) + l3_debug(pc->st, "Restart for channel %d", chan); + } + newl3state(pc, 2); + up = pc->st->l3.proc; + while (up) { + if ((ri & 7) == 7) + up->st->lli.l4l3(up->st, CC_RESTART | REQUEST, up); + else if (up->para.bchannel == chan) + up->st->lli.l4l3(up->st, CC_RESTART | REQUEST, up); + up = up->next; + } + p = tmp; + MsgHead(p, pc->callref, MT_RESTART_ACKNOWLEDGE); + if (chan) { + *p++ = IE_CHANNEL_ID; + *p++ = 1; + *p++ = ch | 0x80; + } + *p++ = 0x79; /* RESTART Ind */ + *p++ = 1; + *p++ = ri; + l = p - tmp; + if (!(skb = l3_alloc_skb(l))) + return; + skb_put_data(skb, tmp, l); + newl3state(pc, 0); + l3_msg(pc->st, DL_DATA | REQUEST, skb); +} + +static void +l3dss1_dl_reset(struct l3_process *pc, u_char pr, void *arg) +{ + pc->para.cause = 0x29; /* Temporary failure */ + pc->para.loc = 0; + l3dss1_disconnect_req(pc, pr, NULL); + pc->st->l3.l3l4(pc->st, CC_SETUP_ERR, pc); +} + +static void +l3dss1_dl_release(struct l3_process *pc, u_char pr, void *arg) +{ + newl3state(pc, 0); + pc->para.cause = 0x1b; /* Destination out of order */ + pc->para.loc = 0; + pc->st->l3.l3l4(pc->st, CC_RELEASE | INDICATION, pc); + release_l3_process(pc); +} + +static void +l3dss1_dl_reestablish(struct l3_process *pc, u_char pr, void *arg) +{ + L3DelTimer(&pc->timer); + L3AddTimer(&pc->timer, T309, CC_T309); + l3_msg(pc->st, DL_ESTABLISH | REQUEST, NULL); +} + +static void +l3dss1_dl_reest_status(struct l3_process *pc, u_char pr, void *arg) +{ + L3DelTimer(&pc->timer); + + pc->para.cause = 0x1F; /* normal, unspecified */ + l3dss1_status_send(pc, 0, NULL); +} + +/* *INDENT-OFF* */ +static struct stateentry downstatelist[] = +{ + {SBIT(0), + CC_SETUP | REQUEST, l3dss1_setup_req}, + {SBIT(0), + CC_RESUME | REQUEST, l3dss1_resume_req}, + {SBIT(1) | SBIT(2) | SBIT(3) | SBIT(4) | SBIT(6) | SBIT(7) | SBIT(8) | SBIT(9) | SBIT(10) | SBIT(25), + CC_DISCONNECT | REQUEST, l3dss1_disconnect_req}, + {SBIT(12), + CC_RELEASE | REQUEST, l3dss1_release_req}, + {ALL_STATES, + CC_RESTART | REQUEST, l3dss1_restart}, + {SBIT(6) | SBIT(25), + CC_IGNORE | REQUEST, l3dss1_reset}, + {SBIT(6) | SBIT(25), + CC_REJECT | REQUEST, l3dss1_reject_req}, + {SBIT(6) | SBIT(25), + CC_PROCEED_SEND | REQUEST, l3dss1_proceed_req}, + {SBIT(6), + CC_MORE_INFO | REQUEST, l3dss1_setup_ack_req}, + {SBIT(25), + CC_MORE_INFO | REQUEST, l3dss1_dummy}, + {SBIT(6) | SBIT(9) | SBIT(25), + CC_ALERTING | REQUEST, l3dss1_alert_req}, + {SBIT(6) | SBIT(7) | SBIT(9) | SBIT(25), + CC_SETUP | RESPONSE, l3dss1_setup_rsp}, + {SBIT(10), + CC_SUSPEND | REQUEST, l3dss1_suspend_req}, + {SBIT(7) | SBIT(9) | SBIT(25), + CC_REDIR | REQUEST, l3dss1_redir_req}, + {SBIT(6), + CC_REDIR | REQUEST, l3dss1_redir_req_early}, + {SBIT(9) | SBIT(25), + CC_DISCONNECT | REQUEST, l3dss1_disconnect_req}, + {SBIT(25), + CC_T302, l3dss1_t302}, + {SBIT(1), + CC_T303, l3dss1_t303}, + {SBIT(2), + CC_T304, l3dss1_t304}, + {SBIT(3), + CC_T310, l3dss1_t310}, + {SBIT(8), + CC_T313, l3dss1_t313}, + {SBIT(11), + CC_T305, l3dss1_t305}, + {SBIT(15), + CC_T319, l3dss1_t319}, + {SBIT(17), + CC_T318, l3dss1_t318}, + {SBIT(19), + CC_T308_1, l3dss1_t308_1}, + {SBIT(19), + CC_T308_2, l3dss1_t308_2}, + {SBIT(10), + CC_T309, l3dss1_dl_release}, +}; + +static struct stateentry datastatelist[] = +{ + {ALL_STATES, + MT_STATUS_ENQUIRY, l3dss1_status_enq}, + {ALL_STATES, + MT_FACILITY, l3dss1_facility}, + {SBIT(19), + MT_STATUS, l3dss1_release_ind}, + {ALL_STATES, + MT_STATUS, l3dss1_status}, + {SBIT(0), + MT_SETUP, l3dss1_setup}, + {SBIT(6) | SBIT(7) | SBIT(8) | SBIT(9) | SBIT(10) | SBIT(11) | SBIT(12) | + SBIT(15) | SBIT(17) | SBIT(19) | SBIT(25), + MT_SETUP, l3dss1_dummy}, + {SBIT(1) | SBIT(2), + MT_CALL_PROCEEDING, l3dss1_call_proc}, + {SBIT(1), + MT_SETUP_ACKNOWLEDGE, l3dss1_setup_ack}, + {SBIT(2) | SBIT(3), + MT_ALERTING, l3dss1_alerting}, + {SBIT(2) | SBIT(3), + MT_PROGRESS, l3dss1_progress}, + {SBIT(2) | SBIT(3) | SBIT(4) | SBIT(7) | SBIT(8) | SBIT(9) | SBIT(10) | + SBIT(11) | SBIT(12) | SBIT(15) | SBIT(17) | SBIT(19) | SBIT(25), + MT_INFORMATION, l3dss1_information}, + {SBIT(10) | SBIT(11) | SBIT(15), + MT_NOTIFY, l3dss1_notify}, + {SBIT(0) | SBIT(1) | SBIT(2) | SBIT(3) | SBIT(4) | SBIT(7) | SBIT(8) | SBIT(10) | + SBIT(11) | SBIT(12) | SBIT(15) | SBIT(17) | SBIT(19) | SBIT(25), + MT_RELEASE_COMPLETE, l3dss1_release_cmpl}, + {SBIT(1) | SBIT(2) | SBIT(3) | SBIT(4) | SBIT(7) | SBIT(8) | SBIT(9) | SBIT(10) | SBIT(11) | SBIT(12) | SBIT(15) | SBIT(17) | SBIT(25), + MT_RELEASE, l3dss1_release}, + {SBIT(19), MT_RELEASE, l3dss1_release_ind}, + {SBIT(1) | SBIT(2) | SBIT(3) | SBIT(4) | SBIT(7) | SBIT(8) | SBIT(9) | SBIT(10) | SBIT(11) | SBIT(15) | SBIT(17) | SBIT(25), + MT_DISCONNECT, l3dss1_disconnect}, + {SBIT(19), + MT_DISCONNECT, l3dss1_dummy}, + {SBIT(1) | SBIT(2) | SBIT(3) | SBIT(4), + MT_CONNECT, l3dss1_connect}, + {SBIT(8), + MT_CONNECT_ACKNOWLEDGE, l3dss1_connect_ack}, + {SBIT(15), + MT_SUSPEND_ACKNOWLEDGE, l3dss1_suspend_ack}, + {SBIT(15), + MT_SUSPEND_REJECT, l3dss1_suspend_rej}, + {SBIT(17), + MT_RESUME_ACKNOWLEDGE, l3dss1_resume_ack}, + {SBIT(17), + MT_RESUME_REJECT, l3dss1_resume_rej}, +}; + +static struct stateentry globalmes_list[] = +{ + {ALL_STATES, + MT_STATUS, l3dss1_status}, + {SBIT(0), + MT_RESTART, l3dss1_global_restart}, +/* {SBIT(1), + MT_RESTART_ACKNOWLEDGE, l3dss1_restart_ack}, +*/ +}; + +static struct stateentry manstatelist[] = +{ + {SBIT(2), + DL_ESTABLISH | INDICATION, l3dss1_dl_reset}, + {SBIT(10), + DL_ESTABLISH | CONFIRM, l3dss1_dl_reest_status}, + {SBIT(10), + DL_RELEASE | INDICATION, l3dss1_dl_reestablish}, + {ALL_STATES, + DL_RELEASE | INDICATION, l3dss1_dl_release}, +}; + +/* *INDENT-ON* */ + + +static void +global_handler(struct PStack *st, int mt, struct sk_buff *skb) +{ + u_char tmp[16]; + u_char *p = tmp; + int l; + int i; + struct l3_process *proc = st->l3.global; + + proc->callref = skb->data[2]; /* cr flag */ + for (i = 0; i < ARRAY_SIZE(globalmes_list); i++) + if ((mt == globalmes_list[i].primitive) && + ((1 << proc->state) & globalmes_list[i].state)) + break; + if (i == ARRAY_SIZE(globalmes_list)) { + if (st->l3.debug & L3_DEB_STATE) { + l3_debug(st, "dss1 global state %d mt %x unhandled", + proc->state, mt); + } + MsgHead(p, proc->callref, MT_STATUS); + *p++ = IE_CAUSE; + *p++ = 0x2; + *p++ = 0x80; + *p++ = 81 | 0x80; /* invalid cr */ + *p++ = 0x14; /* CallState */ + *p++ = 0x1; + *p++ = proc->state & 0x3f; + l = p - tmp; + if (!(skb = l3_alloc_skb(l))) + return; + skb_put_data(skb, tmp, l); + l3_msg(proc->st, DL_DATA | REQUEST, skb); + } else { + if (st->l3.debug & L3_DEB_STATE) { + l3_debug(st, "dss1 global %d mt %x", + proc->state, mt); + } + globalmes_list[i].rout(proc, mt, skb); + } +} + +static void +dss1up(struct PStack *st, int pr, void *arg) +{ + int i, mt, cr, callState; + char *ptr; + u_char *p; + struct sk_buff *skb = arg; + struct l3_process *proc; + + switch (pr) { + case (DL_DATA | INDICATION): + case (DL_UNIT_DATA | INDICATION): + break; + case (DL_ESTABLISH | CONFIRM): + case (DL_ESTABLISH | INDICATION): + case (DL_RELEASE | INDICATION): + case (DL_RELEASE | CONFIRM): + l3_msg(st, pr, arg); + return; + break; + default: + printk(KERN_ERR "HiSax dss1up unknown pr=%04x\n", pr); + return; + } + if (skb->len < 3) { + l3_debug(st, "dss1up frame too short(%d)", skb->len); + dev_kfree_skb(skb); + return; + } + + if (skb->data[0] != PROTO_DIS_EURO) { + if (st->l3.debug & L3_DEB_PROTERR) { + l3_debug(st, "dss1up%sunexpected discriminator %x message len %d", + (pr == (DL_DATA | INDICATION)) ? " " : "(broadcast) ", + skb->data[0], skb->len); + } + dev_kfree_skb(skb); + return; + } + cr = getcallref(skb->data); + if (skb->len < ((skb->data[1] & 0x0f) + 3)) { + l3_debug(st, "dss1up frame too short(%d)", skb->len); + dev_kfree_skb(skb); + return; + } + mt = skb->data[skb->data[1] + 2]; + if (st->l3.debug & L3_DEB_STATE) + l3_debug(st, "dss1up cr %d", cr); + if (cr == -2) { /* wrong Callref */ + if (st->l3.debug & L3_DEB_WARN) + l3_debug(st, "dss1up wrong Callref"); + dev_kfree_skb(skb); + return; + } else if (cr == -1) { /* Dummy Callref */ + if (mt == MT_FACILITY) + if ((p = findie(skb->data, skb->len, IE_FACILITY, 0))) { + l3dss1_parse_facility(st, NULL, + (pr == (DL_DATA | INDICATION)) ? -1 : -2, p); + dev_kfree_skb(skb); + return; + } + if (st->l3.debug & L3_DEB_WARN) + l3_debug(st, "dss1up dummy Callref (no facility msg or ie)"); + dev_kfree_skb(skb); + return; + } else if ((((skb->data[1] & 0x0f) == 1) && (0 == (cr & 0x7f))) || + (((skb->data[1] & 0x0f) == 2) && (0 == (cr & 0x7fff)))) { /* Global CallRef */ + if (st->l3.debug & L3_DEB_STATE) + l3_debug(st, "dss1up Global CallRef"); + global_handler(st, mt, skb); + dev_kfree_skb(skb); + return; + } else if (!(proc = getl3proc(st, cr))) { + /* No transaction process exist, that means no call with + * this callreference is active + */ + if (mt == MT_SETUP) { + /* Setup creates a new transaction process */ + if (skb->data[2] & 0x80) { + /* Setup with wrong CREF flag */ + if (st->l3.debug & L3_DEB_STATE) + l3_debug(st, "dss1up wrong CRef flag"); + dev_kfree_skb(skb); + return; + } + if (!(proc = dss1_new_l3_process(st, cr))) { + /* May be to answer with RELEASE_COMPLETE and + * CAUSE 0x2f "Resource unavailable", but this + * need a new_l3_process too ... arghh + */ + dev_kfree_skb(skb); + return; + } + } else if (mt == MT_STATUS) { + if ((ptr = findie(skb->data, skb->len, IE_CAUSE, 0)) != NULL) { + ptr++; + if (*ptr++ == 2) + ptr++; + } + callState = 0; + if ((ptr = findie(skb->data, skb->len, IE_CALL_STATE, 0)) != NULL) { + ptr++; + if (*ptr++ == 2) + ptr++; + callState = *ptr; + } + /* ETS 300-104 part 2.4.1 + * if setup has not been made and a message type + * MT_STATUS is received with call state == 0, + * we must send nothing + */ + if (callState != 0) { + /* ETS 300-104 part 2.4.2 + * if setup has not been made and a message type + * MT_STATUS is received with call state != 0, + * we must send MT_RELEASE_COMPLETE cause 101 + */ + if ((proc = dss1_new_l3_process(st, cr))) { + proc->para.cause = 101; + l3dss1_msg_without_setup(proc, 0, NULL); + } + } + dev_kfree_skb(skb); + return; + } else if (mt == MT_RELEASE_COMPLETE) { + dev_kfree_skb(skb); + return; + } else { + /* ETS 300-104 part 2 + * if setup has not been made and a message type + * (except MT_SETUP and RELEASE_COMPLETE) is received, + * we must send MT_RELEASE_COMPLETE cause 81 */ + dev_kfree_skb(skb); + if ((proc = dss1_new_l3_process(st, cr))) { + proc->para.cause = 81; + l3dss1_msg_without_setup(proc, 0, NULL); + } + return; + } + } + if (l3dss1_check_messagetype_validity(proc, mt, skb)) { + dev_kfree_skb(skb); + return; + } + if ((p = findie(skb->data, skb->len, IE_DISPLAY, 0)) != NULL) + l3dss1_deliver_display(proc, pr, p); /* Display IE included */ + for (i = 0; i < ARRAY_SIZE(datastatelist); i++) + if ((mt == datastatelist[i].primitive) && + ((1 << proc->state) & datastatelist[i].state)) + break; + if (i == ARRAY_SIZE(datastatelist)) { + if (st->l3.debug & L3_DEB_STATE) { + l3_debug(st, "dss1up%sstate %d mt %#x unhandled", + (pr == (DL_DATA | INDICATION)) ? " " : "(broadcast) ", + proc->state, mt); + } + if ((MT_RELEASE_COMPLETE != mt) && (MT_RELEASE != mt)) { + proc->para.cause = 101; + l3dss1_status_send(proc, pr, skb); + } + } else { + if (st->l3.debug & L3_DEB_STATE) { + l3_debug(st, "dss1up%sstate %d mt %x", + (pr == (DL_DATA | INDICATION)) ? " " : "(broadcast) ", + proc->state, mt); + } + datastatelist[i].rout(proc, pr, skb); + } + dev_kfree_skb(skb); + return; +} + +static void +dss1down(struct PStack *st, int pr, void *arg) +{ + int i, cr; + struct l3_process *proc; + struct Channel *chan; + + if ((DL_ESTABLISH | REQUEST) == pr) { + l3_msg(st, pr, NULL); + return; + } else if (((CC_SETUP | REQUEST) == pr) || ((CC_RESUME | REQUEST) == pr)) { + chan = arg; + cr = newcallref(); + cr |= 0x80; + if ((proc = dss1_new_l3_process(st, cr))) { + proc->chan = chan; + chan->proc = proc; + memcpy(&proc->para.setup, &chan->setup, sizeof(setup_parm)); + proc->callref = cr; + } + } else { + proc = arg; + } + if (!proc) { + printk(KERN_ERR "HiSax dss1down without proc pr=%04x\n", pr); + return; + } + + if (pr == (CC_TDSS1_IO | REQUEST)) { + l3dss1_io_timer(proc); /* timer expires */ + return; + } + + for (i = 0; i < ARRAY_SIZE(downstatelist); i++) + if ((pr == downstatelist[i].primitive) && + ((1 << proc->state) & downstatelist[i].state)) + break; + if (i == ARRAY_SIZE(downstatelist)) { + if (st->l3.debug & L3_DEB_STATE) { + l3_debug(st, "dss1down state %d prim %#x unhandled", + proc->state, pr); + } + } else { + if (st->l3.debug & L3_DEB_STATE) { + l3_debug(st, "dss1down state %d prim %#x", + proc->state, pr); + } + downstatelist[i].rout(proc, pr, arg); + } +} + +static void +dss1man(struct PStack *st, int pr, void *arg) +{ + int i; + struct l3_process *proc = arg; + + if (!proc) { + printk(KERN_ERR "HiSax dss1man without proc pr=%04x\n", pr); + return; + } + for (i = 0; i < ARRAY_SIZE(manstatelist); i++) + if ((pr == manstatelist[i].primitive) && + ((1 << proc->state) & manstatelist[i].state)) + break; + if (i == ARRAY_SIZE(manstatelist)) { + if (st->l3.debug & L3_DEB_STATE) { + l3_debug(st, "cr %d dss1man state %d prim %#x unhandled", + proc->callref & 0x7f, proc->state, pr); + } + } else { + if (st->l3.debug & L3_DEB_STATE) { + l3_debug(st, "cr %d dss1man state %d prim %#x", + proc->callref & 0x7f, proc->state, pr); + } + manstatelist[i].rout(proc, pr, arg); + } +} + +void +setstack_dss1(struct PStack *st) +{ + char tmp[64]; + int i; + + st->lli.l4l3 = dss1down; + st->lli.l4l3_proto = l3dss1_cmd_global; + st->l2.l2l3 = dss1up; + st->l3.l3ml3 = dss1man; + st->l3.N303 = 1; + st->prot.dss1.last_invoke_id = 0; + st->prot.dss1.invoke_used[0] = 1; /* Bit 0 must always be set to 1 */ + i = 1; + while (i < 32) + st->prot.dss1.invoke_used[i++] = 0; + + if (!(st->l3.global = kmalloc(sizeof(struct l3_process), GFP_ATOMIC))) { + printk(KERN_ERR "HiSax can't get memory for dss1 global CR\n"); + } else { + st->l3.global->state = 0; + st->l3.global->callref = 0; + st->l3.global->next = NULL; + st->l3.global->debug = L3_DEB_WARN; + st->l3.global->st = st; + st->l3.global->N303 = 1; + st->l3.global->prot.dss1.invoke_id = 0; + + L3InitTimer(st->l3.global, &st->l3.global->timer); + } + strcpy(tmp, dss1_revision); + printk(KERN_INFO "HiSax: DSS1 Rev. %s\n", HiSax_getrev(tmp)); +} diff --git a/drivers/isdn/hisax/l3dss1.h b/drivers/isdn/hisax/l3dss1.h new file mode 100644 index 000000000..a7807e8a9 --- /dev/null +++ b/drivers/isdn/hisax/l3dss1.h @@ -0,0 +1,124 @@ +/* $Id: l3dss1.h,v 1.10.6.2 2001/09/23 22:24:50 kai Exp $ + * + * DSS1 (Euro) D-channel protocol defines + * + * This software may be used and distributed according to the terms + * of the GNU General Public License, incorporated herein by reference. + * + */ + +#ifndef l3dss1_process + +#define T302 15000 +#define T303 4000 +#define T304 30000 +#define T305 30000 +#define T308 4000 +/* for layer 1 certification T309 < layer1 T3 (e.g. 4000) */ +/* This makes some tests easier and quicker */ +#define T309 40000 +#define T310 30000 +#define T313 4000 +#define T318 4000 +#define T319 4000 + +/* + * Message-Types + */ + +#define MT_ALERTING 0x01 +#define MT_CALL_PROCEEDING 0x02 +#define MT_CONNECT 0x07 +#define MT_CONNECT_ACKNOWLEDGE 0x0f +#define MT_PROGRESS 0x03 +#define MT_SETUP 0x05 +#define MT_SETUP_ACKNOWLEDGE 0x0d +#define MT_RESUME 0x26 +#define MT_RESUME_ACKNOWLEDGE 0x2e +#define MT_RESUME_REJECT 0x22 +#define MT_SUSPEND 0x25 +#define MT_SUSPEND_ACKNOWLEDGE 0x2d +#define MT_SUSPEND_REJECT 0x21 +#define MT_USER_INFORMATION 0x20 +#define MT_DISCONNECT 0x45 +#define MT_RELEASE 0x4d +#define MT_RELEASE_COMPLETE 0x5a +#define MT_RESTART 0x46 +#define MT_RESTART_ACKNOWLEDGE 0x4e +#define MT_SEGMENT 0x60 +#define MT_CONGESTION_CONTROL 0x79 +#define MT_INFORMATION 0x7b +#define MT_FACILITY 0x62 +#define MT_NOTIFY 0x6e +#define MT_STATUS 0x7d +#define MT_STATUS_ENQUIRY 0x75 + +#define IE_SEGMENT 0x00 +#define IE_BEARER 0x04 +#define IE_CAUSE 0x08 +#define IE_CALL_ID 0x10 +#define IE_CALL_STATE 0x14 +#define IE_CHANNEL_ID 0x18 +#define IE_FACILITY 0x1c +#define IE_PROGRESS 0x1e +#define IE_NET_FAC 0x20 +#define IE_NOTIFY 0x27 +#define IE_DISPLAY 0x28 +#define IE_DATE 0x29 +#define IE_KEYPAD 0x2c +#define IE_SIGNAL 0x34 +#define IE_INFORATE 0x40 +#define IE_E2E_TDELAY 0x42 +#define IE_TDELAY_SEL 0x43 +#define IE_PACK_BINPARA 0x44 +#define IE_PACK_WINSIZE 0x45 +#define IE_PACK_SIZE 0x46 +#define IE_CUG 0x47 +#define IE_REV_CHARGE 0x4a +#define IE_CONNECT_PN 0x4c +#define IE_CONNECT_SUB 0x4d +#define IE_CALLING_PN 0x6c +#define IE_CALLING_SUB 0x6d +#define IE_CALLED_PN 0x70 +#define IE_CALLED_SUB 0x71 +#define IE_REDIR_NR 0x74 +#define IE_TRANS_SEL 0x78 +#define IE_RESTART_IND 0x79 +#define IE_LLC 0x7c +#define IE_HLC 0x7d +#define IE_USER_USER 0x7e +#define IE_ESCAPE 0x7f +#define IE_SHIFT 0x90 +#define IE_MORE_DATA 0xa0 +#define IE_COMPLETE 0xa1 +#define IE_CONGESTION 0xb0 +#define IE_REPEAT 0xd0 + +#define IE_MANDATORY 0x0100 +/* mandatory not in every case */ +#define IE_MANDATORY_1 0x0200 + +#define ERR_IE_COMPREHENSION 1 +#define ERR_IE_UNRECOGNIZED -1 +#define ERR_IE_LENGTH -2 +#define ERR_IE_SEQUENCE -3 + +#else /* only l3dss1_process */ + +/* l3dss1 specific data in l3 process */ +typedef struct +{ unsigned char invoke_id; /* used invoke id in remote ops, 0 = not active */ + ulong ll_id; /* remebered ll id */ + u8 remote_operation; /* handled remote operation, 0 = not active */ + int proc; /* rememered procedure */ + ulong remote_result; /* result of remote operation for statcallb */ + char uus1_data[35]; /* data send during alerting or disconnect */ +} dss1_proc_priv; + +/* l3dss1 specific data in protocol stack */ +typedef struct +{ unsigned char last_invoke_id; /* last used value for invoking */ + unsigned char invoke_used[32]; /* 256 bits for 256 values */ +} dss1_stk_priv; + +#endif /* only l3dss1_process */ diff --git a/drivers/isdn/hisax/l3ni1.c b/drivers/isdn/hisax/l3ni1.c new file mode 100644 index 000000000..ea311e7df --- /dev/null +++ b/drivers/isdn/hisax/l3ni1.c @@ -0,0 +1,3182 @@ +/* $Id: l3ni1.c,v 2.8.2.3 2004/01/13 14:31:25 keil Exp $ + * + * NI1 D-channel protocol + * + * Author Matt Henderson & Guy Ellis + * Copyright by Traverse Technologies Pty Ltd, www.travers.com.au + * + * This software may be used and distributed according to the terms + * of the GNU General Public License, incorporated herein by reference. + * + * 2000.6.6 Initial implementation of routines for US NI1 + * Layer 3 protocol based on the EURO/DSS1 D-channel protocol + * driver written by Karsten Keil et al. + * NI-1 Hall of Fame - Thanks to.... + * Ragnar Paulson - for some handy code fragments + * Will Scales - beta tester extraordinaire + * Brett Whittacre - beta tester and remote devel system in Vegas + * + */ + +#include "hisax.h" +#include "isdnl3.h" +#include "l3ni1.h" +#include <linux/ctype.h> +#include <linux/slab.h> + +extern char *HiSax_getrev(const char *revision); +static const char *ni1_revision = "$Revision: 2.8.2.3 $"; + +#define EXT_BEARER_CAPS 1 + +#define MsgHead(ptr, cref, mty) \ + *ptr++ = 0x8; \ + if (cref == -1) { \ + *ptr++ = 0x0; \ + } else { \ + *ptr++ = 0x1; \ + *ptr++ = cref^0x80; \ + } \ + *ptr++ = mty + + +/**********************************************/ +/* get a new invoke id for remote operations. */ +/* Only a return value != 0 is valid */ +/**********************************************/ +static unsigned char new_invoke_id(struct PStack *p) +{ + unsigned char retval; + int i; + + i = 32; /* maximum search depth */ + + retval = p->prot.ni1.last_invoke_id + 1; /* try new id */ + while ((i) && (p->prot.ni1.invoke_used[retval >> 3] == 0xFF)) { + p->prot.ni1.last_invoke_id = (retval & 0xF8) + 8; + i--; + } + if (i) { + while (p->prot.ni1.invoke_used[retval >> 3] & (1 << (retval & 7))) + retval++; + } else + retval = 0; + p->prot.ni1.last_invoke_id = retval; + p->prot.ni1.invoke_used[retval >> 3] |= (1 << (retval & 7)); + return (retval); +} /* new_invoke_id */ + +/*************************/ +/* free a used invoke id */ +/*************************/ +static void free_invoke_id(struct PStack *p, unsigned char id) +{ + + if (!id) return; /* 0 = invalid value */ + + p->prot.ni1.invoke_used[id >> 3] &= ~(1 << (id & 7)); +} /* free_invoke_id */ + + +/**********************************************************/ +/* create a new l3 process and fill in ni1 specific data */ +/**********************************************************/ +static struct l3_process +*ni1_new_l3_process(struct PStack *st, int cr) +{ struct l3_process *proc; + + if (!(proc = new_l3_process(st, cr))) + return (NULL); + + proc->prot.ni1.invoke_id = 0; + proc->prot.ni1.remote_operation = 0; + proc->prot.ni1.uus1_data[0] = '\0'; + + return (proc); +} /* ni1_new_l3_process */ + +/************************************************/ +/* free a l3 process and all ni1 specific data */ +/************************************************/ +static void +ni1_release_l3_process(struct l3_process *p) +{ + free_invoke_id(p->st, p->prot.ni1.invoke_id); + release_l3_process(p); +} /* ni1_release_l3_process */ + +/********************************************************/ +/* search a process with invoke id id and dummy callref */ +/********************************************************/ +static struct l3_process * +l3ni1_search_dummy_proc(struct PStack *st, int id) +{ struct l3_process *pc = st->l3.proc; /* start of processes */ + + if (!id) return (NULL); + + while (pc) + { if ((pc->callref == -1) && (pc->prot.ni1.invoke_id == id)) + return (pc); + pc = pc->next; + } + return (NULL); +} /* l3ni1_search_dummy_proc */ + +/*******************************************************************/ +/* called when a facility message with a dummy callref is received */ +/* and a return result is delivered. id specifies the invoke id. */ +/*******************************************************************/ +static void +l3ni1_dummy_return_result(struct PStack *st, int id, u_char *p, u_char nlen) +{ isdn_ctrl ic; + struct IsdnCardState *cs; + struct l3_process *pc = NULL; + + if ((pc = l3ni1_search_dummy_proc(st, id))) + { L3DelTimer(&pc->timer); /* remove timer */ + + cs = pc->st->l1.hardware; + ic.driver = cs->myid; + ic.command = ISDN_STAT_PROT; + ic.arg = NI1_STAT_INVOKE_RES; + ic.parm.ni1_io.hl_id = pc->prot.ni1.invoke_id; + ic.parm.ni1_io.ll_id = pc->prot.ni1.ll_id; + ic.parm.ni1_io.proc = pc->prot.ni1.proc; + ic.parm.ni1_io.timeout = 0; + ic.parm.ni1_io.datalen = nlen; + ic.parm.ni1_io.data = p; + free_invoke_id(pc->st, pc->prot.ni1.invoke_id); + pc->prot.ni1.invoke_id = 0; /* reset id */ + + cs->iif.statcallb(&ic); + ni1_release_l3_process(pc); + } + else + l3_debug(st, "dummy return result id=0x%x result len=%d", id, nlen); +} /* l3ni1_dummy_return_result */ + +/*******************************************************************/ +/* called when a facility message with a dummy callref is received */ +/* and a return error is delivered. id specifies the invoke id. */ +/*******************************************************************/ +static void +l3ni1_dummy_error_return(struct PStack *st, int id, ulong error) +{ isdn_ctrl ic; + struct IsdnCardState *cs; + struct l3_process *pc = NULL; + + if ((pc = l3ni1_search_dummy_proc(st, id))) + { L3DelTimer(&pc->timer); /* remove timer */ + + cs = pc->st->l1.hardware; + ic.driver = cs->myid; + ic.command = ISDN_STAT_PROT; + ic.arg = NI1_STAT_INVOKE_ERR; + ic.parm.ni1_io.hl_id = pc->prot.ni1.invoke_id; + ic.parm.ni1_io.ll_id = pc->prot.ni1.ll_id; + ic.parm.ni1_io.proc = pc->prot.ni1.proc; + ic.parm.ni1_io.timeout = error; + ic.parm.ni1_io.datalen = 0; + ic.parm.ni1_io.data = NULL; + free_invoke_id(pc->st, pc->prot.ni1.invoke_id); + pc->prot.ni1.invoke_id = 0; /* reset id */ + + cs->iif.statcallb(&ic); + ni1_release_l3_process(pc); + } + else + l3_debug(st, "dummy return error id=0x%x error=0x%lx", id, error); +} /* l3ni1_error_return */ + +/*******************************************************************/ +/* called when a facility message with a dummy callref is received */ +/* and a invoke is delivered. id specifies the invoke id. */ +/*******************************************************************/ +static void +l3ni1_dummy_invoke(struct PStack *st, int cr, int id, + int ident, u_char *p, u_char nlen) +{ isdn_ctrl ic; + struct IsdnCardState *cs; + + l3_debug(st, "dummy invoke %s id=0x%x ident=0x%x datalen=%d", + (cr == -1) ? "local" : "broadcast", id, ident, nlen); + if (cr >= -1) return; /* ignore local data */ + + cs = st->l1.hardware; + ic.driver = cs->myid; + ic.command = ISDN_STAT_PROT; + ic.arg = NI1_STAT_INVOKE_BRD; + ic.parm.ni1_io.hl_id = id; + ic.parm.ni1_io.ll_id = 0; + ic.parm.ni1_io.proc = ident; + ic.parm.ni1_io.timeout = 0; + ic.parm.ni1_io.datalen = nlen; + ic.parm.ni1_io.data = p; + + cs->iif.statcallb(&ic); +} /* l3ni1_dummy_invoke */ + +static void +l3ni1_parse_facility(struct PStack *st, struct l3_process *pc, + int cr, u_char *p) +{ + int qd_len = 0; + unsigned char nlen = 0, ilen, cp_tag; + int ident, id; + ulong err_ret; + + if (pc) + st = pc->st; /* valid Stack */ + else + if ((!st) || (cr >= 0)) return; /* neither pc nor st specified */ + + p++; + qd_len = *p++; + if (qd_len == 0) { + l3_debug(st, "qd_len == 0"); + return; + } + if ((*p & 0x1F) != 0x11) { /* Service discriminator, supplementary service */ + l3_debug(st, "supplementary service != 0x11"); + return; + } + while (qd_len > 0 && !(*p & 0x80)) { /* extension ? */ + p++; + qd_len--; + } + if (qd_len < 2) { + l3_debug(st, "qd_len < 2"); + return; + } + p++; + qd_len--; + if ((*p & 0xE0) != 0xA0) { /* class and form */ + l3_debug(st, "class and form != 0xA0"); + return; + } + + cp_tag = *p & 0x1F; /* remember tag value */ + + p++; + qd_len--; + if (qd_len < 1) + { l3_debug(st, "qd_len < 1"); + return; + } + if (*p & 0x80) + { /* length format indefinite or limited */ + nlen = *p++ & 0x7F; /* number of len bytes or indefinite */ + if ((qd_len-- < ((!nlen) ? 3 : (1 + nlen))) || + (nlen > 1)) + { l3_debug(st, "length format error or not implemented"); + return; + } + if (nlen == 1) + { nlen = *p++; /* complete length */ + qd_len--; + } + else + { qd_len -= 2; /* trailing null bytes */ + if ((*(p + qd_len)) || (*(p + qd_len + 1))) + { l3_debug(st, "length format indefinite error"); + return; + } + nlen = qd_len; + } + } + else + { nlen = *p++; + qd_len--; + } + if (qd_len < nlen) + { l3_debug(st, "qd_len < nlen"); + return; + } + qd_len -= nlen; + + if (nlen < 2) + { l3_debug(st, "nlen < 2"); + return; + } + if (*p != 0x02) + { /* invoke identifier tag */ + l3_debug(st, "invoke identifier tag !=0x02"); + return; + } + p++; + nlen--; + if (*p & 0x80) + { /* length format */ + l3_debug(st, "invoke id length format 2"); + return; + } + ilen = *p++; + nlen--; + if (ilen > nlen || ilen == 0) + { l3_debug(st, "ilen > nlen || ilen == 0"); + return; + } + nlen -= ilen; + id = 0; + while (ilen > 0) + { id = (id << 8) | (*p++ & 0xFF); /* invoke identifier */ + ilen--; + } + + switch (cp_tag) { /* component tag */ + case 1: /* invoke */ + if (nlen < 2) { + l3_debug(st, "nlen < 2 22"); + return; + } + if (*p != 0x02) { /* operation value */ + l3_debug(st, "operation value !=0x02"); + return; + } + p++; + nlen--; + ilen = *p++; + nlen--; + if (ilen > nlen || ilen == 0) { + l3_debug(st, "ilen > nlen || ilen == 0 22"); + return; + } + nlen -= ilen; + ident = 0; + while (ilen > 0) { + ident = (ident << 8) | (*p++ & 0xFF); + ilen--; + } + + if (!pc) + { + l3ni1_dummy_invoke(st, cr, id, ident, p, nlen); + return; + } + l3_debug(st, "invoke break"); + break; + case 2: /* return result */ + /* if no process available handle separately */ + if (!pc) + { if (cr == -1) + l3ni1_dummy_return_result(st, id, p, nlen); + return; + } + if ((pc->prot.ni1.invoke_id) && (pc->prot.ni1.invoke_id == id)) + { /* Diversion successful */ + free_invoke_id(st, pc->prot.ni1.invoke_id); + pc->prot.ni1.remote_result = 0; /* success */ + pc->prot.ni1.invoke_id = 0; + pc->redir_result = pc->prot.ni1.remote_result; + st->l3.l3l4(st, CC_REDIR | INDICATION, pc); } /* Diversion successful */ + else + l3_debug(st, "return error unknown identifier"); + break; + case 3: /* return error */ + err_ret = 0; + if (nlen < 2) + { l3_debug(st, "return error nlen < 2"); + return; + } + if (*p != 0x02) + { /* result tag */ + l3_debug(st, "invoke error tag !=0x02"); + return; + } + p++; + nlen--; + if (*p > 4) + { /* length format */ + l3_debug(st, "invoke return errlen > 4 "); + return; + } + ilen = *p++; + nlen--; + if (ilen > nlen || ilen == 0) + { l3_debug(st, "error return ilen > nlen || ilen == 0"); + return; + } + nlen -= ilen; + while (ilen > 0) + { err_ret = (err_ret << 8) | (*p++ & 0xFF); /* error value */ + ilen--; + } + /* if no process available handle separately */ + if (!pc) + { if (cr == -1) + l3ni1_dummy_error_return(st, id, err_ret); + return; + } + if ((pc->prot.ni1.invoke_id) && (pc->prot.ni1.invoke_id == id)) + { /* Deflection error */ + free_invoke_id(st, pc->prot.ni1.invoke_id); + pc->prot.ni1.remote_result = err_ret; /* result */ + pc->prot.ni1.invoke_id = 0; + pc->redir_result = pc->prot.ni1.remote_result; + st->l3.l3l4(st, CC_REDIR | INDICATION, pc); + } /* Deflection error */ + else + l3_debug(st, "return result unknown identifier"); + break; + default: + l3_debug(st, "facility default break tag=0x%02x", cp_tag); + break; + } +} + +static void +l3ni1_message(struct l3_process *pc, u_char mt) +{ + struct sk_buff *skb; + u_char *p; + + if (!(skb = l3_alloc_skb(4))) + return; + p = skb_put(skb, 4); + MsgHead(p, pc->callref, mt); + l3_msg(pc->st, DL_DATA | REQUEST, skb); +} + +static void +l3ni1_message_plus_chid(struct l3_process *pc, u_char mt) +/* sends an l3 messages plus channel id - added GE 05/09/00 */ +{ + struct sk_buff *skb; + u_char tmp[16]; + u_char *p = tmp; + u_char chid; + + chid = (u_char)(pc->para.bchannel & 0x03) | 0x88; + MsgHead(p, pc->callref, mt); + *p++ = IE_CHANNEL_ID; + *p++ = 0x01; + *p++ = chid; + + if (!(skb = l3_alloc_skb(7))) + return; + skb_put_data(skb, tmp, 7); + l3_msg(pc->st, DL_DATA | REQUEST, skb); +} + +static void +l3ni1_message_cause(struct l3_process *pc, u_char mt, u_char cause) +{ + struct sk_buff *skb; + u_char tmp[16]; + u_char *p = tmp; + int l; + + MsgHead(p, pc->callref, mt); + *p++ = IE_CAUSE; + *p++ = 0x2; + *p++ = 0x80; + *p++ = cause | 0x80; + + l = p - tmp; + if (!(skb = l3_alloc_skb(l))) + return; + skb_put_data(skb, tmp, l); + l3_msg(pc->st, DL_DATA | REQUEST, skb); +} + +static void +l3ni1_status_send(struct l3_process *pc, u_char pr, void *arg) +{ + u_char tmp[16]; + u_char *p = tmp; + int l; + struct sk_buff *skb; + + MsgHead(p, pc->callref, MT_STATUS); + + *p++ = IE_CAUSE; + *p++ = 0x2; + *p++ = 0x80; + *p++ = pc->para.cause | 0x80; + + *p++ = IE_CALL_STATE; + *p++ = 0x1; + *p++ = pc->state & 0x3f; + + l = p - tmp; + if (!(skb = l3_alloc_skb(l))) + return; + skb_put_data(skb, tmp, l); + l3_msg(pc->st, DL_DATA | REQUEST, skb); +} + +static void +l3ni1_msg_without_setup(struct l3_process *pc, u_char pr, void *arg) +{ + /* This routine is called if here was no SETUP made (checks in ni1up and in + * l3ni1_setup) and a RELEASE_COMPLETE have to be sent with an error code + * MT_STATUS_ENQUIRE in the NULL state is handled too + */ + u_char tmp[16]; + u_char *p = tmp; + int l; + struct sk_buff *skb; + + switch (pc->para.cause) { + case 81: /* invalid callreference */ + case 88: /* incomp destination */ + case 96: /* mandory IE missing */ + case 100: /* invalid IE contents */ + case 101: /* incompatible Callstate */ + MsgHead(p, pc->callref, MT_RELEASE_COMPLETE); + *p++ = IE_CAUSE; + *p++ = 0x2; + *p++ = 0x80; + *p++ = pc->para.cause | 0x80; + break; + default: + printk(KERN_ERR "HiSax l3ni1_msg_without_setup wrong cause %d\n", + pc->para.cause); + return; + } + l = p - tmp; + if (!(skb = l3_alloc_skb(l))) + return; + skb_put_data(skb, tmp, l); + l3_msg(pc->st, DL_DATA | REQUEST, skb); + ni1_release_l3_process(pc); +} + +static int ie_ALERTING[] = {IE_BEARER, IE_CHANNEL_ID | IE_MANDATORY_1, + IE_FACILITY, IE_PROGRESS, IE_DISPLAY, IE_SIGNAL, IE_HLC, + IE_USER_USER, -1}; +static int ie_CALL_PROCEEDING[] = {IE_BEARER, IE_CHANNEL_ID | IE_MANDATORY_1, + IE_FACILITY, IE_PROGRESS, IE_DISPLAY, IE_HLC, -1}; +static int ie_CONNECT[] = {IE_BEARER, IE_CHANNEL_ID | IE_MANDATORY_1, + IE_FACILITY, IE_PROGRESS, IE_DISPLAY, IE_DATE, IE_SIGNAL, + IE_CONNECT_PN, IE_CONNECT_SUB, IE_LLC, IE_HLC, IE_USER_USER, -1}; +static int ie_CONNECT_ACKNOWLEDGE[] = {IE_CHANNEL_ID, IE_DISPLAY, IE_SIGNAL, -1}; +static int ie_DISCONNECT[] = {IE_CAUSE | IE_MANDATORY, IE_FACILITY, + IE_PROGRESS, IE_DISPLAY, IE_SIGNAL, IE_USER_USER, -1}; +static int ie_INFORMATION[] = {IE_COMPLETE, IE_DISPLAY, IE_KEYPAD, IE_SIGNAL, + IE_CALLED_PN, -1}; +static int ie_NOTIFY[] = {IE_BEARER, IE_NOTIFY | IE_MANDATORY, IE_DISPLAY, -1}; +static int ie_PROGRESS[] = {IE_BEARER, IE_CAUSE, IE_FACILITY, IE_PROGRESS | + IE_MANDATORY, IE_DISPLAY, IE_HLC, IE_USER_USER, -1}; +static int ie_RELEASE[] = {IE_CAUSE | IE_MANDATORY_1, IE_FACILITY, IE_DISPLAY, + IE_SIGNAL, IE_USER_USER, -1}; +/* a RELEASE_COMPLETE with errors don't require special actions + static int ie_RELEASE_COMPLETE[] = {IE_CAUSE | IE_MANDATORY_1, IE_DISPLAY, IE_SIGNAL, IE_USER_USER, -1}; +*/ +static int ie_RESUME_ACKNOWLEDGE[] = {IE_CHANNEL_ID | IE_MANDATORY, IE_FACILITY, + IE_DISPLAY, -1}; +static int ie_RESUME_REJECT[] = {IE_CAUSE | IE_MANDATORY, IE_DISPLAY, -1}; +static int ie_SETUP[] = {IE_COMPLETE, IE_BEARER | IE_MANDATORY, + IE_CHANNEL_ID | IE_MANDATORY, IE_FACILITY, IE_PROGRESS, + IE_NET_FAC, IE_DISPLAY, IE_KEYPAD, IE_SIGNAL, IE_CALLING_PN, + IE_CALLING_SUB, IE_CALLED_PN, IE_CALLED_SUB, IE_REDIR_NR, + IE_LLC, IE_HLC, IE_USER_USER, -1}; +static int ie_SETUP_ACKNOWLEDGE[] = {IE_CHANNEL_ID | IE_MANDATORY, IE_FACILITY, + IE_PROGRESS, IE_DISPLAY, IE_SIGNAL, -1}; +static int ie_STATUS[] = {IE_CAUSE | IE_MANDATORY, IE_CALL_STATE | + IE_MANDATORY, IE_DISPLAY, -1}; +static int ie_STATUS_ENQUIRY[] = {IE_DISPLAY, -1}; +static int ie_SUSPEND_ACKNOWLEDGE[] = {IE_DISPLAY, IE_FACILITY, -1}; +static int ie_SUSPEND_REJECT[] = {IE_CAUSE | IE_MANDATORY, IE_DISPLAY, -1}; +/* not used + * static int ie_CONGESTION_CONTROL[] = {IE_CONGESTION | IE_MANDATORY, + * IE_CAUSE | IE_MANDATORY, IE_DISPLAY, -1}; + * static int ie_USER_INFORMATION[] = {IE_MORE_DATA, IE_USER_USER | IE_MANDATORY, -1}; + * static int ie_RESTART[] = {IE_CHANNEL_ID, IE_DISPLAY, IE_RESTART_IND | + * IE_MANDATORY, -1}; + */ +static int ie_FACILITY[] = {IE_FACILITY | IE_MANDATORY, IE_DISPLAY, -1}; +static int comp_required[] = {1, 2, 3, 5, 6, 7, 9, 10, 11, 14, 15, -1}; +static int l3_valid_states[] = {0, 1, 2, 3, 4, 6, 7, 8, 9, 10, 11, 12, 15, 17, 19, 25, -1}; + +struct ie_len { + int ie; + int len; +}; + +static +struct ie_len max_ie_len[] = { + {IE_SEGMENT, 4}, + {IE_BEARER, 12}, + {IE_CAUSE, 32}, + {IE_CALL_ID, 10}, + {IE_CALL_STATE, 3}, + {IE_CHANNEL_ID, 34}, + {IE_FACILITY, 255}, + {IE_PROGRESS, 4}, + {IE_NET_FAC, 255}, + {IE_NOTIFY, 3}, + {IE_DISPLAY, 82}, + {IE_DATE, 8}, + {IE_KEYPAD, 34}, + {IE_SIGNAL, 3}, + {IE_INFORATE, 6}, + {IE_E2E_TDELAY, 11}, + {IE_TDELAY_SEL, 5}, + {IE_PACK_BINPARA, 3}, + {IE_PACK_WINSIZE, 4}, + {IE_PACK_SIZE, 4}, + {IE_CUG, 7}, + {IE_REV_CHARGE, 3}, + {IE_CALLING_PN, 24}, + {IE_CALLING_SUB, 23}, + {IE_CALLED_PN, 24}, + {IE_CALLED_SUB, 23}, + {IE_REDIR_NR, 255}, + {IE_TRANS_SEL, 255}, + {IE_RESTART_IND, 3}, + {IE_LLC, 18}, + {IE_HLC, 5}, + {IE_USER_USER, 131}, + {-1, 0}, +}; + +static int +getmax_ie_len(u_char ie) { + int i = 0; + while (max_ie_len[i].ie != -1) { + if (max_ie_len[i].ie == ie) + return (max_ie_len[i].len); + i++; + } + return (255); +} + +static int +ie_in_set(struct l3_process *pc, u_char ie, int *checklist) { + int ret = 1; + + while (*checklist != -1) { + if ((*checklist & 0xff) == ie) { + if (ie & 0x80) + return (-ret); + else + return (ret); + } + ret++; + checklist++; + } + return (0); +} + +static int +check_infoelements(struct l3_process *pc, struct sk_buff *skb, int *checklist) +{ + int *cl = checklist; + u_char mt; + u_char *p, ie; + int l, newpos, oldpos; + int err_seq = 0, err_len = 0, err_compr = 0, err_ureg = 0; + u_char codeset = 0; + u_char old_codeset = 0; + u_char codelock = 1; + + p = skb->data; + /* skip cr */ + p++; + l = (*p++) & 0xf; + p += l; + mt = *p++; + oldpos = 0; + while ((p - skb->data) < skb->len) { + if ((*p & 0xf0) == 0x90) { /* shift codeset */ + old_codeset = codeset; + codeset = *p & 7; + if (*p & 0x08) + codelock = 0; + else + codelock = 1; + if (pc->debug & L3_DEB_CHECK) + l3_debug(pc->st, "check IE shift%scodeset %d->%d", + codelock ? " locking " : " ", old_codeset, codeset); + p++; + continue; + } + if (!codeset) { /* only codeset 0 */ + if ((newpos = ie_in_set(pc, *p, cl))) { + if (newpos > 0) { + if (newpos < oldpos) + err_seq++; + else + oldpos = newpos; + } + } else { + if (ie_in_set(pc, *p, comp_required)) + err_compr++; + else + err_ureg++; + } + } + ie = *p++; + if (ie & 0x80) { + l = 1; + } else { + l = *p++; + p += l; + l += 2; + } + if (!codeset && (l > getmax_ie_len(ie))) + err_len++; + if (!codelock) { + if (pc->debug & L3_DEB_CHECK) + l3_debug(pc->st, "check IE shift back codeset %d->%d", + codeset, old_codeset); + codeset = old_codeset; + codelock = 1; + } + } + if (err_compr | err_ureg | err_len | err_seq) { + if (pc->debug & L3_DEB_CHECK) + l3_debug(pc->st, "check IE MT(%x) %d/%d/%d/%d", + mt, err_compr, err_ureg, err_len, err_seq); + if (err_compr) + return (ERR_IE_COMPREHENSION); + if (err_ureg) + return (ERR_IE_UNRECOGNIZED); + if (err_len) + return (ERR_IE_LENGTH); + if (err_seq) + return (ERR_IE_SEQUENCE); + } + return (0); +} + +/* verify if a message type exists and contain no IE error */ +static int +l3ni1_check_messagetype_validity(struct l3_process *pc, int mt, void *arg) +{ + switch (mt) { + case MT_ALERTING: + case MT_CALL_PROCEEDING: + case MT_CONNECT: + case MT_CONNECT_ACKNOWLEDGE: + case MT_DISCONNECT: + case MT_INFORMATION: + case MT_FACILITY: + case MT_NOTIFY: + case MT_PROGRESS: + case MT_RELEASE: + case MT_RELEASE_COMPLETE: + case MT_SETUP: + case MT_SETUP_ACKNOWLEDGE: + case MT_RESUME_ACKNOWLEDGE: + case MT_RESUME_REJECT: + case MT_SUSPEND_ACKNOWLEDGE: + case MT_SUSPEND_REJECT: + case MT_USER_INFORMATION: + case MT_RESTART: + case MT_RESTART_ACKNOWLEDGE: + case MT_CONGESTION_CONTROL: + case MT_STATUS: + case MT_STATUS_ENQUIRY: + if (pc->debug & L3_DEB_CHECK) + l3_debug(pc->st, "l3ni1_check_messagetype_validity mt(%x) OK", mt); + break; + case MT_RESUME: /* RESUME only in user->net */ + case MT_SUSPEND: /* SUSPEND only in user->net */ + default: + if (pc->debug & (L3_DEB_CHECK | L3_DEB_WARN)) + l3_debug(pc->st, "l3ni1_check_messagetype_validity mt(%x) fail", mt); + pc->para.cause = 97; + l3ni1_status_send(pc, 0, NULL); + return (1); + } + return (0); +} + +static void +l3ni1_std_ie_err(struct l3_process *pc, int ret) { + + if (pc->debug & L3_DEB_CHECK) + l3_debug(pc->st, "check_infoelements ret %d", ret); + switch (ret) { + case 0: + break; + case ERR_IE_COMPREHENSION: + pc->para.cause = 96; + l3ni1_status_send(pc, 0, NULL); + break; + case ERR_IE_UNRECOGNIZED: + pc->para.cause = 99; + l3ni1_status_send(pc, 0, NULL); + break; + case ERR_IE_LENGTH: + pc->para.cause = 100; + l3ni1_status_send(pc, 0, NULL); + break; + case ERR_IE_SEQUENCE: + default: + break; + } +} + +static int +l3ni1_get_channel_id(struct l3_process *pc, struct sk_buff *skb) { + u_char *p; + + p = skb->data; + if ((p = findie(p, skb->len, IE_CHANNEL_ID, 0))) { + p++; + if (*p != 1) { /* len for BRI = 1 */ + if (pc->debug & L3_DEB_WARN) + l3_debug(pc->st, "wrong chid len %d", *p); + return (-2); + } + p++; + if (*p & 0x60) { /* only base rate interface */ + if (pc->debug & L3_DEB_WARN) + l3_debug(pc->st, "wrong chid %x", *p); + return (-3); + } + return (*p & 0x3); + } else + return (-1); +} + +static int +l3ni1_get_cause(struct l3_process *pc, struct sk_buff *skb) { + u_char l, i = 0; + u_char *p; + + p = skb->data; + pc->para.cause = 31; + pc->para.loc = 0; + if ((p = findie(p, skb->len, IE_CAUSE, 0))) { + p++; + l = *p++; + if (l > 30) + return (1); + if (l) { + pc->para.loc = *p++; + l--; + } else { + return (2); + } + if (l && !(pc->para.loc & 0x80)) { + l--; + p++; /* skip recommendation */ + } + if (l) { + pc->para.cause = *p++; + l--; + if (!(pc->para.cause & 0x80)) + return (3); + } else + return (4); + while (l && (i < 6)) { + pc->para.diag[i++] = *p++; + l--; + } + } else + return (-1); + return (0); +} + +static void +l3ni1_msg_with_uus(struct l3_process *pc, u_char cmd) +{ + struct sk_buff *skb; + u_char tmp[16 + 40]; + u_char *p = tmp; + int l; + + MsgHead(p, pc->callref, cmd); + + if (pc->prot.ni1.uus1_data[0]) + { *p++ = IE_USER_USER; /* UUS info element */ + *p++ = strlen(pc->prot.ni1.uus1_data) + 1; + *p++ = 0x04; /* IA5 chars */ + strcpy(p, pc->prot.ni1.uus1_data); + p += strlen(pc->prot.ni1.uus1_data); + pc->prot.ni1.uus1_data[0] = '\0'; + } + + l = p - tmp; + if (!(skb = l3_alloc_skb(l))) + return; + skb_put_data(skb, tmp, l); + l3_msg(pc->st, DL_DATA | REQUEST, skb); +} /* l3ni1_msg_with_uus */ + +static void +l3ni1_release_req(struct l3_process *pc, u_char pr, void *arg) +{ + StopAllL3Timer(pc); + newl3state(pc, 19); + if (!pc->prot.ni1.uus1_data[0]) + l3ni1_message(pc, MT_RELEASE); + else + l3ni1_msg_with_uus(pc, MT_RELEASE); + L3AddTimer(&pc->timer, T308, CC_T308_1); +} + +static void +l3ni1_release_cmpl(struct l3_process *pc, u_char pr, void *arg) +{ + struct sk_buff *skb = arg; + int ret; + + if ((ret = l3ni1_get_cause(pc, skb)) > 0) { + if (pc->debug & L3_DEB_WARN) + l3_debug(pc->st, "RELCMPL get_cause ret(%d)", ret); + } else if (ret < 0) + pc->para.cause = NO_CAUSE; + StopAllL3Timer(pc); + newl3state(pc, 0); + pc->st->l3.l3l4(pc->st, CC_RELEASE | CONFIRM, pc); + ni1_release_l3_process(pc); +} + +#if EXT_BEARER_CAPS + +static u_char * +EncodeASyncParams(u_char *p, u_char si2) +{ // 7c 06 88 90 21 42 00 bb + + p[0] = 0; + p[1] = 0x40; // Intermediate rate: 16 kbit/s jj 2000.02.19 + p[2] = 0x80; + if (si2 & 32) // 7 data bits + + p[2] += 16; + else // 8 data bits + + p[2] += 24; + + if (si2 & 16) // 2 stop bits + + p[2] += 96; + else // 1 stop bit + + p[2] += 32; + + if (si2 & 8) // even parity + + p[2] += 2; + else // no parity + + p[2] += 3; + + switch (si2 & 0x07) { + case 0: + p[0] = 66; // 1200 bit/s + + break; + case 1: + p[0] = 88; // 1200/75 bit/s + + break; + case 2: + p[0] = 87; // 75/1200 bit/s + + break; + case 3: + p[0] = 67; // 2400 bit/s + + break; + case 4: + p[0] = 69; // 4800 bit/s + + break; + case 5: + p[0] = 72; // 9600 bit/s + + break; + case 6: + p[0] = 73; // 14400 bit/s + + break; + case 7: + p[0] = 75; // 19200 bit/s + + break; + } + return p + 3; +} + +static u_char +EncodeSyncParams(u_char si2, u_char ai) +{ + + switch (si2) { + case 0: + return ai + 2; // 1200 bit/s + + case 1: + return ai + 24; // 1200/75 bit/s + + case 2: + return ai + 23; // 75/1200 bit/s + + case 3: + return ai + 3; // 2400 bit/s + + case 4: + return ai + 5; // 4800 bit/s + + case 5: + return ai + 8; // 9600 bit/s + + case 6: + return ai + 9; // 14400 bit/s + + case 7: + return ai + 11; // 19200 bit/s + + case 8: + return ai + 14; // 48000 bit/s + + case 9: + return ai + 15; // 56000 bit/s + + case 15: + return ai + 40; // negotiate bit/s + + default: + break; + } + return ai; +} + + +static u_char +DecodeASyncParams(u_char si2, u_char *p) +{ + u_char info; + + switch (p[5]) { + case 66: // 1200 bit/s + + break; // si2 don't change + + case 88: // 1200/75 bit/s + + si2 += 1; + break; + case 87: // 75/1200 bit/s + + si2 += 2; + break; + case 67: // 2400 bit/s + + si2 += 3; + break; + case 69: // 4800 bit/s + + si2 += 4; + break; + case 72: // 9600 bit/s + + si2 += 5; + break; + case 73: // 14400 bit/s + + si2 += 6; + break; + case 75: // 19200 bit/s + + si2 += 7; + break; + } + + info = p[7] & 0x7f; + if ((info & 16) && (!(info & 8))) // 7 data bits + + si2 += 32; // else 8 data bits + + if ((info & 96) == 96) // 2 stop bits + + si2 += 16; // else 1 stop bit + + if ((info & 2) && (!(info & 1))) // even parity + + si2 += 8; // else no parity + + return si2; +} + + +static u_char +DecodeSyncParams(u_char si2, u_char info) +{ + info &= 0x7f; + switch (info) { + case 40: // bit/s negotiation failed ai := 165 not 175! + + return si2 + 15; + case 15: // 56000 bit/s failed, ai := 0 not 169 ! + + return si2 + 9; + case 14: // 48000 bit/s + + return si2 + 8; + case 11: // 19200 bit/s + + return si2 + 7; + case 9: // 14400 bit/s + + return si2 + 6; + case 8: // 9600 bit/s + + return si2 + 5; + case 5: // 4800 bit/s + + return si2 + 4; + case 3: // 2400 bit/s + + return si2 + 3; + case 23: // 75/1200 bit/s + + return si2 + 2; + case 24: // 1200/75 bit/s + + return si2 + 1; + default: // 1200 bit/s + + return si2; + } +} + +static u_char +DecodeSI2(struct sk_buff *skb) +{ + u_char *p; //, *pend=skb->data + skb->len; + + if ((p = findie(skb->data, skb->len, 0x7c, 0))) { + switch (p[4] & 0x0f) { + case 0x01: + if (p[1] == 0x04) // sync. Bitratenadaption + + return DecodeSyncParams(160, p[5]); // V.110/X.30 + + else if (p[1] == 0x06) // async. Bitratenadaption + + return DecodeASyncParams(192, p); // V.110/X.30 + + break; + case 0x08: // if (p[5] == 0x02) // sync. Bitratenadaption + if (p[1] > 3) + return DecodeSyncParams(176, p[5]); // V.120 + break; + } + } + return 0; +} + +#endif + + +static void +l3ni1_setup_req(struct l3_process *pc, u_char pr, + void *arg) +{ + struct sk_buff *skb; + u_char tmp[128]; + u_char *p = tmp; + + u_char *teln; + u_char *sub; + u_char *sp; + int l; + + MsgHead(p, pc->callref, MT_SETUP); + + teln = pc->para.setup.phone; + + *p++ = 0xa1; /* complete indicator */ + /* + * Set Bearer Capability, Map info from 1TR6-convention to NI1 + */ + switch (pc->para.setup.si1) { + case 1: /* Telephony */ + *p++ = IE_BEARER; + *p++ = 0x3; /* Length */ + *p++ = 0x90; /* 3.1khz Audio */ + *p++ = 0x90; /* Circuit-Mode 64kbps */ + *p++ = 0xa2; /* u-Law Audio */ + break; + case 5: /* Datatransmission 64k, BTX */ + case 7: /* Datatransmission 64k */ + default: + *p++ = IE_BEARER; + *p++ = 0x2; /* Length */ + *p++ = 0x88; /* Coding Std. CCITT, unrestr. dig. Inform. */ + *p++ = 0x90; /* Circuit-Mode 64kbps */ + break; + } + + sub = NULL; + sp = teln; + while (*sp) { + if ('.' == *sp) { + sub = sp; + *sp = 0; + } else + sp++; + } + + *p++ = IE_KEYPAD; + *p++ = strlen(teln); + while (*teln) + *p++ = (*teln++) & 0x7F; + + if (sub) + *sub++ = '.'; + +#if EXT_BEARER_CAPS + if ((pc->para.setup.si2 >= 160) && (pc->para.setup.si2 <= 175)) { // sync. Bitratenadaption, V.110/X.30 + + *p++ = IE_LLC; + *p++ = 0x04; + *p++ = 0x88; + *p++ = 0x90; + *p++ = 0x21; + *p++ = EncodeSyncParams(pc->para.setup.si2 - 160, 0x80); + } else if ((pc->para.setup.si2 >= 176) && (pc->para.setup.si2 <= 191)) { // sync. Bitratenadaption, V.120 + + *p++ = IE_LLC; + *p++ = 0x05; + *p++ = 0x88; + *p++ = 0x90; + *p++ = 0x28; + *p++ = EncodeSyncParams(pc->para.setup.si2 - 176, 0); + *p++ = 0x82; + } else if (pc->para.setup.si2 >= 192) { // async. Bitratenadaption, V.110/X.30 + + *p++ = IE_LLC; + *p++ = 0x06; + *p++ = 0x88; + *p++ = 0x90; + *p++ = 0x21; + p = EncodeASyncParams(p, pc->para.setup.si2 - 192); + } else { + switch (pc->para.setup.si1) { + case 1: /* Telephony */ + *p++ = IE_LLC; + *p++ = 0x3; /* Length */ + *p++ = 0x90; /* Coding Std. CCITT, 3.1 kHz audio */ + *p++ = 0x90; /* Circuit-Mode 64kbps */ + *p++ = 0xa2; /* u-Law Audio */ + break; + case 5: /* Datatransmission 64k, BTX */ + case 7: /* Datatransmission 64k */ + default: + *p++ = IE_LLC; + *p++ = 0x2; /* Length */ + *p++ = 0x88; /* Coding Std. CCITT, unrestr. dig. Inform. */ + *p++ = 0x90; /* Circuit-Mode 64kbps */ + break; + } + } +#endif + l = p - tmp; + if (!(skb = l3_alloc_skb(l))) + { + return; + } + skb_put_data(skb, tmp, l); + L3DelTimer(&pc->timer); + L3AddTimer(&pc->timer, T303, CC_T303); + newl3state(pc, 1); + l3_msg(pc->st, DL_DATA | REQUEST, skb); +} + +static void +l3ni1_call_proc(struct l3_process *pc, u_char pr, void *arg) +{ + struct sk_buff *skb = arg; + int id, ret; + + if ((id = l3ni1_get_channel_id(pc, skb)) >= 0) { + if ((0 == id) || ((3 == id) && (0x10 == pc->para.moderate))) { + if (pc->debug & L3_DEB_WARN) + l3_debug(pc->st, "setup answer with wrong chid %x", id); + pc->para.cause = 100; + l3ni1_status_send(pc, pr, NULL); + return; + } + pc->para.bchannel = id; + } else if (1 == pc->state) { + if (pc->debug & L3_DEB_WARN) + l3_debug(pc->st, "setup answer wrong chid (ret %d)", id); + if (id == -1) + pc->para.cause = 96; + else + pc->para.cause = 100; + l3ni1_status_send(pc, pr, NULL); + return; + } + /* Now we are on none mandatory IEs */ + ret = check_infoelements(pc, skb, ie_CALL_PROCEEDING); + if (ERR_IE_COMPREHENSION == ret) { + l3ni1_std_ie_err(pc, ret); + return; + } + L3DelTimer(&pc->timer); + newl3state(pc, 3); + L3AddTimer(&pc->timer, T310, CC_T310); + if (ret) /* STATUS for none mandatory IE errors after actions are taken */ + l3ni1_std_ie_err(pc, ret); + pc->st->l3.l3l4(pc->st, CC_PROCEEDING | INDICATION, pc); +} + +static void +l3ni1_setup_ack(struct l3_process *pc, u_char pr, void *arg) +{ + struct sk_buff *skb = arg; + int id, ret; + + if ((id = l3ni1_get_channel_id(pc, skb)) >= 0) { + if ((0 == id) || ((3 == id) && (0x10 == pc->para.moderate))) { + if (pc->debug & L3_DEB_WARN) + l3_debug(pc->st, "setup answer with wrong chid %x", id); + pc->para.cause = 100; + l3ni1_status_send(pc, pr, NULL); + return; + } + pc->para.bchannel = id; + } else { + if (pc->debug & L3_DEB_WARN) + l3_debug(pc->st, "setup answer wrong chid (ret %d)", id); + if (id == -1) + pc->para.cause = 96; + else + pc->para.cause = 100; + l3ni1_status_send(pc, pr, NULL); + return; + } + /* Now we are on none mandatory IEs */ + ret = check_infoelements(pc, skb, ie_SETUP_ACKNOWLEDGE); + if (ERR_IE_COMPREHENSION == ret) { + l3ni1_std_ie_err(pc, ret); + return; + } + L3DelTimer(&pc->timer); + newl3state(pc, 2); + L3AddTimer(&pc->timer, T304, CC_T304); + if (ret) /* STATUS for none mandatory IE errors after actions are taken */ + l3ni1_std_ie_err(pc, ret); + pc->st->l3.l3l4(pc->st, CC_MORE_INFO | INDICATION, pc); +} + +static void +l3ni1_disconnect(struct l3_process *pc, u_char pr, void *arg) +{ + struct sk_buff *skb = arg; + u_char *p; + int ret; + u_char cause = 0; + + StopAllL3Timer(pc); + if ((ret = l3ni1_get_cause(pc, skb))) { + if (pc->debug & L3_DEB_WARN) + l3_debug(pc->st, "DISC get_cause ret(%d)", ret); + if (ret < 0) + cause = 96; + else if (ret > 0) + cause = 100; + } + if ((p = findie(skb->data, skb->len, IE_FACILITY, 0))) + l3ni1_parse_facility(pc->st, pc, pc->callref, p); + ret = check_infoelements(pc, skb, ie_DISCONNECT); + if (ERR_IE_COMPREHENSION == ret) + cause = 96; + else if ((!cause) && (ERR_IE_UNRECOGNIZED == ret)) + cause = 99; + ret = pc->state; + newl3state(pc, 12); + if (cause) + newl3state(pc, 19); + if (11 != ret) + pc->st->l3.l3l4(pc->st, CC_DISCONNECT | INDICATION, pc); + else if (!cause) + l3ni1_release_req(pc, pr, NULL); + if (cause) { + l3ni1_message_cause(pc, MT_RELEASE, cause); + L3AddTimer(&pc->timer, T308, CC_T308_1); + } +} + +static void +l3ni1_connect(struct l3_process *pc, u_char pr, void *arg) +{ + struct sk_buff *skb = arg; + int ret; + + ret = check_infoelements(pc, skb, ie_CONNECT); + if (ERR_IE_COMPREHENSION == ret) { + l3ni1_std_ie_err(pc, ret); + return; + } + L3DelTimer(&pc->timer); /* T310 */ + newl3state(pc, 10); + pc->para.chargeinfo = 0; + /* here should inserted COLP handling KKe */ + if (ret) + l3ni1_std_ie_err(pc, ret); + pc->st->l3.l3l4(pc->st, CC_SETUP | CONFIRM, pc); +} + +static void +l3ni1_alerting(struct l3_process *pc, u_char pr, void *arg) +{ + struct sk_buff *skb = arg; + int ret; + + ret = check_infoelements(pc, skb, ie_ALERTING); + if (ERR_IE_COMPREHENSION == ret) { + l3ni1_std_ie_err(pc, ret); + return; + } + L3DelTimer(&pc->timer); /* T304 */ + newl3state(pc, 4); + if (ret) + l3ni1_std_ie_err(pc, ret); + pc->st->l3.l3l4(pc->st, CC_ALERTING | INDICATION, pc); +} + +static void +l3ni1_setup(struct l3_process *pc, u_char pr, void *arg) +{ + u_char *p; + int bcfound = 0; + char tmp[80]; + struct sk_buff *skb = arg; + int id; + int err = 0; + + /* + * Bearer Capabilities + */ + p = skb->data; + /* only the first occurrence 'll be detected ! */ + if ((p = findie(p, skb->len, 0x04, 0))) { + if ((p[1] < 2) || (p[1] > 11)) + err = 1; + else { + pc->para.setup.si2 = 0; + switch (p[2] & 0x7f) { + case 0x00: /* Speech */ + case 0x10: /* 3.1 Khz audio */ + pc->para.setup.si1 = 1; + break; + case 0x08: /* Unrestricted digital information */ + pc->para.setup.si1 = 7; +/* JIM, 05.11.97 I wanna set service indicator 2 */ +#if EXT_BEARER_CAPS + pc->para.setup.si2 = DecodeSI2(skb); +#endif + break; + case 0x09: /* Restricted digital information */ + pc->para.setup.si1 = 2; + break; + case 0x11: + /* Unrestr. digital information with + * tones/announcements ( or 7 kHz audio + */ + pc->para.setup.si1 = 3; + break; + case 0x18: /* Video */ + pc->para.setup.si1 = 4; + break; + default: + err = 2; + break; + } + switch (p[3] & 0x7f) { + case 0x40: /* packed mode */ + pc->para.setup.si1 = 8; + break; + case 0x10: /* 64 kbit */ + case 0x11: /* 2*64 kbit */ + case 0x13: /* 384 kbit */ + case 0x15: /* 1536 kbit */ + case 0x17: /* 1920 kbit */ + pc->para.moderate = p[3] & 0x7f; + break; + default: + err = 3; + break; + } + } + if (pc->debug & L3_DEB_SI) + l3_debug(pc->st, "SI=%d, AI=%d", + pc->para.setup.si1, pc->para.setup.si2); + if (err) { + if (pc->debug & L3_DEB_WARN) + l3_debug(pc->st, "setup with wrong bearer(l=%d:%x,%x)", + p[1], p[2], p[3]); + pc->para.cause = 100; + l3ni1_msg_without_setup(pc, pr, NULL); + return; + } + } else { + if (pc->debug & L3_DEB_WARN) + l3_debug(pc->st, "setup without bearer capabilities"); + /* ETS 300-104 1.3.3 */ + pc->para.cause = 96; + l3ni1_msg_without_setup(pc, pr, NULL); + return; + } + /* + * Channel Identification + */ + if ((id = l3ni1_get_channel_id(pc, skb)) >= 0) { + if ((pc->para.bchannel = id)) { + if ((3 == id) && (0x10 == pc->para.moderate)) { + if (pc->debug & L3_DEB_WARN) + l3_debug(pc->st, "setup with wrong chid %x", + id); + pc->para.cause = 100; + l3ni1_msg_without_setup(pc, pr, NULL); + return; + } + bcfound++; + } else + { if (pc->debug & L3_DEB_WARN) + l3_debug(pc->st, "setup without bchannel, call waiting"); + bcfound++; + } + } else { + if (pc->debug & L3_DEB_WARN) + l3_debug(pc->st, "setup with wrong chid ret %d", id); + if (id == -1) + pc->para.cause = 96; + else + pc->para.cause = 100; + l3ni1_msg_without_setup(pc, pr, NULL); + return; + } + /* Now we are on none mandatory IEs */ + err = check_infoelements(pc, skb, ie_SETUP); + if (ERR_IE_COMPREHENSION == err) { + pc->para.cause = 96; + l3ni1_msg_without_setup(pc, pr, NULL); + return; + } + p = skb->data; + if ((p = findie(p, skb->len, 0x70, 0))) + iecpy(pc->para.setup.eazmsn, p, 1); + else + pc->para.setup.eazmsn[0] = 0; + + p = skb->data; + if ((p = findie(p, skb->len, 0x71, 0))) { + /* Called party subaddress */ + if ((p[1] >= 2) && (p[2] == 0x80) && (p[3] == 0x50)) { + tmp[0] = '.'; + iecpy(&tmp[1], p, 2); + strcat(pc->para.setup.eazmsn, tmp); + } else if (pc->debug & L3_DEB_WARN) + l3_debug(pc->st, "wrong called subaddress"); + } + p = skb->data; + if ((p = findie(p, skb->len, 0x6c, 0))) { + pc->para.setup.plan = p[2]; + if (p[2] & 0x80) { + iecpy(pc->para.setup.phone, p, 1); + pc->para.setup.screen = 0; + } else { + iecpy(pc->para.setup.phone, p, 2); + pc->para.setup.screen = p[3]; + } + } else { + pc->para.setup.phone[0] = 0; + pc->para.setup.plan = 0; + pc->para.setup.screen = 0; + } + p = skb->data; + if ((p = findie(p, skb->len, 0x6d, 0))) { + /* Calling party subaddress */ + if ((p[1] >= 2) && (p[2] == 0x80) && (p[3] == 0x50)) { + tmp[0] = '.'; + iecpy(&tmp[1], p, 2); + strcat(pc->para.setup.phone, tmp); + } else if (pc->debug & L3_DEB_WARN) + l3_debug(pc->st, "wrong calling subaddress"); + } + newl3state(pc, 6); + if (err) /* STATUS for none mandatory IE errors after actions are taken */ + l3ni1_std_ie_err(pc, err); + pc->st->l3.l3l4(pc->st, CC_SETUP | INDICATION, pc); +} + +static void +l3ni1_reset(struct l3_process *pc, u_char pr, void *arg) +{ + ni1_release_l3_process(pc); +} + +static void +l3ni1_disconnect_req(struct l3_process *pc, u_char pr, void *arg) +{ + struct sk_buff *skb; + u_char tmp[16 + 40]; + u_char *p = tmp; + int l; + u_char cause = 16; + + if (pc->para.cause != NO_CAUSE) + cause = pc->para.cause; + + StopAllL3Timer(pc); + + MsgHead(p, pc->callref, MT_DISCONNECT); + + *p++ = IE_CAUSE; + *p++ = 0x2; + *p++ = 0x80; + *p++ = cause | 0x80; + + if (pc->prot.ni1.uus1_data[0]) + { *p++ = IE_USER_USER; /* UUS info element */ + *p++ = strlen(pc->prot.ni1.uus1_data) + 1; + *p++ = 0x04; /* IA5 chars */ + strcpy(p, pc->prot.ni1.uus1_data); + p += strlen(pc->prot.ni1.uus1_data); + pc->prot.ni1.uus1_data[0] = '\0'; + } + + l = p - tmp; + if (!(skb = l3_alloc_skb(l))) + return; + skb_put_data(skb, tmp, l); + newl3state(pc, 11); + l3_msg(pc->st, DL_DATA | REQUEST, skb); + L3AddTimer(&pc->timer, T305, CC_T305); +} + +static void +l3ni1_setup_rsp(struct l3_process *pc, u_char pr, + void *arg) +{ + if (!pc->para.bchannel) + { if (pc->debug & L3_DEB_WARN) + l3_debug(pc->st, "D-chan connect for waiting call"); + l3ni1_disconnect_req(pc, pr, arg); + return; + } + newl3state(pc, 8); + if (pc->debug & L3_DEB_WARN) + l3_debug(pc->st, "D-chan connect for waiting call"); + l3ni1_message_plus_chid(pc, MT_CONNECT); /* GE 05/09/00 */ + L3DelTimer(&pc->timer); + L3AddTimer(&pc->timer, T313, CC_T313); +} + +static void +l3ni1_connect_ack(struct l3_process *pc, u_char pr, void *arg) +{ + struct sk_buff *skb = arg; + int ret; + + ret = check_infoelements(pc, skb, ie_CONNECT_ACKNOWLEDGE); + if (ERR_IE_COMPREHENSION == ret) { + l3ni1_std_ie_err(pc, ret); + return; + } + newl3state(pc, 10); + L3DelTimer(&pc->timer); + if (ret) + l3ni1_std_ie_err(pc, ret); + pc->st->l3.l3l4(pc->st, CC_SETUP_COMPL | INDICATION, pc); +} + +static void +l3ni1_reject_req(struct l3_process *pc, u_char pr, void *arg) +{ + struct sk_buff *skb; + u_char tmp[16]; + u_char *p = tmp; + int l; + u_char cause = 21; + + if (pc->para.cause != NO_CAUSE) + cause = pc->para.cause; + + MsgHead(p, pc->callref, MT_RELEASE_COMPLETE); + + *p++ = IE_CAUSE; + *p++ = 0x2; + *p++ = 0x80; + *p++ = cause | 0x80; + + l = p - tmp; + if (!(skb = l3_alloc_skb(l))) + return; + skb_put_data(skb, tmp, l); + l3_msg(pc->st, DL_DATA | REQUEST, skb); + pc->st->l3.l3l4(pc->st, CC_RELEASE | INDICATION, pc); + newl3state(pc, 0); + ni1_release_l3_process(pc); +} + +static void +l3ni1_release(struct l3_process *pc, u_char pr, void *arg) +{ + struct sk_buff *skb = arg; + u_char *p; + int ret, cause = 0; + + StopAllL3Timer(pc); + if ((ret = l3ni1_get_cause(pc, skb)) > 0) { + if (pc->debug & L3_DEB_WARN) + l3_debug(pc->st, "REL get_cause ret(%d)", ret); + } else if (ret < 0) + pc->para.cause = NO_CAUSE; + if ((p = findie(skb->data, skb->len, IE_FACILITY, 0))) { + l3ni1_parse_facility(pc->st, pc, pc->callref, p); + } + if ((ret < 0) && (pc->state != 11)) + cause = 96; + else if (ret > 0) + cause = 100; + ret = check_infoelements(pc, skb, ie_RELEASE); + if (ERR_IE_COMPREHENSION == ret) + cause = 96; + else if ((ERR_IE_UNRECOGNIZED == ret) && (!cause)) + cause = 99; + if (cause) + l3ni1_message_cause(pc, MT_RELEASE_COMPLETE, cause); + else + l3ni1_message(pc, MT_RELEASE_COMPLETE); + pc->st->l3.l3l4(pc->st, CC_RELEASE | INDICATION, pc); + newl3state(pc, 0); + ni1_release_l3_process(pc); +} + +static void +l3ni1_alert_req(struct l3_process *pc, u_char pr, + void *arg) +{ + newl3state(pc, 7); + if (!pc->prot.ni1.uus1_data[0]) + l3ni1_message(pc, MT_ALERTING); + else + l3ni1_msg_with_uus(pc, MT_ALERTING); +} + +static void +l3ni1_proceed_req(struct l3_process *pc, u_char pr, + void *arg) +{ + newl3state(pc, 9); + l3ni1_message(pc, MT_CALL_PROCEEDING); + pc->st->l3.l3l4(pc->st, CC_PROCEED_SEND | INDICATION, pc); +} + +static void +l3ni1_setup_ack_req(struct l3_process *pc, u_char pr, + void *arg) +{ + newl3state(pc, 25); + L3DelTimer(&pc->timer); + L3AddTimer(&pc->timer, T302, CC_T302); + l3ni1_message(pc, MT_SETUP_ACKNOWLEDGE); +} + +/********************************************/ +/* deliver a incoming display message to HL */ +/********************************************/ +static void +l3ni1_deliver_display(struct l3_process *pc, int pr, u_char *infp) +{ u_char len; + isdn_ctrl ic; + struct IsdnCardState *cs; + char *p; + + if (*infp++ != IE_DISPLAY) return; + if ((len = *infp++) > 80) return; /* total length <= 82 */ + if (!pc->chan) return; + + p = ic.parm.display; + while (len--) + *p++ = *infp++; + *p = '\0'; + ic.command = ISDN_STAT_DISPLAY; + cs = pc->st->l1.hardware; + ic.driver = cs->myid; + ic.arg = pc->chan->chan; + cs->iif.statcallb(&ic); +} /* l3ni1_deliver_display */ + + +static void +l3ni1_progress(struct l3_process *pc, u_char pr, void *arg) +{ + struct sk_buff *skb = arg; + int err = 0; + u_char *p; + + if ((p = findie(skb->data, skb->len, IE_PROGRESS, 0))) { + if (p[1] != 2) { + err = 1; + pc->para.cause = 100; + } else if (!(p[2] & 0x70)) { + switch (p[2]) { + case 0x80: + case 0x81: + case 0x82: + case 0x84: + case 0x85: + case 0x87: + case 0x8a: + switch (p[3]) { + case 0x81: + case 0x82: + case 0x83: + case 0x84: + case 0x88: + break; + default: + err = 2; + pc->para.cause = 100; + break; + } + break; + default: + err = 3; + pc->para.cause = 100; + break; + } + } + } else { + pc->para.cause = 96; + err = 4; + } + if (err) { + if (pc->debug & L3_DEB_WARN) + l3_debug(pc->st, "progress error %d", err); + l3ni1_status_send(pc, pr, NULL); + return; + } + /* Now we are on none mandatory IEs */ + err = check_infoelements(pc, skb, ie_PROGRESS); + if (err) + l3ni1_std_ie_err(pc, err); + if (ERR_IE_COMPREHENSION != err) + pc->st->l3.l3l4(pc->st, CC_PROGRESS | INDICATION, pc); +} + +static void +l3ni1_notify(struct l3_process *pc, u_char pr, void *arg) +{ + struct sk_buff *skb = arg; + int err = 0; + u_char *p; + + if ((p = findie(skb->data, skb->len, IE_NOTIFY, 0))) { + if (p[1] != 1) { + err = 1; + pc->para.cause = 100; + } else { + switch (p[2]) { + case 0x80: + case 0x81: + case 0x82: + break; + default: + pc->para.cause = 100; + err = 2; + break; + } + } + } else { + pc->para.cause = 96; + err = 3; + } + if (err) { + if (pc->debug & L3_DEB_WARN) + l3_debug(pc->st, "notify error %d", err); + l3ni1_status_send(pc, pr, NULL); + return; + } + /* Now we are on none mandatory IEs */ + err = check_infoelements(pc, skb, ie_NOTIFY); + if (err) + l3ni1_std_ie_err(pc, err); + if (ERR_IE_COMPREHENSION != err) + pc->st->l3.l3l4(pc->st, CC_NOTIFY | INDICATION, pc); +} + +static void +l3ni1_status_enq(struct l3_process *pc, u_char pr, void *arg) +{ + int ret; + struct sk_buff *skb = arg; + + ret = check_infoelements(pc, skb, ie_STATUS_ENQUIRY); + l3ni1_std_ie_err(pc, ret); + pc->para.cause = 30; /* response to STATUS_ENQUIRY */ + l3ni1_status_send(pc, pr, NULL); +} + +static void +l3ni1_information(struct l3_process *pc, u_char pr, void *arg) +{ + int ret; + struct sk_buff *skb = arg; + u_char *p; + char tmp[32]; + + ret = check_infoelements(pc, skb, ie_INFORMATION); + if (ret) + l3ni1_std_ie_err(pc, ret); + if (pc->state == 25) { /* overlap receiving */ + L3DelTimer(&pc->timer); + p = skb->data; + if ((p = findie(p, skb->len, 0x70, 0))) { + iecpy(tmp, p, 1); + strcat(pc->para.setup.eazmsn, tmp); + pc->st->l3.l3l4(pc->st, CC_MORE_INFO | INDICATION, pc); + } + L3AddTimer(&pc->timer, T302, CC_T302); + } +} + +/******************************/ +/* handle deflection requests */ +/******************************/ +static void l3ni1_redir_req(struct l3_process *pc, u_char pr, void *arg) +{ + struct sk_buff *skb; + u_char tmp[128]; + u_char *p = tmp; + u_char *subp; + u_char len_phone = 0; + u_char len_sub = 0; + int l; + + + strcpy(pc->prot.ni1.uus1_data, pc->chan->setup.eazmsn); /* copy uus element if available */ + if (!pc->chan->setup.phone[0]) + { pc->para.cause = -1; + l3ni1_disconnect_req(pc, pr, arg); /* disconnect immediately */ + return; + } /* only uus */ + + if (pc->prot.ni1.invoke_id) + free_invoke_id(pc->st, pc->prot.ni1.invoke_id); + + if (!(pc->prot.ni1.invoke_id = new_invoke_id(pc->st))) + return; + + MsgHead(p, pc->callref, MT_FACILITY); + + for (subp = pc->chan->setup.phone; (*subp) && (*subp != '.'); subp++) len_phone++; /* len of phone number */ + if (*subp++ == '.') len_sub = strlen(subp) + 2; /* length including info subaddress element */ + + *p++ = 0x1c; /* Facility info element */ + *p++ = len_phone + len_sub + 2 + 2 + 8 + 3 + 3; /* length of element */ + *p++ = 0x91; /* remote operations protocol */ + *p++ = 0xa1; /* invoke component */ + + *p++ = len_phone + len_sub + 2 + 2 + 8 + 3; /* length of data */ + *p++ = 0x02; /* invoke id tag, integer */ + *p++ = 0x01; /* length */ + *p++ = pc->prot.ni1.invoke_id; /* invoke id */ + *p++ = 0x02; /* operation value tag, integer */ + *p++ = 0x01; /* length */ + *p++ = 0x0D; /* Call Deflect */ + + *p++ = 0x30; /* sequence phone number */ + *p++ = len_phone + 2 + 2 + 3 + len_sub; /* length */ + + *p++ = 0x30; /* Deflected to UserNumber */ + *p++ = len_phone + 2 + len_sub; /* length */ + *p++ = 0x80; /* NumberDigits */ + *p++ = len_phone; /* length */ + for (l = 0; l < len_phone; l++) + *p++ = pc->chan->setup.phone[l]; + + if (len_sub) + { *p++ = 0x04; /* called party subaddress */ + *p++ = len_sub - 2; + while (*subp) *p++ = *subp++; + } + + *p++ = 0x01; /* screening identifier */ + *p++ = 0x01; + *p++ = pc->chan->setup.screen; + + l = p - tmp; + if (!(skb = l3_alloc_skb(l))) return; + skb_put_data(skb, tmp, l); + + l3_msg(pc->st, DL_DATA | REQUEST, skb); +} /* l3ni1_redir_req */ + +/********************************************/ +/* handle deflection request in early state */ +/********************************************/ +static void l3ni1_redir_req_early(struct l3_process *pc, u_char pr, void *arg) +{ + l3ni1_proceed_req(pc, pr, arg); + l3ni1_redir_req(pc, pr, arg); +} /* l3ni1_redir_req_early */ + +/***********************************************/ +/* handle special commands for this protocol. */ +/* Examples are call independent services like */ +/* remote operations with dummy callref. */ +/***********************************************/ +static int l3ni1_cmd_global(struct PStack *st, isdn_ctrl *ic) +{ u_char id; + u_char temp[265]; + u_char *p = temp; + int i, l, proc_len; + struct sk_buff *skb; + struct l3_process *pc = NULL; + + switch (ic->arg) + { case NI1_CMD_INVOKE: + if (ic->parm.ni1_io.datalen < 0) return (-2); /* invalid parameter */ + + for (proc_len = 1, i = ic->parm.ni1_io.proc >> 8; i; i++) + i = i >> 8; /* add one byte */ + l = ic->parm.ni1_io.datalen + proc_len + 8; /* length excluding ie header */ + if (l > 255) + return (-2); /* too long */ + + if (!(id = new_invoke_id(st))) + return (0); /* first get a invoke id -> return if no available */ + + i = -1; + MsgHead(p, i, MT_FACILITY); /* build message head */ + *p++ = 0x1C; /* Facility IE */ + *p++ = l; /* length of ie */ + *p++ = 0x91; /* remote operations */ + *p++ = 0xA1; /* invoke */ + *p++ = l - 3; /* length of invoke */ + *p++ = 0x02; /* invoke id tag */ + *p++ = 0x01; /* length is 1 */ + *p++ = id; /* invoke id */ + *p++ = 0x02; /* operation */ + *p++ = proc_len; /* length of operation */ + + for (i = proc_len; i; i--) + *p++ = (ic->parm.ni1_io.proc >> (i - 1)) & 0xFF; + memcpy(p, ic->parm.ni1_io.data, ic->parm.ni1_io.datalen); /* copy data */ + l = (p - temp) + ic->parm.ni1_io.datalen; /* total length */ + + if (ic->parm.ni1_io.timeout > 0) { + pc = ni1_new_l3_process(st, -1); + if (!pc) { + free_invoke_id(st, id); + return (-2); + } + /* remember id */ + pc->prot.ni1.ll_id = ic->parm.ni1_io.ll_id; + /* and procedure */ + pc->prot.ni1.proc = ic->parm.ni1_io.proc; + } + + if (!(skb = l3_alloc_skb(l))) + { free_invoke_id(st, id); + if (pc) ni1_release_l3_process(pc); + return (-2); + } + skb_put_data(skb, temp, l); + + if (pc) + { pc->prot.ni1.invoke_id = id; /* remember id */ + L3AddTimer(&pc->timer, ic->parm.ni1_io.timeout, CC_TNI1_IO | REQUEST); + } + + l3_msg(st, DL_DATA | REQUEST, skb); + ic->parm.ni1_io.hl_id = id; /* return id */ + return (0); + + case NI1_CMD_INVOKE_ABORT: + if ((pc = l3ni1_search_dummy_proc(st, ic->parm.ni1_io.hl_id))) + { L3DelTimer(&pc->timer); /* remove timer */ + ni1_release_l3_process(pc); + return (0); + } + else + { l3_debug(st, "l3ni1_cmd_global abort unknown id"); + return (-2); + } + break; + + default: + l3_debug(st, "l3ni1_cmd_global unknown cmd 0x%lx", ic->arg); + return (-1); + } /* switch ic-> arg */ + return (-1); +} /* l3ni1_cmd_global */ + +static void +l3ni1_io_timer(struct l3_process *pc) +{ isdn_ctrl ic; + struct IsdnCardState *cs = pc->st->l1.hardware; + + L3DelTimer(&pc->timer); /* remove timer */ + + ic.driver = cs->myid; + ic.command = ISDN_STAT_PROT; + ic.arg = NI1_STAT_INVOKE_ERR; + ic.parm.ni1_io.hl_id = pc->prot.ni1.invoke_id; + ic.parm.ni1_io.ll_id = pc->prot.ni1.ll_id; + ic.parm.ni1_io.proc = pc->prot.ni1.proc; + ic.parm.ni1_io.timeout = -1; + ic.parm.ni1_io.datalen = 0; + ic.parm.ni1_io.data = NULL; + free_invoke_id(pc->st, pc->prot.ni1.invoke_id); + pc->prot.ni1.invoke_id = 0; /* reset id */ + + cs->iif.statcallb(&ic); + + ni1_release_l3_process(pc); +} /* l3ni1_io_timer */ + +static void +l3ni1_release_ind(struct l3_process *pc, u_char pr, void *arg) +{ + u_char *p; + struct sk_buff *skb = arg; + int callState = 0; + p = skb->data; + + if ((p = findie(p, skb->len, IE_CALL_STATE, 0))) { + p++; + if (1 == *p++) + callState = *p; + } + if (callState == 0) { + /* ETS 300-104 7.6.1, 8.6.1, 10.6.1... and 16.1 + * set down layer 3 without sending any message + */ + pc->st->l3.l3l4(pc->st, CC_RELEASE | INDICATION, pc); + newl3state(pc, 0); + ni1_release_l3_process(pc); + } else { + pc->st->l3.l3l4(pc->st, CC_IGNORE | INDICATION, pc); + } +} + +static void +l3ni1_dummy(struct l3_process *pc, u_char pr, void *arg) +{ +} + +static void +l3ni1_t302(struct l3_process *pc, u_char pr, void *arg) +{ + L3DelTimer(&pc->timer); + pc->para.loc = 0; + pc->para.cause = 28; /* invalid number */ + l3ni1_disconnect_req(pc, pr, NULL); + pc->st->l3.l3l4(pc->st, CC_SETUP_ERR, pc); +} + +static void +l3ni1_t303(struct l3_process *pc, u_char pr, void *arg) +{ + if (pc->N303 > 0) { + pc->N303--; + L3DelTimer(&pc->timer); + l3ni1_setup_req(pc, pr, arg); + } else { + L3DelTimer(&pc->timer); + l3ni1_message_cause(pc, MT_RELEASE_COMPLETE, 102); + pc->st->l3.l3l4(pc->st, CC_NOSETUP_RSP, pc); + ni1_release_l3_process(pc); + } +} + +static void +l3ni1_t304(struct l3_process *pc, u_char pr, void *arg) +{ + L3DelTimer(&pc->timer); + pc->para.loc = 0; + pc->para.cause = 102; + l3ni1_disconnect_req(pc, pr, NULL); + pc->st->l3.l3l4(pc->st, CC_SETUP_ERR, pc); + +} + +static void +l3ni1_t305(struct l3_process *pc, u_char pr, void *arg) +{ + u_char tmp[16]; + u_char *p = tmp; + int l; + struct sk_buff *skb; + u_char cause = 16; + + L3DelTimer(&pc->timer); + if (pc->para.cause != NO_CAUSE) + cause = pc->para.cause; + + MsgHead(p, pc->callref, MT_RELEASE); + + *p++ = IE_CAUSE; + *p++ = 0x2; + *p++ = 0x80; + *p++ = cause | 0x80; + + l = p - tmp; + if (!(skb = l3_alloc_skb(l))) + return; + skb_put_data(skb, tmp, l); + newl3state(pc, 19); + l3_msg(pc->st, DL_DATA | REQUEST, skb); + L3AddTimer(&pc->timer, T308, CC_T308_1); +} + +static void +l3ni1_t310(struct l3_process *pc, u_char pr, void *arg) +{ + L3DelTimer(&pc->timer); + pc->para.loc = 0; + pc->para.cause = 102; + l3ni1_disconnect_req(pc, pr, NULL); + pc->st->l3.l3l4(pc->st, CC_SETUP_ERR, pc); +} + +static void +l3ni1_t313(struct l3_process *pc, u_char pr, void *arg) +{ + L3DelTimer(&pc->timer); + pc->para.loc = 0; + pc->para.cause = 102; + l3ni1_disconnect_req(pc, pr, NULL); + pc->st->l3.l3l4(pc->st, CC_CONNECT_ERR, pc); +} + +static void +l3ni1_t308_1(struct l3_process *pc, u_char pr, void *arg) +{ + newl3state(pc, 19); + L3DelTimer(&pc->timer); + l3ni1_message(pc, MT_RELEASE); + L3AddTimer(&pc->timer, T308, CC_T308_2); +} + +static void +l3ni1_t308_2(struct l3_process *pc, u_char pr, void *arg) +{ + L3DelTimer(&pc->timer); + pc->st->l3.l3l4(pc->st, CC_RELEASE_ERR, pc); + ni1_release_l3_process(pc); +} + +static void +l3ni1_t318(struct l3_process *pc, u_char pr, void *arg) +{ + L3DelTimer(&pc->timer); + pc->para.cause = 102; /* Timer expiry */ + pc->para.loc = 0; /* local */ + pc->st->l3.l3l4(pc->st, CC_RESUME_ERR, pc); + newl3state(pc, 19); + l3ni1_message(pc, MT_RELEASE); + L3AddTimer(&pc->timer, T308, CC_T308_1); +} + +static void +l3ni1_t319(struct l3_process *pc, u_char pr, void *arg) +{ + L3DelTimer(&pc->timer); + pc->para.cause = 102; /* Timer expiry */ + pc->para.loc = 0; /* local */ + pc->st->l3.l3l4(pc->st, CC_SUSPEND_ERR, pc); + newl3state(pc, 10); +} + +static void +l3ni1_restart(struct l3_process *pc, u_char pr, void *arg) +{ + L3DelTimer(&pc->timer); + pc->st->l3.l3l4(pc->st, CC_RELEASE | INDICATION, pc); + ni1_release_l3_process(pc); +} + +static void +l3ni1_status(struct l3_process *pc, u_char pr, void *arg) +{ + u_char *p; + struct sk_buff *skb = arg; + int ret; + u_char cause = 0, callState = 0; + + if ((ret = l3ni1_get_cause(pc, skb))) { + if (pc->debug & L3_DEB_WARN) + l3_debug(pc->st, "STATUS get_cause ret(%d)", ret); + if (ret < 0) + cause = 96; + else if (ret > 0) + cause = 100; + } + if ((p = findie(skb->data, skb->len, IE_CALL_STATE, 0))) { + p++; + if (1 == *p++) { + callState = *p; + if (!ie_in_set(pc, *p, l3_valid_states)) + cause = 100; + } else + cause = 100; + } else + cause = 96; + if (!cause) { /* no error before */ + ret = check_infoelements(pc, skb, ie_STATUS); + if (ERR_IE_COMPREHENSION == ret) + cause = 96; + else if (ERR_IE_UNRECOGNIZED == ret) + cause = 99; + } + if (cause) { + u_char tmp; + + if (pc->debug & L3_DEB_WARN) + l3_debug(pc->st, "STATUS error(%d/%d)", ret, cause); + tmp = pc->para.cause; + pc->para.cause = cause; + l3ni1_status_send(pc, 0, NULL); + if (cause == 99) + pc->para.cause = tmp; + else + return; + } + cause = pc->para.cause; + if (((cause & 0x7f) == 111) && (callState == 0)) { + /* ETS 300-104 7.6.1, 8.6.1, 10.6.1... + * if received MT_STATUS with cause == 111 and call + * state == 0, then we must set down layer 3 + */ + pc->st->l3.l3l4(pc->st, CC_RELEASE | INDICATION, pc); + newl3state(pc, 0); + ni1_release_l3_process(pc); + } +} + +static void +l3ni1_facility(struct l3_process *pc, u_char pr, void *arg) +{ + struct sk_buff *skb = arg; + int ret; + + ret = check_infoelements(pc, skb, ie_FACILITY); + l3ni1_std_ie_err(pc, ret); + { + u_char *p; + if ((p = findie(skb->data, skb->len, IE_FACILITY, 0))) + l3ni1_parse_facility(pc->st, pc, pc->callref, p); + } +} + +static void +l3ni1_suspend_req(struct l3_process *pc, u_char pr, void *arg) +{ + struct sk_buff *skb; + u_char tmp[32]; + u_char *p = tmp; + u_char i, l; + u_char *msg = pc->chan->setup.phone; + + MsgHead(p, pc->callref, MT_SUSPEND); + l = *msg++; + if (l && (l <= 10)) { /* Max length 10 octets */ + *p++ = IE_CALL_ID; + *p++ = l; + for (i = 0; i < l; i++) + *p++ = *msg++; + } else if (l) { + l3_debug(pc->st, "SUS wrong CALL_ID len %d", l); + return; + } + l = p - tmp; + if (!(skb = l3_alloc_skb(l))) + return; + skb_put_data(skb, tmp, l); + l3_msg(pc->st, DL_DATA | REQUEST, skb); + newl3state(pc, 15); + L3AddTimer(&pc->timer, T319, CC_T319); +} + +static void +l3ni1_suspend_ack(struct l3_process *pc, u_char pr, void *arg) +{ + struct sk_buff *skb = arg; + int ret; + + L3DelTimer(&pc->timer); + newl3state(pc, 0); + pc->para.cause = NO_CAUSE; + pc->st->l3.l3l4(pc->st, CC_SUSPEND | CONFIRM, pc); + /* We don't handle suspend_ack for IE errors now */ + if ((ret = check_infoelements(pc, skb, ie_SUSPEND_ACKNOWLEDGE))) + if (pc->debug & L3_DEB_WARN) + l3_debug(pc->st, "SUSPACK check ie(%d)", ret); + ni1_release_l3_process(pc); +} + +static void +l3ni1_suspend_rej(struct l3_process *pc, u_char pr, void *arg) +{ + struct sk_buff *skb = arg; + int ret; + + if ((ret = l3ni1_get_cause(pc, skb))) { + if (pc->debug & L3_DEB_WARN) + l3_debug(pc->st, "SUSP_REJ get_cause ret(%d)", ret); + if (ret < 0) + pc->para.cause = 96; + else + pc->para.cause = 100; + l3ni1_status_send(pc, pr, NULL); + return; + } + ret = check_infoelements(pc, skb, ie_SUSPEND_REJECT); + if (ERR_IE_COMPREHENSION == ret) { + l3ni1_std_ie_err(pc, ret); + return; + } + L3DelTimer(&pc->timer); + pc->st->l3.l3l4(pc->st, CC_SUSPEND_ERR, pc); + newl3state(pc, 10); + if (ret) /* STATUS for none mandatory IE errors after actions are taken */ + l3ni1_std_ie_err(pc, ret); +} + +static void +l3ni1_resume_req(struct l3_process *pc, u_char pr, void *arg) +{ + struct sk_buff *skb; + u_char tmp[32]; + u_char *p = tmp; + u_char i, l; + u_char *msg = pc->para.setup.phone; + + MsgHead(p, pc->callref, MT_RESUME); + + l = *msg++; + if (l && (l <= 10)) { /* Max length 10 octets */ + *p++ = IE_CALL_ID; + *p++ = l; + for (i = 0; i < l; i++) + *p++ = *msg++; + } else if (l) { + l3_debug(pc->st, "RES wrong CALL_ID len %d", l); + return; + } + l = p - tmp; + if (!(skb = l3_alloc_skb(l))) + return; + skb_put_data(skb, tmp, l); + l3_msg(pc->st, DL_DATA | REQUEST, skb); + newl3state(pc, 17); + L3AddTimer(&pc->timer, T318, CC_T318); +} + +static void +l3ni1_resume_ack(struct l3_process *pc, u_char pr, void *arg) +{ + struct sk_buff *skb = arg; + int id, ret; + + if ((id = l3ni1_get_channel_id(pc, skb)) > 0) { + if ((0 == id) || ((3 == id) && (0x10 == pc->para.moderate))) { + if (pc->debug & L3_DEB_WARN) + l3_debug(pc->st, "resume ack with wrong chid %x", id); + pc->para.cause = 100; + l3ni1_status_send(pc, pr, NULL); + return; + } + pc->para.bchannel = id; + } else if (1 == pc->state) { + if (pc->debug & L3_DEB_WARN) + l3_debug(pc->st, "resume ack without chid (ret %d)", id); + pc->para.cause = 96; + l3ni1_status_send(pc, pr, NULL); + return; + } + ret = check_infoelements(pc, skb, ie_RESUME_ACKNOWLEDGE); + if (ERR_IE_COMPREHENSION == ret) { + l3ni1_std_ie_err(pc, ret); + return; + } + L3DelTimer(&pc->timer); + pc->st->l3.l3l4(pc->st, CC_RESUME | CONFIRM, pc); + newl3state(pc, 10); + if (ret) /* STATUS for none mandatory IE errors after actions are taken */ + l3ni1_std_ie_err(pc, ret); +} + +static void +l3ni1_resume_rej(struct l3_process *pc, u_char pr, void *arg) +{ + struct sk_buff *skb = arg; + int ret; + + if ((ret = l3ni1_get_cause(pc, skb))) { + if (pc->debug & L3_DEB_WARN) + l3_debug(pc->st, "RES_REJ get_cause ret(%d)", ret); + if (ret < 0) + pc->para.cause = 96; + else + pc->para.cause = 100; + l3ni1_status_send(pc, pr, NULL); + return; + } + ret = check_infoelements(pc, skb, ie_RESUME_REJECT); + if (ERR_IE_COMPREHENSION == ret) { + l3ni1_std_ie_err(pc, ret); + return; + } + L3DelTimer(&pc->timer); + pc->st->l3.l3l4(pc->st, CC_RESUME_ERR, pc); + newl3state(pc, 0); + if (ret) /* STATUS for none mandatory IE errors after actions are taken */ + l3ni1_std_ie_err(pc, ret); + ni1_release_l3_process(pc); +} + +static void +l3ni1_global_restart(struct l3_process *pc, u_char pr, void *arg) +{ + u_char tmp[32]; + u_char *p; + u_char ri, ch = 0, chan = 0; + int l; + struct sk_buff *skb = arg; + struct l3_process *up; + + newl3state(pc, 2); + L3DelTimer(&pc->timer); + p = skb->data; + if ((p = findie(p, skb->len, IE_RESTART_IND, 0))) { + ri = p[2]; + l3_debug(pc->st, "Restart %x", ri); + } else { + l3_debug(pc->st, "Restart without restart IE"); + ri = 0x86; + } + p = skb->data; + if ((p = findie(p, skb->len, IE_CHANNEL_ID, 0))) { + chan = p[2] & 3; + ch = p[2]; + if (pc->st->l3.debug) + l3_debug(pc->st, "Restart for channel %d", chan); + } + newl3state(pc, 2); + up = pc->st->l3.proc; + while (up) { + if ((ri & 7) == 7) + up->st->lli.l4l3(up->st, CC_RESTART | REQUEST, up); + else if (up->para.bchannel == chan) + up->st->lli.l4l3(up->st, CC_RESTART | REQUEST, up); + + up = up->next; + } + p = tmp; + MsgHead(p, pc->callref, MT_RESTART_ACKNOWLEDGE); + if (chan) { + *p++ = IE_CHANNEL_ID; + *p++ = 1; + *p++ = ch | 0x80; + } + *p++ = 0x79; /* RESTART Ind */ + *p++ = 1; + *p++ = ri; + l = p - tmp; + if (!(skb = l3_alloc_skb(l))) + return; + skb_put_data(skb, tmp, l); + newl3state(pc, 0); + l3_msg(pc->st, DL_DATA | REQUEST, skb); +} + +static void +l3ni1_dl_reset(struct l3_process *pc, u_char pr, void *arg) +{ + pc->para.cause = 0x29; /* Temporary failure */ + pc->para.loc = 0; + l3ni1_disconnect_req(pc, pr, NULL); + pc->st->l3.l3l4(pc->st, CC_SETUP_ERR, pc); +} + +static void +l3ni1_dl_release(struct l3_process *pc, u_char pr, void *arg) +{ + newl3state(pc, 0); + pc->para.cause = 0x1b; /* Destination out of order */ + pc->para.loc = 0; + pc->st->l3.l3l4(pc->st, CC_RELEASE | INDICATION, pc); + release_l3_process(pc); +} + +static void +l3ni1_dl_reestablish(struct l3_process *pc, u_char pr, void *arg) +{ + L3DelTimer(&pc->timer); + L3AddTimer(&pc->timer, T309, CC_T309); + l3_msg(pc->st, DL_ESTABLISH | REQUEST, NULL); +} + +static void +l3ni1_dl_reest_status(struct l3_process *pc, u_char pr, void *arg) +{ + L3DelTimer(&pc->timer); + + pc->para.cause = 0x1F; /* normal, unspecified */ + l3ni1_status_send(pc, 0, NULL); +} + +static void l3ni1_SendSpid(struct l3_process *pc, u_char pr, struct sk_buff *skb, int iNewState) +{ + u_char *p; + char *pSPID; + struct Channel *pChan = pc->st->lli.userdata; + int l; + + if (skb) + dev_kfree_skb(skb); + + if (!(pSPID = strchr(pChan->setup.eazmsn, ':'))) + { + printk(KERN_ERR "SPID not supplied in EAZMSN %s\n", pChan->setup.eazmsn); + newl3state(pc, 0); + pc->st->l3.l3l2(pc->st, DL_RELEASE | REQUEST, NULL); + return; + } + + l = strlen(++pSPID); + if (!(skb = l3_alloc_skb(5 + l))) + { + printk(KERN_ERR "HiSax can't get memory to send SPID\n"); + return; + } + + p = skb_put(skb, 5); + *p++ = PROTO_DIS_EURO; + *p++ = 0; + *p++ = MT_INFORMATION; + *p++ = IE_SPID; + *p++ = l; + + skb_put_data(skb, pSPID, l); + + newl3state(pc, iNewState); + + L3DelTimer(&pc->timer); + L3AddTimer(&pc->timer, TSPID, CC_TSPID); + + pc->st->l3.l3l2(pc->st, DL_DATA | REQUEST, skb); +} + +static void l3ni1_spid_send(struct l3_process *pc, u_char pr, void *arg) +{ + l3ni1_SendSpid(pc, pr, arg, 20); +} + +static void l3ni1_spid_epid(struct l3_process *pc, u_char pr, void *arg) +{ + struct sk_buff *skb = arg; + + if (skb->data[1] == 0) + if (skb->data[3] == IE_ENDPOINT_ID) + { + L3DelTimer(&pc->timer); + newl3state(pc, 0); + l3_msg(pc->st, DL_ESTABLISH | CONFIRM, NULL); + } + dev_kfree_skb(skb); +} + +static void l3ni1_spid_tout(struct l3_process *pc, u_char pr, void *arg) +{ + if (pc->state < 22) + l3ni1_SendSpid(pc, pr, arg, pc->state + 1); + else + { + L3DelTimer(&pc->timer); + dev_kfree_skb(arg); + + printk(KERN_ERR "SPID not accepted\n"); + newl3state(pc, 0); + pc->st->l3.l3l2(pc->st, DL_RELEASE | REQUEST, NULL); + } +} + +/* *INDENT-OFF* */ +static struct stateentry downstatelist[] = +{ + {SBIT(0), + CC_SETUP | REQUEST, l3ni1_setup_req}, + {SBIT(0), + CC_RESUME | REQUEST, l3ni1_resume_req}, + {SBIT(1) | SBIT(2) | SBIT(3) | SBIT(4) | SBIT(6) | SBIT(7) | SBIT(8) | SBIT(9) | SBIT(10) | SBIT(25), + CC_DISCONNECT | REQUEST, l3ni1_disconnect_req}, + {SBIT(12), + CC_RELEASE | REQUEST, l3ni1_release_req}, + {ALL_STATES, + CC_RESTART | REQUEST, l3ni1_restart}, + {SBIT(6) | SBIT(25), + CC_IGNORE | REQUEST, l3ni1_reset}, + {SBIT(6) | SBIT(25), + CC_REJECT | REQUEST, l3ni1_reject_req}, + {SBIT(6) | SBIT(25), + CC_PROCEED_SEND | REQUEST, l3ni1_proceed_req}, + {SBIT(6), + CC_MORE_INFO | REQUEST, l3ni1_setup_ack_req}, + {SBIT(25), + CC_MORE_INFO | REQUEST, l3ni1_dummy}, + {SBIT(6) | SBIT(9) | SBIT(25), + CC_ALERTING | REQUEST, l3ni1_alert_req}, + {SBIT(6) | SBIT(7) | SBIT(9) | SBIT(25), + CC_SETUP | RESPONSE, l3ni1_setup_rsp}, + {SBIT(10), + CC_SUSPEND | REQUEST, l3ni1_suspend_req}, + {SBIT(7) | SBIT(9) | SBIT(25), + CC_REDIR | REQUEST, l3ni1_redir_req}, + {SBIT(6), + CC_REDIR | REQUEST, l3ni1_redir_req_early}, + {SBIT(9) | SBIT(25), + CC_DISCONNECT | REQUEST, l3ni1_disconnect_req}, + {SBIT(25), + CC_T302, l3ni1_t302}, + {SBIT(1), + CC_T303, l3ni1_t303}, + {SBIT(2), + CC_T304, l3ni1_t304}, + {SBIT(3), + CC_T310, l3ni1_t310}, + {SBIT(8), + CC_T313, l3ni1_t313}, + {SBIT(11), + CC_T305, l3ni1_t305}, + {SBIT(15), + CC_T319, l3ni1_t319}, + {SBIT(17), + CC_T318, l3ni1_t318}, + {SBIT(19), + CC_T308_1, l3ni1_t308_1}, + {SBIT(19), + CC_T308_2, l3ni1_t308_2}, + {SBIT(10), + CC_T309, l3ni1_dl_release}, + { SBIT(20) | SBIT(21) | SBIT(22), + CC_TSPID, l3ni1_spid_tout }, +}; + +static struct stateentry datastatelist[] = +{ + {ALL_STATES, + MT_STATUS_ENQUIRY, l3ni1_status_enq}, + {ALL_STATES, + MT_FACILITY, l3ni1_facility}, + {SBIT(19), + MT_STATUS, l3ni1_release_ind}, + {ALL_STATES, + MT_STATUS, l3ni1_status}, + {SBIT(0), + MT_SETUP, l3ni1_setup}, + {SBIT(6) | SBIT(7) | SBIT(8) | SBIT(9) | SBIT(10) | SBIT(11) | SBIT(12) | + SBIT(15) | SBIT(17) | SBIT(19) | SBIT(25), + MT_SETUP, l3ni1_dummy}, + {SBIT(1) | SBIT(2), + MT_CALL_PROCEEDING, l3ni1_call_proc}, + {SBIT(1), + MT_SETUP_ACKNOWLEDGE, l3ni1_setup_ack}, + {SBIT(2) | SBIT(3), + MT_ALERTING, l3ni1_alerting}, + {SBIT(2) | SBIT(3), + MT_PROGRESS, l3ni1_progress}, + {SBIT(2) | SBIT(3) | SBIT(4) | SBIT(7) | SBIT(8) | SBIT(9) | SBIT(10) | + SBIT(11) | SBIT(12) | SBIT(15) | SBIT(17) | SBIT(19) | SBIT(25), + MT_INFORMATION, l3ni1_information}, + {SBIT(10) | SBIT(11) | SBIT(15), + MT_NOTIFY, l3ni1_notify}, + {SBIT(0) | SBIT(1) | SBIT(2) | SBIT(3) | SBIT(4) | SBIT(7) | SBIT(8) | SBIT(10) | + SBIT(11) | SBIT(12) | SBIT(15) | SBIT(17) | SBIT(19) | SBIT(25), + MT_RELEASE_COMPLETE, l3ni1_release_cmpl}, + {SBIT(1) | SBIT(2) | SBIT(3) | SBIT(4) | SBIT(7) | SBIT(8) | SBIT(9) | SBIT(10) | SBIT(11) | SBIT(12) | SBIT(15) | SBIT(17) | SBIT(25), + MT_RELEASE, l3ni1_release}, + {SBIT(19), MT_RELEASE, l3ni1_release_ind}, + {SBIT(1) | SBIT(2) | SBIT(3) | SBIT(4) | SBIT(7) | SBIT(8) | SBIT(9) | SBIT(10) | SBIT(11) | SBIT(15) | SBIT(17) | SBIT(25), + MT_DISCONNECT, l3ni1_disconnect}, + {SBIT(19), + MT_DISCONNECT, l3ni1_dummy}, + {SBIT(1) | SBIT(2) | SBIT(3) | SBIT(4), + MT_CONNECT, l3ni1_connect}, + {SBIT(8), + MT_CONNECT_ACKNOWLEDGE, l3ni1_connect_ack}, + {SBIT(15), + MT_SUSPEND_ACKNOWLEDGE, l3ni1_suspend_ack}, + {SBIT(15), + MT_SUSPEND_REJECT, l3ni1_suspend_rej}, + {SBIT(17), + MT_RESUME_ACKNOWLEDGE, l3ni1_resume_ack}, + {SBIT(17), + MT_RESUME_REJECT, l3ni1_resume_rej}, +}; + +static struct stateentry globalmes_list[] = +{ + {ALL_STATES, + MT_STATUS, l3ni1_status}, + {SBIT(0), + MT_RESTART, l3ni1_global_restart}, +/* {SBIT(1), + MT_RESTART_ACKNOWLEDGE, l3ni1_restart_ack}, +*/ + { SBIT(0), MT_DL_ESTABLISHED, l3ni1_spid_send }, + { SBIT(20) | SBIT(21) | SBIT(22), MT_INFORMATION, l3ni1_spid_epid }, +}; + +static struct stateentry manstatelist[] = +{ + {SBIT(2), + DL_ESTABLISH | INDICATION, l3ni1_dl_reset}, + {SBIT(10), + DL_ESTABLISH | CONFIRM, l3ni1_dl_reest_status}, + {SBIT(10), + DL_RELEASE | INDICATION, l3ni1_dl_reestablish}, + {ALL_STATES, + DL_RELEASE | INDICATION, l3ni1_dl_release}, +}; + +/* *INDENT-ON* */ + + +static void +global_handler(struct PStack *st, int mt, struct sk_buff *skb) +{ + u_char tmp[16]; + u_char *p = tmp; + int l; + int i; + struct l3_process *proc = st->l3.global; + + if (skb) + proc->callref = skb->data[2]; /* cr flag */ + else + proc->callref = 0; + for (i = 0; i < ARRAY_SIZE(globalmes_list); i++) + if ((mt == globalmes_list[i].primitive) && + ((1 << proc->state) & globalmes_list[i].state)) + break; + if (i == ARRAY_SIZE(globalmes_list)) { + if (st->l3.debug & L3_DEB_STATE) { + l3_debug(st, "ni1 global state %d mt %x unhandled", + proc->state, mt); + } + MsgHead(p, proc->callref, MT_STATUS); + *p++ = IE_CAUSE; + *p++ = 0x2; + *p++ = 0x80; + *p++ = 81 | 0x80; /* invalid cr */ + *p++ = 0x14; /* CallState */ + *p++ = 0x1; + *p++ = proc->state & 0x3f; + l = p - tmp; + if (!(skb = l3_alloc_skb(l))) + return; + skb_put_data(skb, tmp, l); + l3_msg(proc->st, DL_DATA | REQUEST, skb); + } else { + if (st->l3.debug & L3_DEB_STATE) { + l3_debug(st, "ni1 global %d mt %x", + proc->state, mt); + } + globalmes_list[i].rout(proc, mt, skb); + } +} + +static void +ni1up(struct PStack *st, int pr, void *arg) +{ + int i, mt, cr, callState; + char *ptr; + u_char *p; + struct sk_buff *skb = arg; + struct l3_process *proc; + + switch (pr) { + case (DL_DATA | INDICATION): + case (DL_UNIT_DATA | INDICATION): + break; + case (DL_ESTABLISH | INDICATION): + case (DL_RELEASE | INDICATION): + case (DL_RELEASE | CONFIRM): + l3_msg(st, pr, arg); + return; + break; + + case (DL_ESTABLISH | CONFIRM): + global_handler(st, MT_DL_ESTABLISHED, NULL); + return; + + default: + printk(KERN_ERR "HiSax ni1up unknown pr=%04x\n", pr); + return; + } + if (skb->len < 3) { + l3_debug(st, "ni1up frame too short(%d)", skb->len); + dev_kfree_skb(skb); + return; + } + + if (skb->data[0] != PROTO_DIS_EURO) { + if (st->l3.debug & L3_DEB_PROTERR) { + l3_debug(st, "ni1up%sunexpected discriminator %x message len %d", + (pr == (DL_DATA | INDICATION)) ? " " : "(broadcast) ", + skb->data[0], skb->len); + } + dev_kfree_skb(skb); + return; + } + cr = getcallref(skb->data); + if (skb->len < ((skb->data[1] & 0x0f) + 3)) { + l3_debug(st, "ni1up frame too short(%d)", skb->len); + dev_kfree_skb(skb); + return; + } + mt = skb->data[skb->data[1] + 2]; + if (st->l3.debug & L3_DEB_STATE) + l3_debug(st, "ni1up cr %d", cr); + if (cr == -2) { /* wrong Callref */ + if (st->l3.debug & L3_DEB_WARN) + l3_debug(st, "ni1up wrong Callref"); + dev_kfree_skb(skb); + return; + } else if (cr == -1) { /* Dummy Callref */ + if (mt == MT_FACILITY) + { + if ((p = findie(skb->data, skb->len, IE_FACILITY, 0))) { + l3ni1_parse_facility(st, NULL, + (pr == (DL_DATA | INDICATION)) ? -1 : -2, p); + dev_kfree_skb(skb); + return; + } + } + else + { + global_handler(st, mt, skb); + return; + } + + if (st->l3.debug & L3_DEB_WARN) + l3_debug(st, "ni1up dummy Callref (no facility msg or ie)"); + dev_kfree_skb(skb); + return; + } else if ((((skb->data[1] & 0x0f) == 1) && (0 == (cr & 0x7f))) || + (((skb->data[1] & 0x0f) == 2) && (0 == (cr & 0x7fff)))) { /* Global CallRef */ + if (st->l3.debug & L3_DEB_STATE) + l3_debug(st, "ni1up Global CallRef"); + global_handler(st, mt, skb); + dev_kfree_skb(skb); + return; + } else if (!(proc = getl3proc(st, cr))) { + /* No transaction process exist, that means no call with + * this callreference is active + */ + if (mt == MT_SETUP) { + /* Setup creates a new transaction process */ + if (skb->data[2] & 0x80) { + /* Setup with wrong CREF flag */ + if (st->l3.debug & L3_DEB_STATE) + l3_debug(st, "ni1up wrong CRef flag"); + dev_kfree_skb(skb); + return; + } + if (!(proc = ni1_new_l3_process(st, cr))) { + /* May be to answer with RELEASE_COMPLETE and + * CAUSE 0x2f "Resource unavailable", but this + * need a new_l3_process too ... arghh + */ + dev_kfree_skb(skb); + return; + } + } else if (mt == MT_STATUS) { + if ((ptr = findie(skb->data, skb->len, IE_CAUSE, 0)) != NULL) { + ptr++; + if (*ptr++ == 2) + ptr++; + } + callState = 0; + if ((ptr = findie(skb->data, skb->len, IE_CALL_STATE, 0)) != NULL) { + ptr++; + if (*ptr++ == 2) + ptr++; + callState = *ptr; + } + /* ETS 300-104 part 2.4.1 + * if setup has not been made and a message type + * MT_STATUS is received with call state == 0, + * we must send nothing + */ + if (callState != 0) { + /* ETS 300-104 part 2.4.2 + * if setup has not been made and a message type + * MT_STATUS is received with call state != 0, + * we must send MT_RELEASE_COMPLETE cause 101 + */ + if ((proc = ni1_new_l3_process(st, cr))) { + proc->para.cause = 101; + l3ni1_msg_without_setup(proc, 0, NULL); + } + } + dev_kfree_skb(skb); + return; + } else if (mt == MT_RELEASE_COMPLETE) { + dev_kfree_skb(skb); + return; + } else { + /* ETS 300-104 part 2 + * if setup has not been made and a message type + * (except MT_SETUP and RELEASE_COMPLETE) is received, + * we must send MT_RELEASE_COMPLETE cause 81 */ + dev_kfree_skb(skb); + if ((proc = ni1_new_l3_process(st, cr))) { + proc->para.cause = 81; + l3ni1_msg_without_setup(proc, 0, NULL); + } + return; + } + } + if (l3ni1_check_messagetype_validity(proc, mt, skb)) { + dev_kfree_skb(skb); + return; + } + if ((p = findie(skb->data, skb->len, IE_DISPLAY, 0)) != NULL) + l3ni1_deliver_display(proc, pr, p); /* Display IE included */ + for (i = 0; i < ARRAY_SIZE(datastatelist); i++) + if ((mt == datastatelist[i].primitive) && + ((1 << proc->state) & datastatelist[i].state)) + break; + if (i == ARRAY_SIZE(datastatelist)) { + if (st->l3.debug & L3_DEB_STATE) { + l3_debug(st, "ni1up%sstate %d mt %#x unhandled", + (pr == (DL_DATA | INDICATION)) ? " " : "(broadcast) ", + proc->state, mt); + } + if ((MT_RELEASE_COMPLETE != mt) && (MT_RELEASE != mt)) { + proc->para.cause = 101; + l3ni1_status_send(proc, pr, skb); + } + } else { + if (st->l3.debug & L3_DEB_STATE) { + l3_debug(st, "ni1up%sstate %d mt %x", + (pr == (DL_DATA | INDICATION)) ? " " : "(broadcast) ", + proc->state, mt); + } + datastatelist[i].rout(proc, pr, skb); + } + dev_kfree_skb(skb); + return; +} + +static void +ni1down(struct PStack *st, int pr, void *arg) +{ + int i, cr; + struct l3_process *proc; + struct Channel *chan; + + if ((DL_ESTABLISH | REQUEST) == pr) { + l3_msg(st, pr, NULL); + return; + } else if (((CC_SETUP | REQUEST) == pr) || ((CC_RESUME | REQUEST) == pr)) { + chan = arg; + cr = newcallref(); + cr |= 0x80; + if ((proc = ni1_new_l3_process(st, cr))) { + proc->chan = chan; + chan->proc = proc; + memcpy(&proc->para.setup, &chan->setup, sizeof(setup_parm)); + proc->callref = cr; + } + } else { + proc = arg; + } + if (!proc) { + printk(KERN_ERR "HiSax ni1down without proc pr=%04x\n", pr); + return; + } + + if (pr == (CC_TNI1_IO | REQUEST)) { + l3ni1_io_timer(proc); /* timer expires */ + return; + } + + for (i = 0; i < ARRAY_SIZE(downstatelist); i++) + if ((pr == downstatelist[i].primitive) && + ((1 << proc->state) & downstatelist[i].state)) + break; + if (i == ARRAY_SIZE(downstatelist)) { + if (st->l3.debug & L3_DEB_STATE) { + l3_debug(st, "ni1down state %d prim %#x unhandled", + proc->state, pr); + } + } else { + if (st->l3.debug & L3_DEB_STATE) { + l3_debug(st, "ni1down state %d prim %#x", + proc->state, pr); + } + downstatelist[i].rout(proc, pr, arg); + } +} + +static void +ni1man(struct PStack *st, int pr, void *arg) +{ + int i; + struct l3_process *proc = arg; + + if (!proc) { + printk(KERN_ERR "HiSax ni1man without proc pr=%04x\n", pr); + return; + } + for (i = 0; i < ARRAY_SIZE(manstatelist); i++) + if ((pr == manstatelist[i].primitive) && + ((1 << proc->state) & manstatelist[i].state)) + break; + if (i == ARRAY_SIZE(manstatelist)) { + if (st->l3.debug & L3_DEB_STATE) { + l3_debug(st, "cr %d ni1man state %d prim %#x unhandled", + proc->callref & 0x7f, proc->state, pr); + } + } else { + if (st->l3.debug & L3_DEB_STATE) { + l3_debug(st, "cr %d ni1man state %d prim %#x", + proc->callref & 0x7f, proc->state, pr); + } + manstatelist[i].rout(proc, pr, arg); + } +} + +void +setstack_ni1(struct PStack *st) +{ + char tmp[64]; + int i; + + st->lli.l4l3 = ni1down; + st->lli.l4l3_proto = l3ni1_cmd_global; + st->l2.l2l3 = ni1up; + st->l3.l3ml3 = ni1man; + st->l3.N303 = 1; + st->prot.ni1.last_invoke_id = 0; + st->prot.ni1.invoke_used[0] = 1; /* Bit 0 must always be set to 1 */ + i = 1; + while (i < 32) + st->prot.ni1.invoke_used[i++] = 0; + + if (!(st->l3.global = kmalloc(sizeof(struct l3_process), GFP_ATOMIC))) { + printk(KERN_ERR "HiSax can't get memory for ni1 global CR\n"); + } else { + st->l3.global->state = 0; + st->l3.global->callref = 0; + st->l3.global->next = NULL; + st->l3.global->debug = L3_DEB_WARN; + st->l3.global->st = st; + st->l3.global->N303 = 1; + st->l3.global->prot.ni1.invoke_id = 0; + + L3InitTimer(st->l3.global, &st->l3.global->timer); + } + strcpy(tmp, ni1_revision); + printk(KERN_INFO "HiSax: National ISDN-1 Rev. %s\n", HiSax_getrev(tmp)); +} diff --git a/drivers/isdn/hisax/l3ni1.h b/drivers/isdn/hisax/l3ni1.h new file mode 100644 index 000000000..99d37d2ce --- /dev/null +++ b/drivers/isdn/hisax/l3ni1.h @@ -0,0 +1,136 @@ +/* $Id: l3ni1.h,v 2.3.6.2 2001/09/23 22:24:50 kai Exp $ + * + * NI1 D-channel protocol + * + * Author Matt Henderson & Guy Ellis + * Copyright by Traverse Technologies Pty Ltd, www.travers.com.au + * + * This software may be used and distributed according to the terms + * of the GNU General Public License, incorporated herein by reference. + * + * 2000.6.6 Initial implementation of routines for US NI1 + * Layer 3 protocol based on the EURO/DSS1 D-channel protocol + * driver written by Karsten Keil et al. Thanks also for the + * code provided by Ragnar Paulson. + * + */ + +#ifndef l3ni1_process + +#define T302 15000 +#define T303 4000 +#define T304 30000 +#define T305 30000 +#define T308 4000 +/* for layer 1 certification T309 < layer1 T3 (e.g. 4000) */ +/* This makes some tests easier and quicker */ +#define T309 40000 +#define T310 30000 +#define T313 4000 +#define T318 4000 +#define T319 4000 +#define TSPID 5000 /* was 2000 - Guy Ellis */ + +/* + * Message-Types + */ + +#define MT_ALERTING 0x01 +#define MT_CALL_PROCEEDING 0x02 +#define MT_CONNECT 0x07 +#define MT_CONNECT_ACKNOWLEDGE 0x0f +#define MT_PROGRESS 0x03 +#define MT_SETUP 0x05 +#define MT_SETUP_ACKNOWLEDGE 0x0d +#define MT_RESUME 0x26 +#define MT_RESUME_ACKNOWLEDGE 0x2e +#define MT_RESUME_REJECT 0x22 +#define MT_SUSPEND 0x25 +#define MT_SUSPEND_ACKNOWLEDGE 0x2d +#define MT_SUSPEND_REJECT 0x21 +#define MT_USER_INFORMATION 0x20 +#define MT_DISCONNECT 0x45 +#define MT_RELEASE 0x4d +#define MT_RELEASE_COMPLETE 0x5a +#define MT_RESTART 0x46 +#define MT_RESTART_ACKNOWLEDGE 0x4e +#define MT_SEGMENT 0x60 +#define MT_CONGESTION_CONTROL 0x79 +#define MT_INFORMATION 0x7b +#define MT_FACILITY 0x62 +#define MT_NOTIFY 0x6e +#define MT_STATUS 0x7d +#define MT_STATUS_ENQUIRY 0x75 +#define MT_DL_ESTABLISHED 0xfe + +#define IE_SEGMENT 0x00 +#define IE_BEARER 0x04 +#define IE_CAUSE 0x08 +#define IE_CALL_ID 0x10 +#define IE_CALL_STATE 0x14 +#define IE_CHANNEL_ID 0x18 +#define IE_FACILITY 0x1c +#define IE_PROGRESS 0x1e +#define IE_NET_FAC 0x20 +#define IE_NOTIFY 0x27 +#define IE_DISPLAY 0x28 +#define IE_DATE 0x29 +#define IE_KEYPAD 0x2c +#define IE_SIGNAL 0x34 +#define IE_SPID 0x3a +#define IE_ENDPOINT_ID 0x3b +#define IE_INFORATE 0x40 +#define IE_E2E_TDELAY 0x42 +#define IE_TDELAY_SEL 0x43 +#define IE_PACK_BINPARA 0x44 +#define IE_PACK_WINSIZE 0x45 +#define IE_PACK_SIZE 0x46 +#define IE_CUG 0x47 +#define IE_REV_CHARGE 0x4a +#define IE_CONNECT_PN 0x4c +#define IE_CONNECT_SUB 0x4d +#define IE_CALLING_PN 0x6c +#define IE_CALLING_SUB 0x6d +#define IE_CALLED_PN 0x70 +#define IE_CALLED_SUB 0x71 +#define IE_REDIR_NR 0x74 +#define IE_TRANS_SEL 0x78 +#define IE_RESTART_IND 0x79 +#define IE_LLC 0x7c +#define IE_HLC 0x7d +#define IE_USER_USER 0x7e +#define IE_ESCAPE 0x7f +#define IE_SHIFT 0x90 +#define IE_MORE_DATA 0xa0 +#define IE_COMPLETE 0xa1 +#define IE_CONGESTION 0xb0 +#define IE_REPEAT 0xd0 + +#define IE_MANDATORY 0x0100 +/* mandatory not in every case */ +#define IE_MANDATORY_1 0x0200 + +#define ERR_IE_COMPREHENSION 1 +#define ERR_IE_UNRECOGNIZED -1 +#define ERR_IE_LENGTH -2 +#define ERR_IE_SEQUENCE -3 + +#else /* only l3ni1_process */ + +/* l3ni1 specific data in l3 process */ +typedef struct +{ unsigned char invoke_id; /* used invoke id in remote ops, 0 = not active */ + ulong ll_id; /* remebered ll id */ + u8 remote_operation; /* handled remote operation, 0 = not active */ + int proc; /* rememered procedure */ + ulong remote_result; /* result of remote operation for statcallb */ + char uus1_data[35]; /* data send during alerting or disconnect */ +} ni1_proc_priv; + +/* l3dni1 specific data in protocol stack */ +typedef struct +{ unsigned char last_invoke_id; /* last used value for invoking */ + unsigned char invoke_used[32]; /* 256 bits for 256 values */ +} ni1_stk_priv; + +#endif /* only l3dni1_process */ diff --git a/drivers/isdn/hisax/lmgr.c b/drivers/isdn/hisax/lmgr.c new file mode 100644 index 000000000..5b63eb660 --- /dev/null +++ b/drivers/isdn/hisax/lmgr.c @@ -0,0 +1,50 @@ +/* $Id: lmgr.c,v 1.7.6.2 2001/09/23 22:24:50 kai Exp $ + * + * Layermanagement module + * + * Author Karsten Keil + * Copyright by Karsten Keil <keil@isdn4linux.de> + * + * This software may be used and distributed according to the terms + * of the GNU General Public License, incorporated herein by reference. + * + */ + +#include "hisax.h" + +static void +error_handling_dchan(struct PStack *st, int Error) +{ + switch (Error) { + case 'C': + case 'D': + case 'G': + case 'H': + st->l2.l2tei(st, MDL_ERROR | REQUEST, NULL); + break; + } +} + +static void +hisax_manager(struct PStack *st, int pr, void *arg) +{ + long Code; + + switch (pr) { + case (MDL_ERROR | INDICATION): + Code = (long) arg; + HiSax_putstatus(st->l1.hardware, "manager: MDL_ERROR", + " %c %s", (char)Code, + test_bit(FLG_LAPD, &st->l2.flag) ? + "D-channel" : "B-channel"); + if (test_bit(FLG_LAPD, &st->l2.flag)) + error_handling_dchan(st, Code); + break; + } +} + +void +setstack_manager(struct PStack *st) +{ + st->ma.layer = hisax_manager; +} diff --git a/drivers/isdn/hisax/mic.c b/drivers/isdn/hisax/mic.c new file mode 100644 index 000000000..93398676f --- /dev/null +++ b/drivers/isdn/hisax/mic.c @@ -0,0 +1,235 @@ +/* $Id: mic.c,v 1.12.2.4 2004/01/13 23:48:39 keil Exp $ + * + * low level stuff for mic cards + * + * Author Stephan von Krawczynski + * Copyright by Stephan von Krawczynski <skraw@ithnet.com> + * + * This software may be used and distributed according to the terms + * of the GNU General Public License, incorporated herein by reference. + * + */ + +#include <linux/init.h> +#include "hisax.h" +#include "isac.h" +#include "hscx.h" +#include "isdnl1.h" + +static const char *mic_revision = "$Revision: 1.12.2.4 $"; + +#define byteout(addr, val) outb(val, addr) +#define bytein(addr) inb(addr) + +#define MIC_ISAC 2 +#define MIC_HSCX 1 +#define MIC_ADR 7 + +/* CARD_ADR (Write) */ +#define MIC_RESET 0x3 /* same as DOS driver */ + +static inline u_char +readreg(unsigned int ale, unsigned int adr, u_char off) +{ + register u_char ret; + + byteout(ale, off); + ret = bytein(adr); + return (ret); +} + +static inline void +readfifo(unsigned int ale, unsigned int adr, u_char off, u_char *data, int size) +{ + byteout(ale, off); + insb(adr, data, size); +} + + +static inline void +writereg(unsigned int ale, unsigned int adr, u_char off, u_char data) +{ + byteout(ale, off); + byteout(adr, data); +} + +static inline void +writefifo(unsigned int ale, unsigned int adr, u_char off, u_char *data, int size) +{ + byteout(ale, off); + outsb(adr, data, size); +} + +/* Interface functions */ + +static u_char +ReadISAC(struct IsdnCardState *cs, u_char offset) +{ + return (readreg(cs->hw.mic.adr, cs->hw.mic.isac, offset)); +} + +static void +WriteISAC(struct IsdnCardState *cs, u_char offset, u_char value) +{ + writereg(cs->hw.mic.adr, cs->hw.mic.isac, offset, value); +} + +static void +ReadISACfifo(struct IsdnCardState *cs, u_char *data, int size) +{ + readfifo(cs->hw.mic.adr, cs->hw.mic.isac, 0, data, size); +} + +static void +WriteISACfifo(struct IsdnCardState *cs, u_char *data, int size) +{ + writefifo(cs->hw.mic.adr, cs->hw.mic.isac, 0, data, size); +} + +static u_char +ReadHSCX(struct IsdnCardState *cs, int hscx, u_char offset) +{ + return (readreg(cs->hw.mic.adr, + cs->hw.mic.hscx, offset + (hscx ? 0x40 : 0))); +} + +static void +WriteHSCX(struct IsdnCardState *cs, int hscx, u_char offset, u_char value) +{ + writereg(cs->hw.mic.adr, + cs->hw.mic.hscx, offset + (hscx ? 0x40 : 0), value); +} + +/* + * fast interrupt HSCX stuff goes here + */ + +#define READHSCX(cs, nr, reg) readreg(cs->hw.mic.adr, \ + cs->hw.mic.hscx, reg + (nr ? 0x40 : 0)) +#define WRITEHSCX(cs, nr, reg, data) writereg(cs->hw.mic.adr, \ + cs->hw.mic.hscx, reg + (nr ? 0x40 : 0), data) + +#define READHSCXFIFO(cs, nr, ptr, cnt) readfifo(cs->hw.mic.adr, \ + cs->hw.mic.hscx, (nr ? 0x40 : 0), ptr, cnt) + +#define WRITEHSCXFIFO(cs, nr, ptr, cnt) writefifo(cs->hw.mic.adr, \ + cs->hw.mic.hscx, (nr ? 0x40 : 0), ptr, cnt) + +#include "hscx_irq.c" + +static irqreturn_t +mic_interrupt(int intno, void *dev_id) +{ + struct IsdnCardState *cs = dev_id; + u_char val; + u_long flags; + + spin_lock_irqsave(&cs->lock, flags); + val = readreg(cs->hw.mic.adr, cs->hw.mic.hscx, HSCX_ISTA + 0x40); +Start_HSCX: + if (val) + hscx_int_main(cs, val); + val = readreg(cs->hw.mic.adr, cs->hw.mic.isac, ISAC_ISTA); +Start_ISAC: + if (val) + isac_interrupt(cs, val); + val = readreg(cs->hw.mic.adr, cs->hw.mic.hscx, HSCX_ISTA + 0x40); + if (val) { + if (cs->debug & L1_DEB_HSCX) + debugl1(cs, "HSCX IntStat after IntRoutine"); + goto Start_HSCX; + } + val = readreg(cs->hw.mic.adr, cs->hw.mic.isac, ISAC_ISTA); + if (val) { + if (cs->debug & L1_DEB_ISAC) + debugl1(cs, "ISAC IntStat after IntRoutine"); + goto Start_ISAC; + } + writereg(cs->hw.mic.adr, cs->hw.mic.hscx, HSCX_MASK, 0xFF); + writereg(cs->hw.mic.adr, cs->hw.mic.hscx, HSCX_MASK + 0x40, 0xFF); + writereg(cs->hw.mic.adr, cs->hw.mic.isac, ISAC_MASK, 0xFF); + writereg(cs->hw.mic.adr, cs->hw.mic.isac, ISAC_MASK, 0x0); + writereg(cs->hw.mic.adr, cs->hw.mic.hscx, HSCX_MASK, 0x0); + writereg(cs->hw.mic.adr, cs->hw.mic.hscx, HSCX_MASK + 0x40, 0x0); + spin_unlock_irqrestore(&cs->lock, flags); + return IRQ_HANDLED; +} + +static void +release_io_mic(struct IsdnCardState *cs) +{ + int bytecnt = 8; + + if (cs->hw.mic.cfg_reg) + release_region(cs->hw.mic.cfg_reg, bytecnt); +} + +static int +mic_card_msg(struct IsdnCardState *cs, int mt, void *arg) +{ + u_long flags; + + switch (mt) { + case CARD_RESET: + return (0); + case CARD_RELEASE: + release_io_mic(cs); + return (0); + case CARD_INIT: + spin_lock_irqsave(&cs->lock, flags); + inithscx(cs); /* /RTSA := ISAC RST */ + inithscxisac(cs, 3); + spin_unlock_irqrestore(&cs->lock, flags); + return (0); + case CARD_TEST: + return (0); + } + return (0); +} + +int setup_mic(struct IsdnCard *card) +{ + int bytecnt; + struct IsdnCardState *cs = card->cs; + char tmp[64]; + + strcpy(tmp, mic_revision); + printk(KERN_INFO "HiSax: mic driver Rev. %s\n", HiSax_getrev(tmp)); + if (cs->typ != ISDN_CTYPE_MIC) + return (0); + + bytecnt = 8; + cs->hw.mic.cfg_reg = card->para[1]; + cs->irq = card->para[0]; + cs->hw.mic.adr = cs->hw.mic.cfg_reg + MIC_ADR; + cs->hw.mic.isac = cs->hw.mic.cfg_reg + MIC_ISAC; + cs->hw.mic.hscx = cs->hw.mic.cfg_reg + MIC_HSCX; + + if (!request_region(cs->hw.mic.cfg_reg, bytecnt, "mic isdn")) { + printk(KERN_WARNING + "HiSax: ith mic config port %x-%x already in use\n", + cs->hw.mic.cfg_reg, + cs->hw.mic.cfg_reg + bytecnt); + return (0); + } + printk(KERN_INFO "mic: defined at 0x%x IRQ %d\n", + cs->hw.mic.cfg_reg, cs->irq); + setup_isac(cs); + cs->readisac = &ReadISAC; + cs->writeisac = &WriteISAC; + cs->readisacfifo = &ReadISACfifo; + cs->writeisacfifo = &WriteISACfifo; + cs->BC_Read_Reg = &ReadHSCX; + cs->BC_Write_Reg = &WriteHSCX; + cs->BC_Send_Data = &hscx_fill_fifo; + cs->cardmsg = &mic_card_msg; + cs->irq_func = &mic_interrupt; + ISACVersion(cs, "mic:"); + if (HscxVersion(cs, "mic:")) { + printk(KERN_WARNING + "mic: wrong HSCX versions check IO address\n"); + release_io_mic(cs); + return (0); + } + return (1); +} diff --git a/drivers/isdn/hisax/netjet.c b/drivers/isdn/hisax/netjet.c new file mode 100644 index 000000000..e932a152c --- /dev/null +++ b/drivers/isdn/hisax/netjet.c @@ -0,0 +1,985 @@ +/* $Id: netjet.c,v 1.29.2.4 2004/02/11 13:21:34 keil Exp $ + * + * low level stuff for Traverse Technologie NETJet ISDN cards + * + * Author Karsten Keil + * Copyright by Karsten Keil <keil@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 Traverse Technologies Australia for documents and information + * + * 16-Apr-2002 - led code added - Guy Ellis (guy@traverse.com.au) + * + */ + +#include <linux/init.h> +#include "hisax.h" +#include "isac.h" +#include "hscx.h" +#include "isdnl1.h" +#include <linux/interrupt.h> +#include <linux/ppp_defs.h> +#include <linux/slab.h> +#include <asm/io.h> +#include "netjet.h" + +/* Interface functions */ + +u_char +NETjet_ReadIC(struct IsdnCardState *cs, u_char offset) +{ + u_char ret; + + cs->hw.njet.auxd &= 0xfc; + cs->hw.njet.auxd |= (offset >> 4) & 3; + byteout(cs->hw.njet.auxa, cs->hw.njet.auxd); + ret = bytein(cs->hw.njet.isac + ((offset & 0xf) << 2)); + return (ret); +} + +void +NETjet_WriteIC(struct IsdnCardState *cs, u_char offset, u_char value) +{ + cs->hw.njet.auxd &= 0xfc; + cs->hw.njet.auxd |= (offset >> 4) & 3; + byteout(cs->hw.njet.auxa, cs->hw.njet.auxd); + byteout(cs->hw.njet.isac + ((offset & 0xf) << 2), value); +} + +void +NETjet_ReadICfifo(struct IsdnCardState *cs, u_char *data, int size) +{ + cs->hw.njet.auxd &= 0xfc; + byteout(cs->hw.njet.auxa, cs->hw.njet.auxd); + insb(cs->hw.njet.isac, data, size); +} + +void +NETjet_WriteICfifo(struct IsdnCardState *cs, u_char *data, int size) +{ + cs->hw.njet.auxd &= 0xfc; + byteout(cs->hw.njet.auxa, cs->hw.njet.auxd); + outsb(cs->hw.njet.isac, data, size); +} + +static void fill_mem(struct BCState *bcs, u_int *pos, u_int cnt, int chan, u_char fill) +{ + u_int mask = 0x000000ff, val = 0, *p = pos; + u_int i; + + val |= fill; + if (chan) { + val <<= 8; + mask <<= 8; + } + mask ^= 0xffffffff; + for (i = 0; i < cnt; i++) { + *p &= mask; + *p++ |= val; + if (p > bcs->hw.tiger.s_end) + p = bcs->hw.tiger.send; + } +} + +static void +mode_tiger(struct BCState *bcs, int mode, int bc) +{ + struct IsdnCardState *cs = bcs->cs; + u_char led; + + if (cs->debug & L1_DEB_HSCX) + debugl1(cs, "Tiger mode %d bchan %d/%d", + mode, bc, bcs->channel); + bcs->mode = mode; + bcs->channel = bc; + switch (mode) { + case (L1_MODE_NULL): + fill_mem(bcs, bcs->hw.tiger.send, + NETJET_DMA_TXSIZE, bc, 0xff); + if (cs->debug & L1_DEB_HSCX) + debugl1(cs, "Tiger stat rec %d/%d send %d", + bcs->hw.tiger.r_tot, bcs->hw.tiger.r_err, + bcs->hw.tiger.s_tot); + if ((cs->bcs[0].mode == L1_MODE_NULL) && + (cs->bcs[1].mode == L1_MODE_NULL)) { + cs->hw.njet.dmactrl = 0; + byteout(cs->hw.njet.base + NETJET_DMACTRL, + cs->hw.njet.dmactrl); + byteout(cs->hw.njet.base + NETJET_IRQMASK0, 0); + } + if (cs->typ == ISDN_CTYPE_NETJET_S) + { + // led off + led = bc & 0x01; + led = 0x01 << (6 + led); // convert to mask + led = ~led; + cs->hw.njet.auxd &= led; + byteout(cs->hw.njet.auxa, cs->hw.njet.auxd); + } + break; + case (L1_MODE_TRANS): + break; + case (L1_MODE_HDLC_56K): + case (L1_MODE_HDLC): + fill_mem(bcs, bcs->hw.tiger.send, + NETJET_DMA_TXSIZE, bc, 0xff); + bcs->hw.tiger.r_state = HDLC_ZERO_SEARCH; + bcs->hw.tiger.r_tot = 0; + bcs->hw.tiger.r_bitcnt = 0; + bcs->hw.tiger.r_one = 0; + bcs->hw.tiger.r_err = 0; + bcs->hw.tiger.s_tot = 0; + if (!cs->hw.njet.dmactrl) { + fill_mem(bcs, bcs->hw.tiger.send, + NETJET_DMA_TXSIZE, !bc, 0xff); + cs->hw.njet.dmactrl = 1; + byteout(cs->hw.njet.base + NETJET_DMACTRL, + cs->hw.njet.dmactrl); + byteout(cs->hw.njet.base + NETJET_IRQMASK0, 0x0f); + /* was 0x3f now 0x0f for TJ300 and TJ320 GE 13/07/00 */ + } + bcs->hw.tiger.sendp = bcs->hw.tiger.send; + bcs->hw.tiger.free = NETJET_DMA_TXSIZE; + test_and_set_bit(BC_FLG_EMPTY, &bcs->Flag); + if (cs->typ == ISDN_CTYPE_NETJET_S) + { + // led on + led = bc & 0x01; + led = 0x01 << (6 + led); // convert to mask + cs->hw.njet.auxd |= led; + byteout(cs->hw.njet.auxa, cs->hw.njet.auxd); + } + break; + } + if (cs->debug & L1_DEB_HSCX) + debugl1(cs, "tiger: set %x %x %x %x/%x pulse=%d", + bytein(cs->hw.njet.base + NETJET_DMACTRL), + bytein(cs->hw.njet.base + NETJET_IRQMASK0), + bytein(cs->hw.njet.base + NETJET_IRQSTAT0), + inl(cs->hw.njet.base + NETJET_DMA_READ_ADR), + inl(cs->hw.njet.base + NETJET_DMA_WRITE_ADR), + bytein(cs->hw.njet.base + NETJET_PULSE_CNT)); +} + +static void printframe(struct IsdnCardState *cs, u_char *buf, int count, char *s) { + char tmp[128]; + char *t = tmp; + int i = count, j; + u_char *p = buf; + + t += sprintf(t, "tiger %s(%4d)", s, count); + while (i > 0) { + if (i > 16) + j = 16; + else + j = i; + QuickHex(t, p, j); + debugl1(cs, "%s", tmp); + p += j; + i -= j; + t = tmp; + t += sprintf(t, "tiger %s ", s); + } +} + +// macro for 64k + +#define MAKE_RAW_BYTE for (j = 0; j < 8; j++) { \ + bitcnt++; \ + s_val >>= 1; \ + if (val & 1) { \ + s_one++; \ + s_val |= 0x80; \ + } else { \ + s_one = 0; \ + s_val &= 0x7f; \ + } \ + if (bitcnt == 8) { \ + bcs->hw.tiger.sendbuf[s_cnt++] = s_val; \ + bitcnt = 0; \ + } \ + if (s_one == 5) { \ + s_val >>= 1; \ + s_val &= 0x7f; \ + bitcnt++; \ + s_one = 0; \ + } \ + if (bitcnt == 8) { \ + bcs->hw.tiger.sendbuf[s_cnt++] = s_val; \ + bitcnt = 0; \ + } \ + val >>= 1; \ + } + +static int make_raw_data(struct BCState *bcs) { +// this make_raw is for 64k + register u_int i, s_cnt = 0; + register u_char j; + register u_char val; + register u_char s_one = 0; + register u_char s_val = 0; + register u_char bitcnt = 0; + u_int fcs; + + if (!bcs->tx_skb) { + debugl1(bcs->cs, "tiger make_raw: NULL skb"); + return (1); + } + bcs->hw.tiger.sendbuf[s_cnt++] = HDLC_FLAG_VALUE; + fcs = PPP_INITFCS; + for (i = 0; i < bcs->tx_skb->len; i++) { + val = bcs->tx_skb->data[i]; + fcs = PPP_FCS(fcs, val); + MAKE_RAW_BYTE; + } + fcs ^= 0xffff; + val = fcs & 0xff; + MAKE_RAW_BYTE; + val = (fcs >> 8) & 0xff; + MAKE_RAW_BYTE; + val = HDLC_FLAG_VALUE; + for (j = 0; j < 8; j++) { + bitcnt++; + s_val >>= 1; + if (val & 1) + s_val |= 0x80; + else + s_val &= 0x7f; + if (bitcnt == 8) { + bcs->hw.tiger.sendbuf[s_cnt++] = s_val; + bitcnt = 0; + } + val >>= 1; + } + if (bcs->cs->debug & L1_DEB_HSCX) + debugl1(bcs->cs, "tiger make_raw: in %u out %d.%d", + bcs->tx_skb->len, s_cnt, bitcnt); + if (bitcnt) { + while (8 > bitcnt++) { + s_val >>= 1; + s_val |= 0x80; + } + bcs->hw.tiger.sendbuf[s_cnt++] = s_val; + bcs->hw.tiger.sendbuf[s_cnt++] = 0xff; // NJ<->NJ thoughput bug fix + } + bcs->hw.tiger.sendcnt = s_cnt; + bcs->tx_cnt -= bcs->tx_skb->len; + bcs->hw.tiger.sp = bcs->hw.tiger.sendbuf; + return (0); +} + +// macro for 56k + +#define MAKE_RAW_BYTE_56K for (j = 0; j < 8; j++) { \ + bitcnt++; \ + s_val >>= 1; \ + if (val & 1) { \ + s_one++; \ + s_val |= 0x80; \ + } else { \ + s_one = 0; \ + s_val &= 0x7f; \ + } \ + if (bitcnt == 7) { \ + s_val >>= 1; \ + s_val |= 0x80; \ + bcs->hw.tiger.sendbuf[s_cnt++] = s_val; \ + bitcnt = 0; \ + } \ + if (s_one == 5) { \ + s_val >>= 1; \ + s_val &= 0x7f; \ + bitcnt++; \ + s_one = 0; \ + } \ + if (bitcnt == 7) { \ + s_val >>= 1; \ + s_val |= 0x80; \ + bcs->hw.tiger.sendbuf[s_cnt++] = s_val; \ + bitcnt = 0; \ + } \ + val >>= 1; \ + } + +static int make_raw_data_56k(struct BCState *bcs) { +// this make_raw is for 56k + register u_int i, s_cnt = 0; + register u_char j; + register u_char val; + register u_char s_one = 0; + register u_char s_val = 0; + register u_char bitcnt = 0; + u_int fcs; + + if (!bcs->tx_skb) { + debugl1(bcs->cs, "tiger make_raw_56k: NULL skb"); + return (1); + } + val = HDLC_FLAG_VALUE; + for (j = 0; j < 8; j++) { + bitcnt++; + s_val >>= 1; + if (val & 1) + s_val |= 0x80; + else + s_val &= 0x7f; + if (bitcnt == 7) { + s_val >>= 1; + s_val |= 0x80; + bcs->hw.tiger.sendbuf[s_cnt++] = s_val; + bitcnt = 0; + } + val >>= 1; + }; + fcs = PPP_INITFCS; + for (i = 0; i < bcs->tx_skb->len; i++) { + val = bcs->tx_skb->data[i]; + fcs = PPP_FCS(fcs, val); + MAKE_RAW_BYTE_56K; + } + fcs ^= 0xffff; + val = fcs & 0xff; + MAKE_RAW_BYTE_56K; + val = (fcs >> 8) & 0xff; + MAKE_RAW_BYTE_56K; + val = HDLC_FLAG_VALUE; + for (j = 0; j < 8; j++) { + bitcnt++; + s_val >>= 1; + if (val & 1) + s_val |= 0x80; + else + s_val &= 0x7f; + if (bitcnt == 7) { + s_val >>= 1; + s_val |= 0x80; + bcs->hw.tiger.sendbuf[s_cnt++] = s_val; + bitcnt = 0; + } + val >>= 1; + } + if (bcs->cs->debug & L1_DEB_HSCX) + debugl1(bcs->cs, "tiger make_raw_56k: in %u out %d.%d", + bcs->tx_skb->len, s_cnt, bitcnt); + if (bitcnt) { + while (8 > bitcnt++) { + s_val >>= 1; + s_val |= 0x80; + } + bcs->hw.tiger.sendbuf[s_cnt++] = s_val; + bcs->hw.tiger.sendbuf[s_cnt++] = 0xff; // NJ<->NJ thoughput bug fix + } + bcs->hw.tiger.sendcnt = s_cnt; + bcs->tx_cnt -= bcs->tx_skb->len; + bcs->hw.tiger.sp = bcs->hw.tiger.sendbuf; + return (0); +} + +static void got_frame(struct BCState *bcs, int count) { + struct sk_buff *skb; + + if (!(skb = dev_alloc_skb(count))) + printk(KERN_WARNING "TIGER: receive out of memory\n"); + else { + skb_put_data(skb, bcs->hw.tiger.rcvbuf, count); + skb_queue_tail(&bcs->rqueue, skb); + } + test_and_set_bit(B_RCVBUFREADY, &bcs->event); + schedule_work(&bcs->tqueue); + + if (bcs->cs->debug & L1_DEB_RECEIVE_FRAME) + printframe(bcs->cs, bcs->hw.tiger.rcvbuf, count, "rec"); +} + + + +static void read_raw(struct BCState *bcs, u_int *buf, int cnt) { + int i; + register u_char j; + register u_char val; + u_int *pend = bcs->hw.tiger.rec + NETJET_DMA_RXSIZE - 1; + register u_char state = bcs->hw.tiger.r_state; + register u_char r_one = bcs->hw.tiger.r_one; + register u_char r_val = bcs->hw.tiger.r_val; + register u_int bitcnt = bcs->hw.tiger.r_bitcnt; + u_int *p = buf; + int bits; + u_char mask; + + if (bcs->mode == L1_MODE_HDLC) { // it's 64k + mask = 0xff; + bits = 8; + } + else { // it's 56K + mask = 0x7f; + bits = 7; + }; + for (i = 0; i < cnt; i++) { + val = bcs->channel ? ((*p >> 8) & 0xff) : (*p & 0xff); + p++; + if (p > pend) + p = bcs->hw.tiger.rec; + if ((val & mask) == mask) { + state = HDLC_ZERO_SEARCH; + bcs->hw.tiger.r_tot++; + bitcnt = 0; + r_one = 0; + continue; + } + for (j = 0; j < bits; j++) { + if (state == HDLC_ZERO_SEARCH) { + if (val & 1) { + r_one++; + } else { + r_one = 0; + state = HDLC_FLAG_SEARCH; + if (bcs->cs->debug & L1_DEB_HSCX) + debugl1(bcs->cs, "tiger read_raw: zBit(%d,%d,%d) %x", + bcs->hw.tiger.r_tot, i, j, val); + } + } else if (state == HDLC_FLAG_SEARCH) { + if (val & 1) { + r_one++; + if (r_one > 6) { + state = HDLC_ZERO_SEARCH; + } + } else { + if (r_one == 6) { + bitcnt = 0; + r_val = 0; + state = HDLC_FLAG_FOUND; + if (bcs->cs->debug & L1_DEB_HSCX) + debugl1(bcs->cs, "tiger read_raw: flag(%d,%d,%d) %x", + bcs->hw.tiger.r_tot, i, j, val); + } + r_one = 0; + } + } else if (state == HDLC_FLAG_FOUND) { + if (val & 1) { + r_one++; + if (r_one > 6) { + state = HDLC_ZERO_SEARCH; + } else { + r_val >>= 1; + r_val |= 0x80; + bitcnt++; + } + } else { + if (r_one == 6) { + bitcnt = 0; + r_val = 0; + r_one = 0; + val >>= 1; + continue; + } else if (r_one != 5) { + r_val >>= 1; + r_val &= 0x7f; + bitcnt++; + } + r_one = 0; + } + if ((state != HDLC_ZERO_SEARCH) && + !(bitcnt & 7)) { + state = HDLC_FRAME_FOUND; + bcs->hw.tiger.r_fcs = PPP_INITFCS; + bcs->hw.tiger.rcvbuf[0] = r_val; + bcs->hw.tiger.r_fcs = PPP_FCS(bcs->hw.tiger.r_fcs, r_val); + if (bcs->cs->debug & L1_DEB_HSCX) + debugl1(bcs->cs, "tiger read_raw: byte1(%d,%d,%d) rval %x val %x i %x", + bcs->hw.tiger.r_tot, i, j, r_val, val, + bcs->cs->hw.njet.irqstat0); + } + } else if (state == HDLC_FRAME_FOUND) { + if (val & 1) { + r_one++; + if (r_one > 6) { + state = HDLC_ZERO_SEARCH; + bitcnt = 0; + } else { + r_val >>= 1; + r_val |= 0x80; + bitcnt++; + } + } else { + if (r_one == 6) { + r_val = 0; + r_one = 0; + bitcnt++; + if (bitcnt & 7) { + debugl1(bcs->cs, "tiger: frame not byte aligned"); + state = HDLC_FLAG_SEARCH; + bcs->hw.tiger.r_err++; +#ifdef ERROR_STATISTIC + bcs->err_inv++; +#endif + } else { + if (bcs->cs->debug & L1_DEB_HSCX) + debugl1(bcs->cs, "tiger frame end(%d,%d): fcs(%x) i %x", + i, j, bcs->hw.tiger.r_fcs, bcs->cs->hw.njet.irqstat0); + if (bcs->hw.tiger.r_fcs == PPP_GOODFCS) { + got_frame(bcs, (bitcnt >> 3) - 3); + } else { + if (bcs->cs->debug) { + debugl1(bcs->cs, "tiger FCS error"); + printframe(bcs->cs, bcs->hw.tiger.rcvbuf, + (bitcnt >> 3) - 1, "rec"); + bcs->hw.tiger.r_err++; + } +#ifdef ERROR_STATISTIC + bcs->err_crc++; +#endif + } + state = HDLC_FLAG_FOUND; + } + bitcnt = 0; + } else if (r_one == 5) { + val >>= 1; + r_one = 0; + continue; + } else { + r_val >>= 1; + r_val &= 0x7f; + bitcnt++; + } + r_one = 0; + } + if ((state == HDLC_FRAME_FOUND) && + !(bitcnt & 7)) { + if ((bitcnt >> 3) >= HSCX_BUFMAX) { + debugl1(bcs->cs, "tiger: frame too big"); + r_val = 0; + state = HDLC_FLAG_SEARCH; + bcs->hw.tiger.r_err++; +#ifdef ERROR_STATISTIC + bcs->err_inv++; +#endif + } else { + bcs->hw.tiger.rcvbuf[(bitcnt >> 3) - 1] = r_val; + bcs->hw.tiger.r_fcs = + PPP_FCS(bcs->hw.tiger.r_fcs, r_val); + } + } + } + val >>= 1; + } + bcs->hw.tiger.r_tot++; + } + bcs->hw.tiger.r_state = state; + bcs->hw.tiger.r_one = r_one; + bcs->hw.tiger.r_val = r_val; + bcs->hw.tiger.r_bitcnt = bitcnt; +} + +void read_tiger(struct IsdnCardState *cs) { + u_int *p; + int cnt = NETJET_DMA_RXSIZE / 2; + + if ((cs->hw.njet.irqstat0 & cs->hw.njet.last_is0) & NETJET_IRQM0_READ) { + debugl1(cs, "tiger warn read double dma %x/%x", + cs->hw.njet.irqstat0, cs->hw.njet.last_is0); +#ifdef ERROR_STATISTIC + if (cs->bcs[0].mode) + cs->bcs[0].err_rdo++; + if (cs->bcs[1].mode) + cs->bcs[1].err_rdo++; +#endif + return; + } else { + cs->hw.njet.last_is0 &= ~NETJET_IRQM0_READ; + cs->hw.njet.last_is0 |= (cs->hw.njet.irqstat0 & NETJET_IRQM0_READ); + } + if (cs->hw.njet.irqstat0 & NETJET_IRQM0_READ_1) + p = cs->bcs[0].hw.tiger.rec + NETJET_DMA_RXSIZE - 1; + else + p = cs->bcs[0].hw.tiger.rec + cnt - 1; + if ((cs->bcs[0].mode == L1_MODE_HDLC) || (cs->bcs[0].mode == L1_MODE_HDLC_56K)) + read_raw(cs->bcs, p, cnt); + + if ((cs->bcs[1].mode == L1_MODE_HDLC) || (cs->bcs[1].mode == L1_MODE_HDLC_56K)) + read_raw(cs->bcs + 1, p, cnt); + cs->hw.njet.irqstat0 &= ~NETJET_IRQM0_READ; +} + +static void write_raw(struct BCState *bcs, u_int *buf, int cnt); + +void netjet_fill_dma(struct BCState *bcs) +{ + register u_int *p, *sp; + register int cnt; + + if (!bcs->tx_skb) + return; + if (bcs->cs->debug & L1_DEB_HSCX) + debugl1(bcs->cs, "tiger fill_dma1: c%d %4lx", bcs->channel, + bcs->Flag); + if (test_and_set_bit(BC_FLG_BUSY, &bcs->Flag)) + return; + if (bcs->mode == L1_MODE_HDLC) { // it's 64k + if (make_raw_data(bcs)) + return; + } + else { // it's 56k + if (make_raw_data_56k(bcs)) + return; + }; + if (bcs->cs->debug & L1_DEB_HSCX) + debugl1(bcs->cs, "tiger fill_dma2: c%d %4lx", bcs->channel, + bcs->Flag); + if (test_and_clear_bit(BC_FLG_NOFRAME, &bcs->Flag)) { + write_raw(bcs, bcs->hw.tiger.sendp, bcs->hw.tiger.free); + } else if (test_and_clear_bit(BC_FLG_HALF, &bcs->Flag)) { + p = bus_to_virt(inl(bcs->cs->hw.njet.base + NETJET_DMA_READ_ADR)); + sp = bcs->hw.tiger.sendp; + if (p == bcs->hw.tiger.s_end) + p = bcs->hw.tiger.send - 1; + if (sp == bcs->hw.tiger.s_end) + sp = bcs->hw.tiger.send - 1; + cnt = p - sp; + if (cnt < 0) { + write_raw(bcs, bcs->hw.tiger.sendp, bcs->hw.tiger.free); + } else { + p++; + cnt++; + if (p > bcs->hw.tiger.s_end) + p = bcs->hw.tiger.send; + p++; + cnt++; + if (p > bcs->hw.tiger.s_end) + p = bcs->hw.tiger.send; + write_raw(bcs, p, bcs->hw.tiger.free - cnt); + } + } else if (test_and_clear_bit(BC_FLG_EMPTY, &bcs->Flag)) { + p = bus_to_virt(inl(bcs->cs->hw.njet.base + NETJET_DMA_READ_ADR)); + cnt = bcs->hw.tiger.s_end - p; + if (cnt < 2) { + p = bcs->hw.tiger.send + 1; + cnt = NETJET_DMA_TXSIZE / 2 - 2; + } else { + p++; + p++; + if (cnt <= (NETJET_DMA_TXSIZE / 2)) + cnt += NETJET_DMA_TXSIZE / 2; + cnt--; + cnt--; + } + write_raw(bcs, p, cnt); + } + if (bcs->cs->debug & L1_DEB_HSCX) + debugl1(bcs->cs, "tiger fill_dma3: c%d %4lx", bcs->channel, + bcs->Flag); +} + +static void write_raw(struct BCState *bcs, u_int *buf, int cnt) { + u_int mask, val, *p = buf; + u_int i, s_cnt; + + if (cnt <= 0) + return; + if (test_bit(BC_FLG_BUSY, &bcs->Flag)) { + if (bcs->hw.tiger.sendcnt > cnt) { + s_cnt = cnt; + bcs->hw.tiger.sendcnt -= cnt; + } else { + s_cnt = bcs->hw.tiger.sendcnt; + bcs->hw.tiger.sendcnt = 0; + } + if (bcs->channel) + mask = 0xffff00ff; + else + mask = 0xffffff00; + for (i = 0; i < s_cnt; i++) { + val = bcs->channel ? ((bcs->hw.tiger.sp[i] << 8) & 0xff00) : + (bcs->hw.tiger.sp[i]); + *p &= mask; + *p++ |= val; + if (p > bcs->hw.tiger.s_end) + p = bcs->hw.tiger.send; + } + bcs->hw.tiger.s_tot += s_cnt; + if (bcs->cs->debug & L1_DEB_HSCX) + debugl1(bcs->cs, "tiger write_raw: c%d %p-%p %d/%d %d %x", bcs->channel, + buf, p, s_cnt, cnt, + bcs->hw.tiger.sendcnt, bcs->cs->hw.njet.irqstat0); + if (bcs->cs->debug & L1_DEB_HSCX_FIFO) + printframe(bcs->cs, bcs->hw.tiger.sp, s_cnt, "snd"); + bcs->hw.tiger.sp += s_cnt; + bcs->hw.tiger.sendp = p; + if (!bcs->hw.tiger.sendcnt) { + if (!bcs->tx_skb) { + debugl1(bcs->cs, "tiger write_raw: NULL skb s_cnt %d", s_cnt); + } else { + if (test_bit(FLG_LLI_L1WAKEUP, &bcs->st->lli.flag) && + (PACKET_NOACK != bcs->tx_skb->pkt_type)) { + u_long flags; + spin_lock_irqsave(&bcs->aclock, flags); + bcs->ackcnt += bcs->tx_skb->len; + spin_unlock_irqrestore(&bcs->aclock, flags); + schedule_event(bcs, B_ACKPENDING); + } + dev_kfree_skb_any(bcs->tx_skb); + bcs->tx_skb = NULL; + } + test_and_clear_bit(BC_FLG_BUSY, &bcs->Flag); + bcs->hw.tiger.free = cnt - s_cnt; + if (bcs->hw.tiger.free > (NETJET_DMA_TXSIZE / 2)) + test_and_set_bit(BC_FLG_HALF, &bcs->Flag); + else { + test_and_clear_bit(BC_FLG_HALF, &bcs->Flag); + test_and_set_bit(BC_FLG_NOFRAME, &bcs->Flag); + } + if ((bcs->tx_skb = skb_dequeue(&bcs->squeue))) { + netjet_fill_dma(bcs); + } else { + mask ^= 0xffffffff; + if (s_cnt < cnt) { + for (i = s_cnt; i < cnt; i++) { + *p++ |= mask; + if (p > bcs->hw.tiger.s_end) + p = bcs->hw.tiger.send; + } + if (bcs->cs->debug & L1_DEB_HSCX) + debugl1(bcs->cs, "tiger write_raw: fill rest %d", + cnt - s_cnt); + } + test_and_set_bit(B_XMTBUFREADY, &bcs->event); + schedule_work(&bcs->tqueue); + } + } + } else if (test_and_clear_bit(BC_FLG_NOFRAME, &bcs->Flag)) { + test_and_set_bit(BC_FLG_HALF, &bcs->Flag); + fill_mem(bcs, buf, cnt, bcs->channel, 0xff); + bcs->hw.tiger.free += cnt; + if (bcs->cs->debug & L1_DEB_HSCX) + debugl1(bcs->cs, "tiger write_raw: fill half"); + } else if (test_and_clear_bit(BC_FLG_HALF, &bcs->Flag)) { + test_and_set_bit(BC_FLG_EMPTY, &bcs->Flag); + fill_mem(bcs, buf, cnt, bcs->channel, 0xff); + if (bcs->cs->debug & L1_DEB_HSCX) + debugl1(bcs->cs, "tiger write_raw: fill full"); + } +} + +void write_tiger(struct IsdnCardState *cs) { + u_int *p, cnt = NETJET_DMA_TXSIZE / 2; + + if ((cs->hw.njet.irqstat0 & cs->hw.njet.last_is0) & NETJET_IRQM0_WRITE) { + debugl1(cs, "tiger warn write double dma %x/%x", + cs->hw.njet.irqstat0, cs->hw.njet.last_is0); +#ifdef ERROR_STATISTIC + if (cs->bcs[0].mode) + cs->bcs[0].err_tx++; + if (cs->bcs[1].mode) + cs->bcs[1].err_tx++; +#endif + return; + } else { + cs->hw.njet.last_is0 &= ~NETJET_IRQM0_WRITE; + cs->hw.njet.last_is0 |= (cs->hw.njet.irqstat0 & NETJET_IRQM0_WRITE); + } + if (cs->hw.njet.irqstat0 & NETJET_IRQM0_WRITE_1) + p = cs->bcs[0].hw.tiger.send + NETJET_DMA_TXSIZE - 1; + else + p = cs->bcs[0].hw.tiger.send + cnt - 1; + if ((cs->bcs[0].mode == L1_MODE_HDLC) || (cs->bcs[0].mode == L1_MODE_HDLC_56K)) + write_raw(cs->bcs, p, cnt); + if ((cs->bcs[1].mode == L1_MODE_HDLC) || (cs->bcs[1].mode == L1_MODE_HDLC_56K)) + write_raw(cs->bcs + 1, p, cnt); + cs->hw.njet.irqstat0 &= ~NETJET_IRQM0_WRITE; +} + +static void +tiger_l2l1(struct PStack *st, int pr, void *arg) +{ + struct BCState *bcs = st->l1.bcs; + struct sk_buff *skb = arg; + u_long flags; + + switch (pr) { + case (PH_DATA | REQUEST): + spin_lock_irqsave(&bcs->cs->lock, flags); + if (bcs->tx_skb) { + skb_queue_tail(&bcs->squeue, skb); + } else { + bcs->tx_skb = skb; + bcs->cs->BC_Send_Data(bcs); + } + spin_unlock_irqrestore(&bcs->cs->lock, flags); + break; + case (PH_PULL | INDICATION): + spin_lock_irqsave(&bcs->cs->lock, flags); + if (bcs->tx_skb) { + printk(KERN_WARNING "tiger_l2l1: this shouldn't happen\n"); + } else { + bcs->tx_skb = skb; + bcs->cs->BC_Send_Data(bcs); + } + spin_unlock_irqrestore(&bcs->cs->lock, flags); + break; + case (PH_PULL | REQUEST): + if (!bcs->tx_skb) { + test_and_clear_bit(FLG_L1_PULL_REQ, &st->l1.Flags); + st->l1.l1l2(st, PH_PULL | CONFIRM, NULL); + } else + test_and_set_bit(FLG_L1_PULL_REQ, &st->l1.Flags); + break; + case (PH_ACTIVATE | REQUEST): + spin_lock_irqsave(&bcs->cs->lock, flags); + test_and_set_bit(BC_FLG_ACTIV, &bcs->Flag); + mode_tiger(bcs, st->l1.mode, st->l1.bc); + /* 2001/10/04 Christoph Ersfeld, Formula-n Europe AG */ + spin_unlock_irqrestore(&bcs->cs->lock, flags); + bcs->cs->cardmsg(bcs->cs, MDL_BC_ASSIGN, (void *)(&st->l1.bc)); + l1_msg_b(st, pr, arg); + break; + case (PH_DEACTIVATE | REQUEST): + /* 2001/10/04 Christoph Ersfeld, Formula-n Europe AG */ + bcs->cs->cardmsg(bcs->cs, MDL_BC_RELEASE, (void *)(&st->l1.bc)); + l1_msg_b(st, pr, arg); + break; + case (PH_DEACTIVATE | CONFIRM): + spin_lock_irqsave(&bcs->cs->lock, flags); + test_and_clear_bit(BC_FLG_ACTIV, &bcs->Flag); + test_and_clear_bit(BC_FLG_BUSY, &bcs->Flag); + mode_tiger(bcs, 0, st->l1.bc); + spin_unlock_irqrestore(&bcs->cs->lock, flags); + st->l1.l1l2(st, PH_DEACTIVATE | CONFIRM, NULL); + break; + } +} + + +static void +close_tigerstate(struct BCState *bcs) +{ + mode_tiger(bcs, 0, bcs->channel); + if (test_and_clear_bit(BC_FLG_INIT, &bcs->Flag)) { + kfree(bcs->hw.tiger.rcvbuf); + bcs->hw.tiger.rcvbuf = NULL; + kfree(bcs->hw.tiger.sendbuf); + bcs->hw.tiger.sendbuf = NULL; + skb_queue_purge(&bcs->rqueue); + skb_queue_purge(&bcs->squeue); + if (bcs->tx_skb) { + dev_kfree_skb_any(bcs->tx_skb); + bcs->tx_skb = NULL; + test_and_clear_bit(BC_FLG_BUSY, &bcs->Flag); + } + } +} + +static int +open_tigerstate(struct IsdnCardState *cs, struct BCState *bcs) +{ + if (!test_and_set_bit(BC_FLG_INIT, &bcs->Flag)) { + if (!(bcs->hw.tiger.rcvbuf = kmalloc(HSCX_BUFMAX, GFP_ATOMIC))) { + printk(KERN_WARNING + "HiSax: No memory for tiger.rcvbuf\n"); + return (1); + } + if (!(bcs->hw.tiger.sendbuf = kmalloc(RAW_BUFMAX, GFP_ATOMIC))) { + printk(KERN_WARNING + "HiSax: No memory for tiger.sendbuf\n"); + return (1); + } + skb_queue_head_init(&bcs->rqueue); + skb_queue_head_init(&bcs->squeue); + } + bcs->tx_skb = NULL; + bcs->hw.tiger.sendcnt = 0; + test_and_clear_bit(BC_FLG_BUSY, &bcs->Flag); + bcs->event = 0; + bcs->tx_cnt = 0; + return (0); +} + +static int +setstack_tiger(struct PStack *st, struct BCState *bcs) +{ + bcs->channel = st->l1.bc; + if (open_tigerstate(st->l1.hardware, bcs)) + return (-1); + st->l1.bcs = bcs; + st->l2.l2l1 = tiger_l2l1; + setstack_manager(st); + bcs->st = st; + setstack_l1_B(st); + return (0); +} + + +void +inittiger(struct IsdnCardState *cs) +{ + cs->bcs[0].hw.tiger.send = kmalloc_array(NETJET_DMA_TXSIZE, + sizeof(unsigned int), + GFP_KERNEL | GFP_DMA); + if (!cs->bcs[0].hw.tiger.send) { + printk(KERN_WARNING + "HiSax: No memory for tiger.send\n"); + return; + } + cs->bcs[0].hw.tiger.s_irq = cs->bcs[0].hw.tiger.send + NETJET_DMA_TXSIZE / 2 - 1; + cs->bcs[0].hw.tiger.s_end = cs->bcs[0].hw.tiger.send + NETJET_DMA_TXSIZE - 1; + cs->bcs[1].hw.tiger.send = cs->bcs[0].hw.tiger.send; + cs->bcs[1].hw.tiger.s_irq = cs->bcs[0].hw.tiger.s_irq; + cs->bcs[1].hw.tiger.s_end = cs->bcs[0].hw.tiger.s_end; + + memset(cs->bcs[0].hw.tiger.send, 0xff, NETJET_DMA_TXSIZE * sizeof(unsigned int)); + debugl1(cs, "tiger: send buf %p - %p", cs->bcs[0].hw.tiger.send, + cs->bcs[0].hw.tiger.send + NETJET_DMA_TXSIZE - 1); + outl(virt_to_bus(cs->bcs[0].hw.tiger.send), + cs->hw.njet.base + NETJET_DMA_READ_START); + outl(virt_to_bus(cs->bcs[0].hw.tiger.s_irq), + cs->hw.njet.base + NETJET_DMA_READ_IRQ); + outl(virt_to_bus(cs->bcs[0].hw.tiger.s_end), + cs->hw.njet.base + NETJET_DMA_READ_END); + cs->bcs[0].hw.tiger.rec = kmalloc_array(NETJET_DMA_RXSIZE, + sizeof(unsigned int), + GFP_KERNEL | GFP_DMA); + if (!cs->bcs[0].hw.tiger.rec) { + printk(KERN_WARNING + "HiSax: No memory for tiger.rec\n"); + return; + } + debugl1(cs, "tiger: rec buf %p - %p", cs->bcs[0].hw.tiger.rec, + cs->bcs[0].hw.tiger.rec + NETJET_DMA_RXSIZE - 1); + cs->bcs[1].hw.tiger.rec = cs->bcs[0].hw.tiger.rec; + memset(cs->bcs[0].hw.tiger.rec, 0xff, NETJET_DMA_RXSIZE * sizeof(unsigned int)); + outl(virt_to_bus(cs->bcs[0].hw.tiger.rec), + cs->hw.njet.base + NETJET_DMA_WRITE_START); + outl(virt_to_bus(cs->bcs[0].hw.tiger.rec + NETJET_DMA_RXSIZE / 2 - 1), + cs->hw.njet.base + NETJET_DMA_WRITE_IRQ); + outl(virt_to_bus(cs->bcs[0].hw.tiger.rec + NETJET_DMA_RXSIZE - 1), + cs->hw.njet.base + NETJET_DMA_WRITE_END); + debugl1(cs, "tiger: dmacfg %x/%x pulse=%d", + inl(cs->hw.njet.base + NETJET_DMA_WRITE_ADR), + inl(cs->hw.njet.base + NETJET_DMA_READ_ADR), + bytein(cs->hw.njet.base + NETJET_PULSE_CNT)); + cs->hw.njet.last_is0 = 0; + cs->bcs[0].BC_SetStack = setstack_tiger; + cs->bcs[1].BC_SetStack = setstack_tiger; + cs->bcs[0].BC_Close = close_tigerstate; + cs->bcs[1].BC_Close = close_tigerstate; +} + +static void +releasetiger(struct IsdnCardState *cs) +{ + kfree(cs->bcs[0].hw.tiger.send); + cs->bcs[0].hw.tiger.send = NULL; + cs->bcs[1].hw.tiger.send = NULL; + kfree(cs->bcs[0].hw.tiger.rec); + cs->bcs[0].hw.tiger.rec = NULL; + cs->bcs[1].hw.tiger.rec = NULL; +} + +void +release_io_netjet(struct IsdnCardState *cs) +{ + byteout(cs->hw.njet.base + NETJET_IRQMASK0, 0); + byteout(cs->hw.njet.base + NETJET_IRQMASK1, 0); + releasetiger(cs); + release_region(cs->hw.njet.base, 256); +} diff --git a/drivers/isdn/hisax/netjet.h b/drivers/isdn/hisax/netjet.h new file mode 100644 index 000000000..70590d5d5 --- /dev/null +++ b/drivers/isdn/hisax/netjet.h @@ -0,0 +1,69 @@ +/* $Id: netjet.h,v 2.8.2.2 2004/01/12 22:52:28 keil Exp $ + * + * NETjet common header file + * + * Author Karsten Keil + * Copyright by Karsten Keil <keil@isdn4linux.de> + * by Matt Henderson, + * Traverse Technologies P/L www.traverse.com.au + * + * This software may be used and distributed according to the terms + * of the GNU General Public License, incorporated herein by reference. + * + */ + +#define byteout(addr, val) outb(val, addr) +#define bytein(addr) inb(addr) + +#define NETJET_CTRL 0x00 +#define NETJET_DMACTRL 0x01 +#define NETJET_AUXCTRL 0x02 +#define NETJET_AUXDATA 0x03 +#define NETJET_IRQMASK0 0x04 +#define NETJET_IRQMASK1 0x05 +#define NETJET_IRQSTAT0 0x06 +#define NETJET_IRQSTAT1 0x07 +#define NETJET_DMA_READ_START 0x08 +#define NETJET_DMA_READ_IRQ 0x0c +#define NETJET_DMA_READ_END 0x10 +#define NETJET_DMA_READ_ADR 0x14 +#define NETJET_DMA_WRITE_START 0x18 +#define NETJET_DMA_WRITE_IRQ 0x1c +#define NETJET_DMA_WRITE_END 0x20 +#define NETJET_DMA_WRITE_ADR 0x24 +#define NETJET_PULSE_CNT 0x28 + +#define NETJET_ISAC_OFF 0xc0 +#define NETJET_ISACIRQ 0x10 +#define NETJET_IRQM0_READ 0x0c +#define NETJET_IRQM0_READ_1 0x04 +#define NETJET_IRQM0_READ_2 0x08 +#define NETJET_IRQM0_WRITE 0x03 +#define NETJET_IRQM0_WRITE_1 0x01 +#define NETJET_IRQM0_WRITE_2 0x02 + +#define NETJET_DMA_TXSIZE 512 +#define NETJET_DMA_RXSIZE 128 + +#define HDLC_ZERO_SEARCH 0 +#define HDLC_FLAG_SEARCH 1 +#define HDLC_FLAG_FOUND 2 +#define HDLC_FRAME_FOUND 3 +#define HDLC_NULL 4 +#define HDLC_PART 5 +#define HDLC_FULL 6 + +#define HDLC_FLAG_VALUE 0x7e + +u_char NETjet_ReadIC(struct IsdnCardState *cs, u_char offset); +void NETjet_WriteIC(struct IsdnCardState *cs, u_char offset, u_char value); +void NETjet_ReadICfifo(struct IsdnCardState *cs, u_char *data, int size); +void NETjet_WriteICfifo(struct IsdnCardState *cs, u_char *data, int size); + +void read_tiger(struct IsdnCardState *cs); +void write_tiger(struct IsdnCardState *cs); + +void netjet_fill_dma(struct BCState *bcs); +void netjet_interrupt(int intno, void *dev_id); +void inittiger(struct IsdnCardState *cs); +void release_io_netjet(struct IsdnCardState *cs); diff --git a/drivers/isdn/hisax/niccy.c b/drivers/isdn/hisax/niccy.c new file mode 100644 index 000000000..dfbcd2eaa --- /dev/null +++ b/drivers/isdn/hisax/niccy.c @@ -0,0 +1,380 @@ +/* $Id: niccy.c,v 1.21.2.4 2004/01/13 23:48:39 keil Exp $ + * + * low level stuff for Dr. Neuhaus NICCY PnP and NICCY PCI and + * compatible (SAGEM cybermodem) + * + * Author Karsten Keil + * Copyright by Karsten Keil <keil@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 Dr. Neuhaus and SAGEM for information + * + */ + +#include <linux/init.h> +#include "hisax.h" +#include "isac.h" +#include "hscx.h" +#include "isdnl1.h" +#include <linux/pci.h> +#include <linux/isapnp.h> + +static const char *niccy_revision = "$Revision: 1.21.2.4 $"; + +#define byteout(addr, val) outb(val, addr) +#define bytein(addr) inb(addr) + +#define ISAC_PCI_DATA 0 +#define HSCX_PCI_DATA 1 +#define ISAC_PCI_ADDR 2 +#define HSCX_PCI_ADDR 3 +#define ISAC_PNP 0 +#define HSCX_PNP 1 + +/* SUB Types */ +#define NICCY_PNP 1 +#define NICCY_PCI 2 + +/* PCI stuff */ +#define PCI_IRQ_CTRL_REG 0x38 +#define PCI_IRQ_ENABLE 0x1f00 +#define PCI_IRQ_DISABLE 0xff0000 +#define PCI_IRQ_ASSERT 0x800000 + +static inline u_char readreg(unsigned int ale, unsigned int adr, u_char off) +{ + register u_char ret; + + byteout(ale, off); + ret = bytein(adr); + return ret; +} + +static inline void readfifo(unsigned int ale, unsigned int adr, u_char off, + u_char *data, int size) +{ + byteout(ale, off); + insb(adr, data, size); +} + +static inline void writereg(unsigned int ale, unsigned int adr, u_char off, + u_char data) +{ + byteout(ale, off); + byteout(adr, data); +} + +static inline void writefifo(unsigned int ale, unsigned int adr, u_char off, + u_char *data, int size) +{ + byteout(ale, off); + outsb(adr, data, size); +} + +/* Interface functions */ + +static u_char ReadISAC(struct IsdnCardState *cs, u_char offset) +{ + return readreg(cs->hw.niccy.isac_ale, cs->hw.niccy.isac, offset); +} + +static void WriteISAC(struct IsdnCardState *cs, u_char offset, u_char value) +{ + writereg(cs->hw.niccy.isac_ale, cs->hw.niccy.isac, offset, value); +} + +static void ReadISACfifo(struct IsdnCardState *cs, u_char *data, int size) +{ + readfifo(cs->hw.niccy.isac_ale, cs->hw.niccy.isac, 0, data, size); +} + +static void WriteISACfifo(struct IsdnCardState *cs, u_char *data, int size) +{ + writefifo(cs->hw.niccy.isac_ale, cs->hw.niccy.isac, 0, data, size); +} + +static u_char ReadHSCX(struct IsdnCardState *cs, int hscx, u_char offset) +{ + return readreg(cs->hw.niccy.hscx_ale, + cs->hw.niccy.hscx, offset + (hscx ? 0x40 : 0)); +} + +static void WriteHSCX(struct IsdnCardState *cs, int hscx, u_char offset, + u_char value) +{ + writereg(cs->hw.niccy.hscx_ale, + cs->hw.niccy.hscx, offset + (hscx ? 0x40 : 0), value); +} + +#define READHSCX(cs, nr, reg) readreg(cs->hw.niccy.hscx_ale, \ + cs->hw.niccy.hscx, reg + (nr ? 0x40 : 0)) +#define WRITEHSCX(cs, nr, reg, data) writereg(cs->hw.niccy.hscx_ale, \ + cs->hw.niccy.hscx, reg + (nr ? 0x40 : 0), data) + +#define READHSCXFIFO(cs, nr, ptr, cnt) readfifo(cs->hw.niccy.hscx_ale, \ + cs->hw.niccy.hscx, (nr ? 0x40 : 0), ptr, cnt) + +#define WRITEHSCXFIFO(cs, nr, ptr, cnt) writefifo(cs->hw.niccy.hscx_ale, \ + cs->hw.niccy.hscx, (nr ? 0x40 : 0), ptr, cnt) + +#include "hscx_irq.c" + +static irqreturn_t niccy_interrupt(int intno, void *dev_id) +{ + struct IsdnCardState *cs = dev_id; + u_char val; + u_long flags; + + spin_lock_irqsave(&cs->lock, flags); + if (cs->subtyp == NICCY_PCI) { + int ival; + ival = inl(cs->hw.niccy.cfg_reg + PCI_IRQ_CTRL_REG); + if (!(ival & PCI_IRQ_ASSERT)) { /* IRQ not for us (shared) */ + spin_unlock_irqrestore(&cs->lock, flags); + return IRQ_NONE; + } + outl(ival, cs->hw.niccy.cfg_reg + PCI_IRQ_CTRL_REG); + } + val = readreg(cs->hw.niccy.hscx_ale, cs->hw.niccy.hscx, + HSCX_ISTA + 0x40); +Start_HSCX: + if (val) + hscx_int_main(cs, val); + val = readreg(cs->hw.niccy.isac_ale, cs->hw.niccy.isac, ISAC_ISTA); +Start_ISAC: + if (val) + isac_interrupt(cs, val); + val = readreg(cs->hw.niccy.hscx_ale, cs->hw.niccy.hscx, + HSCX_ISTA + 0x40); + if (val) { + if (cs->debug & L1_DEB_HSCX) + debugl1(cs, "HSCX IntStat after IntRoutine"); + goto Start_HSCX; + } + val = readreg(cs->hw.niccy.isac_ale, cs->hw.niccy.isac, ISAC_ISTA); + if (val) { + if (cs->debug & L1_DEB_ISAC) + debugl1(cs, "ISAC IntStat after IntRoutine"); + goto Start_ISAC; + } + writereg(cs->hw.niccy.hscx_ale, cs->hw.niccy.hscx, HSCX_MASK, 0xFF); + writereg(cs->hw.niccy.hscx_ale, cs->hw.niccy.hscx, HSCX_MASK + 0x40, + 0xFF); + writereg(cs->hw.niccy.isac_ale, cs->hw.niccy.isac, ISAC_MASK, 0xFF); + writereg(cs->hw.niccy.isac_ale, cs->hw.niccy.isac, ISAC_MASK, 0); + writereg(cs->hw.niccy.hscx_ale, cs->hw.niccy.hscx, HSCX_MASK, 0); + writereg(cs->hw.niccy.hscx_ale, cs->hw.niccy.hscx, HSCX_MASK + 0x40, 0); + spin_unlock_irqrestore(&cs->lock, flags); + return IRQ_HANDLED; +} + +static void release_io_niccy(struct IsdnCardState *cs) +{ + if (cs->subtyp == NICCY_PCI) { + int val; + + val = inl(cs->hw.niccy.cfg_reg + PCI_IRQ_CTRL_REG); + val &= PCI_IRQ_DISABLE; + outl(val, cs->hw.niccy.cfg_reg + PCI_IRQ_CTRL_REG); + release_region(cs->hw.niccy.cfg_reg, 0x40); + release_region(cs->hw.niccy.isac, 4); + } else { + release_region(cs->hw.niccy.isac, 2); + release_region(cs->hw.niccy.isac_ale, 2); + } +} + +static void niccy_reset(struct IsdnCardState *cs) +{ + if (cs->subtyp == NICCY_PCI) { + int val; + + val = inl(cs->hw.niccy.cfg_reg + PCI_IRQ_CTRL_REG); + val |= PCI_IRQ_ENABLE; + outl(val, cs->hw.niccy.cfg_reg + PCI_IRQ_CTRL_REG); + } + inithscxisac(cs, 3); +} + +static int niccy_card_msg(struct IsdnCardState *cs, int mt, void *arg) +{ + u_long flags; + + switch (mt) { + case CARD_RESET: + spin_lock_irqsave(&cs->lock, flags); + niccy_reset(cs); + spin_unlock_irqrestore(&cs->lock, flags); + return 0; + case CARD_RELEASE: + release_io_niccy(cs); + return 0; + case CARD_INIT: + spin_lock_irqsave(&cs->lock, flags); + niccy_reset(cs); + spin_unlock_irqrestore(&cs->lock, flags); + return 0; + case CARD_TEST: + return 0; + } + return 0; +} + +#ifdef __ISAPNP__ +static struct pnp_card *pnp_c = NULL; +#endif + +int setup_niccy(struct IsdnCard *card) +{ + struct IsdnCardState *cs = card->cs; + char tmp[64]; + + strcpy(tmp, niccy_revision); + printk(KERN_INFO "HiSax: Niccy driver Rev. %s\n", HiSax_getrev(tmp)); + if (cs->typ != ISDN_CTYPE_NICCY) + return 0; +#ifdef __ISAPNP__ + if (!card->para[1] && isapnp_present()) { + struct pnp_dev *pnp_d = NULL; + int err; + + pnp_c = pnp_find_card(ISAPNP_VENDOR('S', 'D', 'A'), + ISAPNP_FUNCTION(0x0150), pnp_c); + if (pnp_c) { + pnp_d = pnp_find_dev(pnp_c, + ISAPNP_VENDOR('S', 'D', 'A'), + ISAPNP_FUNCTION(0x0150), pnp_d); + if (!pnp_d) { + printk(KERN_ERR "NiccyPnP: PnP error card " + "found, no device\n"); + return 0; + } + pnp_disable_dev(pnp_d); + err = pnp_activate_dev(pnp_d); + if (err < 0) { + printk(KERN_WARNING "%s: pnp_activate_dev " + "ret(%d)\n", __func__, err); + return 0; + } + card->para[1] = pnp_port_start(pnp_d, 0); + card->para[2] = pnp_port_start(pnp_d, 1); + card->para[0] = pnp_irq(pnp_d, 0); + if (card->para[0] == -1 || !card->para[1] || + !card->para[2]) { + printk(KERN_ERR "NiccyPnP:some resources are " + "missing %ld/%lx/%lx\n", + card->para[0], card->para[1], + card->para[2]); + pnp_disable_dev(pnp_d); + return 0; + } + } else + printk(KERN_INFO "NiccyPnP: no ISAPnP card found\n"); + } +#endif + if (card->para[1]) { + cs->hw.niccy.isac = card->para[1] + ISAC_PNP; + cs->hw.niccy.hscx = card->para[1] + HSCX_PNP; + cs->hw.niccy.isac_ale = card->para[2] + ISAC_PNP; + cs->hw.niccy.hscx_ale = card->para[2] + HSCX_PNP; + cs->hw.niccy.cfg_reg = 0; + cs->subtyp = NICCY_PNP; + cs->irq = card->para[0]; + if (!request_region(cs->hw.niccy.isac, 2, "niccy data")) { + printk(KERN_WARNING "HiSax: NICCY data port %x-%x " + "already in use\n", + cs->hw.niccy.isac, cs->hw.niccy.isac + 1); + return 0; + } + if (!request_region(cs->hw.niccy.isac_ale, 2, "niccy addr")) { + printk(KERN_WARNING "HiSax: NICCY address port %x-%x " + "already in use\n", + cs->hw.niccy.isac_ale, + cs->hw.niccy.isac_ale + 1); + release_region(cs->hw.niccy.isac, 2); + return 0; + } + } else { +#ifdef CONFIG_PCI + static struct pci_dev *niccy_dev; + + u_int pci_ioaddr; + cs->subtyp = 0; + if ((niccy_dev = hisax_find_pci_device(PCI_VENDOR_ID_SATSAGEM, + PCI_DEVICE_ID_SATSAGEM_NICCY, + niccy_dev))) { + if (pci_enable_device(niccy_dev)) + return 0; + /* get IRQ */ + if (!niccy_dev->irq) { + printk(KERN_WARNING + "Niccy: No IRQ for PCI card found\n"); + return 0; + } + cs->irq = niccy_dev->irq; + cs->hw.niccy.cfg_reg = pci_resource_start(niccy_dev, 0); + if (!cs->hw.niccy.cfg_reg) { + printk(KERN_WARNING + "Niccy: No IO-Adr for PCI cfg found\n"); + return 0; + } + pci_ioaddr = pci_resource_start(niccy_dev, 1); + if (!pci_ioaddr) { + printk(KERN_WARNING + "Niccy: No IO-Adr for PCI card found\n"); + return 0; + } + cs->subtyp = NICCY_PCI; + } else { + printk(KERN_WARNING "Niccy: No PCI card found\n"); + return 0; + } + cs->irq_flags |= IRQF_SHARED; + cs->hw.niccy.isac = pci_ioaddr + ISAC_PCI_DATA; + cs->hw.niccy.isac_ale = pci_ioaddr + ISAC_PCI_ADDR; + cs->hw.niccy.hscx = pci_ioaddr + HSCX_PCI_DATA; + cs->hw.niccy.hscx_ale = pci_ioaddr + HSCX_PCI_ADDR; + if (!request_region(cs->hw.niccy.isac, 4, "niccy")) { + printk(KERN_WARNING + "HiSax: NICCY data port %x-%x already in use\n", + cs->hw.niccy.isac, cs->hw.niccy.isac + 4); + return 0; + } + if (!request_region(cs->hw.niccy.cfg_reg, 0x40, "niccy pci")) { + printk(KERN_WARNING + "HiSax: NICCY pci port %x-%x already in use\n", + cs->hw.niccy.cfg_reg, + cs->hw.niccy.cfg_reg + 0x40); + release_region(cs->hw.niccy.isac, 4); + return 0; + } +#else + printk(KERN_WARNING "Niccy: io0 0 and NO_PCI_BIOS\n"); + printk(KERN_WARNING "Niccy: unable to config NICCY PCI\n"); + return 0; +#endif /* CONFIG_PCI */ + } + printk(KERN_INFO "HiSax: NICCY %s config irq:%d data:0x%X ale:0x%X\n", + (cs->subtyp == 1) ? "PnP" : "PCI", + cs->irq, cs->hw.niccy.isac, cs->hw.niccy.isac_ale); + setup_isac(cs); + cs->readisac = &ReadISAC; + cs->writeisac = &WriteISAC; + cs->readisacfifo = &ReadISACfifo; + cs->writeisacfifo = &WriteISACfifo; + cs->BC_Read_Reg = &ReadHSCX; + cs->BC_Write_Reg = &WriteHSCX; + cs->BC_Send_Data = &hscx_fill_fifo; + cs->cardmsg = &niccy_card_msg; + cs->irq_func = &niccy_interrupt; + ISACVersion(cs, "Niccy:"); + if (HscxVersion(cs, "Niccy:")) { + printk(KERN_WARNING "Niccy: wrong HSCX versions check IO " + "address\n"); + release_io_niccy(cs); + return 0; + } + return 1; +} diff --git a/drivers/isdn/hisax/nj_s.c b/drivers/isdn/hisax/nj_s.c new file mode 100644 index 000000000..32b4bbd18 --- /dev/null +++ b/drivers/isdn/hisax/nj_s.c @@ -0,0 +1,294 @@ +/* $Id: nj_s.c,v 2.13.2.4 2004/01/16 01:53:48 keil Exp $ + * + * This software may be used and distributed according to the terms + * of the GNU General Public License, incorporated herein by reference. + * + */ + +#include <linux/init.h> +#include "hisax.h" +#include "isac.h" +#include "isdnl1.h" +#include <linux/pci.h> +#include <linux/interrupt.h> +#include <linux/ppp_defs.h> +#include "netjet.h" + +static const char *NETjet_S_revision = "$Revision: 2.13.2.4 $"; + +static u_char dummyrr(struct IsdnCardState *cs, int chan, u_char off) +{ + return (5); +} + +static void dummywr(struct IsdnCardState *cs, int chan, u_char off, u_char value) +{ +} + +static irqreturn_t +netjet_s_interrupt(int intno, void *dev_id) +{ + struct IsdnCardState *cs = dev_id; + u_char val, s1val, s0val; + u_long flags; + + spin_lock_irqsave(&cs->lock, flags); + s1val = bytein(cs->hw.njet.base + NETJET_IRQSTAT1); + if (!(s1val & NETJET_ISACIRQ)) { + val = NETjet_ReadIC(cs, ISAC_ISTA); + if (cs->debug & L1_DEB_ISAC) + debugl1(cs, "tiger: i1 %x %x", s1val, val); + if (val) { + isac_interrupt(cs, val); + NETjet_WriteIC(cs, ISAC_MASK, 0xFF); + NETjet_WriteIC(cs, ISAC_MASK, 0x0); + } + s1val = 1; + } else + s1val = 0; + /* + * read/write stat0 is better, because lower IRQ rate + * Note the IRQ is on for 125 us if a condition match + * thats long on modern CPU and so the IRQ is reentered + * all the time. + */ + s0val = bytein(cs->hw.njet.base + NETJET_IRQSTAT0); + if ((s0val | s1val) == 0) { // shared IRQ + spin_unlock_irqrestore(&cs->lock, flags); + return IRQ_NONE; + } + if (s0val) + byteout(cs->hw.njet.base + NETJET_IRQSTAT0, s0val); + /* start new code 13/07/00 GE */ + /* set bits in sval to indicate which page is free */ + if (inl(cs->hw.njet.base + NETJET_DMA_WRITE_ADR) < + inl(cs->hw.njet.base + NETJET_DMA_WRITE_IRQ)) + /* the 2nd write page is free */ + s0val = 0x08; + else /* the 1st write page is free */ + s0val = 0x04; + if (inl(cs->hw.njet.base + NETJET_DMA_READ_ADR) < + inl(cs->hw.njet.base + NETJET_DMA_READ_IRQ)) + /* the 2nd read page is free */ + s0val |= 0x02; + else /* the 1st read page is free */ + s0val |= 0x01; + if (s0val != cs->hw.njet.last_is0) /* we have a DMA interrupt */ + { + if (test_and_set_bit(FLG_LOCK_ATOMIC, &cs->HW_Flags)) { + printk(KERN_WARNING "nj LOCK_ATOMIC s0val %x->%x\n", + cs->hw.njet.last_is0, s0val); + spin_unlock_irqrestore(&cs->lock, flags); + return IRQ_HANDLED; + } + cs->hw.njet.irqstat0 = s0val; + if ((cs->hw.njet.irqstat0 & NETJET_IRQM0_READ) != + (cs->hw.njet.last_is0 & NETJET_IRQM0_READ)) + /* we have a read dma int */ + read_tiger(cs); + if ((cs->hw.njet.irqstat0 & NETJET_IRQM0_WRITE) != + (cs->hw.njet.last_is0 & NETJET_IRQM0_WRITE)) + /* we have a write dma int */ + write_tiger(cs); + /* end new code 13/07/00 GE */ + test_and_clear_bit(FLG_LOCK_ATOMIC, &cs->HW_Flags); + } + spin_unlock_irqrestore(&cs->lock, flags); + return IRQ_HANDLED; +} + +static void +reset_netjet_s(struct IsdnCardState *cs) +{ + cs->hw.njet.ctrl_reg = 0xff; /* Reset On */ + byteout(cs->hw.njet.base + NETJET_CTRL, cs->hw.njet.ctrl_reg); + mdelay(10); + /* now edge triggered for TJ320 GE 13/07/00 */ + /* see comment in IRQ function */ + if (cs->subtyp) /* TJ320 */ + cs->hw.njet.ctrl_reg = 0x40; /* Reset Off and status read clear */ + else + cs->hw.njet.ctrl_reg = 0x00; /* Reset Off and status read clear */ + byteout(cs->hw.njet.base + NETJET_CTRL, cs->hw.njet.ctrl_reg); + mdelay(10); + cs->hw.njet.auxd = 0; + cs->hw.njet.dmactrl = 0; + byteout(cs->hw.njet.base + NETJET_AUXCTRL, ~NETJET_ISACIRQ); + byteout(cs->hw.njet.base + NETJET_IRQMASK1, NETJET_ISACIRQ); + byteout(cs->hw.njet.auxa, cs->hw.njet.auxd); +} + +static int +NETjet_S_card_msg(struct IsdnCardState *cs, int mt, void *arg) +{ + u_long flags; + + switch (mt) { + case CARD_RESET: + spin_lock_irqsave(&cs->lock, flags); + reset_netjet_s(cs); + spin_unlock_irqrestore(&cs->lock, flags); + return (0); + case CARD_RELEASE: + release_io_netjet(cs); + return (0); + case CARD_INIT: + reset_netjet_s(cs); + inittiger(cs); + spin_lock_irqsave(&cs->lock, flags); + clear_pending_isac_ints(cs); + initisac(cs); + /* Reenable all IRQ */ + cs->writeisac(cs, ISAC_MASK, 0); + spin_unlock_irqrestore(&cs->lock, flags); + return (0); + case CARD_TEST: + return (0); + } + return (0); +} + +static int njs_pci_probe(struct pci_dev *dev_netjet, struct IsdnCardState *cs) +{ + u32 cfg; + + if (pci_enable_device(dev_netjet)) + return (0); + pci_set_master(dev_netjet); + cs->irq = dev_netjet->irq; + if (!cs->irq) { + printk(KERN_WARNING "NETjet-S: No IRQ for PCI card found\n"); + return (0); + } + cs->hw.njet.base = pci_resource_start(dev_netjet, 0); + if (!cs->hw.njet.base) { + printk(KERN_WARNING "NETjet-S: No IO-Adr for PCI card found\n"); + return (0); + } + /* the TJ300 and TJ320 must be detected, the IRQ handling is different + * unfortunately the chips use the same device ID, but the TJ320 has + * the bit20 in status PCI cfg register set + */ + pci_read_config_dword(dev_netjet, 0x04, &cfg); + if (cfg & 0x00100000) + cs->subtyp = 1; /* TJ320 */ + else + cs->subtyp = 0; /* TJ300 */ + /* 2001/10/04 Christoph Ersfeld, Formula-n Europe AG www.formula-n.com */ + if ((dev_netjet->subsystem_vendor == 0x55) && + (dev_netjet->subsystem_device == 0x02)) { + printk(KERN_WARNING "Netjet: You tried to load this driver with an incompatible TigerJet-card\n"); + printk(KERN_WARNING "Use type=41 for Formula-n enter:now ISDN PCI and compatible\n"); + return (0); + } + /* end new code */ + + return (1); +} + +static int njs_cs_init(struct IsdnCard *card, struct IsdnCardState *cs) +{ + + cs->hw.njet.auxa = cs->hw.njet.base + NETJET_AUXDATA; + cs->hw.njet.isac = cs->hw.njet.base | NETJET_ISAC_OFF; + + cs->hw.njet.ctrl_reg = 0xff; /* Reset On */ + byteout(cs->hw.njet.base + NETJET_CTRL, cs->hw.njet.ctrl_reg); + mdelay(10); + + cs->hw.njet.ctrl_reg = 0x00; /* Reset Off and status read clear */ + byteout(cs->hw.njet.base + NETJET_CTRL, cs->hw.njet.ctrl_reg); + mdelay(10); + + cs->hw.njet.auxd = 0xC0; + cs->hw.njet.dmactrl = 0; + + byteout(cs->hw.njet.base + NETJET_AUXCTRL, ~NETJET_ISACIRQ); + byteout(cs->hw.njet.base + NETJET_IRQMASK1, NETJET_ISACIRQ); + byteout(cs->hw.njet.auxa, cs->hw.njet.auxd); + + switch (((NETjet_ReadIC(cs, ISAC_RBCH) >> 5) & 3)) + { + case 0: + return 1; /* end loop */ + + case 3: + printk(KERN_WARNING "NETjet-S: NETspider-U PCI card found\n"); + return -1; /* continue looping */ + + default: + printk(KERN_WARNING "NETjet-S: No PCI card found\n"); + return 0; /* end loop & function */ + } + return 1; /* end loop */ +} + +static int njs_cs_init_rest(struct IsdnCard *card, struct IsdnCardState *cs) +{ + const int bytecnt = 256; + + printk(KERN_INFO + "NETjet-S: %s card configured at %#lx IRQ %d\n", + cs->subtyp ? "TJ320" : "TJ300", cs->hw.njet.base, cs->irq); + if (!request_region(cs->hw.njet.base, bytecnt, "netjet-s isdn")) { + printk(KERN_WARNING + "HiSax: NETjet-S config port %#lx-%#lx already in use\n", + cs->hw.njet.base, + cs->hw.njet.base + bytecnt); + return (0); + } + cs->readisac = &NETjet_ReadIC; + cs->writeisac = &NETjet_WriteIC; + cs->readisacfifo = &NETjet_ReadICfifo; + cs->writeisacfifo = &NETjet_WriteICfifo; + cs->BC_Read_Reg = &dummyrr; + cs->BC_Write_Reg = &dummywr; + cs->BC_Send_Data = &netjet_fill_dma; + setup_isac(cs); + cs->cardmsg = &NETjet_S_card_msg; + cs->irq_func = &netjet_s_interrupt; + cs->irq_flags |= IRQF_SHARED; + ISACVersion(cs, "NETjet-S:"); + + return (1); +} + +static struct pci_dev *dev_netjet = NULL; + +int setup_netjet_s(struct IsdnCard *card) +{ + int ret; + struct IsdnCardState *cs = card->cs; + char tmp[64]; + +#ifdef __BIG_ENDIAN +#error "not running on big endian machines now" +#endif + strcpy(tmp, NETjet_S_revision); + printk(KERN_INFO "HiSax: Traverse Tech. NETjet-S driver Rev. %s\n", HiSax_getrev(tmp)); + if (cs->typ != ISDN_CTYPE_NETJET_S) + return (0); + test_and_clear_bit(FLG_LOCK_ATOMIC, &cs->HW_Flags); + + for (;;) + { + if ((dev_netjet = hisax_find_pci_device(PCI_VENDOR_ID_TIGERJET, + PCI_DEVICE_ID_TIGERJET_300, dev_netjet))) { + ret = njs_pci_probe(dev_netjet, cs); + if (!ret) + return (0); + } else { + printk(KERN_WARNING "NETjet-S: No PCI card found\n"); + return (0); + } + + ret = njs_cs_init(card, cs); + if (!ret) + return (0); + if (ret > 0) + break; + /* otherwise, ret < 0, continue looping */ + } + + return njs_cs_init_rest(card, cs); +} diff --git a/drivers/isdn/hisax/nj_u.c b/drivers/isdn/hisax/nj_u.c new file mode 100644 index 000000000..4e8adbede --- /dev/null +++ b/drivers/isdn/hisax/nj_u.c @@ -0,0 +1,258 @@ +/* $Id: nj_u.c,v 2.14.2.3 2004/01/13 14:31:26 keil Exp $ + * + * This software may be used and distributed according to the terms + * of the GNU General Public License, incorporated herein by reference. + * + */ + +#include <linux/init.h> +#include "hisax.h" +#include "icc.h" +#include "isdnl1.h" +#include <linux/pci.h> +#include <linux/interrupt.h> +#include <linux/ppp_defs.h> +#include "netjet.h" + +static const char *NETjet_U_revision = "$Revision: 2.14.2.3 $"; + +static u_char dummyrr(struct IsdnCardState *cs, int chan, u_char off) +{ + return (5); +} + +static void dummywr(struct IsdnCardState *cs, int chan, u_char off, u_char value) +{ +} + +static irqreturn_t +netjet_u_interrupt(int intno, void *dev_id) +{ + struct IsdnCardState *cs = dev_id; + u_char val, sval; + u_long flags; + + spin_lock_irqsave(&cs->lock, flags); + if (!((sval = bytein(cs->hw.njet.base + NETJET_IRQSTAT1)) & + NETJET_ISACIRQ)) { + val = NETjet_ReadIC(cs, ICC_ISTA); + if (cs->debug & L1_DEB_ISAC) + debugl1(cs, "tiger: i1 %x %x", sval, val); + if (val) { + icc_interrupt(cs, val); + NETjet_WriteIC(cs, ICC_MASK, 0xFF); + NETjet_WriteIC(cs, ICC_MASK, 0x0); + } + } + /* start new code 13/07/00 GE */ + /* set bits in sval to indicate which page is free */ + if (inl(cs->hw.njet.base + NETJET_DMA_WRITE_ADR) < + inl(cs->hw.njet.base + NETJET_DMA_WRITE_IRQ)) + /* the 2nd write page is free */ + sval = 0x08; + else /* the 1st write page is free */ + sval = 0x04; + if (inl(cs->hw.njet.base + NETJET_DMA_READ_ADR) < + inl(cs->hw.njet.base + NETJET_DMA_READ_IRQ)) + /* the 2nd read page is free */ + sval = sval | 0x02; + else /* the 1st read page is free */ + sval = sval | 0x01; + if (sval != cs->hw.njet.last_is0) /* we have a DMA interrupt */ + { + if (test_and_set_bit(FLG_LOCK_ATOMIC, &cs->HW_Flags)) { + spin_unlock_irqrestore(&cs->lock, flags); + return IRQ_HANDLED; + } + cs->hw.njet.irqstat0 = sval; + if ((cs->hw.njet.irqstat0 & NETJET_IRQM0_READ) != + (cs->hw.njet.last_is0 & NETJET_IRQM0_READ)) + /* we have a read dma int */ + read_tiger(cs); + if ((cs->hw.njet.irqstat0 & NETJET_IRQM0_WRITE) != + (cs->hw.njet.last_is0 & NETJET_IRQM0_WRITE)) + /* we have a write dma int */ + write_tiger(cs); + /* end new code 13/07/00 GE */ + test_and_clear_bit(FLG_LOCK_ATOMIC, &cs->HW_Flags); + } + spin_unlock_irqrestore(&cs->lock, flags); + return IRQ_HANDLED; +} + +static void +reset_netjet_u(struct IsdnCardState *cs) +{ + cs->hw.njet.ctrl_reg = 0xff; /* Reset On */ + byteout(cs->hw.njet.base + NETJET_CTRL, cs->hw.njet.ctrl_reg); + mdelay(10); + cs->hw.njet.ctrl_reg = 0x40; /* Reset Off and status read clear */ + /* now edge triggered for TJ320 GE 13/07/00 */ + byteout(cs->hw.njet.base + NETJET_CTRL, cs->hw.njet.ctrl_reg); + mdelay(10); + cs->hw.njet.auxd = 0xC0; + cs->hw.njet.dmactrl = 0; + byteout(cs->hw.njet.auxa, 0); + byteout(cs->hw.njet.base + NETJET_AUXCTRL, ~NETJET_ISACIRQ); + byteout(cs->hw.njet.base + NETJET_IRQMASK1, NETJET_ISACIRQ); + byteout(cs->hw.njet.auxa, cs->hw.njet.auxd); +} + +static int +NETjet_U_card_msg(struct IsdnCardState *cs, int mt, void *arg) +{ + u_long flags; + + switch (mt) { + case CARD_RESET: + spin_lock_irqsave(&cs->lock, flags); + reset_netjet_u(cs); + spin_unlock_irqrestore(&cs->lock, flags); + return (0); + case CARD_RELEASE: + release_io_netjet(cs); + return (0); + case CARD_INIT: + spin_lock_irqsave(&cs->lock, flags); + inittiger(cs); + reset_netjet_u(cs); + clear_pending_icc_ints(cs); + initicc(cs); + /* Reenable all IRQ */ + cs->writeisac(cs, ICC_MASK, 0); + spin_unlock_irqrestore(&cs->lock, flags); + return (0); + case CARD_TEST: + return (0); + } + return (0); +} + +static int nju_pci_probe(struct pci_dev *dev_netjet, struct IsdnCardState *cs) +{ + if (pci_enable_device(dev_netjet)) + return (0); + pci_set_master(dev_netjet); + cs->irq = dev_netjet->irq; + if (!cs->irq) { + printk(KERN_WARNING "NETspider-U: No IRQ for PCI card found\n"); + return (0); + } + cs->hw.njet.base = pci_resource_start(dev_netjet, 0); + if (!cs->hw.njet.base) { + printk(KERN_WARNING "NETspider-U: No IO-Adr for PCI card found\n"); + return (0); + } + + return (1); +} + +static int nju_cs_init(struct IsdnCard *card, struct IsdnCardState *cs) +{ + cs->hw.njet.auxa = cs->hw.njet.base + NETJET_AUXDATA; + cs->hw.njet.isac = cs->hw.njet.base | NETJET_ISAC_OFF; + mdelay(10); + + cs->hw.njet.ctrl_reg = 0xff; /* Reset On */ + byteout(cs->hw.njet.base + NETJET_CTRL, cs->hw.njet.ctrl_reg); + mdelay(10); + + cs->hw.njet.ctrl_reg = 0x00; /* Reset Off and status read clear */ + byteout(cs->hw.njet.base + NETJET_CTRL, cs->hw.njet.ctrl_reg); + mdelay(10); + + cs->hw.njet.auxd = 0xC0; + cs->hw.njet.dmactrl = 0; + + byteout(cs->hw.njet.auxa, 0); + byteout(cs->hw.njet.base + NETJET_AUXCTRL, ~NETJET_ISACIRQ); + byteout(cs->hw.njet.base + NETJET_IRQMASK1, NETJET_ISACIRQ); + byteout(cs->hw.njet.auxa, cs->hw.njet.auxd); + + switch (((NETjet_ReadIC(cs, ICC_RBCH) >> 5) & 3)) + { + case 3: + return 1; /* end loop */ + + case 0: + printk(KERN_WARNING "NETspider-U: NETjet-S PCI card found\n"); + return -1; /* continue looping */ + + default: + printk(KERN_WARNING "NETspider-U: No PCI card found\n"); + return 0; /* end loop & function */ + } + return 1; /* end loop */ +} + +static int nju_cs_init_rest(struct IsdnCard *card, struct IsdnCardState *cs) +{ + const int bytecnt = 256; + + printk(KERN_INFO + "NETspider-U: PCI card configured at %#lx IRQ %d\n", + cs->hw.njet.base, cs->irq); + if (!request_region(cs->hw.njet.base, bytecnt, "netspider-u isdn")) { + printk(KERN_WARNING + "HiSax: NETspider-U config port %#lx-%#lx " + "already in use\n", + cs->hw.njet.base, + cs->hw.njet.base + bytecnt); + return (0); + } + setup_icc(cs); + cs->readisac = &NETjet_ReadIC; + cs->writeisac = &NETjet_WriteIC; + cs->readisacfifo = &NETjet_ReadICfifo; + cs->writeisacfifo = &NETjet_WriteICfifo; + cs->BC_Read_Reg = &dummyrr; + cs->BC_Write_Reg = &dummywr; + cs->BC_Send_Data = &netjet_fill_dma; + cs->cardmsg = &NETjet_U_card_msg; + cs->irq_func = &netjet_u_interrupt; + cs->irq_flags |= IRQF_SHARED; + ICCVersion(cs, "NETspider-U:"); + + return (1); +} + +static struct pci_dev *dev_netjet = NULL; + +int setup_netjet_u(struct IsdnCard *card) +{ + int ret; + struct IsdnCardState *cs = card->cs; + char tmp[64]; + +#ifdef __BIG_ENDIAN +#error "not running on big endian machines now" +#endif + + strcpy(tmp, NETjet_U_revision); + printk(KERN_INFO "HiSax: Traverse Tech. NETspider-U driver Rev. %s\n", HiSax_getrev(tmp)); + if (cs->typ != ISDN_CTYPE_NETJET_U) + return (0); + test_and_clear_bit(FLG_LOCK_ATOMIC, &cs->HW_Flags); + + for (;;) + { + if ((dev_netjet = hisax_find_pci_device(PCI_VENDOR_ID_TIGERJET, + PCI_DEVICE_ID_TIGERJET_300, dev_netjet))) { + ret = nju_pci_probe(dev_netjet, cs); + if (!ret) + return (0); + } else { + printk(KERN_WARNING "NETspider-U: No PCI card found\n"); + return (0); + } + + ret = nju_cs_init(card, cs); + if (!ret) + return (0); + if (ret > 0) + break; + /* ret < 0 == continue looping */ + } + + return nju_cs_init_rest(card, cs); +} diff --git a/drivers/isdn/hisax/q931.c b/drivers/isdn/hisax/q931.c new file mode 100644 index 000000000..298c8dba0 --- /dev/null +++ b/drivers/isdn/hisax/q931.c @@ -0,0 +1,1513 @@ +/* $Id: q931.c,v 1.12.2.3 2004/01/13 14:31:26 keil Exp $ + * + * code to decode ITU Q.931 call control messages + * + * Author Jan den Ouden + * Copyright by Jan den Ouden + * + * This software may be used and distributed according to the terms + * of the GNU General Public License, incorporated herein by reference. + * + * Changelog: + * + * Pauline Middelink general improvements + * Beat Doebeli cause texts, display information element + * Karsten Keil cause texts, display information element for 1TR6 + * + */ + + +#include "hisax.h" +#include "l3_1tr6.h" + +void +iecpy(u_char *dest, u_char *iestart, int ieoffset) +{ + u_char *p; + int l; + + p = iestart + ieoffset + 2; + l = iestart[1] - ieoffset; + while (l--) + *dest++ = *p++; + *dest++ = '\0'; +} + +/* + * According to Table 4-2/Q.931 + */ +static +struct MessageType { + u_char nr; + char *descr; +} mtlist[] = { + + { + 0x1, "ALERTING" + }, + { + 0x2, "CALL PROCEEDING" + }, + { + 0x7, "CONNECT" + }, + { + 0xf, "CONNECT ACKNOWLEDGE" + }, + { + 0x3, "PROGRESS" + }, + { + 0x5, "SETUP" + }, + { + 0xd, "SETUP ACKNOWLEDGE" + }, + { + 0x24, "HOLD" + }, + { + 0x28, "HOLD ACKNOWLEDGE" + }, + { + 0x30, "HOLD REJECT" + }, + { + 0x31, "RETRIEVE" + }, + { + 0x33, "RETRIEVE ACKNOWLEDGE" + }, + { + 0x37, "RETRIEVE REJECT" + }, + { + 0x26, "RESUME" + }, + { + 0x2e, "RESUME ACKNOWLEDGE" + }, + { + 0x22, "RESUME REJECT" + }, + { + 0x25, "SUSPEND" + }, + { + 0x2d, "SUSPEND ACKNOWLEDGE" + }, + { + 0x21, "SUSPEND REJECT" + }, + { + 0x20, "USER INFORMATION" + }, + { + 0x45, "DISCONNECT" + }, + { + 0x4d, "RELEASE" + }, + { + 0x5a, "RELEASE COMPLETE" + }, + { + 0x46, "RESTART" + }, + { + 0x4e, "RESTART ACKNOWLEDGE" + }, + { + 0x60, "SEGMENT" + }, + { + 0x79, "CONGESTION CONTROL" + }, + { + 0x7b, "INFORMATION" + }, + { + 0x62, "FACILITY" + }, + { + 0x6e, "NOTIFY" + }, + { + 0x7d, "STATUS" + }, + { + 0x75, "STATUS ENQUIRY" + } +}; + +#define MTSIZE ARRAY_SIZE(mtlist) + +static +struct MessageType mt_n0[] = +{ + {MT_N0_REG_IND, "REGister INDication"}, + {MT_N0_CANC_IND, "CANCel INDication"}, + {MT_N0_FAC_STA, "FACility STAtus"}, + {MT_N0_STA_ACK, "STAtus ACKnowledge"}, + {MT_N0_STA_REJ, "STAtus REJect"}, + {MT_N0_FAC_INF, "FACility INFormation"}, + {MT_N0_INF_ACK, "INFormation ACKnowledge"}, + {MT_N0_INF_REJ, "INFormation REJect"}, + {MT_N0_CLOSE, "CLOSE"}, + {MT_N0_CLO_ACK, "CLOse ACKnowledge"} +}; + +#define MT_N0_LEN ARRAY_SIZE(mt_n0) + +static +struct MessageType mt_n1[] = +{ + {MT_N1_ESC, "ESCape"}, + {MT_N1_ALERT, "ALERT"}, + {MT_N1_CALL_SENT, "CALL SENT"}, + {MT_N1_CONN, "CONNect"}, + {MT_N1_CONN_ACK, "CONNect ACKnowledge"}, + {MT_N1_SETUP, "SETUP"}, + {MT_N1_SETUP_ACK, "SETUP ACKnowledge"}, + {MT_N1_RES, "RESume"}, + {MT_N1_RES_ACK, "RESume ACKnowledge"}, + {MT_N1_RES_REJ, "RESume REJect"}, + {MT_N1_SUSP, "SUSPend"}, + {MT_N1_SUSP_ACK, "SUSPend ACKnowledge"}, + {MT_N1_SUSP_REJ, "SUSPend REJect"}, + {MT_N1_USER_INFO, "USER INFO"}, + {MT_N1_DET, "DETach"}, + {MT_N1_DISC, "DISConnect"}, + {MT_N1_REL, "RELease"}, + {MT_N1_REL_ACK, "RELease ACKnowledge"}, + {MT_N1_CANC_ACK, "CANCel ACKnowledge"}, + {MT_N1_CANC_REJ, "CANCel REJect"}, + {MT_N1_CON_CON, "CONgestion CONtrol"}, + {MT_N1_FAC, "FACility"}, + {MT_N1_FAC_ACK, "FACility ACKnowledge"}, + {MT_N1_FAC_CAN, "FACility CANcel"}, + {MT_N1_FAC_REG, "FACility REGister"}, + {MT_N1_FAC_REJ, "FACility REJect"}, + {MT_N1_INFO, "INFOrmation"}, + {MT_N1_REG_ACK, "REGister ACKnowledge"}, + {MT_N1_REG_REJ, "REGister REJect"}, + {MT_N1_STAT, "STATus"} +}; + +#define MT_N1_LEN ARRAY_SIZE(mt_n1) + + +static int +prbits(char *dest, u_char b, int start, int len) +{ + char *dp = dest; + + b = b << (8 - start); + while (len--) { + if (b & 0x80) + *dp++ = '1'; + else + *dp++ = '0'; + b = b << 1; + } + return (dp - dest); +} + +static +u_char * +skipext(u_char *p) +{ + while (!(*p++ & 0x80)); + return (p); +} + +/* + * Cause Values According to Q.850 + * edescr: English description + * ddescr: German description used by Swissnet II (Swiss Telecom + * not yet written... + */ + +static +struct CauseValue { + u_char nr; + char *edescr; + char *ddescr; +} cvlist[] = { + + { + 0x01, "Unallocated (unassigned) number", "Nummer nicht zugeteilt" + }, + { + 0x02, "No route to specified transit network", "" + }, + { + 0x03, "No route to destination", "" + }, + { + 0x04, "Send special information tone", "" + }, + { + 0x05, "Misdialled trunk prefix", "" + }, + { + 0x06, "Channel unacceptable", "Kanal nicht akzeptierbar" + }, + { + 0x07, "Channel awarded and being delivered in an established channel", "" + }, + { + 0x08, "Preemption", "" + }, + { + 0x09, "Preemption - circuit reserved for reuse", "" + }, + { + 0x10, "Normal call clearing", "Normale Ausloesung" + }, + { + 0x11, "User busy", "TNB besetzt" + }, + { + 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", "" + }, +}; + +#define CVSIZE ARRAY_SIZE(cvlist) + +static +int +prcause(char *dest, u_char *p) +{ + u_char *end; + char *dp = dest; + int i, cause; + + end = p + p[1] + 1; + p += 2; + dp += sprintf(dp, " coding "); + dp += prbits(dp, *p, 7, 2); + dp += sprintf(dp, " location "); + dp += prbits(dp, *p, 4, 4); + *dp++ = '\n'; + p = skipext(p); + + cause = 0x7f & *p++; + + /* locate cause value */ + for (i = 0; i < CVSIZE; i++) + if (cvlist[i].nr == cause) + break; + + /* display cause value if it exists */ + if (i == CVSIZE) + dp += sprintf(dp, "Unknown cause type %x!\n", cause); + else + dp += sprintf(dp, " cause value %x : %s \n", cause, cvlist[i].edescr); + + while (!0) { + if (p > end) + break; + dp += sprintf(dp, " diag attribute %d ", *p++ & 0x7f); + dp += sprintf(dp, " rej %d ", *p & 0x7f); + if (*p & 0x80) { + *dp++ = '\n'; + break; + } else + dp += sprintf(dp, " av %d\n", (*++p) & 0x7f); + } + return (dp - dest); + +} + +static +struct MessageType cause_1tr6[] = +{ + {CAUSE_InvCRef, "Invalid Call Reference"}, + {CAUSE_BearerNotImpl, "Bearer Service Not Implemented"}, + {CAUSE_CIDunknown, "Caller Identity unknown"}, + {CAUSE_CIDinUse, "Caller Identity in Use"}, + {CAUSE_NoChans, "No Channels available"}, + {CAUSE_FacNotImpl, "Facility Not Implemented"}, + {CAUSE_FacNotSubscr, "Facility Not Subscribed"}, + {CAUSE_OutgoingBarred, "Outgoing calls barred"}, + {CAUSE_UserAccessBusy, "User Access Busy"}, + {CAUSE_NegativeGBG, "Negative GBG"}, + {CAUSE_UnknownGBG, "Unknown GBG"}, + {CAUSE_NoSPVknown, "No SPV known"}, + {CAUSE_DestNotObtain, "Destination not obtainable"}, + {CAUSE_NumberChanged, "Number changed"}, + {CAUSE_OutOfOrder, "Out Of Order"}, + {CAUSE_NoUserResponse, "No User Response"}, + {CAUSE_UserBusy, "User Busy"}, + {CAUSE_IncomingBarred, "Incoming Barred"}, + {CAUSE_CallRejected, "Call Rejected"}, + {CAUSE_NetworkCongestion, "Network Congestion"}, + {CAUSE_RemoteUser, "Remote User initiated"}, + {CAUSE_LocalProcErr, "Local Procedure Error"}, + {CAUSE_RemoteProcErr, "Remote Procedure Error"}, + {CAUSE_RemoteUserSuspend, "Remote User Suspend"}, + {CAUSE_RemoteUserResumed, "Remote User Resumed"}, + {CAUSE_UserInfoDiscarded, "User Info Discarded"} +}; + +static int cause_1tr6_len = ARRAY_SIZE(cause_1tr6); + +static int +prcause_1tr6(char *dest, u_char *p) +{ + char *dp = dest; + int i, cause; + + p++; + if (0 == *p) { + dp += sprintf(dp, " OK (cause length=0)\n"); + return (dp - dest); + } else if (*p > 1) { + dp += sprintf(dp, " coding "); + dp += prbits(dp, p[2], 7, 2); + dp += sprintf(dp, " location "); + dp += prbits(dp, p[2], 4, 4); + *dp++ = '\n'; + } + p++; + cause = 0x7f & *p; + + /* locate cause value */ + for (i = 0; i < cause_1tr6_len; i++) + if (cause_1tr6[i].nr == cause) + break; + + /* display cause value if it exists */ + if (i == cause_1tr6_len) + dp += sprintf(dp, "Unknown cause type %x!\n", cause); + else + dp += sprintf(dp, " cause value %x : %s \n", cause, cause_1tr6[i].descr); + + return (dp - dest); + +} + +static int +prchident(char *dest, u_char *p) +{ + char *dp = dest; + + p += 2; + dp += sprintf(dp, " octet 3 "); + dp += prbits(dp, *p, 8, 8); + *dp++ = '\n'; + return (dp - dest); +} + +static int +prcalled(char *dest, u_char *p) +{ + int l; + char *dp = dest; + + p++; + l = *p++ - 1; + dp += sprintf(dp, " octet 3 "); + dp += prbits(dp, *p++, 8, 8); + *dp++ = '\n'; + dp += sprintf(dp, " number digits "); + while (l--) + *dp++ = *p++; + *dp++ = '\n'; + return (dp - dest); +} +static int +prcalling(char *dest, u_char *p) +{ + int l; + char *dp = dest; + + p++; + l = *p++ - 1; + dp += sprintf(dp, " octet 3 "); + dp += prbits(dp, *p, 8, 8); + *dp++ = '\n'; + if (!(*p & 0x80)) { + dp += sprintf(dp, " octet 3a "); + dp += prbits(dp, *++p, 8, 8); + *dp++ = '\n'; + l--; + }; + p++; + + dp += sprintf(dp, " number digits "); + while (l--) + *dp++ = *p++; + *dp++ = '\n'; + return (dp - dest); +} + +static +int +prbearer(char *dest, u_char *p) +{ + char *dp = dest, ch; + + p += 2; + dp += sprintf(dp, " octet 3 "); + dp += prbits(dp, *p++, 8, 8); + *dp++ = '\n'; + dp += sprintf(dp, " octet 4 "); + dp += prbits(dp, *p, 8, 8); + *dp++ = '\n'; + if ((*p++ & 0x1f) == 0x18) { + dp += sprintf(dp, " octet 4.1 "); + dp += prbits(dp, *p++, 8, 8); + *dp++ = '\n'; + } + /* check for user information layer 1 */ + if ((*p & 0x60) == 0x20) { + ch = ' '; + do { + dp += sprintf(dp, " octet 5%c ", ch); + dp += prbits(dp, *p, 8, 8); + *dp++ = '\n'; + if (ch == ' ') + ch = 'a'; + else + ch++; + } + while (!(*p++ & 0x80)); + } + /* check for user information layer 2 */ + if ((*p & 0x60) == 0x40) { + dp += sprintf(dp, " octet 6 "); + dp += prbits(dp, *p++, 8, 8); + *dp++ = '\n'; + } + /* check for user information layer 3 */ + if ((*p & 0x60) == 0x60) { + dp += sprintf(dp, " octet 7 "); + dp += prbits(dp, *p++, 8, 8); + *dp++ = '\n'; + } + return (dp - dest); +} + + +static +int +prbearer_ni1(char *dest, u_char *p) +{ + char *dp = dest; + u_char len; + + p++; + len = *p++; + dp += sprintf(dp, " octet 3 "); + dp += prbits(dp, *p, 8, 8); + switch (*p++) { + case 0x80: + dp += sprintf(dp, " Speech"); + break; + case 0x88: + dp += sprintf(dp, " Unrestricted digital information"); + break; + case 0x90: + dp += sprintf(dp, " 3.1 kHz audio"); + break; + default: + dp += sprintf(dp, " Unknown information-transfer capability"); + } + *dp++ = '\n'; + dp += sprintf(dp, " octet 4 "); + dp += prbits(dp, *p, 8, 8); + switch (*p++) { + case 0x90: + dp += sprintf(dp, " 64 kbps, circuit mode"); + break; + case 0xc0: + dp += sprintf(dp, " Packet mode"); + break; + default: + dp += sprintf(dp, " Unknown transfer mode"); + } + *dp++ = '\n'; + if (len > 2) { + dp += sprintf(dp, " octet 5 "); + dp += prbits(dp, *p, 8, 8); + switch (*p++) { + case 0x21: + dp += sprintf(dp, " Rate adaption\n"); + dp += sprintf(dp, " octet 5a "); + dp += prbits(dp, *p, 8, 8); + break; + case 0xa2: + dp += sprintf(dp, " u-law"); + break; + default: + dp += sprintf(dp, " Unknown UI layer 1 protocol"); + } + *dp++ = '\n'; + } + return (dp - dest); +} + +static int +general(char *dest, u_char *p) +{ + char *dp = dest; + char ch = ' '; + int l, octet = 3; + + p++; + l = *p++; + /* Iterate over all octets in the information element */ + while (l--) { + dp += sprintf(dp, " octet %d%c ", octet, ch); + dp += prbits(dp, *p++, 8, 8); + *dp++ = '\n'; + + /* last octet in group? */ + if (*p & 0x80) { + octet++; + ch = ' '; + } else if (ch == ' ') + ch = 'a'; + else + ch++; + } + return (dp - dest); +} + +static int +general_ni1(char *dest, u_char *p) +{ + char *dp = dest; + char ch = ' '; + int l, octet = 3; + + p++; + l = *p++; + /* Iterate over all octets in the information element */ + while (l--) { + dp += sprintf(dp, " octet %d%c ", octet, ch); + dp += prbits(dp, *p, 8, 8); + *dp++ = '\n'; + + /* last octet in group? */ + if (*p++ & 0x80) { + octet++; + ch = ' '; + } else if (ch == ' ') + ch = 'a'; + else + ch++; + } + return (dp - dest); +} + +static int +prcharge(char *dest, u_char *p) +{ + char *dp = dest; + int l; + + p++; + l = *p++ - 1; + dp += sprintf(dp, " GEA "); + dp += prbits(dp, *p++, 8, 8); + dp += sprintf(dp, " Anzahl: "); + /* Iterate over all octets in the * information element */ + while (l--) + *dp++ = *p++; + *dp++ = '\n'; + return (dp - dest); +} +static int +prtext(char *dest, u_char *p) +{ + char *dp = dest; + int l; + + p++; + l = *p++; + dp += sprintf(dp, " "); + /* Iterate over all octets in the * information element */ + while (l--) + *dp++ = *p++; + *dp++ = '\n'; + return (dp - dest); +} + +static int +prfeatureind(char *dest, u_char *p) +{ + char *dp = dest; + + p += 2; /* skip id, len */ + dp += sprintf(dp, " octet 3 "); + dp += prbits(dp, *p, 8, 8); + *dp++ = '\n'; + if (!(*p++ & 0x80)) { + dp += sprintf(dp, " octet 4 "); + dp += prbits(dp, *p++, 8, 8); + *dp++ = '\n'; + } + dp += sprintf(dp, " Status: "); + switch (*p) { + case 0: + dp += sprintf(dp, "Idle"); + break; + case 1: + dp += sprintf(dp, "Active"); + break; + case 2: + dp += sprintf(dp, "Prompt"); + break; + case 3: + dp += sprintf(dp, "Pending"); + break; + default: + dp += sprintf(dp, "(Reserved)"); + break; + } + *dp++ = '\n'; + return (dp - dest); +} + +static +struct DTag { /* Display tags */ + u_char nr; + char *descr; +} dtaglist[] = { + { 0x82, "Continuation" }, + { 0x83, "Called address" }, + { 0x84, "Cause" }, + { 0x85, "Progress indicator" }, + { 0x86, "Notification indicator" }, + { 0x87, "Prompt" }, + { 0x88, "Accumlated digits" }, + { 0x89, "Status" }, + { 0x8a, "Inband" }, + { 0x8b, "Calling address" }, + { 0x8c, "Reason" }, + { 0x8d, "Calling party name" }, + { 0x8e, "Called party name" }, + { 0x8f, "Original called name" }, + { 0x90, "Redirecting name" }, + { 0x91, "Connected name" }, + { 0x92, "Originating restrictions" }, + { 0x93, "Date & time of day" }, + { 0x94, "Call Appearance ID" }, + { 0x95, "Feature address" }, + { 0x96, "Redirection name" }, + { 0x9e, "Text" }, +}; +#define DTAGSIZE ARRAY_SIZE(dtaglist) + +static int +disptext_ni1(char *dest, u_char *p) +{ + char *dp = dest; + int l, tag, len, i; + + p++; + l = *p++ - 1; + if (*p++ != 0x80) { + dp += sprintf(dp, " Unknown display type\n"); + return (dp - dest); + } + /* Iterate over all tag,length,text fields */ + while (l > 0) { + tag = *p++; + len = *p++; + l -= len + 2; + /* Don't space or skip */ + if ((tag == 0x80) || (tag == 0x81)) p++; + else { + for (i = 0; i < DTAGSIZE; i++) + if (tag == dtaglist[i].nr) + break; + + /* When not found, give appropriate msg */ + if (i != DTAGSIZE) { + dp += sprintf(dp, " %s: ", dtaglist[i].descr); + while (len--) + *dp++ = *p++; + } else { + dp += sprintf(dp, " (unknown display tag %2x): ", tag); + while (len--) + *dp++ = *p++; + } + dp += sprintf(dp, "\n"); + } + } + return (dp - dest); +} +static int +display(char *dest, u_char *p) +{ + char *dp = dest; + char ch = ' '; + int l, octet = 3; + + p++; + l = *p++; + /* Iterate over all octets in the * display-information element */ + dp += sprintf(dp, " \""); + while (l--) { + dp += sprintf(dp, "%c", *p++); + + /* last octet in group? */ + if (*p & 0x80) { + octet++; + ch = ' '; + } else if (ch == ' ') + ch = 'a'; + + else + ch++; + } + *dp++ = '\"'; + *dp++ = '\n'; + return (dp - dest); +} + +static int +prfacility(char *dest, u_char *p) +{ + char *dp = dest; + int l, l2; + + p++; + l = *p++; + dp += sprintf(dp, " octet 3 "); + dp += prbits(dp, *p++, 8, 8); + dp += sprintf(dp, "\n"); + l -= 1; + + while (l > 0) { + dp += sprintf(dp, " octet 4 "); + dp += prbits(dp, *p++, 8, 8); + dp += sprintf(dp, "\n"); + dp += sprintf(dp, " octet 5 %d\n", l2 = *p++ & 0x7f); + l -= 2; + dp += sprintf(dp, " contents "); + while (l2--) { + dp += sprintf(dp, "%2x ", *p++); + l--; + } + dp += sprintf(dp, "\n"); + } + + return (dp - dest); +} + +static +struct InformationElement { + u_char nr; + char *descr; + int (*f) (char *, u_char *); +} ielist[] = { + + { + 0x00, "Segmented message", general + }, + { + 0x04, "Bearer capability", prbearer + }, + { + 0x08, "Cause", prcause + }, + { + 0x10, "Call identity", general + }, + { + 0x14, "Call state", general + }, + { + 0x18, "Channel identification", prchident + }, + { + 0x1c, "Facility", prfacility + }, + { + 0x1e, "Progress indicator", general + }, + { + 0x20, "Network-specific facilities", general + }, + { + 0x27, "Notification indicator", general + }, + { + 0x28, "Display", display + }, + { + 0x29, "Date/Time", general + }, + { + 0x2c, "Keypad facility", general + }, + { + 0x34, "Signal", general + }, + { + 0x40, "Information rate", general + }, + { + 0x42, "End-to-end delay", general + }, + { + 0x43, "Transit delay selection and indication", general + }, + { + 0x44, "Packet layer binary parameters", general + }, + { + 0x45, "Packet layer window size", general + }, + { + 0x46, "Packet size", general + }, + { + 0x47, "Closed user group", general + }, + { + 0x4a, "Reverse charge indication", general + }, + { + 0x6c, "Calling party number", prcalling + }, + { + 0x6d, "Calling party subaddress", general + }, + { + 0x70, "Called party number", prcalled + }, + { + 0x71, "Called party subaddress", general + }, + { + 0x74, "Redirecting number", general + }, + { + 0x78, "Transit network selection", general + }, + { + 0x79, "Restart indicator", general + }, + { + 0x7c, "Low layer compatibility", general + }, + { + 0x7d, "High layer compatibility", general + }, + { + 0x7e, "User-user", general + }, + { + 0x7f, "Escape for extension", general + }, +}; + + +#define IESIZE ARRAY_SIZE(ielist) + +static +struct InformationElement ielist_ni1[] = { + { 0x04, "Bearer Capability", prbearer_ni1 }, + { 0x08, "Cause", prcause }, + { 0x14, "Call State", general_ni1 }, + { 0x18, "Channel Identification", prchident }, + { 0x1e, "Progress Indicator", general_ni1 }, + { 0x27, "Notification Indicator", general_ni1 }, + { 0x2c, "Keypad Facility", prtext }, + { 0x32, "Information Request", general_ni1 }, + { 0x34, "Signal", general_ni1 }, + { 0x38, "Feature Activation", general_ni1 }, + { 0x39, "Feature Indication", prfeatureind }, + { 0x3a, "Service Profile Identification (SPID)", prtext }, + { 0x3b, "Endpoint Identifier", general_ni1 }, + { 0x6c, "Calling Party Number", prcalling }, + { 0x6d, "Calling Party Subaddress", general_ni1 }, + { 0x70, "Called Party Number", prcalled }, + { 0x71, "Called Party Subaddress", general_ni1 }, + { 0x74, "Redirecting Number", general_ni1 }, + { 0x78, "Transit Network Selection", general_ni1 }, + { 0x7c, "Low Layer Compatibility", general_ni1 }, + { 0x7d, "High Layer Compatibility", general_ni1 }, +}; + + +#define IESIZE_NI1 ARRAY_SIZE(ielist_ni1) + +static +struct InformationElement ielist_ni1_cs5[] = { + { 0x1d, "Operator system access", general_ni1 }, + { 0x2a, "Display text", disptext_ni1 }, +}; + +#define IESIZE_NI1_CS5 ARRAY_SIZE(ielist_ni1_cs5) + +static +struct InformationElement ielist_ni1_cs6[] = { + { 0x7b, "Call appearance", general_ni1 }, +}; + +#define IESIZE_NI1_CS6 ARRAY_SIZE(ielist_ni1_cs6) + +static struct InformationElement we_0[] = +{ + {WE0_cause, "Cause", prcause_1tr6}, + {WE0_connAddr, "Connecting Address", prcalled}, + {WE0_callID, "Call IDentity", general}, + {WE0_chanID, "Channel IDentity", general}, + {WE0_netSpecFac, "Network Specific Facility", general}, + {WE0_display, "Display", general}, + {WE0_keypad, "Keypad", general}, + {WE0_origAddr, "Origination Address", prcalled}, + {WE0_destAddr, "Destination Address", prcalled}, + {WE0_userInfo, "User Info", general} +}; + +#define WE_0_LEN ARRAY_SIZE(we_0) + +static struct InformationElement we_6[] = +{ + {WE6_serviceInd, "Service Indicator", general}, + {WE6_chargingInfo, "Charging Information", prcharge}, + {WE6_date, "Date", prtext}, + {WE6_facSelect, "Facility Select", general}, + {WE6_facStatus, "Facility Status", general}, + {WE6_statusCalled, "Status Called", general}, + {WE6_addTransAttr, "Additional Transmission Attributes", general} +}; +#define WE_6_LEN ARRAY_SIZE(we_6) + +int +QuickHex(char *txt, u_char *p, int cnt) +{ + register int i; + register char *t = txt; + + for (i = 0; i < cnt; i++) { + *t++ = ' '; + *t++ = hex_asc_hi(p[i]); + *t++ = hex_asc_lo(p[i]); + } + *t++ = 0; + return (t - txt); +} + +void +LogFrame(struct IsdnCardState *cs, u_char *buf, int size) +{ + char *dp; + + if (size < 1) + return; + dp = cs->dlog; + if (size < MAX_DLOG_SPACE / 3 - 10) { + *dp++ = 'H'; + *dp++ = 'E'; + *dp++ = 'X'; + *dp++ = ':'; + dp += QuickHex(dp, buf, size); + dp--; + *dp++ = '\n'; + *dp = 0; + HiSax_putstatus(cs, NULL, cs->dlog); + } else + HiSax_putstatus(cs, "LogFrame: ", "warning Frame too big (%d)", size); +} + +void +dlogframe(struct IsdnCardState *cs, struct sk_buff *skb, int dir) +{ + u_char *bend, *buf; + char *dp; + unsigned char pd, cr_l, cr, mt; + unsigned char sapi, tei, ftyp; + int i, cset = 0, cs_old = 0, cs_fest = 0; + int size, finish = 0; + + if (skb->len < 3) + return; + /* display header */ + dp = cs->dlog; + dp += jiftime(dp, jiffies); + *dp++ = ' '; + sapi = skb->data[0] >> 2; + tei = skb->data[1] >> 1; + ftyp = skb->data[2]; + buf = skb->data; + dp += sprintf(dp, "frame %s ", dir ? "network->user" : "user->network"); + size = skb->len; + + if (tei == GROUP_TEI) { + if (sapi == CTRL_SAPI) { /* sapi 0 */ + if (ftyp == 3) { + dp += sprintf(dp, "broadcast\n"); + buf += 3; + size -= 3; + } else { + dp += sprintf(dp, "no UI broadcast\n"); + finish = 1; + } + } else if (sapi == TEI_SAPI) { + dp += sprintf(dp, "tei management\n"); + finish = 1; + } else { + dp += sprintf(dp, "unknown sapi %d broadcast\n", sapi); + finish = 1; + } + } else { + if (sapi == CTRL_SAPI) { + if (!(ftyp & 1)) { /* IFrame */ + dp += sprintf(dp, "with tei %d\n", tei); + buf += 4; + size -= 4; + } else { + dp += sprintf(dp, "SFrame with tei %d\n", tei); + finish = 1; + } + } else { + dp += sprintf(dp, "unknown sapi %d tei %d\n", sapi, tei); + finish = 1; + } + } + bend = skb->data + skb->len; + if (buf >= bend) { + dp += sprintf(dp, "frame too short\n"); + finish = 1; + } + if (finish) { + *dp = 0; + HiSax_putstatus(cs, NULL, cs->dlog); + return; + } + if ((0xfe & buf[0]) == PROTO_DIS_N0) { /* 1TR6 */ + /* locate message type */ + pd = *buf++; + cr_l = *buf++; + if (cr_l) + cr = *buf++; + else + cr = 0; + mt = *buf++; + if (pd == PROTO_DIS_N0) { /* N0 */ + for (i = 0; i < MT_N0_LEN; i++) + if (mt_n0[i].nr == mt) + break; + /* display message type if it exists */ + if (i == MT_N0_LEN) + dp += sprintf(dp, "callref %d %s size %d unknown message type N0 %x!\n", + cr & 0x7f, (cr & 0x80) ? "called" : "caller", + size, mt); + else + dp += sprintf(dp, "callref %d %s size %d message type %s\n", + cr & 0x7f, (cr & 0x80) ? "called" : "caller", + size, mt_n0[i].descr); + } else { /* N1 */ + for (i = 0; i < MT_N1_LEN; i++) + if (mt_n1[i].nr == mt) + break; + /* display message type if it exists */ + if (i == MT_N1_LEN) + dp += sprintf(dp, "callref %d %s size %d unknown message type N1 %x!\n", + cr & 0x7f, (cr & 0x80) ? "called" : "caller", + size, mt); + else + dp += sprintf(dp, "callref %d %s size %d message type %s\n", + cr & 0x7f, (cr & 0x80) ? "called" : "caller", + size, mt_n1[i].descr); + } + + /* display each information element */ + while (buf < bend) { + /* Is it a single octet information element? */ + if (*buf & 0x80) { + switch ((*buf >> 4) & 7) { + case 1: + dp += sprintf(dp, " Shift %x\n", *buf & 0xf); + cs_old = cset; + cset = *buf & 7; + cs_fest = *buf & 8; + break; + case 3: + dp += sprintf(dp, " Congestion level %x\n", *buf & 0xf); + break; + case 2: + if (*buf == 0xa0) { + dp += sprintf(dp, " More data\n"); + break; + } + if (*buf == 0xa1) { + dp += sprintf(dp, " Sending complete\n"); + } + break; + /* fall through */ + default: + dp += sprintf(dp, " Reserved %x\n", *buf); + break; + } + buf++; + continue; + } + /* No, locate it in the table */ + if (cset == 0) { + for (i = 0; i < WE_0_LEN; i++) + if (*buf == we_0[i].nr) + break; + + /* When found, give appropriate msg */ + if (i != WE_0_LEN) { + dp += sprintf(dp, " %s\n", we_0[i].descr); + dp += we_0[i].f(dp, buf); + } else + dp += sprintf(dp, " Codeset %d attribute %x attribute size %d\n", cset, *buf, buf[1]); + } else if (cset == 6) { + for (i = 0; i < WE_6_LEN; i++) + if (*buf == we_6[i].nr) + break; + + /* When found, give appropriate msg */ + if (i != WE_6_LEN) { + dp += sprintf(dp, " %s\n", we_6[i].descr); + dp += we_6[i].f(dp, buf); + } else + dp += sprintf(dp, " Codeset %d attribute %x attribute size %d\n", cset, *buf, buf[1]); + } else + dp += sprintf(dp, " Unknown Codeset %d attribute %x attribute size %d\n", cset, *buf, buf[1]); + /* Skip to next element */ + if (cs_fest == 8) { + cset = cs_old; + cs_old = 0; + cs_fest = 0; + } + buf += buf[1] + 2; + } + } else if ((buf[0] == 8) && (cs->protocol == ISDN_PTYPE_NI1)) { /* NI-1 */ + /* locate message type */ + buf++; + cr_l = *buf++; + if (cr_l) + cr = *buf++; + else + cr = 0; + mt = *buf++; + for (i = 0; i < MTSIZE; i++) + if (mtlist[i].nr == mt) + break; + + /* display message type if it exists */ + if (i == MTSIZE) + dp += sprintf(dp, "callref %d %s size %d unknown message type %x!\n", + cr & 0x7f, (cr & 0x80) ? "called" : "caller", + size, mt); + else + dp += sprintf(dp, "callref %d %s size %d message type %s\n", + cr & 0x7f, (cr & 0x80) ? "called" : "caller", + size, mtlist[i].descr); + + /* display each information element */ + while (buf < bend) { + /* Is it a single octet information element? */ + if (*buf & 0x80) { + switch ((*buf >> 4) & 7) { + case 1: + dp += sprintf(dp, " Shift %x\n", *buf & 0xf); + cs_old = cset; + cset = *buf & 7; + cs_fest = *buf & 8; + break; + default: + dp += sprintf(dp, " Unknown single-octet IE %x\n", *buf); + break; + } + buf++; + continue; + } + /* No, locate it in the table */ + if (cset == 0) { + for (i = 0; i < IESIZE_NI1; i++) + if (*buf == ielist_ni1[i].nr) + break; + + /* When not found, give appropriate msg */ + if (i != IESIZE_NI1) { + dp += sprintf(dp, " %s\n", ielist_ni1[i].descr); + dp += ielist_ni1[i].f(dp, buf); + } else + dp += sprintf(dp, " attribute %x attribute size %d\n", *buf, buf[1]); + } else if (cset == 5) { + for (i = 0; i < IESIZE_NI1_CS5; i++) + if (*buf == ielist_ni1_cs5[i].nr) + break; + + /* When not found, give appropriate msg */ + if (i != IESIZE_NI1_CS5) { + dp += sprintf(dp, " %s\n", ielist_ni1_cs5[i].descr); + dp += ielist_ni1_cs5[i].f(dp, buf); + } else + dp += sprintf(dp, " attribute %x attribute size %d\n", *buf, buf[1]); + } else if (cset == 6) { + for (i = 0; i < IESIZE_NI1_CS6; i++) + if (*buf == ielist_ni1_cs6[i].nr) + break; + + /* When not found, give appropriate msg */ + if (i != IESIZE_NI1_CS6) { + dp += sprintf(dp, " %s\n", ielist_ni1_cs6[i].descr); + dp += ielist_ni1_cs6[i].f(dp, buf); + } else + dp += sprintf(dp, " attribute %x attribute size %d\n", *buf, buf[1]); + } else + dp += sprintf(dp, " Unknown Codeset %d attribute %x attribute size %d\n", cset, *buf, buf[1]); + + /* Skip to next element */ + if (cs_fest == 8) { + cset = cs_old; + cs_old = 0; + cs_fest = 0; + } + buf += buf[1] + 2; + } + } else if ((buf[0] == 8) && (cs->protocol == ISDN_PTYPE_EURO)) { /* EURO */ + /* locate message type */ + buf++; + cr_l = *buf++; + if (cr_l) + cr = *buf++; + else + cr = 0; + mt = *buf++; + for (i = 0; i < MTSIZE; i++) + if (mtlist[i].nr == mt) + break; + + /* display message type if it exists */ + if (i == MTSIZE) + dp += sprintf(dp, "callref %d %s size %d unknown message type %x!\n", + cr & 0x7f, (cr & 0x80) ? "called" : "caller", + size, mt); + else + dp += sprintf(dp, "callref %d %s size %d message type %s\n", + cr & 0x7f, (cr & 0x80) ? "called" : "caller", + size, mtlist[i].descr); + + /* display each information element */ + while (buf < bend) { + /* Is it a single octet information element? */ + if (*buf & 0x80) { + switch ((*buf >> 4) & 7) { + case 1: + dp += sprintf(dp, " Shift %x\n", *buf & 0xf); + break; + case 3: + dp += sprintf(dp, " Congestion level %x\n", *buf & 0xf); + break; + case 5: + dp += sprintf(dp, " Repeat indicator %x\n", *buf & 0xf); + break; + case 2: + if (*buf == 0xa0) { + dp += sprintf(dp, " More data\n"); + break; + } + if (*buf == 0xa1) { + dp += sprintf(dp, " Sending complete\n"); + } + break; + /* fall through */ + default: + dp += sprintf(dp, " Reserved %x\n", *buf); + break; + } + buf++; + continue; + } + /* No, locate it in the table */ + for (i = 0; i < IESIZE; i++) + if (*buf == ielist[i].nr) + break; + + /* When not found, give appropriate msg */ + if (i != IESIZE) { + dp += sprintf(dp, " %s\n", ielist[i].descr); + dp += ielist[i].f(dp, buf); + } else + dp += sprintf(dp, " attribute %x attribute size %d\n", *buf, buf[1]); + + /* Skip to next element */ + buf += buf[1] + 2; + } + } else { + dp += sprintf(dp, "Unknown protocol %x!", buf[0]); + } + *dp = 0; + HiSax_putstatus(cs, NULL, cs->dlog); +} diff --git a/drivers/isdn/hisax/s0box.c b/drivers/isdn/hisax/s0box.c new file mode 100644 index 000000000..4e7d0aa22 --- /dev/null +++ b/drivers/isdn/hisax/s0box.c @@ -0,0 +1,260 @@ +/* $Id: s0box.c,v 2.6.2.4 2004/01/13 23:48:39 keil Exp $ + * + * low level stuff for Creatix S0BOX + * + * Author Enrik Berkhan + * Copyright by Enrik Berkhan <enrik@starfleet.inka.de> + * + * This software may be used and distributed according to the terms + * of the GNU General Public License, incorporated herein by reference. + * + */ + +#include <linux/init.h> +#include "hisax.h" +#include "isac.h" +#include "hscx.h" +#include "isdnl1.h" + +static const char *s0box_revision = "$Revision: 2.6.2.4 $"; + +static inline void +writereg(unsigned int padr, signed int addr, u_char off, u_char val) { + outb_p(0x1c, padr + 2); + outb_p(0x14, padr + 2); + outb_p((addr + off) & 0x7f, padr); + outb_p(0x16, padr + 2); + outb_p(val, padr); + outb_p(0x17, padr + 2); + outb_p(0x14, padr + 2); + outb_p(0x1c, padr + 2); +} + +static u_char nibtab[] = { 1, 9, 5, 0xd, 3, 0xb, 7, 0xf, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 8, 4, 0xc, 2, 0xa, 6, 0xe }; + +static inline u_char +readreg(unsigned int padr, signed int addr, u_char off) { + register u_char n1, n2; + + outb_p(0x1c, padr + 2); + outb_p(0x14, padr + 2); + outb_p((addr + off) | 0x80, padr); + outb_p(0x16, padr + 2); + outb_p(0x17, padr + 2); + n1 = (inb_p(padr + 1) >> 3) & 0x17; + outb_p(0x16, padr + 2); + n2 = (inb_p(padr + 1) >> 3) & 0x17; + outb_p(0x14, padr + 2); + outb_p(0x1c, padr + 2); + return nibtab[n1] | (nibtab[n2] << 4); +} + +static inline void +read_fifo(unsigned int padr, signed int adr, u_char *data, int size) +{ + int i; + register u_char n1, n2; + + outb_p(0x1c, padr + 2); + outb_p(0x14, padr + 2); + outb_p(adr | 0x80, padr); + outb_p(0x16, padr + 2); + for (i = 0; i < size; i++) { + outb_p(0x17, padr + 2); + n1 = (inb_p(padr + 1) >> 3) & 0x17; + outb_p(0x16, padr + 2); + n2 = (inb_p(padr + 1) >> 3) & 0x17; + *(data++) = nibtab[n1] | (nibtab[n2] << 4); + } + outb_p(0x14, padr + 2); + outb_p(0x1c, padr + 2); + return; +} + +static inline void +write_fifo(unsigned int padr, signed int adr, u_char *data, int size) +{ + int i; + outb_p(0x1c, padr + 2); + outb_p(0x14, padr + 2); + outb_p(adr & 0x7f, padr); + for (i = 0; i < size; i++) { + outb_p(0x16, padr + 2); + outb_p(*(data++), padr); + outb_p(0x17, padr + 2); + } + outb_p(0x14, padr + 2); + outb_p(0x1c, padr + 2); + return; +} + +/* Interface functions */ + +static u_char +ReadISAC(struct IsdnCardState *cs, u_char offset) +{ + return (readreg(cs->hw.teles3.cfg_reg, cs->hw.teles3.isac, offset)); +} + +static void +WriteISAC(struct IsdnCardState *cs, u_char offset, u_char value) +{ + writereg(cs->hw.teles3.cfg_reg, cs->hw.teles3.isac, offset, value); +} + +static void +ReadISACfifo(struct IsdnCardState *cs, u_char *data, int size) +{ + read_fifo(cs->hw.teles3.cfg_reg, cs->hw.teles3.isacfifo, data, size); +} + +static void +WriteISACfifo(struct IsdnCardState *cs, u_char *data, int size) +{ + write_fifo(cs->hw.teles3.cfg_reg, cs->hw.teles3.isacfifo, data, size); +} + +static u_char +ReadHSCX(struct IsdnCardState *cs, int hscx, u_char offset) +{ + return (readreg(cs->hw.teles3.cfg_reg, cs->hw.teles3.hscx[hscx], offset)); +} + +static void +WriteHSCX(struct IsdnCardState *cs, int hscx, u_char offset, u_char value) +{ + writereg(cs->hw.teles3.cfg_reg, cs->hw.teles3.hscx[hscx], offset, value); +} + +/* + * fast interrupt HSCX stuff goes here + */ + +#define READHSCX(cs, nr, reg) readreg(cs->hw.teles3.cfg_reg, cs->hw.teles3.hscx[nr], reg) +#define WRITEHSCX(cs, nr, reg, data) writereg(cs->hw.teles3.cfg_reg, cs->hw.teles3.hscx[nr], reg, data) +#define READHSCXFIFO(cs, nr, ptr, cnt) read_fifo(cs->hw.teles3.cfg_reg, cs->hw.teles3.hscxfifo[nr], ptr, cnt) +#define WRITEHSCXFIFO(cs, nr, ptr, cnt) write_fifo(cs->hw.teles3.cfg_reg, cs->hw.teles3.hscxfifo[nr], ptr, cnt) + +#include "hscx_irq.c" + +static irqreturn_t +s0box_interrupt(int intno, void *dev_id) +{ +#define MAXCOUNT 5 + struct IsdnCardState *cs = dev_id; + u_char val; + u_long flags; + int count = 0; + + spin_lock_irqsave(&cs->lock, flags); + val = readreg(cs->hw.teles3.cfg_reg, cs->hw.teles3.hscx[1], HSCX_ISTA); +Start_HSCX: + if (val) + hscx_int_main(cs, val); + val = readreg(cs->hw.teles3.cfg_reg, cs->hw.teles3.isac, ISAC_ISTA); +Start_ISAC: + if (val) + isac_interrupt(cs, val); + count++; + val = readreg(cs->hw.teles3.cfg_reg, cs->hw.teles3.hscx[1], HSCX_ISTA); + if (val && count < MAXCOUNT) { + if (cs->debug & L1_DEB_HSCX) + debugl1(cs, "HSCX IntStat after IntRoutine"); + goto Start_HSCX; + } + val = readreg(cs->hw.teles3.cfg_reg, cs->hw.teles3.isac, ISAC_ISTA); + if (val && count < MAXCOUNT) { + if (cs->debug & L1_DEB_ISAC) + debugl1(cs, "ISAC IntStat after IntRoutine"); + goto Start_ISAC; + } + if (count >= MAXCOUNT) + printk(KERN_WARNING "S0Box: more than %d loops in s0box_interrupt\n", count); + writereg(cs->hw.teles3.cfg_reg, cs->hw.teles3.hscx[0], HSCX_MASK, 0xFF); + writereg(cs->hw.teles3.cfg_reg, cs->hw.teles3.hscx[1], HSCX_MASK, 0xFF); + writereg(cs->hw.teles3.cfg_reg, cs->hw.teles3.isac, ISAC_MASK, 0xFF); + writereg(cs->hw.teles3.cfg_reg, cs->hw.teles3.isac, ISAC_MASK, 0x0); + writereg(cs->hw.teles3.cfg_reg, cs->hw.teles3.hscx[0], HSCX_MASK, 0x0); + writereg(cs->hw.teles3.cfg_reg, cs->hw.teles3.hscx[1], HSCX_MASK, 0x0); + spin_unlock_irqrestore(&cs->lock, flags); + return IRQ_HANDLED; +} + +static void +release_io_s0box(struct IsdnCardState *cs) +{ + release_region(cs->hw.teles3.cfg_reg, 8); +} + +static int +S0Box_card_msg(struct IsdnCardState *cs, int mt, void *arg) +{ + u_long flags; + + switch (mt) { + case CARD_RESET: + break; + case CARD_RELEASE: + release_io_s0box(cs); + break; + case CARD_INIT: + spin_lock_irqsave(&cs->lock, flags); + inithscxisac(cs, 3); + spin_unlock_irqrestore(&cs->lock, flags); + break; + case CARD_TEST: + break; + } + return (0); +} + +int setup_s0box(struct IsdnCard *card) +{ + struct IsdnCardState *cs = card->cs; + char tmp[64]; + + strcpy(tmp, s0box_revision); + printk(KERN_INFO "HiSax: S0Box IO driver Rev. %s\n", HiSax_getrev(tmp)); + if (cs->typ != ISDN_CTYPE_S0BOX) + return (0); + + cs->hw.teles3.cfg_reg = card->para[1]; + cs->hw.teles3.hscx[0] = -0x20; + cs->hw.teles3.hscx[1] = 0x0; + cs->hw.teles3.isac = 0x20; + cs->hw.teles3.isacfifo = cs->hw.teles3.isac + 0x3e; + cs->hw.teles3.hscxfifo[0] = cs->hw.teles3.hscx[0] + 0x3e; + cs->hw.teles3.hscxfifo[1] = cs->hw.teles3.hscx[1] + 0x3e; + cs->irq = card->para[0]; + if (!request_region(cs->hw.teles3.cfg_reg, 8, "S0Box parallel I/O")) { + printk(KERN_WARNING "HiSax: S0Box ports %x-%x already in use\n", + cs->hw.teles3.cfg_reg, + cs->hw.teles3.cfg_reg + 7); + return 0; + } + printk(KERN_INFO "HiSax: S0Box config irq:%d isac:0x%x cfg:0x%x\n", + cs->irq, + cs->hw.teles3.isac, cs->hw.teles3.cfg_reg); + printk(KERN_INFO "HiSax: hscx A:0x%x hscx B:0x%x\n", + cs->hw.teles3.hscx[0], cs->hw.teles3.hscx[1]); + setup_isac(cs); + cs->readisac = &ReadISAC; + cs->writeisac = &WriteISAC; + cs->readisacfifo = &ReadISACfifo; + cs->writeisacfifo = &WriteISACfifo; + cs->BC_Read_Reg = &ReadHSCX; + cs->BC_Write_Reg = &WriteHSCX; + cs->BC_Send_Data = &hscx_fill_fifo; + cs->cardmsg = &S0Box_card_msg; + cs->irq_func = &s0box_interrupt; + ISACVersion(cs, "S0Box:"); + if (HscxVersion(cs, "S0Box:")) { + printk(KERN_WARNING + "S0Box: wrong HSCX versions check IO address\n"); + release_io_s0box(cs); + return (0); + } + return (1); +} diff --git a/drivers/isdn/hisax/saphir.c b/drivers/isdn/hisax/saphir.c new file mode 100644 index 000000000..db906cb37 --- /dev/null +++ b/drivers/isdn/hisax/saphir.c @@ -0,0 +1,296 @@ +/* $Id: saphir.c,v 1.10.2.4 2004/01/13 23:48:39 keil Exp $ + * + * low level stuff for HST Saphir 1 + * + * Author Karsten Keil + * Copyright by Karsten Keil <keil@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 HST High Soft Tech GmbH + * + */ + +#include <linux/init.h> +#include "hisax.h" +#include "isac.h" +#include "hscx.h" +#include "isdnl1.h" + +static char *saphir_rev = "$Revision: 1.10.2.4 $"; + +#define byteout(addr, val) outb(val, addr) +#define bytein(addr) inb(addr) + +#define ISAC_DATA 0 +#define HSCX_DATA 1 +#define ADDRESS_REG 2 +#define IRQ_REG 3 +#define SPARE_REG 4 +#define RESET_REG 5 + +static inline u_char +readreg(unsigned int ale, unsigned int adr, u_char off) +{ + register u_char ret; + + byteout(ale, off); + ret = bytein(adr); + return (ret); +} + +static inline void +readfifo(unsigned int ale, unsigned int adr, u_char off, u_char *data, int size) +{ + byteout(ale, off); + insb(adr, data, size); +} + + +static inline void +writereg(unsigned int ale, unsigned int adr, u_char off, u_char data) +{ + byteout(ale, off); + byteout(adr, data); +} + +static inline void +writefifo(unsigned int ale, unsigned int adr, u_char off, u_char *data, int size) +{ + byteout(ale, off); + outsb(adr, data, size); +} + +/* Interface functions */ + +static u_char +ReadISAC(struct IsdnCardState *cs, u_char offset) +{ + return (readreg(cs->hw.saphir.ale, cs->hw.saphir.isac, offset)); +} + +static void +WriteISAC(struct IsdnCardState *cs, u_char offset, u_char value) +{ + writereg(cs->hw.saphir.ale, cs->hw.saphir.isac, offset, value); +} + +static void +ReadISACfifo(struct IsdnCardState *cs, u_char *data, int size) +{ + readfifo(cs->hw.saphir.ale, cs->hw.saphir.isac, 0, data, size); +} + +static void +WriteISACfifo(struct IsdnCardState *cs, u_char *data, int size) +{ + writefifo(cs->hw.saphir.ale, cs->hw.saphir.isac, 0, data, size); +} + +static u_char +ReadHSCX(struct IsdnCardState *cs, int hscx, u_char offset) +{ + return (readreg(cs->hw.saphir.ale, cs->hw.saphir.hscx, + offset + (hscx ? 0x40 : 0))); +} + +static void +WriteHSCX(struct IsdnCardState *cs, int hscx, u_char offset, u_char value) +{ + writereg(cs->hw.saphir.ale, cs->hw.saphir.hscx, + offset + (hscx ? 0x40 : 0), value); +} + +#define READHSCX(cs, nr, reg) readreg(cs->hw.saphir.ale, \ + cs->hw.saphir.hscx, reg + (nr ? 0x40 : 0)) +#define WRITEHSCX(cs, nr, reg, data) writereg(cs->hw.saphir.ale, \ + cs->hw.saphir.hscx, reg + (nr ? 0x40 : 0), data) + +#define READHSCXFIFO(cs, nr, ptr, cnt) readfifo(cs->hw.saphir.ale, \ + cs->hw.saphir.hscx, (nr ? 0x40 : 0), ptr, cnt) + +#define WRITEHSCXFIFO(cs, nr, ptr, cnt) writefifo(cs->hw.saphir.ale, \ + cs->hw.saphir.hscx, (nr ? 0x40 : 0), ptr, cnt) + +#include "hscx_irq.c" + +static irqreturn_t +saphir_interrupt(int intno, void *dev_id) +{ + struct IsdnCardState *cs = dev_id; + u_char val; + u_long flags; + + spin_lock_irqsave(&cs->lock, flags); + val = readreg(cs->hw.saphir.ale, cs->hw.saphir.hscx, HSCX_ISTA + 0x40); +Start_HSCX: + if (val) + hscx_int_main(cs, val); + val = readreg(cs->hw.saphir.ale, cs->hw.saphir.isac, ISAC_ISTA); +Start_ISAC: + if (val) + isac_interrupt(cs, val); + val = readreg(cs->hw.saphir.ale, cs->hw.saphir.hscx, HSCX_ISTA + 0x40); + if (val) { + if (cs->debug & L1_DEB_HSCX) + debugl1(cs, "HSCX IntStat after IntRoutine"); + goto Start_HSCX; + } + val = readreg(cs->hw.saphir.ale, cs->hw.saphir.isac, ISAC_ISTA); + if (val) { + if (cs->debug & L1_DEB_ISAC) + debugl1(cs, "ISAC IntStat after IntRoutine"); + goto Start_ISAC; + } + /* Watchdog */ + if (cs->hw.saphir.timer.function) + mod_timer(&cs->hw.saphir.timer, jiffies + 1 * HZ); + else + printk(KERN_WARNING "saphir: Spurious timer!\n"); + writereg(cs->hw.saphir.ale, cs->hw.saphir.hscx, HSCX_MASK, 0xFF); + writereg(cs->hw.saphir.ale, cs->hw.saphir.hscx, HSCX_MASK + 0x40, 0xFF); + writereg(cs->hw.saphir.ale, cs->hw.saphir.isac, ISAC_MASK, 0xFF); + writereg(cs->hw.saphir.ale, cs->hw.saphir.isac, ISAC_MASK, 0); + writereg(cs->hw.saphir.ale, cs->hw.saphir.hscx, HSCX_MASK, 0); + writereg(cs->hw.saphir.ale, cs->hw.saphir.hscx, HSCX_MASK + 0x40, 0); + spin_unlock_irqrestore(&cs->lock, flags); + return IRQ_HANDLED; +} + +static void +SaphirWatchDog(struct timer_list *t) +{ + struct IsdnCardState *cs = from_timer(cs, t, hw.saphir.timer); + u_long flags; + + spin_lock_irqsave(&cs->lock, flags); + /* 5 sec WatchDog, so read at least every 4 sec */ + cs->readisac(cs, ISAC_RBCH); + spin_unlock_irqrestore(&cs->lock, flags); + mod_timer(&cs->hw.saphir.timer, jiffies + 1 * HZ); +} + +static void +release_io_saphir(struct IsdnCardState *cs) +{ + byteout(cs->hw.saphir.cfg_reg + IRQ_REG, 0xff); + del_timer(&cs->hw.saphir.timer); + cs->hw.saphir.timer.function = NULL; + if (cs->hw.saphir.cfg_reg) + release_region(cs->hw.saphir.cfg_reg, 6); +} + +static int +saphir_reset(struct IsdnCardState *cs) +{ + u_char irq_val; + + switch (cs->irq) { + case 5: irq_val = 0; + break; + case 3: irq_val = 1; + break; + case 11: + irq_val = 2; + break; + case 12: + irq_val = 3; + break; + case 15: + irq_val = 4; + break; + default: + printk(KERN_WARNING "HiSax: saphir wrong IRQ %d\n", + cs->irq); + return (1); + } + byteout(cs->hw.saphir.cfg_reg + IRQ_REG, irq_val); + byteout(cs->hw.saphir.cfg_reg + RESET_REG, 1); + mdelay(10); + byteout(cs->hw.saphir.cfg_reg + RESET_REG, 0); + mdelay(10); + byteout(cs->hw.saphir.cfg_reg + IRQ_REG, irq_val); + byteout(cs->hw.saphir.cfg_reg + SPARE_REG, 0x02); + return (0); +} + +static int +saphir_card_msg(struct IsdnCardState *cs, int mt, void *arg) +{ + u_long flags; + + switch (mt) { + case CARD_RESET: + spin_lock_irqsave(&cs->lock, flags); + saphir_reset(cs); + spin_unlock_irqrestore(&cs->lock, flags); + return (0); + case CARD_RELEASE: + release_io_saphir(cs); + return (0); + case CARD_INIT: + spin_lock_irqsave(&cs->lock, flags); + inithscxisac(cs, 3); + spin_unlock_irqrestore(&cs->lock, flags); + return (0); + case CARD_TEST: + return (0); + } + return (0); +} + + +int setup_saphir(struct IsdnCard *card) +{ + struct IsdnCardState *cs = card->cs; + char tmp[64]; + + strcpy(tmp, saphir_rev); + printk(KERN_INFO "HiSax: HST Saphir driver Rev. %s\n", HiSax_getrev(tmp)); + if (cs->typ != ISDN_CTYPE_HSTSAPHIR) + return (0); + + /* IO-Ports */ + cs->hw.saphir.cfg_reg = card->para[1]; + cs->hw.saphir.isac = card->para[1] + ISAC_DATA; + cs->hw.saphir.hscx = card->para[1] + HSCX_DATA; + cs->hw.saphir.ale = card->para[1] + ADDRESS_REG; + cs->irq = card->para[0]; + if (!request_region(cs->hw.saphir.cfg_reg, 6, "saphir")) { + printk(KERN_WARNING + "HiSax: HST Saphir config port %x-%x already in use\n", + cs->hw.saphir.cfg_reg, + cs->hw.saphir.cfg_reg + 5); + return (0); + } + + printk(KERN_INFO "HiSax: HST Saphir config irq:%d io:0x%X\n", + cs->irq, cs->hw.saphir.cfg_reg); + + setup_isac(cs); + timer_setup(&cs->hw.saphir.timer, SaphirWatchDog, 0); + cs->hw.saphir.timer.expires = jiffies + 4 * HZ; + add_timer(&cs->hw.saphir.timer); + if (saphir_reset(cs)) { + release_io_saphir(cs); + return (0); + } + cs->readisac = &ReadISAC; + cs->writeisac = &WriteISAC; + cs->readisacfifo = &ReadISACfifo; + cs->writeisacfifo = &WriteISACfifo; + cs->BC_Read_Reg = &ReadHSCX; + cs->BC_Write_Reg = &WriteHSCX; + cs->BC_Send_Data = &hscx_fill_fifo; + cs->cardmsg = &saphir_card_msg; + cs->irq_func = &saphir_interrupt; + ISACVersion(cs, "saphir:"); + if (HscxVersion(cs, "saphir:")) { + printk(KERN_WARNING + "saphir: wrong HSCX versions check IO address\n"); + release_io_saphir(cs); + return (0); + } + return (1); +} diff --git a/drivers/isdn/hisax/sedlbauer.c b/drivers/isdn/hisax/sedlbauer.c new file mode 100644 index 000000000..c0b97b893 --- /dev/null +++ b/drivers/isdn/hisax/sedlbauer.c @@ -0,0 +1,873 @@ +/* $Id: sedlbauer.c,v 1.34.2.6 2004/01/24 20:47:24 keil Exp $ + * + * low level stuff for Sedlbauer cards + * includes support for the Sedlbauer speed star (speed star II), + * support for the Sedlbauer speed fax+, + * support for the Sedlbauer ISDN-Controller PC/104 and + * support for the Sedlbauer speed pci + * derived from the original file asuscom.c from Karsten Keil + * + * Author Marcus Niemann + * Copyright by Marcus Niemann <niemann@www-bib.fh-bielefeld.de> + * + * This software may be used and distributed according to the terms + * of the GNU General Public License, incorporated herein by reference. + * + * Thanks to Karsten Keil + * Sedlbauer AG for informations + * Edgar Toernig + * + */ + +/* Supported cards: + * Card: Chip: Configuration: Comment: + * --------------------------------------------------------------------- + * Speed Card ISAC_HSCX DIP-SWITCH + * Speed Win ISAC_HSCX ISAPNP + * Speed Fax+ ISAC_ISAR ISAPNP Full analog support + * Speed Star ISAC_HSCX CARDMGR + * Speed Win2 IPAC ISAPNP + * ISDN PC/104 IPAC DIP-SWITCH + * Speed Star2 IPAC CARDMGR + * Speed PCI IPAC PCI PNP + * Speed Fax+ ISAC_ISAR PCI PNP Full analog support + * + * Important: + * For the sedlbauer speed fax+ to work properly you have to download + * the firmware onto the card. + * For example: hisaxctrl <DriverID> 9 ISAR.BIN + */ + +#include <linux/init.h> +#include "hisax.h" +#include "isac.h" +#include "ipac.h" +#include "hscx.h" +#include "isar.h" +#include "isdnl1.h" +#include <linux/pci.h> +#include <linux/isapnp.h> + +static const char *Sedlbauer_revision = "$Revision: 1.34.2.6 $"; + +static const char *Sedlbauer_Types[] = +{"None", "speed card/win", "speed star", "speed fax+", + "speed win II / ISDN PC/104", "speed star II", "speed pci", + "speed fax+ pyramid", "speed fax+ pci", "HST Saphir III"}; + +#define PCI_SUBVENDOR_SPEEDFAX_PYRAMID 0x51 +#define PCI_SUBVENDOR_HST_SAPHIR3 0x52 +#define PCI_SUBVENDOR_SEDLBAUER_PCI 0x53 +#define PCI_SUBVENDOR_SPEEDFAX_PCI 0x54 +#define PCI_SUB_ID_SEDLBAUER 0x01 + +#define SEDL_SPEED_CARD_WIN 1 +#define SEDL_SPEED_STAR 2 +#define SEDL_SPEED_FAX 3 +#define SEDL_SPEED_WIN2_PC104 4 +#define SEDL_SPEED_STAR2 5 +#define SEDL_SPEED_PCI 6 +#define SEDL_SPEEDFAX_PYRAMID 7 +#define SEDL_SPEEDFAX_PCI 8 +#define HST_SAPHIR3 9 + +#define SEDL_CHIP_TEST 0 +#define SEDL_CHIP_ISAC_HSCX 1 +#define SEDL_CHIP_ISAC_ISAR 2 +#define SEDL_CHIP_IPAC 3 + +#define SEDL_BUS_ISA 1 +#define SEDL_BUS_PCI 2 +#define SEDL_BUS_PCMCIA 3 + +#define byteout(addr, val) outb(val, addr) +#define bytein(addr) inb(addr) + +#define SEDL_HSCX_ISA_RESET_ON 0 +#define SEDL_HSCX_ISA_RESET_OFF 1 +#define SEDL_HSCX_ISA_ISAC 2 +#define SEDL_HSCX_ISA_HSCX 3 +#define SEDL_HSCX_ISA_ADR 4 + +#define SEDL_HSCX_PCMCIA_RESET 0 +#define SEDL_HSCX_PCMCIA_ISAC 1 +#define SEDL_HSCX_PCMCIA_HSCX 2 +#define SEDL_HSCX_PCMCIA_ADR 4 + +#define SEDL_ISAR_ISA_ISAC 4 +#define SEDL_ISAR_ISA_ISAR 6 +#define SEDL_ISAR_ISA_ADR 8 +#define SEDL_ISAR_ISA_ISAR_RESET_ON 10 +#define SEDL_ISAR_ISA_ISAR_RESET_OFF 12 + +#define SEDL_IPAC_ANY_ADR 0 +#define SEDL_IPAC_ANY_IPAC 2 + +#define SEDL_IPAC_PCI_BASE 0 +#define SEDL_IPAC_PCI_ADR 0xc0 +#define SEDL_IPAC_PCI_IPAC 0xc8 +#define SEDL_ISAR_PCI_ADR 0xc8 +#define SEDL_ISAR_PCI_ISAC 0xd0 +#define SEDL_ISAR_PCI_ISAR 0xe0 +#define SEDL_ISAR_PCI_ISAR_RESET_ON 0x01 +#define SEDL_ISAR_PCI_ISAR_RESET_OFF 0x18 +#define SEDL_ISAR_PCI_LED1 0x08 +#define SEDL_ISAR_PCI_LED2 0x10 + +#define SEDL_RESET 0x3 /* same as DOS driver */ + +static inline u_char +readreg(unsigned int ale, unsigned int adr, u_char off) +{ + register u_char ret; + + byteout(ale, off); + ret = bytein(adr); + return (ret); +} + +static inline void +readfifo(unsigned int ale, unsigned int adr, u_char off, u_char *data, int size) +{ + byteout(ale, off); + insb(adr, data, size); +} + + +static inline void +writereg(unsigned int ale, unsigned int adr, u_char off, u_char data) +{ + byteout(ale, off); + byteout(adr, data); +} + +static inline void +writefifo(unsigned int ale, unsigned int adr, u_char off, u_char *data, int size) +{ + byteout(ale, off); + outsb(adr, data, size); +} + +/* Interface functions */ + +static u_char +ReadISAC(struct IsdnCardState *cs, u_char offset) +{ + return (readreg(cs->hw.sedl.adr, cs->hw.sedl.isac, offset)); +} + +static void +WriteISAC(struct IsdnCardState *cs, u_char offset, u_char value) +{ + writereg(cs->hw.sedl.adr, cs->hw.sedl.isac, offset, value); +} + +static void +ReadISACfifo(struct IsdnCardState *cs, u_char *data, int size) +{ + readfifo(cs->hw.sedl.adr, cs->hw.sedl.isac, 0, data, size); +} + +static void +WriteISACfifo(struct IsdnCardState *cs, u_char *data, int size) +{ + writefifo(cs->hw.sedl.adr, cs->hw.sedl.isac, 0, data, size); +} + +static u_char +ReadISAC_IPAC(struct IsdnCardState *cs, u_char offset) +{ + return (readreg(cs->hw.sedl.adr, cs->hw.sedl.isac, offset | 0x80)); +} + +static void +WriteISAC_IPAC(struct IsdnCardState *cs, u_char offset, u_char value) +{ + writereg(cs->hw.sedl.adr, cs->hw.sedl.isac, offset | 0x80, value); +} + +static void +ReadISACfifo_IPAC(struct IsdnCardState *cs, u_char *data, int size) +{ + readfifo(cs->hw.sedl.adr, cs->hw.sedl.isac, 0x80, data, size); +} + +static void +WriteISACfifo_IPAC(struct IsdnCardState *cs, u_char *data, int size) +{ + writefifo(cs->hw.sedl.adr, cs->hw.sedl.isac, 0x80, data, size); +} + +static u_char +ReadHSCX(struct IsdnCardState *cs, int hscx, u_char offset) +{ + return (readreg(cs->hw.sedl.adr, + cs->hw.sedl.hscx, offset + (hscx ? 0x40 : 0))); +} + +static void +WriteHSCX(struct IsdnCardState *cs, int hscx, u_char offset, u_char value) +{ + writereg(cs->hw.sedl.adr, + cs->hw.sedl.hscx, offset + (hscx ? 0x40 : 0), value); +} + +/* ISAR access routines + * mode = 0 access with IRQ on + * mode = 1 access with IRQ off + * mode = 2 access with IRQ off and using last offset + */ + +static u_char +ReadISAR(struct IsdnCardState *cs, int mode, u_char offset) +{ + if (mode == 0) + return (readreg(cs->hw.sedl.adr, cs->hw.sedl.hscx, offset)); + else if (mode == 1) + byteout(cs->hw.sedl.adr, offset); + return (bytein(cs->hw.sedl.hscx)); +} + +static void +WriteISAR(struct IsdnCardState *cs, int mode, u_char offset, u_char value) +{ + if (mode == 0) + writereg(cs->hw.sedl.adr, cs->hw.sedl.hscx, offset, value); + else { + if (mode == 1) + byteout(cs->hw.sedl.adr, offset); + byteout(cs->hw.sedl.hscx, value); + } +} + +/* + * fast interrupt HSCX stuff goes here + */ + +#define READHSCX(cs, nr, reg) readreg(cs->hw.sedl.adr, \ + cs->hw.sedl.hscx, reg + (nr ? 0x40 : 0)) +#define WRITEHSCX(cs, nr, reg, data) writereg(cs->hw.sedl.adr, \ + cs->hw.sedl.hscx, reg + (nr ? 0x40 : 0), data) + +#define READHSCXFIFO(cs, nr, ptr, cnt) readfifo(cs->hw.sedl.adr, \ + cs->hw.sedl.hscx, (nr ? 0x40 : 0), ptr, cnt) + +#define WRITEHSCXFIFO(cs, nr, ptr, cnt) writefifo(cs->hw.sedl.adr, \ + cs->hw.sedl.hscx, (nr ? 0x40 : 0), ptr, cnt) + +#include "hscx_irq.c" + +static irqreturn_t +sedlbauer_interrupt(int intno, void *dev_id) +{ + struct IsdnCardState *cs = dev_id; + u_char val; + u_long flags; + + spin_lock_irqsave(&cs->lock, flags); + if ((cs->hw.sedl.bus == SEDL_BUS_PCMCIA) && (*cs->busy_flag == 1)) { + /* The card tends to generate interrupts while being removed + causing us to just crash the kernel. bad. */ + spin_unlock_irqrestore(&cs->lock, flags); + printk(KERN_WARNING "Sedlbauer: card not available!\n"); + return IRQ_NONE; + } + + val = readreg(cs->hw.sedl.adr, cs->hw.sedl.hscx, HSCX_ISTA + 0x40); +Start_HSCX: + if (val) + hscx_int_main(cs, val); + val = readreg(cs->hw.sedl.adr, cs->hw.sedl.isac, ISAC_ISTA); +Start_ISAC: + if (val) + isac_interrupt(cs, val); + val = readreg(cs->hw.sedl.adr, cs->hw.sedl.hscx, HSCX_ISTA + 0x40); + if (val) { + if (cs->debug & L1_DEB_HSCX) + debugl1(cs, "HSCX IntStat after IntRoutine"); + goto Start_HSCX; + } + val = readreg(cs->hw.sedl.adr, cs->hw.sedl.isac, ISAC_ISTA); + if (val) { + if (cs->debug & L1_DEB_ISAC) + debugl1(cs, "ISAC IntStat after IntRoutine"); + goto Start_ISAC; + } + writereg(cs->hw.sedl.adr, cs->hw.sedl.hscx, HSCX_MASK, 0xFF); + writereg(cs->hw.sedl.adr, cs->hw.sedl.hscx, HSCX_MASK + 0x40, 0xFF); + writereg(cs->hw.sedl.adr, cs->hw.sedl.isac, ISAC_MASK, 0xFF); + writereg(cs->hw.sedl.adr, cs->hw.sedl.isac, ISAC_MASK, 0x0); + writereg(cs->hw.sedl.adr, cs->hw.sedl.hscx, HSCX_MASK, 0x0); + writereg(cs->hw.sedl.adr, cs->hw.sedl.hscx, HSCX_MASK + 0x40, 0x0); + spin_unlock_irqrestore(&cs->lock, flags); + return IRQ_HANDLED; +} + +static irqreturn_t +sedlbauer_interrupt_ipac(int intno, void *dev_id) +{ + struct IsdnCardState *cs = dev_id; + u_char ista, val, icnt = 5; + u_long flags; + + spin_lock_irqsave(&cs->lock, flags); + ista = readreg(cs->hw.sedl.adr, cs->hw.sedl.isac, IPAC_ISTA); +Start_IPAC: + if (cs->debug & L1_DEB_IPAC) + debugl1(cs, "IPAC ISTA %02X", ista); + if (ista & 0x0f) { + val = readreg(cs->hw.sedl.adr, cs->hw.sedl.hscx, HSCX_ISTA + 0x40); + if (ista & 0x01) + val |= 0x01; + if (ista & 0x04) + val |= 0x02; + if (ista & 0x08) + val |= 0x04; + if (val) + hscx_int_main(cs, val); + } + if (ista & 0x20) { + val = 0xfe & readreg(cs->hw.sedl.adr, cs->hw.sedl.isac, ISAC_ISTA | 0x80); + if (val) { + isac_interrupt(cs, val); + } + } + if (ista & 0x10) { + val = 0x01; + isac_interrupt(cs, val); + } + ista = readreg(cs->hw.sedl.adr, cs->hw.sedl.isac, IPAC_ISTA); + if ((ista & 0x3f) && icnt) { + icnt--; + goto Start_IPAC; + } + if (!icnt) + if (cs->debug & L1_DEB_ISAC) + debugl1(cs, "Sedlbauer IRQ LOOP"); + writereg(cs->hw.sedl.adr, cs->hw.sedl.isac, IPAC_MASK, 0xFF); + writereg(cs->hw.sedl.adr, cs->hw.sedl.isac, IPAC_MASK, 0xC0); + spin_unlock_irqrestore(&cs->lock, flags); + return IRQ_HANDLED; +} + +static irqreturn_t +sedlbauer_interrupt_isar(int intno, void *dev_id) +{ + struct IsdnCardState *cs = dev_id; + u_char val; + int cnt = 5; + u_long flags; + + spin_lock_irqsave(&cs->lock, flags); + val = readreg(cs->hw.sedl.adr, cs->hw.sedl.hscx, ISAR_IRQBIT); +Start_ISAR: + if (val & ISAR_IRQSTA) + isar_int_main(cs); + val = readreg(cs->hw.sedl.adr, cs->hw.sedl.isac, ISAC_ISTA); +Start_ISAC: + if (val) + isac_interrupt(cs, val); + val = readreg(cs->hw.sedl.adr, cs->hw.sedl.hscx, ISAR_IRQBIT); + if ((val & ISAR_IRQSTA) && --cnt) { + if (cs->debug & L1_DEB_HSCX) + debugl1(cs, "ISAR IntStat after IntRoutine"); + goto Start_ISAR; + } + val = readreg(cs->hw.sedl.adr, cs->hw.sedl.isac, ISAC_ISTA); + if (val && --cnt) { + if (cs->debug & L1_DEB_ISAC) + debugl1(cs, "ISAC IntStat after IntRoutine"); + goto Start_ISAC; + } + if (!cnt) + if (cs->debug & L1_DEB_ISAC) + debugl1(cs, "Sedlbauer IRQ LOOP"); + + writereg(cs->hw.sedl.adr, cs->hw.sedl.hscx, ISAR_IRQBIT, 0); + writereg(cs->hw.sedl.adr, cs->hw.sedl.isac, ISAC_MASK, 0xFF); + writereg(cs->hw.sedl.adr, cs->hw.sedl.isac, ISAC_MASK, 0x0); + writereg(cs->hw.sedl.adr, cs->hw.sedl.hscx, ISAR_IRQBIT, ISAR_IRQMSK); + spin_unlock_irqrestore(&cs->lock, flags); + return IRQ_HANDLED; +} + +static void +release_io_sedlbauer(struct IsdnCardState *cs) +{ + int bytecnt = 8; + + if (cs->subtyp == SEDL_SPEED_FAX) { + bytecnt = 16; + } else if (cs->hw.sedl.bus == SEDL_BUS_PCI) { + bytecnt = 256; + } + if (cs->hw.sedl.cfg_reg) + release_region(cs->hw.sedl.cfg_reg, bytecnt); +} + +static void +reset_sedlbauer(struct IsdnCardState *cs) +{ + printk(KERN_INFO "Sedlbauer: resetting card\n"); + + if (!((cs->hw.sedl.bus == SEDL_BUS_PCMCIA) && + (cs->hw.sedl.chip == SEDL_CHIP_ISAC_HSCX))) { + if (cs->hw.sedl.chip == SEDL_CHIP_IPAC) { + writereg(cs->hw.sedl.adr, cs->hw.sedl.isac, IPAC_POTA2, 0x20); + mdelay(2); + writereg(cs->hw.sedl.adr, cs->hw.sedl.isac, IPAC_POTA2, 0x0); + mdelay(10); + writereg(cs->hw.sedl.adr, cs->hw.sedl.isac, IPAC_CONF, 0x0); + writereg(cs->hw.sedl.adr, cs->hw.sedl.isac, IPAC_ACFG, 0xff); + writereg(cs->hw.sedl.adr, cs->hw.sedl.isac, IPAC_AOE, 0x0); + writereg(cs->hw.sedl.adr, cs->hw.sedl.isac, IPAC_MASK, 0xc0); + writereg(cs->hw.sedl.adr, cs->hw.sedl.isac, IPAC_PCFG, 0x12); + } else if ((cs->hw.sedl.chip == SEDL_CHIP_ISAC_ISAR) && + (cs->hw.sedl.bus == SEDL_BUS_PCI)) { + byteout(cs->hw.sedl.cfg_reg + 3, cs->hw.sedl.reset_on); + mdelay(2); + byteout(cs->hw.sedl.cfg_reg + 3, cs->hw.sedl.reset_off); + mdelay(10); + } else { + byteout(cs->hw.sedl.reset_on, SEDL_RESET); /* Reset On */ + mdelay(2); + byteout(cs->hw.sedl.reset_off, 0); /* Reset Off */ + mdelay(10); + } + } +} + +static int +Sedl_card_msg(struct IsdnCardState *cs, int mt, void *arg) +{ + u_long flags; + + switch (mt) { + case CARD_RESET: + spin_lock_irqsave(&cs->lock, flags); + reset_sedlbauer(cs); + spin_unlock_irqrestore(&cs->lock, flags); + return (0); + case CARD_RELEASE: + if (cs->hw.sedl.bus == SEDL_BUS_PCI) + /* disable all IRQ */ + byteout(cs->hw.sedl.cfg_reg + 5, 0); + if (cs->hw.sedl.chip == SEDL_CHIP_ISAC_ISAR) { + spin_lock_irqsave(&cs->lock, flags); + writereg(cs->hw.sedl.adr, cs->hw.sedl.hscx, + ISAR_IRQBIT, 0); + writereg(cs->hw.sedl.adr, cs->hw.sedl.isac, + ISAC_MASK, 0xFF); + reset_sedlbauer(cs); + writereg(cs->hw.sedl.adr, cs->hw.sedl.hscx, + ISAR_IRQBIT, 0); + writereg(cs->hw.sedl.adr, cs->hw.sedl.isac, + ISAC_MASK, 0xFF); + spin_unlock_irqrestore(&cs->lock, flags); + } + release_io_sedlbauer(cs); + return (0); + case CARD_INIT: + spin_lock_irqsave(&cs->lock, flags); + if (cs->hw.sedl.bus == SEDL_BUS_PCI) + /* enable all IRQ */ + byteout(cs->hw.sedl.cfg_reg + 5, 0x02); + reset_sedlbauer(cs); + if (cs->hw.sedl.chip == SEDL_CHIP_ISAC_ISAR) { + clear_pending_isac_ints(cs); + writereg(cs->hw.sedl.adr, cs->hw.sedl.hscx, + ISAR_IRQBIT, 0); + initisac(cs); + initisar(cs); + /* Reenable all IRQ */ + cs->writeisac(cs, ISAC_MASK, 0); + /* RESET Receiver and Transmitter */ + cs->writeisac(cs, ISAC_CMDR, 0x41); + } else { + inithscxisac(cs, 3); + } + spin_unlock_irqrestore(&cs->lock, flags); + return (0); + case CARD_TEST: + return (0); + case MDL_INFO_CONN: + if (cs->subtyp != SEDL_SPEEDFAX_PYRAMID) + return (0); + spin_lock_irqsave(&cs->lock, flags); + if ((long) arg) + cs->hw.sedl.reset_off &= ~SEDL_ISAR_PCI_LED2; + else + cs->hw.sedl.reset_off &= ~SEDL_ISAR_PCI_LED1; + byteout(cs->hw.sedl.cfg_reg + 3, cs->hw.sedl.reset_off); + spin_unlock_irqrestore(&cs->lock, flags); + break; + case MDL_INFO_REL: + if (cs->subtyp != SEDL_SPEEDFAX_PYRAMID) + return (0); + spin_lock_irqsave(&cs->lock, flags); + if ((long) arg) + cs->hw.sedl.reset_off |= SEDL_ISAR_PCI_LED2; + else + cs->hw.sedl.reset_off |= SEDL_ISAR_PCI_LED1; + byteout(cs->hw.sedl.cfg_reg + 3, cs->hw.sedl.reset_off); + spin_unlock_irqrestore(&cs->lock, flags); + break; + } + return (0); +} + +#ifdef __ISAPNP__ +static struct isapnp_device_id sedl_ids[] = { + { ISAPNP_VENDOR('S', 'A', 'G'), ISAPNP_FUNCTION(0x01), + ISAPNP_VENDOR('S', 'A', 'G'), ISAPNP_FUNCTION(0x01), + (unsigned long) "Speed win" }, + { ISAPNP_VENDOR('S', 'A', 'G'), ISAPNP_FUNCTION(0x02), + ISAPNP_VENDOR('S', 'A', 'G'), ISAPNP_FUNCTION(0x02), + (unsigned long) "Speed Fax+" }, + { 0, } +}; + +static struct isapnp_device_id *ipid = &sedl_ids[0]; +static struct pnp_card *pnp_c = NULL; + +static int setup_sedlbauer_isapnp(struct IsdnCard *card, int *bytecnt) +{ + struct IsdnCardState *cs = card->cs; + struct pnp_dev *pnp_d; + + if (!isapnp_present()) + return -1; + + while (ipid->card_vendor) { + if ((pnp_c = pnp_find_card(ipid->card_vendor, + ipid->card_device, pnp_c))) { + pnp_d = NULL; + if ((pnp_d = pnp_find_dev(pnp_c, + ipid->vendor, ipid->function, pnp_d))) { + int err; + + printk(KERN_INFO "HiSax: %s detected\n", + (char *)ipid->driver_data); + pnp_disable_dev(pnp_d); + err = pnp_activate_dev(pnp_d); + if (err < 0) { + printk(KERN_WARNING "%s: pnp_activate_dev ret(%d)\n", + __func__, err); + return (0); + } + card->para[1] = pnp_port_start(pnp_d, 0); + card->para[0] = pnp_irq(pnp_d, 0); + + if (card->para[0] == -1 || !card->para[1]) { + printk(KERN_ERR "Sedlbauer PnP:some resources are missing %ld/%lx\n", + card->para[0], card->para[1]); + pnp_disable_dev(pnp_d); + return (0); + } + cs->hw.sedl.cfg_reg = card->para[1]; + cs->irq = card->para[0]; + if (ipid->function == ISAPNP_FUNCTION(0x2)) { + cs->subtyp = SEDL_SPEED_FAX; + cs->hw.sedl.chip = SEDL_CHIP_ISAC_ISAR; + *bytecnt = 16; + } else { + cs->subtyp = SEDL_SPEED_CARD_WIN; + cs->hw.sedl.chip = SEDL_CHIP_TEST; + } + + return (1); + } else { + printk(KERN_ERR "Sedlbauer PnP: PnP error card found, no device\n"); + return (0); + } + } + ipid++; + pnp_c = NULL; + } + + printk(KERN_INFO "Sedlbauer PnP: no ISAPnP card found\n"); + return -1; +} +#else + +static int setup_sedlbauer_isapnp(struct IsdnCard *card, int *bytecnt) +{ + return -1; +} +#endif /* __ISAPNP__ */ + +#ifdef CONFIG_PCI +static struct pci_dev *dev_sedl = NULL; + +static int setup_sedlbauer_pci(struct IsdnCard *card) +{ + struct IsdnCardState *cs = card->cs; + u16 sub_vendor_id, sub_id; + + if ((dev_sedl = hisax_find_pci_device(PCI_VENDOR_ID_TIGERJET, + PCI_DEVICE_ID_TIGERJET_100, dev_sedl))) { + if (pci_enable_device(dev_sedl)) + return (0); + cs->irq = dev_sedl->irq; + if (!cs->irq) { + printk(KERN_WARNING "Sedlbauer: No IRQ for PCI card found\n"); + return (0); + } + cs->hw.sedl.cfg_reg = pci_resource_start(dev_sedl, 0); + } else { + printk(KERN_WARNING "Sedlbauer: No PCI card found\n"); + return (0); + } + cs->irq_flags |= IRQF_SHARED; + cs->hw.sedl.bus = SEDL_BUS_PCI; + sub_vendor_id = dev_sedl->subsystem_vendor; + sub_id = dev_sedl->subsystem_device; + printk(KERN_INFO "Sedlbauer: PCI subvendor:%x subid %x\n", + sub_vendor_id, sub_id); + printk(KERN_INFO "Sedlbauer: PCI base adr %#x\n", + cs->hw.sedl.cfg_reg); + if (sub_id != PCI_SUB_ID_SEDLBAUER) { + printk(KERN_ERR "Sedlbauer: unknown sub id %#x\n", sub_id); + return (0); + } + if (sub_vendor_id == PCI_SUBVENDOR_SPEEDFAX_PYRAMID) { + cs->hw.sedl.chip = SEDL_CHIP_ISAC_ISAR; + cs->subtyp = SEDL_SPEEDFAX_PYRAMID; + } else if (sub_vendor_id == PCI_SUBVENDOR_SPEEDFAX_PCI) { + cs->hw.sedl.chip = SEDL_CHIP_ISAC_ISAR; + cs->subtyp = SEDL_SPEEDFAX_PCI; + } else if (sub_vendor_id == PCI_SUBVENDOR_HST_SAPHIR3) { + cs->hw.sedl.chip = SEDL_CHIP_IPAC; + cs->subtyp = HST_SAPHIR3; + } else if (sub_vendor_id == PCI_SUBVENDOR_SEDLBAUER_PCI) { + cs->hw.sedl.chip = SEDL_CHIP_IPAC; + cs->subtyp = SEDL_SPEED_PCI; + } else { + printk(KERN_ERR "Sedlbauer: unknown sub vendor id %#x\n", + sub_vendor_id); + return (0); + } + + cs->hw.sedl.reset_on = SEDL_ISAR_PCI_ISAR_RESET_ON; + cs->hw.sedl.reset_off = SEDL_ISAR_PCI_ISAR_RESET_OFF; + byteout(cs->hw.sedl.cfg_reg, 0xff); + byteout(cs->hw.sedl.cfg_reg, 0x00); + byteout(cs->hw.sedl.cfg_reg + 2, 0xdd); + byteout(cs->hw.sedl.cfg_reg + 5, 0); /* disable all IRQ */ + byteout(cs->hw.sedl.cfg_reg + 3, cs->hw.sedl.reset_on); + mdelay(2); + byteout(cs->hw.sedl.cfg_reg + 3, cs->hw.sedl.reset_off); + mdelay(10); + + return (1); +} + +#else + +static int setup_sedlbauer_pci(struct IsdnCard *card) +{ + return (1); +} + +#endif /* CONFIG_PCI */ + +int setup_sedlbauer(struct IsdnCard *card) +{ + int bytecnt = 8, ver, val, rc; + struct IsdnCardState *cs = card->cs; + char tmp[64]; + + strcpy(tmp, Sedlbauer_revision); + printk(KERN_INFO "HiSax: Sedlbauer driver Rev. %s\n", HiSax_getrev(tmp)); + + if (cs->typ == ISDN_CTYPE_SEDLBAUER) { + cs->subtyp = SEDL_SPEED_CARD_WIN; + cs->hw.sedl.bus = SEDL_BUS_ISA; + cs->hw.sedl.chip = SEDL_CHIP_TEST; + } else if (cs->typ == ISDN_CTYPE_SEDLBAUER_PCMCIA) { + cs->subtyp = SEDL_SPEED_STAR; + cs->hw.sedl.bus = SEDL_BUS_PCMCIA; + cs->hw.sedl.chip = SEDL_CHIP_TEST; + } else if (cs->typ == ISDN_CTYPE_SEDLBAUER_FAX) { + cs->subtyp = SEDL_SPEED_FAX; + cs->hw.sedl.bus = SEDL_BUS_ISA; + cs->hw.sedl.chip = SEDL_CHIP_ISAC_ISAR; + } else + return (0); + + bytecnt = 8; + if (card->para[1]) { + cs->hw.sedl.cfg_reg = card->para[1]; + cs->irq = card->para[0]; + if (cs->hw.sedl.chip == SEDL_CHIP_ISAC_ISAR) { + bytecnt = 16; + } + } else { + rc = setup_sedlbauer_isapnp(card, &bytecnt); + if (!rc) + return (0); + if (rc > 0) + goto ready; + + /* Probe for Sedlbauer speed pci */ + rc = setup_sedlbauer_pci(card); + if (!rc) + return (0); + + bytecnt = 256; + } + +ready: + + /* In case of the sedlbauer pcmcia card, this region is in use, + * reserved for us by the card manager. So we do not check it + * here, it would fail. + */ + if (cs->hw.sedl.bus != SEDL_BUS_PCMCIA && + !request_region(cs->hw.sedl.cfg_reg, bytecnt, "sedlbauer isdn")) { + printk(KERN_WARNING + "HiSax: %s config port %x-%x already in use\n", + CardType[card->typ], + cs->hw.sedl.cfg_reg, + cs->hw.sedl.cfg_reg + bytecnt); + return (0); + } + + printk(KERN_INFO + "Sedlbauer: defined at 0x%x-0x%x IRQ %d\n", + cs->hw.sedl.cfg_reg, + cs->hw.sedl.cfg_reg + bytecnt, + cs->irq); + + cs->BC_Read_Reg = &ReadHSCX; + cs->BC_Write_Reg = &WriteHSCX; + cs->BC_Send_Data = &hscx_fill_fifo; + cs->cardmsg = &Sedl_card_msg; + +/* + * testing ISA and PCMCIA Cards for IPAC, default is ISAC + * do not test for PCI card, because ports are different + * and PCI card uses only IPAC (for the moment) + */ + if (cs->hw.sedl.bus != SEDL_BUS_PCI) { + val = readreg(cs->hw.sedl.cfg_reg + SEDL_IPAC_ANY_ADR, + cs->hw.sedl.cfg_reg + SEDL_IPAC_ANY_IPAC, IPAC_ID); + printk(KERN_DEBUG "Sedlbauer: testing IPAC version %x\n", val); + if ((val == 1) || (val == 2)) { + /* IPAC */ + cs->subtyp = SEDL_SPEED_WIN2_PC104; + if (cs->hw.sedl.bus == SEDL_BUS_PCMCIA) { + cs->subtyp = SEDL_SPEED_STAR2; + } + cs->hw.sedl.chip = SEDL_CHIP_IPAC; + } else { + /* ISAC_HSCX oder ISAC_ISAR */ + if (cs->hw.sedl.chip == SEDL_CHIP_TEST) { + cs->hw.sedl.chip = SEDL_CHIP_ISAC_HSCX; + } + } + } + +/* + * hw.sedl.chip is now properly set + */ + printk(KERN_INFO "Sedlbauer: %s detected\n", + Sedlbauer_Types[cs->subtyp]); + + setup_isac(cs); + if (cs->hw.sedl.chip == SEDL_CHIP_IPAC) { + if (cs->hw.sedl.bus == SEDL_BUS_PCI) { + cs->hw.sedl.adr = cs->hw.sedl.cfg_reg + SEDL_IPAC_PCI_ADR; + cs->hw.sedl.isac = cs->hw.sedl.cfg_reg + SEDL_IPAC_PCI_IPAC; + cs->hw.sedl.hscx = cs->hw.sedl.cfg_reg + SEDL_IPAC_PCI_IPAC; + } else { + cs->hw.sedl.adr = cs->hw.sedl.cfg_reg + SEDL_IPAC_ANY_ADR; + cs->hw.sedl.isac = cs->hw.sedl.cfg_reg + SEDL_IPAC_ANY_IPAC; + cs->hw.sedl.hscx = cs->hw.sedl.cfg_reg + SEDL_IPAC_ANY_IPAC; + } + test_and_set_bit(HW_IPAC, &cs->HW_Flags); + cs->readisac = &ReadISAC_IPAC; + cs->writeisac = &WriteISAC_IPAC; + cs->readisacfifo = &ReadISACfifo_IPAC; + cs->writeisacfifo = &WriteISACfifo_IPAC; + cs->irq_func = &sedlbauer_interrupt_ipac; + val = readreg(cs->hw.sedl.adr, cs->hw.sedl.isac, IPAC_ID); + printk(KERN_INFO "Sedlbauer: IPAC version %x\n", val); + } else { + /* ISAC_HSCX oder ISAC_ISAR */ + cs->readisac = &ReadISAC; + cs->writeisac = &WriteISAC; + cs->readisacfifo = &ReadISACfifo; + cs->writeisacfifo = &WriteISACfifo; + if (cs->hw.sedl.chip == SEDL_CHIP_ISAC_ISAR) { + if (cs->hw.sedl.bus == SEDL_BUS_PCI) { + cs->hw.sedl.adr = cs->hw.sedl.cfg_reg + + SEDL_ISAR_PCI_ADR; + cs->hw.sedl.isac = cs->hw.sedl.cfg_reg + + SEDL_ISAR_PCI_ISAC; + cs->hw.sedl.hscx = cs->hw.sedl.cfg_reg + + SEDL_ISAR_PCI_ISAR; + } else { + cs->hw.sedl.adr = cs->hw.sedl.cfg_reg + + SEDL_ISAR_ISA_ADR; + cs->hw.sedl.isac = cs->hw.sedl.cfg_reg + + SEDL_ISAR_ISA_ISAC; + cs->hw.sedl.hscx = cs->hw.sedl.cfg_reg + + SEDL_ISAR_ISA_ISAR; + cs->hw.sedl.reset_on = cs->hw.sedl.cfg_reg + + SEDL_ISAR_ISA_ISAR_RESET_ON; + cs->hw.sedl.reset_off = cs->hw.sedl.cfg_reg + + SEDL_ISAR_ISA_ISAR_RESET_OFF; + } + cs->bcs[0].hw.isar.reg = &cs->hw.sedl.isar; + cs->bcs[1].hw.isar.reg = &cs->hw.sedl.isar; + test_and_set_bit(HW_ISAR, &cs->HW_Flags); + cs->irq_func = &sedlbauer_interrupt_isar; + cs->auxcmd = &isar_auxcmd; + ISACVersion(cs, "Sedlbauer:"); + cs->BC_Read_Reg = &ReadISAR; + cs->BC_Write_Reg = &WriteISAR; + cs->BC_Send_Data = &isar_fill_fifo; + bytecnt = 3; + while (bytecnt) { + ver = ISARVersion(cs, "Sedlbauer:"); + if (ver < 0) + printk(KERN_WARNING + "Sedlbauer: wrong ISAR version (ret = %d)\n", ver); + else + break; + reset_sedlbauer(cs); + bytecnt--; + } + if (!bytecnt) { + release_io_sedlbauer(cs); + return (0); + } + } else { + if (cs->hw.sedl.bus == SEDL_BUS_PCMCIA) { + cs->hw.sedl.adr = cs->hw.sedl.cfg_reg + SEDL_HSCX_PCMCIA_ADR; + cs->hw.sedl.isac = cs->hw.sedl.cfg_reg + SEDL_HSCX_PCMCIA_ISAC; + cs->hw.sedl.hscx = cs->hw.sedl.cfg_reg + SEDL_HSCX_PCMCIA_HSCX; + cs->hw.sedl.reset_on = cs->hw.sedl.cfg_reg + SEDL_HSCX_PCMCIA_RESET; + cs->hw.sedl.reset_off = cs->hw.sedl.cfg_reg + SEDL_HSCX_PCMCIA_RESET; + cs->irq_flags |= IRQF_SHARED; + } else { + cs->hw.sedl.adr = cs->hw.sedl.cfg_reg + SEDL_HSCX_ISA_ADR; + cs->hw.sedl.isac = cs->hw.sedl.cfg_reg + SEDL_HSCX_ISA_ISAC; + cs->hw.sedl.hscx = cs->hw.sedl.cfg_reg + SEDL_HSCX_ISA_HSCX; + cs->hw.sedl.reset_on = cs->hw.sedl.cfg_reg + SEDL_HSCX_ISA_RESET_ON; + cs->hw.sedl.reset_off = cs->hw.sedl.cfg_reg + SEDL_HSCX_ISA_RESET_OFF; + } + cs->irq_func = &sedlbauer_interrupt; + ISACVersion(cs, "Sedlbauer:"); + + if (HscxVersion(cs, "Sedlbauer:")) { + printk(KERN_WARNING + "Sedlbauer: wrong HSCX versions check IO address\n"); + release_io_sedlbauer(cs); + return (0); + } + } + } + return (1); +} diff --git a/drivers/isdn/hisax/sedlbauer_cs.c b/drivers/isdn/hisax/sedlbauer_cs.c new file mode 100644 index 000000000..92ef62d4c --- /dev/null +++ b/drivers/isdn/hisax/sedlbauer_cs.c @@ -0,0 +1,209 @@ +/*====================================================================== + + A Sedlbauer PCMCIA client driver + + This driver is for the Sedlbauer Speed Star and Speed Star II, + which are ISDN PCMCIA Cards. + + The contents of this file are subject to the Mozilla Public + License Version 1.1 (the "License"); you may not use this file + except in compliance with the License. You may obtain a copy of + the License at http://www.mozilla.org/MPL/ + + Software distributed under the License is distributed on an "AS + IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or + implied. See the License for the specific language governing + rights and limitations under the License. + + The initial developer of the original code is David A. Hinds + <dahinds@users.sourceforge.net>. Portions created by David A. Hinds + are Copyright (C) 1999 David A. Hinds. All Rights Reserved. + + Modifications from dummy_cs.c are Copyright (C) 1999-2001 Marcus Niemann + <maniemann@users.sourceforge.net>. All Rights Reserved. + + Alternatively, the contents of this file may be used under the + terms of the GNU General Public License version 2 (the "GPL"), in + which case the provisions of the GPL are applicable instead of the + above. If you wish to allow the use of your version of this file + only under the terms of the GPL and not to allow others to use + your version of this file under the MPL, indicate your decision + by deleting the provisions above and replace them with the notice + and other provisions required by the GPL. If you do not delete + the provisions above, a recipient may use your version of this + file under either the MPL or the GPL. + + ======================================================================*/ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/ptrace.h> +#include <linux/slab.h> +#include <linux/string.h> +#include <linux/timer.h> +#include <linux/ioport.h> +#include <asm/io.h> + +#include <pcmcia/cistpl.h> +#include <pcmcia/cisreg.h> +#include <pcmcia/ds.h> +#include "hisax_cfg.h" + +MODULE_DESCRIPTION("ISDN4Linux: PCMCIA client driver for Sedlbauer cards"); +MODULE_AUTHOR("Marcus Niemann"); +MODULE_LICENSE("Dual MPL/GPL"); + + +/*====================================================================*/ + +/* Parameters that can be set with 'insmod' */ + +static int protocol = 2; /* EURO-ISDN Default */ +module_param(protocol, int, 0); + +static int sedlbauer_config(struct pcmcia_device *link); +static void sedlbauer_release(struct pcmcia_device *link); + +static void sedlbauer_detach(struct pcmcia_device *p_dev); + +typedef struct local_info_t { + struct pcmcia_device *p_dev; + int stop; + int cardnr; +} local_info_t; + +static int sedlbauer_probe(struct pcmcia_device *link) +{ + local_info_t *local; + + dev_dbg(&link->dev, "sedlbauer_attach()\n"); + + /* Allocate space for private device-specific data */ + local = kzalloc(sizeof(local_info_t), GFP_KERNEL); + if (!local) return -ENOMEM; + local->cardnr = -1; + + local->p_dev = link; + link->priv = local; + + return sedlbauer_config(link); +} /* sedlbauer_attach */ + +static void sedlbauer_detach(struct pcmcia_device *link) +{ + dev_dbg(&link->dev, "sedlbauer_detach(0x%p)\n", link); + + ((local_info_t *)link->priv)->stop = 1; + sedlbauer_release(link); + + /* This points to the parent local_info_t struct */ + kfree(link->priv); +} /* sedlbauer_detach */ + +static int sedlbauer_config_check(struct pcmcia_device *p_dev, void *priv_data) +{ + if (p_dev->config_index == 0) + return -EINVAL; + + p_dev->io_lines = 3; + return pcmcia_request_io(p_dev); +} + +static int sedlbauer_config(struct pcmcia_device *link) +{ + int ret; + IsdnCard_t icard; + + dev_dbg(&link->dev, "sedlbauer_config(0x%p)\n", link); + + link->config_flags |= CONF_ENABLE_IRQ | CONF_AUTO_CHECK_VCC | + CONF_AUTO_SET_VPP | CONF_AUTO_AUDIO | CONF_AUTO_SET_IO; + + ret = pcmcia_loop_config(link, sedlbauer_config_check, NULL); + if (ret) + goto failed; + + ret = pcmcia_enable_device(link); + if (ret) + goto failed; + + icard.para[0] = link->irq; + icard.para[1] = link->resource[0]->start; + icard.protocol = protocol; + icard.typ = ISDN_CTYPE_SEDLBAUER_PCMCIA; + + ret = hisax_init_pcmcia(link, + &(((local_info_t *)link->priv)->stop), &icard); + if (ret < 0) { + printk(KERN_ERR "sedlbauer_cs: failed to initialize SEDLBAUER PCMCIA %d with %pR\n", + ret, link->resource[0]); + sedlbauer_release(link); + return -ENODEV; + } else + ((local_info_t *)link->priv)->cardnr = ret; + + return 0; + +failed: + sedlbauer_release(link); + return -ENODEV; + +} /* sedlbauer_config */ + +static void sedlbauer_release(struct pcmcia_device *link) +{ + local_info_t *local = link->priv; + dev_dbg(&link->dev, "sedlbauer_release(0x%p)\n", link); + + if (local) { + if (local->cardnr >= 0) { + /* no unregister function with hisax */ + HiSax_closecard(local->cardnr); + } + } + + pcmcia_disable_device(link); +} /* sedlbauer_release */ + +static int sedlbauer_suspend(struct pcmcia_device *link) +{ + local_info_t *dev = link->priv; + + dev->stop = 1; + + return 0; +} + +static int sedlbauer_resume(struct pcmcia_device *link) +{ + local_info_t *dev = link->priv; + + dev->stop = 0; + + return 0; +} + + +static const struct pcmcia_device_id sedlbauer_ids[] = { + PCMCIA_DEVICE_PROD_ID123("SEDLBAUER", "speed star II", "V 3.1", 0x81fb79f5, 0xf3612e1d, 0x6b95c78a), + PCMCIA_DEVICE_PROD_ID123("SEDLBAUER", "ISDN-Adapter", "4D67", 0x81fb79f5, 0xe4e9bc12, 0x397b7e90), + PCMCIA_DEVICE_PROD_ID123("SEDLBAUER", "ISDN-Adapter", "4D98", 0x81fb79f5, 0xe4e9bc12, 0x2e5c7fce), + PCMCIA_DEVICE_PROD_ID123("SEDLBAUER", "ISDN-Adapter", " (C) 93-94 VK", 0x81fb79f5, 0xe4e9bc12, 0x8db143fe), + PCMCIA_DEVICE_PROD_ID123("SEDLBAUER", "ISDN-Adapter", " (c) 93-95 VK", 0x81fb79f5, 0xe4e9bc12, 0xb391ab4c), + PCMCIA_DEVICE_PROD_ID12("HST High Soft Tech GmbH", "Saphir II B", 0xd79e0b84, 0x21d083ae), +/* PCMCIA_DEVICE_PROD_ID1234("SEDLBAUER", 0x81fb79f5), */ /* too generic*/ + PCMCIA_DEVICE_NULL +}; +MODULE_DEVICE_TABLE(pcmcia, sedlbauer_ids); + +static struct pcmcia_driver sedlbauer_driver = { + .owner = THIS_MODULE, + .name = "sedlbauer_cs", + .probe = sedlbauer_probe, + .remove = sedlbauer_detach, + .id_table = sedlbauer_ids, + .suspend = sedlbauer_suspend, + .resume = sedlbauer_resume, +}; +module_pcmcia_driver(sedlbauer_driver); diff --git a/drivers/isdn/hisax/sportster.c b/drivers/isdn/hisax/sportster.c new file mode 100644 index 000000000..18cee6360 --- /dev/null +++ b/drivers/isdn/hisax/sportster.c @@ -0,0 +1,267 @@ +/* $Id: sportster.c,v 1.16.2.4 2004/01/13 23:48:39 keil Exp $ + * + * low level stuff for USR Sportster internal TA + * + * Author Karsten Keil + * Copyright by Karsten Keil <keil@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 Christian "naddy" Weisgerber (3Com, US Robotics) for documentation + * + * + */ +#include <linux/init.h> +#include "hisax.h" +#include "isac.h" +#include "hscx.h" +#include "isdnl1.h" + +static const char *sportster_revision = "$Revision: 1.16.2.4 $"; + +#define byteout(addr, val) outb(val, addr) +#define bytein(addr) inb(addr) + +#define SPORTSTER_ISAC 0xC000 +#define SPORTSTER_HSCXA 0x0000 +#define SPORTSTER_HSCXB 0x4000 +#define SPORTSTER_RES_IRQ 0x8000 +#define SPORTSTER_RESET 0x80 +#define SPORTSTER_INTE 0x40 + +static inline int +calc_off(unsigned int base, unsigned int off) +{ + return (base + ((off & 0xfc) << 8) + ((off & 3) << 1)); +} + +static inline void +read_fifo(unsigned int adr, u_char *data, int size) +{ + insb(adr, data, size); +} + +static void +write_fifo(unsigned int adr, u_char *data, int size) +{ + outsb(adr, data, size); +} + +/* Interface functions */ + +static u_char +ReadISAC(struct IsdnCardState *cs, u_char offset) +{ + return (bytein(calc_off(cs->hw.spt.isac, offset))); +} + +static void +WriteISAC(struct IsdnCardState *cs, u_char offset, u_char value) +{ + byteout(calc_off(cs->hw.spt.isac, offset), value); +} + +static void +ReadISACfifo(struct IsdnCardState *cs, u_char *data, int size) +{ + read_fifo(cs->hw.spt.isac, data, size); +} + +static void +WriteISACfifo(struct IsdnCardState *cs, u_char *data, int size) +{ + write_fifo(cs->hw.spt.isac, data, size); +} + +static u_char +ReadHSCX(struct IsdnCardState *cs, int hscx, u_char offset) +{ + return (bytein(calc_off(cs->hw.spt.hscx[hscx], offset))); +} + +static void +WriteHSCX(struct IsdnCardState *cs, int hscx, u_char offset, u_char value) +{ + byteout(calc_off(cs->hw.spt.hscx[hscx], offset), value); +} + +/* + * fast interrupt HSCX stuff goes here + */ + +#define READHSCX(cs, nr, reg) bytein(calc_off(cs->hw.spt.hscx[nr], reg)) +#define WRITEHSCX(cs, nr, reg, data) byteout(calc_off(cs->hw.spt.hscx[nr], reg), data) +#define READHSCXFIFO(cs, nr, ptr, cnt) read_fifo(cs->hw.spt.hscx[nr], ptr, cnt) +#define WRITEHSCXFIFO(cs, nr, ptr, cnt) write_fifo(cs->hw.spt.hscx[nr], ptr, cnt) + +#include "hscx_irq.c" + +static irqreturn_t +sportster_interrupt(int intno, void *dev_id) +{ + struct IsdnCardState *cs = dev_id; + u_char val; + u_long flags; + + spin_lock_irqsave(&cs->lock, flags); + val = READHSCX(cs, 1, HSCX_ISTA); +Start_HSCX: + if (val) + hscx_int_main(cs, val); + val = ReadISAC(cs, ISAC_ISTA); +Start_ISAC: + if (val) + isac_interrupt(cs, val); + val = READHSCX(cs, 1, HSCX_ISTA); + if (val) { + if (cs->debug & L1_DEB_HSCX) + debugl1(cs, "HSCX IntStat after IntRoutine"); + goto Start_HSCX; + } + val = ReadISAC(cs, ISAC_ISTA); + if (val) { + if (cs->debug & L1_DEB_ISAC) + debugl1(cs, "ISAC IntStat after IntRoutine"); + goto Start_ISAC; + } + /* get a new irq impulse if there any pending */ + bytein(cs->hw.spt.cfg_reg + SPORTSTER_RES_IRQ + 1); + spin_unlock_irqrestore(&cs->lock, flags); + return IRQ_HANDLED; +} + +static void +release_io_sportster(struct IsdnCardState *cs) +{ + int i, adr; + + byteout(cs->hw.spt.cfg_reg + SPORTSTER_RES_IRQ, 0); + for (i = 0; i < 64; i++) { + adr = cs->hw.spt.cfg_reg + i * 1024; + release_region(adr, 8); + } +} + +static void +reset_sportster(struct IsdnCardState *cs) +{ + cs->hw.spt.res_irq |= SPORTSTER_RESET; /* Reset On */ + byteout(cs->hw.spt.cfg_reg + SPORTSTER_RES_IRQ, cs->hw.spt.res_irq); + mdelay(10); + cs->hw.spt.res_irq &= ~SPORTSTER_RESET; /* Reset Off */ + byteout(cs->hw.spt.cfg_reg + SPORTSTER_RES_IRQ, cs->hw.spt.res_irq); + mdelay(10); +} + +static int +Sportster_card_msg(struct IsdnCardState *cs, int mt, void *arg) +{ + u_long flags; + + switch (mt) { + case CARD_RESET: + spin_lock_irqsave(&cs->lock, flags); + reset_sportster(cs); + spin_unlock_irqrestore(&cs->lock, flags); + return (0); + case CARD_RELEASE: + release_io_sportster(cs); + return (0); + case CARD_INIT: + spin_lock_irqsave(&cs->lock, flags); + reset_sportster(cs); + inithscxisac(cs, 1); + cs->hw.spt.res_irq |= SPORTSTER_INTE; /* IRQ On */ + byteout(cs->hw.spt.cfg_reg + SPORTSTER_RES_IRQ, cs->hw.spt.res_irq); + inithscxisac(cs, 2); + spin_unlock_irqrestore(&cs->lock, flags); + return (0); + case CARD_TEST: + return (0); + } + return (0); +} + +static int get_io_range(struct IsdnCardState *cs) +{ + int i, j, adr; + + for (i = 0; i < 64; i++) { + adr = cs->hw.spt.cfg_reg + i * 1024; + if (!request_region(adr, 8, "sportster")) { + printk(KERN_WARNING "HiSax: USR Sportster config port " + "%x-%x already in use\n", + adr, adr + 8); + break; + } + } + if (i == 64) + return (1); + else { + for (j = 0; j < i; j++) { + adr = cs->hw.spt.cfg_reg + j * 1024; + release_region(adr, 8); + } + return (0); + } +} + +int setup_sportster(struct IsdnCard *card) +{ + struct IsdnCardState *cs = card->cs; + char tmp[64]; + + strcpy(tmp, sportster_revision); + printk(KERN_INFO "HiSax: USR Sportster driver Rev. %s\n", HiSax_getrev(tmp)); + if (cs->typ != ISDN_CTYPE_SPORTSTER) + return (0); + + cs->hw.spt.cfg_reg = card->para[1]; + cs->irq = card->para[0]; + if (!get_io_range(cs)) + return (0); + cs->hw.spt.isac = cs->hw.spt.cfg_reg + SPORTSTER_ISAC; + cs->hw.spt.hscx[0] = cs->hw.spt.cfg_reg + SPORTSTER_HSCXA; + cs->hw.spt.hscx[1] = cs->hw.spt.cfg_reg + SPORTSTER_HSCXB; + + switch (cs->irq) { + case 5: cs->hw.spt.res_irq = 1; + break; + case 7: cs->hw.spt.res_irq = 2; + break; + case 10:cs->hw.spt.res_irq = 3; + break; + case 11:cs->hw.spt.res_irq = 4; + break; + case 12:cs->hw.spt.res_irq = 5; + break; + case 14:cs->hw.spt.res_irq = 6; + break; + case 15:cs->hw.spt.res_irq = 7; + break; + default:release_io_sportster(cs); + printk(KERN_WARNING "Sportster: wrong IRQ\n"); + return (0); + } + printk(KERN_INFO "HiSax: USR Sportster config irq:%d cfg:0x%X\n", + cs->irq, cs->hw.spt.cfg_reg); + setup_isac(cs); + cs->readisac = &ReadISAC; + cs->writeisac = &WriteISAC; + cs->readisacfifo = &ReadISACfifo; + cs->writeisacfifo = &WriteISACfifo; + cs->BC_Read_Reg = &ReadHSCX; + cs->BC_Write_Reg = &WriteHSCX; + cs->BC_Send_Data = &hscx_fill_fifo; + cs->cardmsg = &Sportster_card_msg; + cs->irq_func = &sportster_interrupt; + ISACVersion(cs, "Sportster:"); + if (HscxVersion(cs, "Sportster:")) { + printk(KERN_WARNING + "Sportster: wrong HSCX versions check IO address\n"); + release_io_sportster(cs); + return (0); + } + return (1); +} diff --git a/drivers/isdn/hisax/st5481.h b/drivers/isdn/hisax/st5481.h new file mode 100644 index 000000000..8cd2d8277 --- /dev/null +++ b/drivers/isdn/hisax/st5481.h @@ -0,0 +1,529 @@ +/* + * Driver for ST5481 USB ISDN modem + * + * Author Frode Isaksen + * Copyright 2001 by Frode Isaksen <fisaksen@bewan.com> + * 2001 by Kai Germaschewski <kai.germaschewski@gmx.de> + * + * This software may be used and distributed according to the terms + * of the GNU General Public License, incorporated herein by reference. + * + */ + +#ifndef _ST5481_H_ +#define _ST5481_H_ + + +// USB IDs, the Product Id is in the range 0x4810-0x481F + +#define ST_VENDOR_ID 0x0483 +#define ST5481_PRODUCT_ID 0x4810 +#define ST5481_PRODUCT_ID_MASK 0xFFF0 + +// ST5481 endpoints when using alternative setting 3 (2B+D). +// To get the endpoint address, OR with 0x80 for IN endpoints. + +#define EP_CTRL 0x00U /* Control endpoint */ +#define EP_INT 0x01U /* Interrupt endpoint */ +#define EP_B1_OUT 0x02U /* B1 channel out */ +#define EP_B1_IN 0x03U /* B1 channel in */ +#define EP_B2_OUT 0x04U /* B2 channel out */ +#define EP_B2_IN 0x05U /* B2 channel in */ +#define EP_D_OUT 0x06U /* D channel out */ +#define EP_D_IN 0x07U /* D channel in */ + +// Number of isochronous packets. With 20 packets we get +// 50 interrupts/sec for each endpoint. + +#define NUM_ISO_PACKETS_D 20 +#define NUM_ISO_PACKETS_B 20 + +// Size of each isochronous packet. +// In outgoing direction we need to match ISDN data rates: +// D: 2 bytes / msec -> 16 kbit / s +// B: 16 bytes / msec -> 64 kbit / s +#define SIZE_ISO_PACKETS_D_IN 16 +#define SIZE_ISO_PACKETS_D_OUT 2 +#define SIZE_ISO_PACKETS_B_IN 32 +#define SIZE_ISO_PACKETS_B_OUT 8 + +// If we overrun/underrun, we send one packet with +/- 2 bytes +#define B_FLOW_ADJUST 2 + +// Registers that are written using vendor specific device request +// on endpoint 0. + +#define LBA 0x02 /* S loopback */ +#define SET_DEFAULT 0x06 /* Soft reset */ +#define LBB 0x1D /* S maintenance loopback */ +#define STT 0x1e /* S force transmission signals */ +#define SDA_MIN 0x20 /* SDA-sin minimal value */ +#define SDA_MAX 0x21 /* SDA-sin maximal value */ +#define SDELAY_VALUE 0x22 /* Delay between Tx and Rx clock */ +#define IN_D_COUNTER 0x36 /* D receive channel fifo counter */ +#define OUT_D_COUNTER 0x37 /* D transmit channel fifo counter */ +#define IN_B1_COUNTER 0x38 /* B1 receive channel fifo counter */ +#define OUT_B1_COUNTER 0x39 /* B1 transmit channel fifo counter */ +#define IN_B2_COUNTER 0x3a /* B2 receive channel fifo counter */ +#define OUT_B2_COUNTER 0x3b /* B2 transmit channel fifo counter */ +#define FFCTRL_IN_D 0x3C /* D receive channel fifo threshold low */ +#define FFCTRH_IN_D 0x3D /* D receive channel fifo threshold high */ +#define FFCTRL_OUT_D 0x3E /* D transmit channel fifo threshold low */ +#define FFCTRH_OUT_D 0x3F /* D transmit channel fifo threshold high */ +#define FFCTRL_IN_B1 0x40 /* B1 receive channel fifo threshold low */ +#define FFCTRH_IN_B1 0x41 /* B1 receive channel fifo threshold high */ +#define FFCTRL_OUT_B1 0x42 /* B1 transmit channel fifo threshold low */ +#define FFCTRH_OUT_B1 0x43 /* B1 transmit channel fifo threshold high */ +#define FFCTRL_IN_B2 0x44 /* B2 receive channel fifo threshold low */ +#define FFCTRH_IN_B2 0x45 /* B2 receive channel fifo threshold high */ +#define FFCTRL_OUT_B2 0x46 /* B2 transmit channel fifo threshold low */ +#define FFCTRH_OUT_B2 0x47 /* B2 transmit channel fifo threshold high */ +#define MPMSK 0x4A /* Multi purpose interrupt MASK register */ +#define FFMSK_D 0x4c /* D fifo interrupt MASK register */ +#define FFMSK_B1 0x4e /* B1 fifo interrupt MASK register */ +#define FFMSK_B2 0x50 /* B2 fifo interrupt MASK register */ +#define GPIO_DIR 0x52 /* GPIO pins direction registers */ +#define GPIO_OUT 0x53 /* GPIO pins output register */ +#define GPIO_IN 0x54 /* GPIO pins input register */ +#define TXCI 0x56 /* CI command to be transmitted */ + + +// Format of the interrupt packet received on endpoint 1: +// +// +--------+--------+--------+--------+--------+--------+ +// !MPINT !FFINT_D !FFINT_B1!FFINT_B2!CCIST !GPIO_INT! +// +--------+--------+--------+--------+--------+--------+ + +// Offsets in the interrupt packet + +#define MPINT 0 +#define FFINT_D 1 +#define FFINT_B1 2 +#define FFINT_B2 3 +#define CCIST 4 +#define GPIO_INT 5 +#define INT_PKT_SIZE 6 + +// MPINT +#define LSD_INT 0x80 /* S line activity detected */ +#define RXCI_INT 0x40 /* Indicate primitive arrived */ +#define DEN_INT 0x20 /* Signal enabling data out of D Tx fifo */ +#define DCOLL_INT 0x10 /* D channel collision */ +#define AMIVN_INT 0x04 /* AMI violation number reached 2 */ +#define INFOI_INT 0x04 /* INFOi changed */ +#define DRXON_INT 0x02 /* Reception channel active */ +#define GPCHG_INT 0x01 /* GPIO pin value changed */ + +// FFINT_x +#define IN_OVERRUN 0x80 /* In fifo overrun */ +#define OUT_UNDERRUN 0x40 /* Out fifo underrun */ +#define IN_UP 0x20 /* In fifo thresholdh up-crossed */ +#define IN_DOWN 0x10 /* In fifo thresholdl down-crossed */ +#define OUT_UP 0x08 /* Out fifo thresholdh up-crossed */ +#define OUT_DOWN 0x04 /* Out fifo thresholdl down-crossed */ +#define IN_COUNTER_ZEROED 0x02 /* In down-counter reached 0 */ +#define OUT_COUNTER_ZEROED 0x01 /* Out down-counter reached 0 */ + +#define ANY_REC_INT (IN_OVERRUN + IN_UP + IN_DOWN + IN_COUNTER_ZEROED) +#define ANY_XMIT_INT (OUT_UNDERRUN + OUT_UP + OUT_DOWN + OUT_COUNTER_ZEROED) + + +// Level 1 commands that are sent using the TXCI device request +#define ST5481_CMD_DR 0x0 /* Deactivation Request */ +#define ST5481_CMD_RES 0x1 /* state machine RESet */ +#define ST5481_CMD_TM1 0x2 /* Test Mode 1 */ +#define ST5481_CMD_TM2 0x3 /* Test Mode 2 */ +#define ST5481_CMD_PUP 0x7 /* Power UP */ +#define ST5481_CMD_AR8 0x8 /* Activation Request class 1 */ +#define ST5481_CMD_AR10 0x9 /* Activation Request class 2 */ +#define ST5481_CMD_ARL 0xA /* Activation Request Loopback */ +#define ST5481_CMD_PDN 0xF /* Power DoWn */ + +// Turn on/off the LEDs using the GPIO device request. +// To use the B LEDs, number_of_leds must be set to 4 +#define B1_LED 0x10U +#define B2_LED 0x20U +#define GREEN_LED 0x40U +#define RED_LED 0x80U + +// D channel out states +enum { + ST_DOUT_NONE, + + ST_DOUT_SHORT_INIT, + ST_DOUT_SHORT_WAIT_DEN, + + ST_DOUT_LONG_INIT, + ST_DOUT_LONG_WAIT_DEN, + ST_DOUT_NORMAL, + + ST_DOUT_WAIT_FOR_UNDERRUN, + ST_DOUT_WAIT_FOR_NOT_BUSY, + ST_DOUT_WAIT_FOR_STOP, + ST_DOUT_WAIT_FOR_RESET, +}; + +#define DOUT_STATE_COUNT (ST_DOUT_WAIT_FOR_RESET + 1) + +// D channel out events +enum { + EV_DOUT_START_XMIT, + EV_DOUT_COMPLETE, + EV_DOUT_DEN, + EV_DOUT_RESETED, + EV_DOUT_STOPPED, + EV_DOUT_COLL, + EV_DOUT_UNDERRUN, +}; + +#define DOUT_EVENT_COUNT (EV_DOUT_UNDERRUN + 1) + +// ---------------------------------------------------------------------- + +enum { + ST_L1_F3, + ST_L1_F4, + ST_L1_F6, + ST_L1_F7, + ST_L1_F8, +}; + +#define L1_STATE_COUNT (ST_L1_F8 + 1) + +// The first 16 entries match the Level 1 indications that +// are found at offset 4 (CCIST) in the interrupt packet + +enum { + EV_IND_DP, // 0000 Deactivation Pending + EV_IND_1, // 0001 + EV_IND_2, // 0010 + EV_IND_3, // 0011 + EV_IND_RSY, // 0100 ReSYnchronizing + EV_IND_5, // 0101 + EV_IND_6, // 0110 + EV_IND_7, // 0111 + EV_IND_AP, // 1000 Activation Pending + EV_IND_9, // 1001 + EV_IND_10, // 1010 + EV_IND_11, // 1011 + EV_IND_AI8, // 1100 Activation Indication class 8 + EV_IND_AI10,// 1101 Activation Indication class 10 + EV_IND_AIL, // 1110 Activation Indication Loopback + EV_IND_DI, // 1111 Deactivation Indication + EV_PH_ACTIVATE_REQ, + EV_PH_DEACTIVATE_REQ, + EV_TIMER3, +}; + +#define L1_EVENT_COUNT (EV_TIMER3 + 1) + +#define ERR(format, arg...) \ + printk(KERN_ERR "%s:%s: " format "\n" , __FILE__, __func__ , ## arg) + +#define WARNING(format, arg...) \ + printk(KERN_WARNING "%s:%s: " format "\n" , __FILE__, __func__ , ## arg) + +#define INFO(format, arg...) \ + printk(KERN_INFO "%s:%s: " format "\n" , __FILE__, __func__ , ## arg) + +#include <linux/isdn/hdlc.h> +#include "fsm.h" +#include "hisax_if.h" +#include <linux/skbuff.h> + +/* ====================================================================== + * FIFO handling + */ + +/* Generic FIFO structure */ +struct fifo { + u_char r, w, count, size; + spinlock_t lock; +}; + +/* + * Init an FIFO + */ +static inline void fifo_init(struct fifo *fifo, int size) +{ + fifo->r = fifo->w = fifo->count = 0; + fifo->size = size; + spin_lock_init(&fifo->lock); +} + +/* + * Add an entry to the FIFO + */ +static inline int fifo_add(struct fifo *fifo) +{ + unsigned long flags; + int index; + + if (!fifo) { + return -1; + } + + spin_lock_irqsave(&fifo->lock, flags); + if (fifo->count == fifo->size) { + // FIFO full + index = -1; + } else { + // Return index where to get the next data to add to the FIFO + index = fifo->w++ & (fifo->size - 1); + fifo->count++; + } + spin_unlock_irqrestore(&fifo->lock, flags); + return index; +} + +/* + * Remove an entry from the FIFO with the index returned. + */ +static inline int fifo_remove(struct fifo *fifo) +{ + unsigned long flags; + int index; + + if (!fifo) { + return -1; + } + + spin_lock_irqsave(&fifo->lock, flags); + if (!fifo->count) { + // FIFO empty + index = -1; + } else { + // Return index where to get the next data from the FIFO + index = fifo->r++ & (fifo->size - 1); + fifo->count--; + } + spin_unlock_irqrestore(&fifo->lock, flags); + + return index; +} + +/* ====================================================================== + * control pipe + */ +typedef void (*ctrl_complete_t)(void *); + +typedef struct ctrl_msg { + struct usb_ctrlrequest dr; + ctrl_complete_t complete; + void *context; +} ctrl_msg; + +/* FIFO of ctrl messages waiting to be sent */ +#define MAX_EP0_MSG 16 +struct ctrl_msg_fifo { + struct fifo f; + struct ctrl_msg data[MAX_EP0_MSG]; +}; + +#define MAX_DFRAME_LEN_L1 300 +#define HSCX_BUFMAX 4096 + +struct st5481_ctrl { + struct ctrl_msg_fifo msg_fifo; + unsigned long busy; + struct urb *urb; +}; + +struct st5481_intr { + // struct evt_fifo evt_fifo; + struct urb *urb; +}; + +struct st5481_d_out { + struct isdnhdlc_vars hdlc_state; + struct urb *urb[2]; /* double buffering */ + unsigned long busy; + struct sk_buff *tx_skb; + struct FsmInst fsm; +}; + +struct st5481_b_out { + struct isdnhdlc_vars hdlc_state; + struct urb *urb[2]; /* double buffering */ + u_char flow_event; + u_long busy; + struct sk_buff *tx_skb; +}; + +struct st5481_in { + struct isdnhdlc_vars hdlc_state; + struct urb *urb[2]; /* double buffering */ + int mode; + int bufsize; + unsigned int num_packets; + unsigned int packet_size; + unsigned char ep, counter; + unsigned char *rcvbuf; + struct st5481_adapter *adapter; + struct hisax_if *hisax_if; +}; + +int st5481_setup_in(struct st5481_in *in); +void st5481_release_in(struct st5481_in *in); +void st5481_in_mode(struct st5481_in *in, int mode); + +struct st5481_bcs { + struct hisax_b_if b_if; + struct st5481_adapter *adapter; + struct st5481_in b_in; + struct st5481_b_out b_out; + int channel; + int mode; +}; + +struct st5481_adapter { + int number_of_leds; + struct usb_device *usb_dev; + struct hisax_d_if hisax_d_if; + + struct st5481_ctrl ctrl; + struct st5481_intr intr; + struct st5481_in d_in; + struct st5481_d_out d_out; + + unsigned char leds; + unsigned int led_counter; + + unsigned long event; + + struct FsmInst l1m; + struct FsmTimer timer; + + struct st5481_bcs bcs[2]; +}; + +#define TIMER3_VALUE 7000 + +/* ====================================================================== + * + */ + +/* + * Submit an URB with error reporting. This is a macro so + * the __func__ returns the caller function name. + */ +#define SUBMIT_URB(urb, mem_flags) \ + ({ \ + int status; \ + if ((status = usb_submit_urb(urb, mem_flags)) < 0) { \ + WARNING("usb_submit_urb failed,status=%d", status); \ + } \ + status; \ + }) + +/* + * USB double buffering, return the URB index (0 or 1). + */ +static inline int get_buf_nr(struct urb *urbs[], struct urb *urb) +{ + return (urbs[0] == urb ? 0 : 1); +} + +/* ---------------------------------------------------------------------- */ + +/* B Channel */ + +int st5481_setup_b(struct st5481_bcs *bcs); +void st5481_release_b(struct st5481_bcs *bcs); +void st5481_d_l2l1(struct hisax_if *hisax_d_if, int pr, void *arg); + +/* D Channel */ + +int st5481_setup_d(struct st5481_adapter *adapter); +void st5481_release_d(struct st5481_adapter *adapter); +void st5481_b_l2l1(struct hisax_if *b_if, int pr, void *arg); +int st5481_d_init(void); +void st5481_d_exit(void); + +/* USB */ +void st5481_ph_command(struct st5481_adapter *adapter, unsigned int command); +int st5481_setup_isocpipes(struct urb *urb[2], struct usb_device *dev, + unsigned int pipe, int num_packets, + int packet_size, int buf_size, + usb_complete_t complete, void *context); +void st5481_release_isocpipes(struct urb *urb[2]); + +void st5481_usb_pipe_reset(struct st5481_adapter *adapter, + u_char pipe, ctrl_complete_t complete, void *context); +void st5481_usb_device_ctrl_msg(struct st5481_adapter *adapter, + u8 request, u16 value, + ctrl_complete_t complete, void *context); +int st5481_setup_usb(struct st5481_adapter *adapter); +void st5481_release_usb(struct st5481_adapter *adapter); +void st5481_start(struct st5481_adapter *adapter); +void st5481_stop(struct st5481_adapter *adapter); + +// ---------------------------------------------------------------------- +// debugging macros + +#define __debug_variable st5481_debug +#include "hisax_debug.h" + +extern int st5481_debug; + +#ifdef CONFIG_HISAX_DEBUG + +#define DBG_ISO_PACKET(level, urb) \ + if (level & __debug_variable) dump_iso_packet(__func__, urb) + +static void __attribute__((unused)) +dump_iso_packet(const char *name, struct urb *urb) +{ + int i, j; + int len, ofs; + u_char *data; + + printk(KERN_DEBUG "%s: packets=%d,errors=%d\n", + name, urb->number_of_packets, urb->error_count); + for (i = 0; i < urb->number_of_packets; ++i) { + if (urb->pipe & USB_DIR_IN) { + len = urb->iso_frame_desc[i].actual_length; + } else { + len = urb->iso_frame_desc[i].length; + } + ofs = urb->iso_frame_desc[i].offset; + printk(KERN_DEBUG "len=%.2d,ofs=%.3d ", len, ofs); + if (len) { + data = urb->transfer_buffer + ofs; + for (j = 0; j < len; j++) { + printk("%.2x", data[j]); + } + } + printk("\n"); + } +} + +static inline const char *ST5481_CMD_string(int evt) +{ + static char s[16]; + + switch (evt) { + case ST5481_CMD_DR: return "DR"; + case ST5481_CMD_RES: return "RES"; + case ST5481_CMD_TM1: return "TM1"; + case ST5481_CMD_TM2: return "TM2"; + case ST5481_CMD_PUP: return "PUP"; + case ST5481_CMD_AR8: return "AR8"; + case ST5481_CMD_AR10: return "AR10"; + case ST5481_CMD_ARL: return "ARL"; + case ST5481_CMD_PDN: return "PDN"; + }; + + sprintf(s, "0x%x", evt); + return s; +} + +#else + +#define DBG_ISO_PACKET(level, urb) do {} while (0) + +#endif + + + +#endif diff --git a/drivers/isdn/hisax/st5481_b.c b/drivers/isdn/hisax/st5481_b.c new file mode 100644 index 000000000..f64a36007 --- /dev/null +++ b/drivers/isdn/hisax/st5481_b.c @@ -0,0 +1,380 @@ +/* + * Driver for ST5481 USB ISDN modem + * + * Author Frode Isaksen + * Copyright 2001 by Frode Isaksen <fisaksen@bewan.com> + * 2001 by Kai Germaschewski <kai.germaschewski@gmx.de> + * + * This software may be used and distributed according to the terms + * of the GNU General Public License, incorporated herein by reference. + * + */ + +#include <linux/init.h> +#include <linux/gfp.h> +#include <linux/usb.h> +#include <linux/netdevice.h> +#include <linux/bitrev.h> +#include "st5481.h" + +static inline void B_L1L2(struct st5481_bcs *bcs, int pr, void *arg) +{ + struct hisax_if *ifc = (struct hisax_if *) &bcs->b_if; + + ifc->l1l2(ifc, pr, arg); +} + +/* + * Encode and transmit next frame. + */ +static void usb_b_out(struct st5481_bcs *bcs, int buf_nr) +{ + struct st5481_b_out *b_out = &bcs->b_out; + struct st5481_adapter *adapter = bcs->adapter; + struct urb *urb; + unsigned int packet_size, offset; + int len, buf_size, bytes_sent; + int i; + struct sk_buff *skb; + + if (test_and_set_bit(buf_nr, &b_out->busy)) { + DBG(4, "ep %d urb %d busy", (bcs->channel + 1) * 2, buf_nr); + return; + } + urb = b_out->urb[buf_nr]; + + // Adjust isoc buffer size according to flow state + if (b_out->flow_event & (OUT_DOWN | OUT_UNDERRUN)) { + buf_size = NUM_ISO_PACKETS_B * SIZE_ISO_PACKETS_B_OUT + B_FLOW_ADJUST; + packet_size = SIZE_ISO_PACKETS_B_OUT + B_FLOW_ADJUST; + DBG(4, "B%d,adjust flow,add %d bytes", bcs->channel + 1, B_FLOW_ADJUST); + } else if (b_out->flow_event & OUT_UP) { + buf_size = NUM_ISO_PACKETS_B * SIZE_ISO_PACKETS_B_OUT - B_FLOW_ADJUST; + packet_size = SIZE_ISO_PACKETS_B_OUT - B_FLOW_ADJUST; + DBG(4, "B%d,adjust flow,remove %d bytes", bcs->channel + 1, B_FLOW_ADJUST); + } else { + buf_size = NUM_ISO_PACKETS_B * SIZE_ISO_PACKETS_B_OUT; + packet_size = 8; + } + b_out->flow_event = 0; + + len = 0; + while (len < buf_size) { + if ((skb = b_out->tx_skb)) { + DBG_SKB(0x100, skb); + DBG(4, "B%d,len=%d", bcs->channel + 1, skb->len); + + if (bcs->mode == L1_MODE_TRANS) { + bytes_sent = buf_size - len; + if (skb->len < bytes_sent) + bytes_sent = skb->len; + { /* swap tx bytes to get hearable audio data */ + register unsigned char *src = skb->data; + register unsigned char *dest = urb->transfer_buffer + len; + register unsigned int count; + for (count = 0; count < bytes_sent; count++) + *dest++ = bitrev8(*src++); + } + len += bytes_sent; + } else { + len += isdnhdlc_encode(&b_out->hdlc_state, + skb->data, skb->len, &bytes_sent, + urb->transfer_buffer + len, buf_size-len); + } + + skb_pull(skb, bytes_sent); + + if (!skb->len) { + // Frame sent + b_out->tx_skb = NULL; + B_L1L2(bcs, PH_DATA | CONFIRM, (void *)(unsigned long) skb->truesize); + dev_kfree_skb_any(skb); + +/* if (!(bcs->tx_skb = skb_dequeue(&bcs->sq))) { */ +/* st5481B_sched_event(bcs, B_XMTBUFREADY); */ +/* } */ + } + } else { + if (bcs->mode == L1_MODE_TRANS) { + memset(urb->transfer_buffer + len, 0xff, buf_size-len); + len = buf_size; + } else { + // Send flags + len += isdnhdlc_encode(&b_out->hdlc_state, + NULL, 0, &bytes_sent, + urb->transfer_buffer + len, buf_size-len); + } + } + } + + // Prepare the URB + for (i = 0, offset = 0; offset < len; i++) { + urb->iso_frame_desc[i].offset = offset; + urb->iso_frame_desc[i].length = packet_size; + offset += packet_size; + packet_size = SIZE_ISO_PACKETS_B_OUT; + } + urb->transfer_buffer_length = len; + urb->number_of_packets = i; + urb->dev = adapter->usb_dev; + + DBG_ISO_PACKET(0x200, urb); + + SUBMIT_URB(urb, GFP_NOIO); +} + +/* + * Start transferring (flags or data) on the B channel, since + * FIFO counters has been set to a non-zero value. + */ +static void st5481B_start_xfer(void *context) +{ + struct st5481_bcs *bcs = context; + + DBG(4, "B%d", bcs->channel + 1); + + // Start transmitting (flags or data) on B channel + + usb_b_out(bcs, 0); + usb_b_out(bcs, 1); +} + +/* + * If the adapter has only 2 LEDs, the green + * LED will blink with a rate depending + * on the number of channels opened. + */ +static void led_blink(struct st5481_adapter *adapter) +{ + u_char leds = adapter->leds; + + // 50 frames/sec for each channel + if (++adapter->led_counter % 50) { + return; + } + + if (adapter->led_counter % 100) { + leds |= GREEN_LED; + } else { + leds &= ~GREEN_LED; + } + + st5481_usb_device_ctrl_msg(adapter, GPIO_OUT, leds, NULL, NULL); +} + +static void usb_b_out_complete(struct urb *urb) +{ + struct st5481_bcs *bcs = urb->context; + struct st5481_b_out *b_out = &bcs->b_out; + struct st5481_adapter *adapter = bcs->adapter; + int buf_nr; + + buf_nr = get_buf_nr(b_out->urb, urb); + test_and_clear_bit(buf_nr, &b_out->busy); + + if (unlikely(urb->status < 0)) { + switch (urb->status) { + case -ENOENT: + case -ESHUTDOWN: + case -ECONNRESET: + DBG(4, "urb killed status %d", urb->status); + return; // Give up + default: + WARNING("urb status %d", urb->status); + if (b_out->busy == 0) { + st5481_usb_pipe_reset(adapter, (bcs->channel + 1) * 2 | USB_DIR_OUT, NULL, NULL); + } + break; + } + } + + usb_b_out(bcs, buf_nr); + + if (adapter->number_of_leds == 2) + led_blink(adapter); +} + +/* + * Start or stop the transfer on the B channel. + */ +static void st5481B_mode(struct st5481_bcs *bcs, int mode) +{ + struct st5481_b_out *b_out = &bcs->b_out; + struct st5481_adapter *adapter = bcs->adapter; + + DBG(4, "B%d,mode=%d", bcs->channel + 1, mode); + + if (bcs->mode == mode) + return; + + bcs->mode = mode; + + // Cancel all USB transfers on this B channel + usb_unlink_urb(b_out->urb[0]); + usb_unlink_urb(b_out->urb[1]); + b_out->busy = 0; + + st5481_in_mode(&bcs->b_in, mode); + if (bcs->mode != L1_MODE_NULL) { + // Open the B channel + if (bcs->mode != L1_MODE_TRANS) { + u32 features = HDLC_BITREVERSE; + if (bcs->mode == L1_MODE_HDLC_56K) + features |= HDLC_56KBIT; + isdnhdlc_out_init(&b_out->hdlc_state, features); + } + st5481_usb_pipe_reset(adapter, (bcs->channel + 1) * 2, NULL, NULL); + + // Enable B channel interrupts + st5481_usb_device_ctrl_msg(adapter, FFMSK_B1 + (bcs->channel * 2), + OUT_UP + OUT_DOWN + OUT_UNDERRUN, NULL, NULL); + + // Enable B channel FIFOs + st5481_usb_device_ctrl_msg(adapter, OUT_B1_COUNTER+(bcs->channel * 2), 32, st5481B_start_xfer, bcs); + if (adapter->number_of_leds == 4) { + if (bcs->channel == 0) { + adapter->leds |= B1_LED; + } else { + adapter->leds |= B2_LED; + } + } + } else { + // Disable B channel interrupts + st5481_usb_device_ctrl_msg(adapter, FFMSK_B1+(bcs->channel * 2), 0, NULL, NULL); + + // Disable B channel FIFOs + st5481_usb_device_ctrl_msg(adapter, OUT_B1_COUNTER+(bcs->channel * 2), 0, NULL, NULL); + + if (adapter->number_of_leds == 4) { + if (bcs->channel == 0) { + adapter->leds &= ~B1_LED; + } else { + adapter->leds &= ~B2_LED; + } + } else { + st5481_usb_device_ctrl_msg(adapter, GPIO_OUT, adapter->leds, NULL, NULL); + } + if (b_out->tx_skb) { + dev_kfree_skb_any(b_out->tx_skb); + b_out->tx_skb = NULL; + } + + } +} + +static int st5481_setup_b_out(struct st5481_bcs *bcs) +{ + struct usb_device *dev = bcs->adapter->usb_dev; + struct usb_interface *intf; + struct usb_host_interface *altsetting = NULL; + struct usb_host_endpoint *endpoint; + struct st5481_b_out *b_out = &bcs->b_out; + + DBG(4, ""); + + intf = usb_ifnum_to_if(dev, 0); + if (intf) + altsetting = usb_altnum_to_altsetting(intf, 3); + if (!altsetting) + return -ENXIO; + + // Allocate URBs and buffers for the B channel out + endpoint = &altsetting->endpoint[EP_B1_OUT - 1 + bcs->channel * 2]; + + DBG(4, "endpoint address=%02x,packet size=%d", + endpoint->desc.bEndpointAddress, le16_to_cpu(endpoint->desc.wMaxPacketSize)); + + // Allocate memory for 8000bytes/sec + extra bytes if underrun + return st5481_setup_isocpipes(b_out->urb, dev, + usb_sndisocpipe(dev, endpoint->desc.bEndpointAddress), + NUM_ISO_PACKETS_B, SIZE_ISO_PACKETS_B_OUT, + NUM_ISO_PACKETS_B * SIZE_ISO_PACKETS_B_OUT + B_FLOW_ADJUST, + usb_b_out_complete, bcs); +} + +static void st5481_release_b_out(struct st5481_bcs *bcs) +{ + struct st5481_b_out *b_out = &bcs->b_out; + + DBG(4, ""); + + st5481_release_isocpipes(b_out->urb); +} + +int st5481_setup_b(struct st5481_bcs *bcs) +{ + int retval; + + DBG(4, ""); + + retval = st5481_setup_b_out(bcs); + if (retval) + goto err; + bcs->b_in.bufsize = HSCX_BUFMAX; + bcs->b_in.num_packets = NUM_ISO_PACKETS_B; + bcs->b_in.packet_size = SIZE_ISO_PACKETS_B_IN; + bcs->b_in.ep = (bcs->channel ? EP_B2_IN : EP_B1_IN) | USB_DIR_IN; + bcs->b_in.counter = bcs->channel ? IN_B2_COUNTER : IN_B1_COUNTER; + bcs->b_in.adapter = bcs->adapter; + bcs->b_in.hisax_if = &bcs->b_if.ifc; + retval = st5481_setup_in(&bcs->b_in); + if (retval) + goto err_b_out; + + + return 0; + +err_b_out: + st5481_release_b_out(bcs); +err: + return retval; +} + +/* + * Release buffers and URBs for the B channels + */ +void st5481_release_b(struct st5481_bcs *bcs) +{ + DBG(4, ""); + + st5481_release_in(&bcs->b_in); + st5481_release_b_out(bcs); +} + +/* + * st5481_b_l2l1 is the entry point for upper layer routines that want to + * transmit on the B channel. PH_DATA | REQUEST is a normal packet that + * we either start transmitting (if idle) or queue (if busy). + * PH_PULL | REQUEST can be called to request a callback message + * (PH_PULL | CONFIRM) + * once the link is idle. After a "pull" callback, the upper layer + * routines can use PH_PULL | INDICATION to send data. + */ +void st5481_b_l2l1(struct hisax_if *ifc, int pr, void *arg) +{ + struct st5481_bcs *bcs = ifc->priv; + struct sk_buff *skb = arg; + long mode; + + DBG(4, ""); + + switch (pr) { + case PH_DATA | REQUEST: + BUG_ON(bcs->b_out.tx_skb); + bcs->b_out.tx_skb = skb; + break; + case PH_ACTIVATE | REQUEST: + mode = (long) arg; + DBG(4, "B%d,PH_ACTIVATE_REQUEST %ld", bcs->channel + 1, mode); + st5481B_mode(bcs, mode); + B_L1L2(bcs, PH_ACTIVATE | INDICATION, NULL); + break; + case PH_DEACTIVATE | REQUEST: + DBG(4, "B%d,PH_DEACTIVATE_REQUEST", bcs->channel + 1); + st5481B_mode(bcs, L1_MODE_NULL); + B_L1L2(bcs, PH_DEACTIVATE | INDICATION, NULL); + break; + default: + WARNING("pr %#x\n", pr); + } +} diff --git a/drivers/isdn/hisax/st5481_d.c b/drivers/isdn/hisax/st5481_d.c new file mode 100644 index 000000000..e88c5c71f --- /dev/null +++ b/drivers/isdn/hisax/st5481_d.c @@ -0,0 +1,780 @@ +/* + * Driver for ST5481 USB ISDN modem + * + * Author Frode Isaksen + * Copyright 2001 by Frode Isaksen <fisaksen@bewan.com> + * 2001 by Kai Germaschewski <kai.germaschewski@gmx.de> + * + * This software may be used and distributed according to the terms + * of the GNU General Public License, incorporated herein by reference. + * + */ + +#include <linux/init.h> +#include <linux/gfp.h> +#include <linux/usb.h> +#include <linux/netdevice.h> +#include "st5481.h" + +static void ph_connect(struct st5481_adapter *adapter); +static void ph_disconnect(struct st5481_adapter *adapter); + +static struct Fsm l1fsm; + +static char *strL1State[] = +{ + "ST_L1_F3", + "ST_L1_F4", + "ST_L1_F6", + "ST_L1_F7", + "ST_L1_F8", +}; + +static char *strL1Event[] = +{ + "EV_IND_DP", + "EV_IND_1", + "EV_IND_2", + "EV_IND_3", + "EV_IND_RSY", + "EV_IND_5", + "EV_IND_6", + "EV_IND_7", + "EV_IND_AP", + "EV_IND_9", + "EV_IND_10", + "EV_IND_11", + "EV_IND_AI8", + "EV_IND_AI10", + "EV_IND_AIL", + "EV_IND_DI", + "EV_PH_ACTIVATE_REQ", + "EV_PH_DEACTIVATE_REQ", + "EV_TIMER3", +}; + +static inline void D_L1L2(struct st5481_adapter *adapter, int pr, void *arg) +{ + struct hisax_if *ifc = (struct hisax_if *) &adapter->hisax_d_if; + + ifc->l1l2(ifc, pr, arg); +} + +static void +l1_go_f3(struct FsmInst *fi, int event, void *arg) +{ + struct st5481_adapter *adapter = fi->userdata; + + if (fi->state == ST_L1_F7) + ph_disconnect(adapter); + + FsmChangeState(fi, ST_L1_F3); + D_L1L2(adapter, PH_DEACTIVATE | INDICATION, NULL); +} + +static void +l1_go_f6(struct FsmInst *fi, int event, void *arg) +{ + struct st5481_adapter *adapter = fi->userdata; + + if (fi->state == ST_L1_F7) + ph_disconnect(adapter); + + FsmChangeState(fi, ST_L1_F6); +} + +static void +l1_go_f7(struct FsmInst *fi, int event, void *arg) +{ + struct st5481_adapter *adapter = fi->userdata; + + FsmDelTimer(&adapter->timer, 0); + ph_connect(adapter); + FsmChangeState(fi, ST_L1_F7); + D_L1L2(adapter, PH_ACTIVATE | INDICATION, NULL); +} + +static void +l1_go_f8(struct FsmInst *fi, int event, void *arg) +{ + struct st5481_adapter *adapter = fi->userdata; + + if (fi->state == ST_L1_F7) + ph_disconnect(adapter); + + FsmChangeState(fi, ST_L1_F8); +} + +static void +l1_timer3(struct FsmInst *fi, int event, void *arg) +{ + struct st5481_adapter *adapter = fi->userdata; + + st5481_ph_command(adapter, ST5481_CMD_DR); + FsmChangeState(fi, ST_L1_F3); + D_L1L2(adapter, PH_DEACTIVATE | INDICATION, NULL); +} + +static void +l1_ignore(struct FsmInst *fi, int event, void *arg) +{ +} + +static void +l1_activate(struct FsmInst *fi, int event, void *arg) +{ + struct st5481_adapter *adapter = fi->userdata; + + st5481_ph_command(adapter, ST5481_CMD_DR); + st5481_ph_command(adapter, ST5481_CMD_PUP); + FsmRestartTimer(&adapter->timer, TIMER3_VALUE, EV_TIMER3, NULL, 2); + st5481_ph_command(adapter, ST5481_CMD_AR8); + FsmChangeState(fi, ST_L1_F4); +} + +static struct FsmNode L1FnList[] __initdata = +{ + {ST_L1_F3, EV_IND_DP, l1_ignore}, + {ST_L1_F3, EV_IND_AP, l1_go_f6}, + {ST_L1_F3, EV_IND_AI8, l1_go_f7}, + {ST_L1_F3, EV_IND_AI10, l1_go_f7}, + {ST_L1_F3, EV_PH_ACTIVATE_REQ, l1_activate}, + + {ST_L1_F4, EV_TIMER3, l1_timer3}, + {ST_L1_F4, EV_IND_DP, l1_go_f3}, + {ST_L1_F4, EV_IND_AP, l1_go_f6}, + {ST_L1_F4, EV_IND_AI8, l1_go_f7}, + {ST_L1_F4, EV_IND_AI10, l1_go_f7}, + + {ST_L1_F6, EV_TIMER3, l1_timer3}, + {ST_L1_F6, EV_IND_DP, l1_go_f3}, + {ST_L1_F6, EV_IND_AP, l1_ignore}, + {ST_L1_F6, EV_IND_AI8, l1_go_f7}, + {ST_L1_F6, EV_IND_AI10, l1_go_f7}, + {ST_L1_F7, EV_IND_RSY, l1_go_f8}, + + {ST_L1_F7, EV_IND_DP, l1_go_f3}, + {ST_L1_F7, EV_IND_AP, l1_go_f6}, + {ST_L1_F7, EV_IND_AI8, l1_ignore}, + {ST_L1_F7, EV_IND_AI10, l1_ignore}, + {ST_L1_F7, EV_IND_RSY, l1_go_f8}, + + {ST_L1_F8, EV_TIMER3, l1_timer3}, + {ST_L1_F8, EV_IND_DP, l1_go_f3}, + {ST_L1_F8, EV_IND_AP, l1_go_f6}, + {ST_L1_F8, EV_IND_AI8, l1_go_f8}, + {ST_L1_F8, EV_IND_AI10, l1_go_f8}, + {ST_L1_F8, EV_IND_RSY, l1_ignore}, +}; + +static __printf(2, 3) + void l1m_debug(struct FsmInst *fi, char *fmt, ...) +{ + va_list args; + char buf[256]; + + va_start(args, fmt); + vsnprintf(buf, sizeof(buf), fmt, args); + DBG(8, "%s", buf); + va_end(args); +} + +/* ====================================================================== + * D-Channel out + */ + +/* + D OUT state machine: + ==================== + + Transmit short frame (< 16 bytes of encoded data): + + L1 FRAME D_OUT_STATE USB D CHANNEL + -------- ----------- --- --------- + + FIXME + + -> [xx..xx] SHORT_INIT -> [7Exx..xxC1C27EFF] + SHORT_WAIT_DEN <> OUT_D_COUNTER=16 + + END_OF_SHORT <- DEN_EVENT -> 7Exx + xxxx + xxxx + xxxx + xxxx + xxxx + C1C1 + 7EFF + WAIT_FOR_RESET_IDLE <- D_UNDERRUN <- (8ms) + IDLE <> Reset pipe + + + + Transmit long frame (>= 16 bytes of encoded data): + + L1 FRAME D_OUT_STATE USB D CHANNEL + -------- ----------- --- --------- + + -> [xx...xx] IDLE + WAIT_FOR_STOP <> OUT_D_COUNTER=0 + WAIT_FOR_RESET <> Reset pipe + STOP + INIT_LONG_FRAME -> [7Exx..xx] + WAIT_DEN <> OUT_D_COUNTER=16 + OUT_NORMAL <- DEN_EVENT -> 7Exx + END_OF_FRAME_BUSY -> [xxxx] xxxx + END_OF_FRAME_NOT_BUSY -> [xxxx] xxxx + -> [xxxx] xxxx + -> [C1C2] xxxx + -> [7EFF] xxxx + xxxx + xxxx + .... + xxxx + C1C2 + 7EFF + <- D_UNDERRUN <- (> 8ms) + WAIT_FOR_STOP <> OUT_D_COUNTER=0 + WAIT_FOR_RESET <> Reset pipe + STOP + +*/ + +static struct Fsm dout_fsm; + +static char *strDoutState[] = +{ + "ST_DOUT_NONE", + + "ST_DOUT_SHORT_INIT", + "ST_DOUT_SHORT_WAIT_DEN", + + "ST_DOUT_LONG_INIT", + "ST_DOUT_LONG_WAIT_DEN", + "ST_DOUT_NORMAL", + + "ST_DOUT_WAIT_FOR_UNDERRUN", + "ST_DOUT_WAIT_FOR_NOT_BUSY", + "ST_DOUT_WAIT_FOR_STOP", + "ST_DOUT_WAIT_FOR_RESET", +}; + +static char *strDoutEvent[] = +{ + "EV_DOUT_START_XMIT", + "EV_DOUT_COMPLETE", + "EV_DOUT_DEN", + "EV_DOUT_RESETED", + "EV_DOUT_STOPPED", + "EV_DOUT_COLL", + "EV_DOUT_UNDERRUN", +}; + +static __printf(2, 3) + void dout_debug(struct FsmInst *fi, char *fmt, ...) +{ + va_list args; + char buf[256]; + + va_start(args, fmt); + vsnprintf(buf, sizeof(buf), fmt, args); + DBG(0x2, "%s", buf); + va_end(args); +} + +static void dout_stop_event(void *context) +{ + struct st5481_adapter *adapter = context; + + FsmEvent(&adapter->d_out.fsm, EV_DOUT_STOPPED, NULL); +} + +/* + * Start the transfer of a D channel frame. + */ +static void usb_d_out(struct st5481_adapter *adapter, int buf_nr) +{ + struct st5481_d_out *d_out = &adapter->d_out; + struct urb *urb; + unsigned int num_packets, packet_offset; + int len, buf_size, bytes_sent; + struct sk_buff *skb; + struct usb_iso_packet_descriptor *desc; + + if (d_out->fsm.state != ST_DOUT_NORMAL) + return; + + if (test_and_set_bit(buf_nr, &d_out->busy)) { + DBG(2, "ep %d urb %d busy %#lx", EP_D_OUT, buf_nr, d_out->busy); + return; + } + urb = d_out->urb[buf_nr]; + + skb = d_out->tx_skb; + + buf_size = NUM_ISO_PACKETS_D * SIZE_ISO_PACKETS_D_OUT; + + if (skb) { + len = isdnhdlc_encode(&d_out->hdlc_state, + skb->data, skb->len, &bytes_sent, + urb->transfer_buffer, buf_size); + skb_pull(skb, bytes_sent); + } else { + // Send flags or idle + len = isdnhdlc_encode(&d_out->hdlc_state, + NULL, 0, &bytes_sent, + urb->transfer_buffer, buf_size); + } + + if (len < buf_size) { + FsmChangeState(&d_out->fsm, ST_DOUT_WAIT_FOR_UNDERRUN); + } + if (skb && !skb->len) { + d_out->tx_skb = NULL; + D_L1L2(adapter, PH_DATA | CONFIRM, NULL); + dev_kfree_skb_any(skb); + } + + // Prepare the URB + urb->transfer_buffer_length = len; + num_packets = 0; + packet_offset = 0; + while (packet_offset < len) { + desc = &urb->iso_frame_desc[num_packets]; + desc->offset = packet_offset; + desc->length = SIZE_ISO_PACKETS_D_OUT; + if (len - packet_offset < desc->length) + desc->length = len - packet_offset; + num_packets++; + packet_offset += desc->length; + } + urb->number_of_packets = num_packets; + + // Prepare the URB + urb->dev = adapter->usb_dev; + // Need to transmit the next buffer 2ms after the DEN_EVENT + urb->transfer_flags = 0; + urb->start_frame = usb_get_current_frame_number(adapter->usb_dev) + 2; + + DBG_ISO_PACKET(0x20, urb); + + if (usb_submit_urb(urb, GFP_KERNEL) < 0) { + // There is another URB queued up + urb->transfer_flags = URB_ISO_ASAP; + SUBMIT_URB(urb, GFP_KERNEL); + } +} + +static void fifo_reseted(void *context) +{ + struct st5481_adapter *adapter = context; + + FsmEvent(&adapter->d_out.fsm, EV_DOUT_RESETED, NULL); +} + +static void usb_d_out_complete(struct urb *urb) +{ + struct st5481_adapter *adapter = urb->context; + struct st5481_d_out *d_out = &adapter->d_out; + long buf_nr; + + DBG(2, ""); + + buf_nr = get_buf_nr(d_out->urb, urb); + test_and_clear_bit(buf_nr, &d_out->busy); + + if (unlikely(urb->status < 0)) { + switch (urb->status) { + case -ENOENT: + case -ESHUTDOWN: + case -ECONNRESET: + DBG(1, "urb killed status %d", urb->status); + break; + default: + WARNING("urb status %d", urb->status); + if (d_out->busy == 0) { + st5481_usb_pipe_reset(adapter, EP_D_OUT | USB_DIR_OUT, fifo_reseted, adapter); + } + break; + } + return; // Give up + } + + FsmEvent(&adapter->d_out.fsm, EV_DOUT_COMPLETE, (void *) buf_nr); +} + +/* ====================================================================== */ + +static void dout_start_xmit(struct FsmInst *fsm, int event, void *arg) +{ + // FIXME unify? + struct st5481_adapter *adapter = fsm->userdata; + struct st5481_d_out *d_out = &adapter->d_out; + struct urb *urb; + int len, bytes_sent; + struct sk_buff *skb; + int buf_nr = 0; + + skb = d_out->tx_skb; + + DBG(2, "len=%d", skb->len); + + isdnhdlc_out_init(&d_out->hdlc_state, HDLC_DCHANNEL | HDLC_BITREVERSE); + + if (test_and_set_bit(buf_nr, &d_out->busy)) { + WARNING("ep %d urb %d busy %#lx", EP_D_OUT, buf_nr, d_out->busy); + return; + } + urb = d_out->urb[buf_nr]; + + DBG_SKB(0x10, skb); + len = isdnhdlc_encode(&d_out->hdlc_state, + skb->data, skb->len, &bytes_sent, + urb->transfer_buffer, 16); + skb_pull(skb, bytes_sent); + + if (len < 16) + FsmChangeState(&d_out->fsm, ST_DOUT_SHORT_INIT); + else + FsmChangeState(&d_out->fsm, ST_DOUT_LONG_INIT); + + if (skb->len == 0) { + d_out->tx_skb = NULL; + D_L1L2(adapter, PH_DATA | CONFIRM, NULL); + dev_kfree_skb_any(skb); + } + +// Prepare the URB + urb->transfer_buffer_length = len; + + urb->iso_frame_desc[0].offset = 0; + urb->iso_frame_desc[0].length = len; + urb->number_of_packets = 1; + + // Prepare the URB + urb->dev = adapter->usb_dev; + urb->transfer_flags = URB_ISO_ASAP; + + DBG_ISO_PACKET(0x20, urb); + SUBMIT_URB(urb, GFP_KERNEL); +} + +static void dout_short_fifo(struct FsmInst *fsm, int event, void *arg) +{ + struct st5481_adapter *adapter = fsm->userdata; + struct st5481_d_out *d_out = &adapter->d_out; + + FsmChangeState(&d_out->fsm, ST_DOUT_SHORT_WAIT_DEN); + st5481_usb_device_ctrl_msg(adapter, OUT_D_COUNTER, 16, NULL, NULL); +} + +static void dout_end_short_frame(struct FsmInst *fsm, int event, void *arg) +{ + struct st5481_adapter *adapter = fsm->userdata; + struct st5481_d_out *d_out = &adapter->d_out; + + FsmChangeState(&d_out->fsm, ST_DOUT_WAIT_FOR_UNDERRUN); +} + +static void dout_long_enable_fifo(struct FsmInst *fsm, int event, void *arg) +{ + struct st5481_adapter *adapter = fsm->userdata; + struct st5481_d_out *d_out = &adapter->d_out; + + st5481_usb_device_ctrl_msg(adapter, OUT_D_COUNTER, 16, NULL, NULL); + FsmChangeState(&d_out->fsm, ST_DOUT_LONG_WAIT_DEN); +} + +static void dout_long_den(struct FsmInst *fsm, int event, void *arg) +{ + struct st5481_adapter *adapter = fsm->userdata; + struct st5481_d_out *d_out = &adapter->d_out; + + FsmChangeState(&d_out->fsm, ST_DOUT_NORMAL); + usb_d_out(adapter, 0); + usb_d_out(adapter, 1); +} + +static void dout_reset(struct FsmInst *fsm, int event, void *arg) +{ + struct st5481_adapter *adapter = fsm->userdata; + struct st5481_d_out *d_out = &adapter->d_out; + + FsmChangeState(&d_out->fsm, ST_DOUT_WAIT_FOR_RESET); + st5481_usb_pipe_reset(adapter, EP_D_OUT | USB_DIR_OUT, fifo_reseted, adapter); +} + +static void dout_stop(struct FsmInst *fsm, int event, void *arg) +{ + struct st5481_adapter *adapter = fsm->userdata; + struct st5481_d_out *d_out = &adapter->d_out; + + FsmChangeState(&d_out->fsm, ST_DOUT_WAIT_FOR_STOP); + st5481_usb_device_ctrl_msg(adapter, OUT_D_COUNTER, 0, dout_stop_event, adapter); +} + +static void dout_underrun(struct FsmInst *fsm, int event, void *arg) +{ + struct st5481_adapter *adapter = fsm->userdata; + struct st5481_d_out *d_out = &adapter->d_out; + + if (test_bit(0, &d_out->busy) || test_bit(1, &d_out->busy)) { + FsmChangeState(&d_out->fsm, ST_DOUT_WAIT_FOR_NOT_BUSY); + } else { + dout_stop(fsm, event, arg); + } +} + +static void dout_check_busy(struct FsmInst *fsm, int event, void *arg) +{ + struct st5481_adapter *adapter = fsm->userdata; + struct st5481_d_out *d_out = &adapter->d_out; + + if (!test_bit(0, &d_out->busy) && !test_bit(1, &d_out->busy)) + dout_stop(fsm, event, arg); +} + +static void dout_reseted(struct FsmInst *fsm, int event, void *arg) +{ + struct st5481_adapter *adapter = fsm->userdata; + struct st5481_d_out *d_out = &adapter->d_out; + + FsmChangeState(&d_out->fsm, ST_DOUT_NONE); + // FIXME locking + if (d_out->tx_skb) + FsmEvent(&d_out->fsm, EV_DOUT_START_XMIT, NULL); +} + +static void dout_complete(struct FsmInst *fsm, int event, void *arg) +{ + struct st5481_adapter *adapter = fsm->userdata; + long buf_nr = (long) arg; + + usb_d_out(adapter, buf_nr); +} + +static void dout_ignore(struct FsmInst *fsm, int event, void *arg) +{ +} + +static struct FsmNode DoutFnList[] __initdata = +{ + {ST_DOUT_NONE, EV_DOUT_START_XMIT, dout_start_xmit}, + + {ST_DOUT_SHORT_INIT, EV_DOUT_COMPLETE, dout_short_fifo}, + + {ST_DOUT_SHORT_WAIT_DEN, EV_DOUT_DEN, dout_end_short_frame}, + {ST_DOUT_SHORT_WAIT_DEN, EV_DOUT_UNDERRUN, dout_underrun}, + + {ST_DOUT_LONG_INIT, EV_DOUT_COMPLETE, dout_long_enable_fifo}, + + {ST_DOUT_LONG_WAIT_DEN, EV_DOUT_DEN, dout_long_den}, + {ST_DOUT_LONG_WAIT_DEN, EV_DOUT_UNDERRUN, dout_underrun}, + + {ST_DOUT_NORMAL, EV_DOUT_UNDERRUN, dout_underrun}, + {ST_DOUT_NORMAL, EV_DOUT_COMPLETE, dout_complete}, + + {ST_DOUT_WAIT_FOR_UNDERRUN, EV_DOUT_UNDERRUN, dout_underrun}, + {ST_DOUT_WAIT_FOR_UNDERRUN, EV_DOUT_COMPLETE, dout_ignore}, + + {ST_DOUT_WAIT_FOR_NOT_BUSY, EV_DOUT_COMPLETE, dout_check_busy}, + + {ST_DOUT_WAIT_FOR_STOP, EV_DOUT_STOPPED, dout_reset}, + + {ST_DOUT_WAIT_FOR_RESET, EV_DOUT_RESETED, dout_reseted}, +}; + +void st5481_d_l2l1(struct hisax_if *hisax_d_if, int pr, void *arg) +{ + struct st5481_adapter *adapter = hisax_d_if->priv; + struct sk_buff *skb = arg; + + switch (pr) { + case PH_ACTIVATE | REQUEST: + FsmEvent(&adapter->l1m, EV_PH_ACTIVATE_REQ, NULL); + break; + case PH_DEACTIVATE | REQUEST: + FsmEvent(&adapter->l1m, EV_PH_DEACTIVATE_REQ, NULL); + break; + case PH_DATA | REQUEST: + DBG(2, "PH_DATA REQUEST len %d", skb->len); + BUG_ON(adapter->d_out.tx_skb); + adapter->d_out.tx_skb = skb; + FsmEvent(&adapter->d_out.fsm, EV_DOUT_START_XMIT, NULL); + break; + default: + WARNING("pr %#x\n", pr); + break; + } +} + +/* ====================================================================== + */ + +/* + * Start receiving on the D channel since entered state F7. + */ +static void ph_connect(struct st5481_adapter *adapter) +{ + struct st5481_d_out *d_out = &adapter->d_out; + struct st5481_in *d_in = &adapter->d_in; + + DBG(8, ""); + + FsmChangeState(&d_out->fsm, ST_DOUT_NONE); + + // st5481_usb_device_ctrl_msg(adapter, FFMSK_D, OUT_UNDERRUN, NULL, NULL); + st5481_usb_device_ctrl_msg(adapter, FFMSK_D, 0xfc, NULL, NULL); + st5481_in_mode(d_in, L1_MODE_HDLC); + +#ifdef LOOPBACK + // Turn loopback on (data sent on B and D looped back) + st5481_usb_device_ctrl_msg(cs, LBB, 0x04, NULL, NULL); +#endif + + st5481_usb_pipe_reset(adapter, EP_D_OUT | USB_DIR_OUT, NULL, NULL); + + // Turn on the green LED to tell that we are in state F7 + adapter->leds |= GREEN_LED; + st5481_usb_device_ctrl_msg(adapter, GPIO_OUT, adapter->leds, NULL, NULL); +} + +/* + * Stop receiving on the D channel since not in state F7. + */ +static void ph_disconnect(struct st5481_adapter *adapter) +{ + DBG(8, ""); + + st5481_in_mode(&adapter->d_in, L1_MODE_NULL); + + // Turn off the green LED to tell that we left state F7 + adapter->leds &= ~GREEN_LED; + st5481_usb_device_ctrl_msg(adapter, GPIO_OUT, adapter->leds, NULL, NULL); +} + +static int st5481_setup_d_out(struct st5481_adapter *adapter) +{ + struct usb_device *dev = adapter->usb_dev; + struct usb_interface *intf; + struct usb_host_interface *altsetting = NULL; + struct usb_host_endpoint *endpoint; + struct st5481_d_out *d_out = &adapter->d_out; + + DBG(2, ""); + + intf = usb_ifnum_to_if(dev, 0); + if (intf) + altsetting = usb_altnum_to_altsetting(intf, 3); + if (!altsetting) + return -ENXIO; + + // Allocate URBs and buffers for the D channel out + endpoint = &altsetting->endpoint[EP_D_OUT-1]; + + DBG(2, "endpoint address=%02x,packet size=%d", + endpoint->desc.bEndpointAddress, le16_to_cpu(endpoint->desc.wMaxPacketSize)); + + return st5481_setup_isocpipes(d_out->urb, dev, + usb_sndisocpipe(dev, endpoint->desc.bEndpointAddress), + NUM_ISO_PACKETS_D, SIZE_ISO_PACKETS_D_OUT, + NUM_ISO_PACKETS_D * SIZE_ISO_PACKETS_D_OUT, + usb_d_out_complete, adapter); +} + +static void st5481_release_d_out(struct st5481_adapter *adapter) +{ + struct st5481_d_out *d_out = &adapter->d_out; + + DBG(2, ""); + + st5481_release_isocpipes(d_out->urb); +} + +int st5481_setup_d(struct st5481_adapter *adapter) +{ + int retval; + + DBG(2, ""); + + retval = st5481_setup_d_out(adapter); + if (retval) + goto err; + adapter->d_in.bufsize = MAX_DFRAME_LEN_L1; + adapter->d_in.num_packets = NUM_ISO_PACKETS_D; + adapter->d_in.packet_size = SIZE_ISO_PACKETS_D_IN; + adapter->d_in.ep = EP_D_IN | USB_DIR_IN; + adapter->d_in.counter = IN_D_COUNTER; + adapter->d_in.adapter = adapter; + adapter->d_in.hisax_if = &adapter->hisax_d_if.ifc; + retval = st5481_setup_in(&adapter->d_in); + if (retval) + goto err_d_out; + + adapter->l1m.fsm = &l1fsm; + adapter->l1m.state = ST_L1_F3; + adapter->l1m.debug = st5481_debug & 0x100; + adapter->l1m.userdata = adapter; + adapter->l1m.printdebug = l1m_debug; + FsmInitTimer(&adapter->l1m, &adapter->timer); + + adapter->d_out.fsm.fsm = &dout_fsm; + adapter->d_out.fsm.state = ST_DOUT_NONE; + adapter->d_out.fsm.debug = st5481_debug & 0x100; + adapter->d_out.fsm.userdata = adapter; + adapter->d_out.fsm.printdebug = dout_debug; + + return 0; + +err_d_out: + st5481_release_d_out(adapter); +err: + return retval; +} + +void st5481_release_d(struct st5481_adapter *adapter) +{ + DBG(2, ""); + + st5481_release_in(&adapter->d_in); + st5481_release_d_out(adapter); +} + +/* ====================================================================== + * init / exit + */ + +int __init st5481_d_init(void) +{ + int retval; + + l1fsm.state_count = L1_STATE_COUNT; + l1fsm.event_count = L1_EVENT_COUNT; + l1fsm.strEvent = strL1Event; + l1fsm.strState = strL1State; + retval = FsmNew(&l1fsm, L1FnList, ARRAY_SIZE(L1FnList)); + if (retval) + goto err; + + dout_fsm.state_count = DOUT_STATE_COUNT; + dout_fsm.event_count = DOUT_EVENT_COUNT; + dout_fsm.strEvent = strDoutEvent; + dout_fsm.strState = strDoutState; + retval = FsmNew(&dout_fsm, DoutFnList, ARRAY_SIZE(DoutFnList)); + if (retval) + goto err_l1; + + return 0; + +err_l1: + FsmFree(&l1fsm); +err: + return retval; +} + +// can't be __exit +void st5481_d_exit(void) +{ + FsmFree(&l1fsm); + FsmFree(&dout_fsm); +} diff --git a/drivers/isdn/hisax/st5481_init.c b/drivers/isdn/hisax/st5481_init.c new file mode 100644 index 000000000..54ef9e4f8 --- /dev/null +++ b/drivers/isdn/hisax/st5481_init.c @@ -0,0 +1,221 @@ +/* + * Driver for ST5481 USB ISDN modem + * + * Author Frode Isaksen + * Copyright 2001 by Frode Isaksen <fisaksen@bewan.com> + * 2001 by Kai Germaschewski <kai.germaschewski@gmx.de> + * + * This software may be used and distributed according to the terms + * of the GNU General Public License, incorporated herein by reference. + * + */ + +/* + * TODO: + * + * b layer1 delay? + * hotplug / unregister issues + * mod_inc/dec_use_count + * unify parts of d/b channel usb handling + * file header + * avoid copy to isoc buffer? + * improve usb delay? + * merge l1 state machines? + * clean up debug + */ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/usb.h> +#include <linux/slab.h> +#include "st5481.h" + +MODULE_DESCRIPTION("ISDN4Linux: driver for ST5481 USB ISDN adapter"); +MODULE_AUTHOR("Frode Isaksen"); +MODULE_LICENSE("GPL"); + +static int protocol = 2; /* EURO-ISDN Default */ +module_param(protocol, int, 0); + +static int number_of_leds = 2; /* 2 LEDs on the adpater default */ +module_param(number_of_leds, int, 0); + +#ifdef CONFIG_HISAX_DEBUG +static int debug = 0; +module_param(debug, int, 0); +#endif +int st5481_debug; + +/* ====================================================================== + * registration/deregistration with the USB layer + */ + +/* + * This function will be called when the adapter is plugged + * into the USB bus. + */ +static int probe_st5481(struct usb_interface *intf, + const struct usb_device_id *id) +{ + struct usb_device *dev = interface_to_usbdev(intf); + struct st5481_adapter *adapter; + struct hisax_b_if *b_if[2]; + int retval, i; + + printk(KERN_INFO "st541: found adapter VendorId %04x, ProductId %04x, LEDs %d\n", + le16_to_cpu(dev->descriptor.idVendor), + le16_to_cpu(dev->descriptor.idProduct), + number_of_leds); + + adapter = kzalloc(sizeof(struct st5481_adapter), GFP_KERNEL); + if (!adapter) + return -ENOMEM; + + adapter->number_of_leds = number_of_leds; + adapter->usb_dev = dev; + + adapter->hisax_d_if.owner = THIS_MODULE; + adapter->hisax_d_if.ifc.priv = adapter; + adapter->hisax_d_if.ifc.l2l1 = st5481_d_l2l1; + + for (i = 0; i < 2; i++) { + adapter->bcs[i].adapter = adapter; + adapter->bcs[i].channel = i; + adapter->bcs[i].b_if.ifc.priv = &adapter->bcs[i]; + adapter->bcs[i].b_if.ifc.l2l1 = st5481_b_l2l1; + } + + retval = st5481_setup_usb(adapter); + if (retval < 0) + goto err; + + retval = st5481_setup_d(adapter); + if (retval < 0) + goto err_usb; + + retval = st5481_setup_b(&adapter->bcs[0]); + if (retval < 0) + goto err_d; + + retval = st5481_setup_b(&adapter->bcs[1]); + if (retval < 0) + goto err_b; + + for (i = 0; i < 2; i++) + b_if[i] = &adapter->bcs[i].b_if; + + if (hisax_register(&adapter->hisax_d_if, b_if, "st5481_usb", + protocol) != 0) + goto err_b1; + + st5481_start(adapter); + + usb_set_intfdata(intf, adapter); + return 0; + +err_b1: + st5481_release_b(&adapter->bcs[1]); +err_b: + st5481_release_b(&adapter->bcs[0]); +err_d: + st5481_release_d(adapter); +err_usb: + st5481_release_usb(adapter); +err: + kfree(adapter); + return -EIO; +} + +/* + * This function will be called when the adapter is removed + * from the USB bus. + */ +static void disconnect_st5481(struct usb_interface *intf) +{ + struct st5481_adapter *adapter = usb_get_intfdata(intf); + + DBG(1, ""); + + usb_set_intfdata(intf, NULL); + if (!adapter) + return; + + st5481_stop(adapter); + st5481_release_b(&adapter->bcs[1]); + st5481_release_b(&adapter->bcs[0]); + st5481_release_d(adapter); + // we would actually better wait for completion of outstanding urbs + mdelay(2); + st5481_release_usb(adapter); + + hisax_unregister(&adapter->hisax_d_if); + + kfree(adapter); +} + +/* + * The last 4 bits in the Product Id is set with 4 pins on the chip. + */ +static struct usb_device_id st5481_ids[] = { + { USB_DEVICE(ST_VENDOR_ID, ST5481_PRODUCT_ID + 0x0) }, + { USB_DEVICE(ST_VENDOR_ID, ST5481_PRODUCT_ID + 0x1) }, + { USB_DEVICE(ST_VENDOR_ID, ST5481_PRODUCT_ID + 0x2) }, + { USB_DEVICE(ST_VENDOR_ID, ST5481_PRODUCT_ID + 0x3) }, + { USB_DEVICE(ST_VENDOR_ID, ST5481_PRODUCT_ID + 0x4) }, + { USB_DEVICE(ST_VENDOR_ID, ST5481_PRODUCT_ID + 0x5) }, + { USB_DEVICE(ST_VENDOR_ID, ST5481_PRODUCT_ID + 0x6) }, + { USB_DEVICE(ST_VENDOR_ID, ST5481_PRODUCT_ID + 0x7) }, + { USB_DEVICE(ST_VENDOR_ID, ST5481_PRODUCT_ID + 0x8) }, + { USB_DEVICE(ST_VENDOR_ID, ST5481_PRODUCT_ID + 0x9) }, + { USB_DEVICE(ST_VENDOR_ID, ST5481_PRODUCT_ID + 0xA) }, + { USB_DEVICE(ST_VENDOR_ID, ST5481_PRODUCT_ID + 0xB) }, + { USB_DEVICE(ST_VENDOR_ID, ST5481_PRODUCT_ID + 0xC) }, + { USB_DEVICE(ST_VENDOR_ID, ST5481_PRODUCT_ID + 0xD) }, + { USB_DEVICE(ST_VENDOR_ID, ST5481_PRODUCT_ID + 0xE) }, + { USB_DEVICE(ST_VENDOR_ID, ST5481_PRODUCT_ID + 0xF) }, + { } +}; +MODULE_DEVICE_TABLE(usb, st5481_ids); + +static struct usb_driver st5481_usb_driver = { + .name = "st5481_usb", + .probe = probe_st5481, + .disconnect = disconnect_st5481, + .id_table = st5481_ids, + .disable_hub_initiated_lpm = 1, +}; + +static int __init st5481_usb_init(void) +{ + int retval; + +#ifdef CONFIG_HISAX_DEBUG + st5481_debug = debug; +#endif + + printk(KERN_INFO "hisax_st5481: ST5481 USB ISDN driver $Revision: 2.4.2.3 $\n"); + + retval = st5481_d_init(); + if (retval < 0) + goto out; + + retval = usb_register(&st5481_usb_driver); + if (retval < 0) + goto out_d_exit; + + return 0; + +out_d_exit: + st5481_d_exit(); +out: + return retval; +} + +static void __exit st5481_usb_exit(void) +{ + usb_deregister(&st5481_usb_driver); + st5481_d_exit(); +} + +module_init(st5481_usb_init); +module_exit(st5481_usb_exit); diff --git a/drivers/isdn/hisax/st5481_usb.c b/drivers/isdn/hisax/st5481_usb.c new file mode 100644 index 000000000..f207fda69 --- /dev/null +++ b/drivers/isdn/hisax/st5481_usb.c @@ -0,0 +1,659 @@ +/* + * Driver for ST5481 USB ISDN modem + * + * Author Frode Isaksen + * Copyright 2001 by Frode Isaksen <fisaksen@bewan.com> + * 2001 by Kai Germaschewski <kai.germaschewski@gmx.de> + * + * This software may be used and distributed according to the terms + * of the GNU General Public License, incorporated herein by reference. + * + */ + +#include <linux/init.h> +#include <linux/usb.h> +#include <linux/slab.h> +#include "st5481.h" + +static int st5481_isoc_flatten(struct urb *urb); + +/* ====================================================================== + * control pipe + */ + +/* + * Send the next endpoint 0 request stored in the FIFO. + * Called either by the completion or by usb_ctrl_msg. + */ +static void usb_next_ctrl_msg(struct urb *urb, + struct st5481_adapter *adapter) +{ + struct st5481_ctrl *ctrl = &adapter->ctrl; + int r_index; + + if (test_and_set_bit(0, &ctrl->busy)) { + return; + } + + if ((r_index = fifo_remove(&ctrl->msg_fifo.f)) < 0) { + test_and_clear_bit(0, &ctrl->busy); + return; + } + urb->setup_packet = + (unsigned char *)&ctrl->msg_fifo.data[r_index]; + + DBG(1, "request=0x%02x,value=0x%04x,index=%x", + ((struct ctrl_msg *)urb->setup_packet)->dr.bRequest, + ((struct ctrl_msg *)urb->setup_packet)->dr.wValue, + ((struct ctrl_msg *)urb->setup_packet)->dr.wIndex); + + // Prepare the URB + urb->dev = adapter->usb_dev; + + SUBMIT_URB(urb, GFP_ATOMIC); +} + +/* + * Asynchronous endpoint 0 request (async version of usb_control_msg). + * The request will be queued up in a FIFO if the endpoint is busy. + */ +static void usb_ctrl_msg(struct st5481_adapter *adapter, + u8 request, u8 requesttype, u16 value, u16 index, + ctrl_complete_t complete, void *context) +{ + struct st5481_ctrl *ctrl = &adapter->ctrl; + int w_index; + struct ctrl_msg *ctrl_msg; + + if ((w_index = fifo_add(&ctrl->msg_fifo.f)) < 0) { + WARNING("control msg FIFO full"); + return; + } + ctrl_msg = &ctrl->msg_fifo.data[w_index]; + + ctrl_msg->dr.bRequestType = requesttype; + ctrl_msg->dr.bRequest = request; + ctrl_msg->dr.wValue = cpu_to_le16p(&value); + ctrl_msg->dr.wIndex = cpu_to_le16p(&index); + ctrl_msg->dr.wLength = 0; + ctrl_msg->complete = complete; + ctrl_msg->context = context; + + usb_next_ctrl_msg(ctrl->urb, adapter); +} + +/* + * Asynchronous endpoint 0 device request. + */ +void st5481_usb_device_ctrl_msg(struct st5481_adapter *adapter, + u8 request, u16 value, + ctrl_complete_t complete, void *context) +{ + usb_ctrl_msg(adapter, request, + USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE, + value, 0, complete, context); +} + +/* + * Asynchronous pipe reset (async version of usb_clear_halt). + */ +void st5481_usb_pipe_reset(struct st5481_adapter *adapter, + u_char pipe, + ctrl_complete_t complete, void *context) +{ + DBG(1, "pipe=%02x", pipe); + + usb_ctrl_msg(adapter, + USB_REQ_CLEAR_FEATURE, USB_DIR_OUT | USB_RECIP_ENDPOINT, + 0, pipe, complete, context); +} + + +/* + Physical level functions +*/ + +void st5481_ph_command(struct st5481_adapter *adapter, unsigned int command) +{ + DBG(8, "command=%s", ST5481_CMD_string(command)); + + st5481_usb_device_ctrl_msg(adapter, TXCI, command, NULL, NULL); +} + +/* + * The request on endpoint 0 has completed. + * Call the user provided completion routine and try + * to send the next request. + */ +static void usb_ctrl_complete(struct urb *urb) +{ + struct st5481_adapter *adapter = urb->context; + struct st5481_ctrl *ctrl = &adapter->ctrl; + struct ctrl_msg *ctrl_msg; + + if (unlikely(urb->status < 0)) { + switch (urb->status) { + case -ENOENT: + case -ESHUTDOWN: + case -ECONNRESET: + DBG(1, "urb killed status %d", urb->status); + return; // Give up + default: + WARNING("urb status %d", urb->status); + break; + } + } + + ctrl_msg = (struct ctrl_msg *)urb->setup_packet; + + if (ctrl_msg->dr.bRequest == USB_REQ_CLEAR_FEATURE) { + /* Special case handling for pipe reset */ + le16_to_cpus(&ctrl_msg->dr.wIndex); + usb_reset_endpoint(adapter->usb_dev, ctrl_msg->dr.wIndex); + } + + if (ctrl_msg->complete) + ctrl_msg->complete(ctrl_msg->context); + + clear_bit(0, &ctrl->busy); + + // Try to send next control message + usb_next_ctrl_msg(urb, adapter); + return; +} + +/* ====================================================================== + * interrupt pipe + */ + +/* + * The interrupt endpoint will be called when any + * of the 6 registers changes state (depending on masks). + * Decode the register values and schedule a private event. + * Called at interrupt. + */ +static void usb_int_complete(struct urb *urb) +{ + u8 *data = urb->transfer_buffer; + u8 irqbyte; + struct st5481_adapter *adapter = urb->context; + int j; + int status; + + switch (urb->status) { + case 0: + /* success */ + break; + case -ECONNRESET: + case -ENOENT: + case -ESHUTDOWN: + /* this urb is terminated, clean up */ + DBG(2, "urb shutting down with status: %d", urb->status); + return; + default: + WARNING("nonzero urb status received: %d", urb->status); + goto exit; + } + + + DBG_PACKET(2, data, INT_PKT_SIZE); + + if (urb->actual_length == 0) { + goto exit; + } + + irqbyte = data[MPINT]; + if (irqbyte & DEN_INT) + FsmEvent(&adapter->d_out.fsm, EV_DOUT_DEN, NULL); + + if (irqbyte & DCOLL_INT) + FsmEvent(&adapter->d_out.fsm, EV_DOUT_COLL, NULL); + + irqbyte = data[FFINT_D]; + if (irqbyte & OUT_UNDERRUN) + FsmEvent(&adapter->d_out.fsm, EV_DOUT_UNDERRUN, NULL); + + if (irqbyte & OUT_DOWN) + ;// printk("OUT_DOWN\n"); + + irqbyte = data[MPINT]; + if (irqbyte & RXCI_INT) + FsmEvent(&adapter->l1m, data[CCIST] & 0x0f, NULL); + + for (j = 0; j < 2; j++) + adapter->bcs[j].b_out.flow_event |= data[FFINT_B1 + j]; + + urb->actual_length = 0; + +exit: + status = usb_submit_urb(urb, GFP_ATOMIC); + if (status) + WARNING("usb_submit_urb failed with result %d", status); +} + +/* ====================================================================== + * initialization + */ + +int st5481_setup_usb(struct st5481_adapter *adapter) +{ + struct usb_device *dev = adapter->usb_dev; + struct st5481_ctrl *ctrl = &adapter->ctrl; + struct st5481_intr *intr = &adapter->intr; + struct usb_interface *intf; + struct usb_host_interface *altsetting = NULL; + struct usb_host_endpoint *endpoint; + int status; + struct urb *urb; + u8 *buf; + + DBG(2, ""); + + if ((status = usb_reset_configuration(dev)) < 0) { + WARNING("reset_configuration failed,status=%d", status); + return status; + } + + intf = usb_ifnum_to_if(dev, 0); + if (intf) + altsetting = usb_altnum_to_altsetting(intf, 3); + if (!altsetting) + return -ENXIO; + + // Check if the config is sane + if (altsetting->desc.bNumEndpoints != 7) { + WARNING("expecting 7 got %d endpoints!", altsetting->desc.bNumEndpoints); + return -EINVAL; + } + + // The descriptor is wrong for some early samples of the ST5481 chip + altsetting->endpoint[3].desc.wMaxPacketSize = cpu_to_le16(32); + altsetting->endpoint[4].desc.wMaxPacketSize = cpu_to_le16(32); + + // Use alternative setting 3 on interface 0 to have 2B+D + if ((status = usb_set_interface(dev, 0, 3)) < 0) { + WARNING("usb_set_interface failed,status=%d", status); + return status; + } + + // Allocate URB for control endpoint + urb = usb_alloc_urb(0, GFP_KERNEL); + if (!urb) { + return -ENOMEM; + } + ctrl->urb = urb; + + // Fill the control URB + usb_fill_control_urb(urb, dev, + usb_sndctrlpipe(dev, 0), + NULL, NULL, 0, usb_ctrl_complete, adapter); + + + fifo_init(&ctrl->msg_fifo.f, ARRAY_SIZE(ctrl->msg_fifo.data)); + + // Allocate URBs and buffers for interrupt endpoint + urb = usb_alloc_urb(0, GFP_KERNEL); + if (!urb) { + goto err1; + } + intr->urb = urb; + + buf = kmalloc(INT_PKT_SIZE, GFP_KERNEL); + if (!buf) { + goto err2; + } + + endpoint = &altsetting->endpoint[EP_INT-1]; + + // Fill the interrupt URB + usb_fill_int_urb(urb, dev, + usb_rcvintpipe(dev, endpoint->desc.bEndpointAddress), + buf, INT_PKT_SIZE, + usb_int_complete, adapter, + endpoint->desc.bInterval); + + return 0; +err2: + usb_free_urb(intr->urb); + intr->urb = NULL; +err1: + usb_free_urb(ctrl->urb); + ctrl->urb = NULL; + + return -ENOMEM; +} + +/* + * Release buffers and URBs for the interrupt and control + * endpoint. + */ +void st5481_release_usb(struct st5481_adapter *adapter) +{ + struct st5481_intr *intr = &adapter->intr; + struct st5481_ctrl *ctrl = &adapter->ctrl; + + DBG(1, ""); + + // Stop and free Control and Interrupt URBs + usb_kill_urb(ctrl->urb); + kfree(ctrl->urb->transfer_buffer); + usb_free_urb(ctrl->urb); + ctrl->urb = NULL; + + usb_kill_urb(intr->urb); + kfree(intr->urb->transfer_buffer); + usb_free_urb(intr->urb); + intr->urb = NULL; +} + +/* + * Initialize the adapter. + */ +void st5481_start(struct st5481_adapter *adapter) +{ + static const u8 init_cmd_table[] = { + SET_DEFAULT, 0, + STT, 0, + SDA_MIN, 0x0d, + SDA_MAX, 0x29, + SDELAY_VALUE, 0x14, + GPIO_DIR, 0x01, + GPIO_OUT, RED_LED, +// FFCTRL_OUT_D,4, +// FFCTRH_OUT_D,12, + FFCTRL_OUT_B1, 6, + FFCTRH_OUT_B1, 20, + FFCTRL_OUT_B2, 6, + FFCTRH_OUT_B2, 20, + MPMSK, RXCI_INT + DEN_INT + DCOLL_INT, + 0 + }; + struct st5481_intr *intr = &adapter->intr; + int i = 0; + u8 request, value; + + DBG(8, ""); + + adapter->leds = RED_LED; + + // Start receiving on the interrupt endpoint + SUBMIT_URB(intr->urb, GFP_KERNEL); + + while ((request = init_cmd_table[i++])) { + value = init_cmd_table[i++]; + st5481_usb_device_ctrl_msg(adapter, request, value, NULL, NULL); + } + st5481_ph_command(adapter, ST5481_CMD_PUP); +} + +/* + * Reset the adapter to default values. + */ +void st5481_stop(struct st5481_adapter *adapter) +{ + DBG(8, ""); + + st5481_usb_device_ctrl_msg(adapter, SET_DEFAULT, 0, NULL, NULL); +} + +/* ====================================================================== + * isochronous USB helpers + */ + +static void +fill_isoc_urb(struct urb *urb, struct usb_device *dev, + unsigned int pipe, void *buf, int num_packets, + int packet_size, usb_complete_t complete, + void *context) +{ + int k; + + usb_fill_int_urb(urb, dev, pipe, buf, num_packets * packet_size, + complete, context, 1); + + urb->number_of_packets = num_packets; + urb->transfer_flags = URB_ISO_ASAP; + for (k = 0; k < num_packets; k++) { + urb->iso_frame_desc[k].offset = packet_size * k; + urb->iso_frame_desc[k].length = packet_size; + urb->iso_frame_desc[k].actual_length = 0; + } +} + +int +st5481_setup_isocpipes(struct urb *urb[2], struct usb_device *dev, + unsigned int pipe, int num_packets, + int packet_size, int buf_size, + usb_complete_t complete, void *context) +{ + int j, retval; + unsigned char *buf; + + for (j = 0; j < 2; j++) { + retval = -ENOMEM; + urb[j] = usb_alloc_urb(num_packets, GFP_KERNEL); + if (!urb[j]) + goto err; + + // Allocate memory for 2000bytes/sec (16Kb/s) + buf = kmalloc(buf_size, GFP_KERNEL); + if (!buf) + goto err; + + // Fill the isochronous URB + fill_isoc_urb(urb[j], dev, pipe, buf, + num_packets, packet_size, complete, + context); + } + return 0; + +err: + for (j = 0; j < 2; j++) { + if (urb[j]) { + kfree(urb[j]->transfer_buffer); + urb[j]->transfer_buffer = NULL; + usb_free_urb(urb[j]); + urb[j] = NULL; + } + } + return retval; +} + +void st5481_release_isocpipes(struct urb *urb[2]) +{ + int j; + + for (j = 0; j < 2; j++) { + usb_kill_urb(urb[j]); + kfree(urb[j]->transfer_buffer); + usb_free_urb(urb[j]); + urb[j] = NULL; + } +} + +/* + * Decode frames received on the B/D channel. + * Note that this function will be called continuously + * with 64Kbit/s / 16Kbit/s of data and hence it will be + * called 50 times per second with 20 ISOC descriptors. + * Called at interrupt. + */ +static void usb_in_complete(struct urb *urb) +{ + struct st5481_in *in = urb->context; + unsigned char *ptr; + struct sk_buff *skb; + int len, count, status; + + if (unlikely(urb->status < 0)) { + switch (urb->status) { + case -ENOENT: + case -ESHUTDOWN: + case -ECONNRESET: + DBG(1, "urb killed status %d", urb->status); + return; // Give up + default: + WARNING("urb status %d", urb->status); + break; + } + } + + DBG_ISO_PACKET(0x80, urb); + + len = st5481_isoc_flatten(urb); + ptr = urb->transfer_buffer; + while (len > 0) { + if (in->mode == L1_MODE_TRANS) { + memcpy(in->rcvbuf, ptr, len); + status = len; + len = 0; + } else { + status = isdnhdlc_decode(&in->hdlc_state, ptr, len, &count, + in->rcvbuf, in->bufsize); + ptr += count; + len -= count; + } + + if (status > 0) { + // Good frame received + DBG(4, "count=%d", status); + DBG_PACKET(0x400, in->rcvbuf, status); + if (!(skb = dev_alloc_skb(status))) { + WARNING("receive out of memory\n"); + break; + } + skb_put_data(skb, in->rcvbuf, status); + in->hisax_if->l1l2(in->hisax_if, PH_DATA | INDICATION, skb); + } else if (status == -HDLC_CRC_ERROR) { + INFO("CRC error"); + } else if (status == -HDLC_FRAMING_ERROR) { + INFO("framing error"); + } else if (status == -HDLC_LENGTH_ERROR) { + INFO("length error"); + } + } + + // Prepare URB for next transfer + urb->dev = in->adapter->usb_dev; + urb->actual_length = 0; + + SUBMIT_URB(urb, GFP_ATOMIC); +} + +int st5481_setup_in(struct st5481_in *in) +{ + struct usb_device *dev = in->adapter->usb_dev; + int retval; + + DBG(4, ""); + + in->rcvbuf = kmalloc(in->bufsize, GFP_KERNEL); + retval = -ENOMEM; + if (!in->rcvbuf) + goto err; + + retval = st5481_setup_isocpipes(in->urb, dev, + usb_rcvisocpipe(dev, in->ep), + in->num_packets, in->packet_size, + in->num_packets * in->packet_size, + usb_in_complete, in); + if (retval) + goto err_free; + return 0; + +err_free: + kfree(in->rcvbuf); +err: + return retval; +} + +void st5481_release_in(struct st5481_in *in) +{ + DBG(2, ""); + + st5481_release_isocpipes(in->urb); +} + +/* + * Make the transfer_buffer contiguous by + * copying from the iso descriptors if necessary. + */ +static int st5481_isoc_flatten(struct urb *urb) +{ + struct usb_iso_packet_descriptor *pipd, *pend; + unsigned char *src, *dst; + unsigned int len; + + if (urb->status < 0) { + return urb->status; + } + for (pipd = &urb->iso_frame_desc[0], + pend = &urb->iso_frame_desc[urb->number_of_packets], + dst = urb->transfer_buffer; + pipd < pend; + pipd++) { + + if (pipd->status < 0) { + return (pipd->status); + } + + len = pipd->actual_length; + pipd->actual_length = 0; + src = urb->transfer_buffer + pipd->offset; + + if (src != dst) { + // Need to copy since isoc buffers not full + while (len--) { + *dst++ = *src++; + } + } else { + // No need to copy, just update destination buffer + dst += len; + } + } + // Return size of flattened buffer + return (dst - (unsigned char *)urb->transfer_buffer); +} + +static void st5481_start_rcv(void *context) +{ + struct st5481_in *in = context; + struct st5481_adapter *adapter = in->adapter; + + DBG(4, ""); + + in->urb[0]->dev = adapter->usb_dev; + SUBMIT_URB(in->urb[0], GFP_KERNEL); + + in->urb[1]->dev = adapter->usb_dev; + SUBMIT_URB(in->urb[1], GFP_KERNEL); +} + +void st5481_in_mode(struct st5481_in *in, int mode) +{ + if (in->mode == mode) + return; + + in->mode = mode; + + usb_unlink_urb(in->urb[0]); + usb_unlink_urb(in->urb[1]); + + if (in->mode != L1_MODE_NULL) { + if (in->mode != L1_MODE_TRANS) { + u32 features = HDLC_BITREVERSE; + + if (in->mode == L1_MODE_HDLC_56K) + features |= HDLC_56KBIT; + isdnhdlc_rcv_init(&in->hdlc_state, features); + } + st5481_usb_pipe_reset(in->adapter, in->ep, NULL, NULL); + st5481_usb_device_ctrl_msg(in->adapter, in->counter, + in->packet_size, + NULL, NULL); + st5481_start_rcv(in); + } else { + st5481_usb_device_ctrl_msg(in->adapter, in->counter, + 0, NULL, NULL); + } +} diff --git a/drivers/isdn/hisax/tei.c b/drivers/isdn/hisax/tei.c new file mode 100644 index 000000000..9195f9fd6 --- /dev/null +++ b/drivers/isdn/hisax/tei.c @@ -0,0 +1,465 @@ +/* $Id: tei.c,v 2.20.2.3 2004/01/13 14:31:26 keil Exp $ + * + * Author Karsten Keil + * based on the teles driver from Jan den Ouden + * Copyright by Karsten Keil <keil@isdn4linux.de> + * + * This software may be used and distributed according to the terms + * of the GNU General Public License, incorporated herein by reference. + * + * For changes and modifications please read + * Documentation/isdn/HiSax.cert + * + * Thanks to Jan den Ouden + * Fritz Elfert + * + */ + +#include "hisax.h" +#include "isdnl2.h" +#include <linux/gfp.h> +#include <linux/init.h> +#include <linux/random.h> + +const char *tei_revision = "$Revision: 2.20.2.3 $"; + +#define ID_REQUEST 1 +#define ID_ASSIGNED 2 +#define ID_DENIED 3 +#define ID_CHK_REQ 4 +#define ID_CHK_RES 5 +#define ID_REMOVE 6 +#define ID_VERIFY 7 + +#define TEI_ENTITY_ID 0xf + +static struct Fsm teifsm; + +void tei_handler(struct PStack *st, u_char pr, struct sk_buff *skb); + +enum { + ST_TEI_NOP, + ST_TEI_IDREQ, + ST_TEI_IDVERIFY, +}; + +#define TEI_STATE_COUNT (ST_TEI_IDVERIFY + 1) + +static char *strTeiState[] = +{ + "ST_TEI_NOP", + "ST_TEI_IDREQ", + "ST_TEI_IDVERIFY", +}; + +enum { + EV_IDREQ, + EV_ASSIGN, + EV_DENIED, + EV_CHKREQ, + EV_REMOVE, + EV_VERIFY, + EV_T202, +}; + +#define TEI_EVENT_COUNT (EV_T202 + 1) + +static char *strTeiEvent[] = +{ + "EV_IDREQ", + "EV_ASSIGN", + "EV_DENIED", + "EV_CHKREQ", + "EV_REMOVE", + "EV_VERIFY", + "EV_T202", +}; + +static unsigned int +random_ri(void) +{ + unsigned int x; + + get_random_bytes(&x, sizeof(x)); + return (x & 0xffff); +} + +static struct PStack * +findtei(struct PStack *st, int tei) +{ + struct PStack *ptr = *(st->l1.stlistp); + + if (tei == 127) + return (NULL); + + while (ptr) + if (ptr->l2.tei == tei) + return (ptr); + else + ptr = ptr->next; + return (NULL); +} + +static void +put_tei_msg(struct PStack *st, u_char m_id, unsigned int ri, u_char tei) +{ + struct sk_buff *skb; + u_char *bp; + + if (!(skb = alloc_skb(8, GFP_ATOMIC))) { + printk(KERN_WARNING "HiSax: No skb for TEI manager\n"); + return; + } + bp = skb_put(skb, 3); + bp[0] = (TEI_SAPI << 2); + bp[1] = (GROUP_TEI << 1) | 0x1; + bp[2] = UI; + bp = skb_put(skb, 5); + bp[0] = TEI_ENTITY_ID; + bp[1] = ri >> 8; + bp[2] = ri & 0xff; + bp[3] = m_id; + bp[4] = (tei << 1) | 1; + st->l2.l2l1(st, PH_DATA | REQUEST, skb); +} + +static void +tei_id_request(struct FsmInst *fi, int event, void *arg) +{ + struct PStack *st = fi->userdata; + + if (st->l2.tei != -1) { + st->ma.tei_m.printdebug(&st->ma.tei_m, + "assign request for already assigned tei %d", + st->l2.tei); + return; + } + st->ma.ri = random_ri(); + if (st->ma.debug) + st->ma.tei_m.printdebug(&st->ma.tei_m, + "assign request ri %d", st->ma.ri); + put_tei_msg(st, ID_REQUEST, st->ma.ri, 127); + FsmChangeState(&st->ma.tei_m, ST_TEI_IDREQ); + FsmAddTimer(&st->ma.t202, st->ma.T202, EV_T202, NULL, 1); + st->ma.N202 = 3; +} + +static void +tei_id_assign(struct FsmInst *fi, int event, void *arg) +{ + struct PStack *ost, *st = fi->userdata; + struct sk_buff *skb = arg; + struct IsdnCardState *cs; + int ri, tei; + + ri = ((unsigned int) skb->data[1] << 8) + skb->data[2]; + tei = skb->data[4] >> 1; + if (st->ma.debug) + st->ma.tei_m.printdebug(&st->ma.tei_m, + "identity assign ri %d tei %d", ri, tei); + if ((ost = findtei(st, tei))) { /* same tei is in use */ + if (ri != ost->ma.ri) { + st->ma.tei_m.printdebug(&st->ma.tei_m, + "possible duplicate assignment tei %d", tei); + ost->l2.l2tei(ost, MDL_ERROR | RESPONSE, NULL); + } + } else if (ri == st->ma.ri) { + FsmDelTimer(&st->ma.t202, 1); + FsmChangeState(&st->ma.tei_m, ST_TEI_NOP); + st->l3.l3l2(st, MDL_ASSIGN | REQUEST, (void *) (long) tei); + cs = (struct IsdnCardState *) st->l1.hardware; + cs->cardmsg(cs, MDL_ASSIGN | REQUEST, NULL); + } +} + +static void +tei_id_test_dup(struct FsmInst *fi, int event, void *arg) +{ + struct PStack *ost, *st = fi->userdata; + struct sk_buff *skb = arg; + int tei, ri; + + ri = ((unsigned int) skb->data[1] << 8) + skb->data[2]; + tei = skb->data[4] >> 1; + if (st->ma.debug) + st->ma.tei_m.printdebug(&st->ma.tei_m, + "foreign identity assign ri %d tei %d", ri, tei); + if ((ost = findtei(st, tei))) { /* same tei is in use */ + if (ri != ost->ma.ri) { /* and it wasn't our request */ + st->ma.tei_m.printdebug(&st->ma.tei_m, + "possible duplicate assignment tei %d", tei); + FsmEvent(&ost->ma.tei_m, EV_VERIFY, NULL); + } + } +} + +static void +tei_id_denied(struct FsmInst *fi, int event, void *arg) +{ + struct PStack *st = fi->userdata; + struct sk_buff *skb = arg; + int ri, tei; + + ri = ((unsigned int) skb->data[1] << 8) + skb->data[2]; + tei = skb->data[4] >> 1; + if (st->ma.debug) + st->ma.tei_m.printdebug(&st->ma.tei_m, + "identity denied ri %d tei %d", ri, tei); +} + +static void +tei_id_chk_req(struct FsmInst *fi, int event, void *arg) +{ + struct PStack *st = fi->userdata; + struct sk_buff *skb = arg; + int tei; + + tei = skb->data[4] >> 1; + if (st->ma.debug) + st->ma.tei_m.printdebug(&st->ma.tei_m, + "identity check req tei %d", tei); + if ((st->l2.tei != -1) && ((tei == GROUP_TEI) || (tei == st->l2.tei))) { + FsmDelTimer(&st->ma.t202, 4); + FsmChangeState(&st->ma.tei_m, ST_TEI_NOP); + put_tei_msg(st, ID_CHK_RES, random_ri(), st->l2.tei); + } +} + +static void +tei_id_remove(struct FsmInst *fi, int event, void *arg) +{ + struct PStack *st = fi->userdata; + struct sk_buff *skb = arg; + struct IsdnCardState *cs; + int tei; + + tei = skb->data[4] >> 1; + if (st->ma.debug) + st->ma.tei_m.printdebug(&st->ma.tei_m, + "identity remove tei %d", tei); + if ((st->l2.tei != -1) && ((tei == GROUP_TEI) || (tei == st->l2.tei))) { + FsmDelTimer(&st->ma.t202, 5); + FsmChangeState(&st->ma.tei_m, ST_TEI_NOP); + st->l3.l3l2(st, MDL_REMOVE | REQUEST, NULL); + cs = (struct IsdnCardState *) st->l1.hardware; + cs->cardmsg(cs, MDL_REMOVE | REQUEST, NULL); + } +} + +static void +tei_id_verify(struct FsmInst *fi, int event, void *arg) +{ + struct PStack *st = fi->userdata; + + if (st->ma.debug) + st->ma.tei_m.printdebug(&st->ma.tei_m, + "id verify request for tei %d", st->l2.tei); + put_tei_msg(st, ID_VERIFY, 0, st->l2.tei); + FsmChangeState(&st->ma.tei_m, ST_TEI_IDVERIFY); + FsmAddTimer(&st->ma.t202, st->ma.T202, EV_T202, NULL, 2); + st->ma.N202 = 2; +} + +static void +tei_id_req_tout(struct FsmInst *fi, int event, void *arg) +{ + struct PStack *st = fi->userdata; + struct IsdnCardState *cs; + + if (--st->ma.N202) { + st->ma.ri = random_ri(); + if (st->ma.debug) + st->ma.tei_m.printdebug(&st->ma.tei_m, + "assign req(%d) ri %d", 4 - st->ma.N202, + st->ma.ri); + put_tei_msg(st, ID_REQUEST, st->ma.ri, 127); + FsmAddTimer(&st->ma.t202, st->ma.T202, EV_T202, NULL, 3); + } else { + st->ma.tei_m.printdebug(&st->ma.tei_m, "assign req failed"); + st->l3.l3l2(st, MDL_ERROR | RESPONSE, NULL); + cs = (struct IsdnCardState *) st->l1.hardware; + cs->cardmsg(cs, MDL_REMOVE | REQUEST, NULL); + FsmChangeState(fi, ST_TEI_NOP); + } +} + +static void +tei_id_ver_tout(struct FsmInst *fi, int event, void *arg) +{ + struct PStack *st = fi->userdata; + struct IsdnCardState *cs; + + if (--st->ma.N202) { + if (st->ma.debug) + st->ma.tei_m.printdebug(&st->ma.tei_m, + "id verify req(%d) for tei %d", + 3 - st->ma.N202, st->l2.tei); + put_tei_msg(st, ID_VERIFY, 0, st->l2.tei); + FsmAddTimer(&st->ma.t202, st->ma.T202, EV_T202, NULL, 4); + } else { + st->ma.tei_m.printdebug(&st->ma.tei_m, + "verify req for tei %d failed", st->l2.tei); + st->l3.l3l2(st, MDL_REMOVE | REQUEST, NULL); + cs = (struct IsdnCardState *) st->l1.hardware; + cs->cardmsg(cs, MDL_REMOVE | REQUEST, NULL); + FsmChangeState(fi, ST_TEI_NOP); + } +} + +static void +tei_l1l2(struct PStack *st, int pr, void *arg) +{ + struct sk_buff *skb = arg; + int mt; + + if (test_bit(FLG_FIXED_TEI, &st->l2.flag)) { + dev_kfree_skb(skb); + return; + } + + if (pr == (PH_DATA | INDICATION)) { + if (skb->len < 3) { + st->ma.tei_m.printdebug(&st->ma.tei_m, + "short mgr frame %ld/3", skb->len); + } else if ((skb->data[0] != ((TEI_SAPI << 2) | 2)) || + (skb->data[1] != ((GROUP_TEI << 1) | 1))) { + st->ma.tei_m.printdebug(&st->ma.tei_m, + "wrong mgr sapi/tei %x/%x", + skb->data[0], skb->data[1]); + } else if ((skb->data[2] & 0xef) != UI) { + st->ma.tei_m.printdebug(&st->ma.tei_m, + "mgr frame is not ui %x", skb->data[2]); + } else { + skb_pull(skb, 3); + if (skb->len < 5) { + st->ma.tei_m.printdebug(&st->ma.tei_m, + "short mgr frame %ld/5", skb->len); + } else if (skb->data[0] != TEI_ENTITY_ID) { + /* wrong management entity identifier, ignore */ + st->ma.tei_m.printdebug(&st->ma.tei_m, + "tei handler wrong entity id %x", + skb->data[0]); + } else { + mt = skb->data[3]; + if (mt == ID_ASSIGNED) + FsmEvent(&st->ma.tei_m, EV_ASSIGN, skb); + else if (mt == ID_DENIED) + FsmEvent(&st->ma.tei_m, EV_DENIED, skb); + else if (mt == ID_CHK_REQ) + FsmEvent(&st->ma.tei_m, EV_CHKREQ, skb); + else if (mt == ID_REMOVE) + FsmEvent(&st->ma.tei_m, EV_REMOVE, skb); + else { + st->ma.tei_m.printdebug(&st->ma.tei_m, + "tei handler wrong mt %x\n", mt); + } + } + } + } else { + st->ma.tei_m.printdebug(&st->ma.tei_m, + "tei handler wrong pr %x\n", pr); + } + dev_kfree_skb(skb); +} + +static void +tei_l2tei(struct PStack *st, int pr, void *arg) +{ + struct IsdnCardState *cs; + + if (test_bit(FLG_FIXED_TEI, &st->l2.flag)) { + if (pr == (MDL_ASSIGN | INDICATION)) { + if (st->ma.debug) + st->ma.tei_m.printdebug(&st->ma.tei_m, + "fixed assign tei %d", st->l2.tei); + st->l3.l3l2(st, MDL_ASSIGN | REQUEST, (void *) (long) st->l2.tei); + cs = (struct IsdnCardState *) st->l1.hardware; + cs->cardmsg(cs, MDL_ASSIGN | REQUEST, NULL); + } + return; + } + switch (pr) { + case (MDL_ASSIGN | INDICATION): + FsmEvent(&st->ma.tei_m, EV_IDREQ, arg); + break; + case (MDL_ERROR | REQUEST): + FsmEvent(&st->ma.tei_m, EV_VERIFY, arg); + break; + default: + break; + } +} + +static void +tei_debug(struct FsmInst *fi, char *fmt, ...) +{ + va_list args; + struct PStack *st = fi->userdata; + + va_start(args, fmt); + VHiSax_putstatus(st->l1.hardware, "tei ", fmt, args); + va_end(args); +} + +void +setstack_tei(struct PStack *st) +{ + st->l2.l2tei = tei_l2tei; + st->ma.T202 = 2000; /* T202 2000 milliseconds */ + st->l1.l1tei = tei_l1l2; + st->ma.debug = 1; + st->ma.tei_m.fsm = &teifsm; + st->ma.tei_m.state = ST_TEI_NOP; + st->ma.tei_m.debug = 1; + st->ma.tei_m.userdata = st; + st->ma.tei_m.userint = 0; + st->ma.tei_m.printdebug = tei_debug; + FsmInitTimer(&st->ma.tei_m, &st->ma.t202); +} + +void +init_tei(struct IsdnCardState *cs, int protocol) +{ +} + +void +release_tei(struct IsdnCardState *cs) +{ + struct PStack *st = cs->stlist; + + while (st) { + FsmDelTimer(&st->ma.t202, 1); + st = st->next; + } +} + +static struct FsmNode TeiFnList[] __initdata = +{ + {ST_TEI_NOP, EV_IDREQ, tei_id_request}, + {ST_TEI_NOP, EV_ASSIGN, tei_id_test_dup}, + {ST_TEI_NOP, EV_VERIFY, tei_id_verify}, + {ST_TEI_NOP, EV_REMOVE, tei_id_remove}, + {ST_TEI_NOP, EV_CHKREQ, tei_id_chk_req}, + {ST_TEI_IDREQ, EV_T202, tei_id_req_tout}, + {ST_TEI_IDREQ, EV_ASSIGN, tei_id_assign}, + {ST_TEI_IDREQ, EV_DENIED, tei_id_denied}, + {ST_TEI_IDVERIFY, EV_T202, tei_id_ver_tout}, + {ST_TEI_IDVERIFY, EV_REMOVE, tei_id_remove}, + {ST_TEI_IDVERIFY, EV_CHKREQ, tei_id_chk_req}, +}; + +int __init +TeiNew(void) +{ + teifsm.state_count = TEI_STATE_COUNT; + teifsm.event_count = TEI_EVENT_COUNT; + teifsm.strEvent = strTeiEvent; + teifsm.strState = strTeiState; + return FsmNew(&teifsm, TeiFnList, ARRAY_SIZE(TeiFnList)); +} + +void +TeiFree(void) +{ + FsmFree(&teifsm); +} diff --git a/drivers/isdn/hisax/teleint.c b/drivers/isdn/hisax/teleint.c new file mode 100644 index 000000000..247aa3307 --- /dev/null +++ b/drivers/isdn/hisax/teleint.c @@ -0,0 +1,334 @@ +/* $Id: teleint.c,v 1.16.2.5 2004/01/19 15:31:50 keil Exp $ + * + * low level stuff for TeleInt isdn cards + * + * Author Karsten Keil + * Copyright by Karsten Keil <keil@isdn4linux.de> + * + * This software may be used and distributed according to the terms + * of the GNU General Public License, incorporated herein by reference. + * + */ + +#include <linux/init.h> +#include "hisax.h" +#include "isac.h" +#include "hfc_2bs0.h" +#include "isdnl1.h" + +static const char *TeleInt_revision = "$Revision: 1.16.2.5 $"; + +#define byteout(addr, val) outb(val, addr) +#define bytein(addr) inb(addr) + +static inline u_char +readreg(unsigned int ale, unsigned int adr, u_char off) +{ + register u_char ret; + int max_delay = 2000; + + byteout(ale, off); + ret = HFC_BUSY & bytein(ale); + while (ret && --max_delay) + ret = HFC_BUSY & bytein(ale); + if (!max_delay) { + printk(KERN_WARNING "TeleInt Busy not inactive\n"); + return (0); + } + ret = bytein(adr); + return (ret); +} + +static inline void +readfifo(unsigned int ale, unsigned int adr, u_char off, u_char *data, int size) +{ + register u_char ret; + register int max_delay = 20000; + register int i; + + byteout(ale, off); + for (i = 0; i < size; i++) { + ret = HFC_BUSY & bytein(ale); + while (ret && --max_delay) + ret = HFC_BUSY & bytein(ale); + if (!max_delay) { + printk(KERN_WARNING "TeleInt Busy not inactive\n"); + return; + } + data[i] = bytein(adr); + } +} + + +static inline void +writereg(unsigned int ale, unsigned int adr, u_char off, u_char data) +{ + register u_char ret; + int max_delay = 2000; + + byteout(ale, off); + ret = HFC_BUSY & bytein(ale); + while (ret && --max_delay) + ret = HFC_BUSY & bytein(ale); + if (!max_delay) { + printk(KERN_WARNING "TeleInt Busy not inactive\n"); + return; + } + byteout(adr, data); +} + +static inline void +writefifo(unsigned int ale, unsigned int adr, u_char off, u_char *data, int size) +{ + register u_char ret; + register int max_delay = 20000; + register int i; + + byteout(ale, off); + for (i = 0; i < size; i++) { + ret = HFC_BUSY & bytein(ale); + while (ret && --max_delay) + ret = HFC_BUSY & bytein(ale); + if (!max_delay) { + printk(KERN_WARNING "TeleInt Busy not inactive\n"); + return; + } + byteout(adr, data[i]); + } +} + +/* Interface functions */ + +static u_char +ReadISAC(struct IsdnCardState *cs, u_char offset) +{ + cs->hw.hfc.cip = offset; + return (readreg(cs->hw.hfc.addr | 1, cs->hw.hfc.addr, offset)); +} + +static void +WriteISAC(struct IsdnCardState *cs, u_char offset, u_char value) +{ + cs->hw.hfc.cip = offset; + writereg(cs->hw.hfc.addr | 1, cs->hw.hfc.addr, offset, value); +} + +static void +ReadISACfifo(struct IsdnCardState *cs, u_char *data, int size) +{ + cs->hw.hfc.cip = 0; + readfifo(cs->hw.hfc.addr | 1, cs->hw.hfc.addr, 0, data, size); +} + +static void +WriteISACfifo(struct IsdnCardState *cs, u_char *data, int size) +{ + cs->hw.hfc.cip = 0; + writefifo(cs->hw.hfc.addr | 1, cs->hw.hfc.addr, 0, data, size); +} + +static u_char +ReadHFC(struct IsdnCardState *cs, int data, u_char reg) +{ + register u_char ret; + + if (data) { + cs->hw.hfc.cip = reg; + byteout(cs->hw.hfc.addr | 1, reg); + ret = bytein(cs->hw.hfc.addr); + if (cs->debug & L1_DEB_HSCX_FIFO && (data != 2)) + debugl1(cs, "hfc RD %02x %02x", reg, ret); + } else + ret = bytein(cs->hw.hfc.addr | 1); + return (ret); +} + +static void +WriteHFC(struct IsdnCardState *cs, int data, u_char reg, u_char value) +{ + byteout(cs->hw.hfc.addr | 1, reg); + cs->hw.hfc.cip = reg; + if (data) + byteout(cs->hw.hfc.addr, value); + if (cs->debug & L1_DEB_HSCX_FIFO && (data != 2)) + debugl1(cs, "hfc W%c %02x %02x", data ? 'D' : 'C', reg, value); +} + +static irqreturn_t +TeleInt_interrupt(int intno, void *dev_id) +{ + struct IsdnCardState *cs = dev_id; + u_char val; + u_long flags; + + spin_lock_irqsave(&cs->lock, flags); + val = readreg(cs->hw.hfc.addr | 1, cs->hw.hfc.addr, ISAC_ISTA); +Start_ISAC: + if (val) + isac_interrupt(cs, val); + val = readreg(cs->hw.hfc.addr | 1, cs->hw.hfc.addr, ISAC_ISTA); + if (val) { + if (cs->debug & L1_DEB_ISAC) + debugl1(cs, "ISAC IntStat after IntRoutine"); + goto Start_ISAC; + } + writereg(cs->hw.hfc.addr | 1, cs->hw.hfc.addr, ISAC_MASK, 0xFF); + writereg(cs->hw.hfc.addr | 1, cs->hw.hfc.addr, ISAC_MASK, 0x0); + spin_unlock_irqrestore(&cs->lock, flags); + return IRQ_HANDLED; +} + +static void +TeleInt_Timer(struct timer_list *t) +{ + struct IsdnCardState *cs = from_timer(cs, t, hw.hfc.timer); + int stat = 0; + u_long flags; + + spin_lock_irqsave(&cs->lock, flags); + if (cs->bcs[0].mode) { + stat |= 1; + main_irq_hfc(&cs->bcs[0]); + } + if (cs->bcs[1].mode) { + stat |= 2; + main_irq_hfc(&cs->bcs[1]); + } + spin_unlock_irqrestore(&cs->lock, flags); + stat = HZ / 100; + if (!stat) + stat = 1; + cs->hw.hfc.timer.expires = jiffies + stat; + add_timer(&cs->hw.hfc.timer); +} + +static void +release_io_TeleInt(struct IsdnCardState *cs) +{ + del_timer(&cs->hw.hfc.timer); + releasehfc(cs); + if (cs->hw.hfc.addr) + release_region(cs->hw.hfc.addr, 2); +} + +static void +reset_TeleInt(struct IsdnCardState *cs) +{ + printk(KERN_INFO "TeleInt: resetting card\n"); + cs->hw.hfc.cirm |= HFC_RESET; + byteout(cs->hw.hfc.addr | 1, cs->hw.hfc.cirm); /* Reset On */ + mdelay(10); + cs->hw.hfc.cirm &= ~HFC_RESET; + byteout(cs->hw.hfc.addr | 1, cs->hw.hfc.cirm); /* Reset Off */ + mdelay(10); +} + +static int +TeleInt_card_msg(struct IsdnCardState *cs, int mt, void *arg) +{ + u_long flags; + int delay; + + switch (mt) { + case CARD_RESET: + spin_lock_irqsave(&cs->lock, flags); + reset_TeleInt(cs); + spin_unlock_irqrestore(&cs->lock, flags); + return (0); + case CARD_RELEASE: + release_io_TeleInt(cs); + return (0); + case CARD_INIT: + spin_lock_irqsave(&cs->lock, flags); + reset_TeleInt(cs); + inithfc(cs); + clear_pending_isac_ints(cs); + initisac(cs); + /* Reenable all IRQ */ + cs->writeisac(cs, ISAC_MASK, 0); + cs->writeisac(cs, ISAC_CMDR, 0x41); + spin_unlock_irqrestore(&cs->lock, flags); + delay = HZ / 100; + if (!delay) + delay = 1; + cs->hw.hfc.timer.expires = jiffies + delay; + add_timer(&cs->hw.hfc.timer); + return (0); + case CARD_TEST: + return (0); + } + return (0); +} + +int setup_TeleInt(struct IsdnCard *card) +{ + struct IsdnCardState *cs = card->cs; + char tmp[64]; + + strcpy(tmp, TeleInt_revision); + printk(KERN_INFO "HiSax: TeleInt driver Rev. %s\n", HiSax_getrev(tmp)); + if (cs->typ != ISDN_CTYPE_TELEINT) + return (0); + + cs->hw.hfc.addr = card->para[1] & 0x3fe; + cs->irq = card->para[0]; + cs->hw.hfc.cirm = HFC_CIRM; + cs->hw.hfc.isac_spcr = 0x00; + cs->hw.hfc.cip = 0; + cs->hw.hfc.ctmt = HFC_CTMT | HFC_CLTIMER; + cs->bcs[0].hw.hfc.send = NULL; + cs->bcs[1].hw.hfc.send = NULL; + cs->hw.hfc.fifosize = 7 * 1024 + 512; + timer_setup(&cs->hw.hfc.timer, TeleInt_Timer, 0); + if (!request_region(cs->hw.hfc.addr, 2, "TeleInt isdn")) { + printk(KERN_WARNING + "HiSax: TeleInt config port %x-%x already in use\n", + cs->hw.hfc.addr, + cs->hw.hfc.addr + 2); + return (0); + } + /* HW IO = IO */ + byteout(cs->hw.hfc.addr, cs->hw.hfc.addr & 0xff); + byteout(cs->hw.hfc.addr | 1, ((cs->hw.hfc.addr & 0x300) >> 8) | 0x54); + switch (cs->irq) { + case 3: + cs->hw.hfc.cirm |= HFC_INTA; + break; + case 4: + cs->hw.hfc.cirm |= HFC_INTB; + break; + case 5: + cs->hw.hfc.cirm |= HFC_INTC; + break; + case 7: + cs->hw.hfc.cirm |= HFC_INTD; + break; + case 10: + cs->hw.hfc.cirm |= HFC_INTE; + break; + case 11: + cs->hw.hfc.cirm |= HFC_INTF; + break; + default: + printk(KERN_WARNING "TeleInt: wrong IRQ\n"); + release_io_TeleInt(cs); + return (0); + } + byteout(cs->hw.hfc.addr | 1, cs->hw.hfc.cirm); + byteout(cs->hw.hfc.addr | 1, cs->hw.hfc.ctmt); + + printk(KERN_INFO "TeleInt: defined at 0x%x IRQ %d\n", + cs->hw.hfc.addr, cs->irq); + + setup_isac(cs); + cs->readisac = &ReadISAC; + cs->writeisac = &WriteISAC; + cs->readisacfifo = &ReadISACfifo; + cs->writeisacfifo = &WriteISACfifo; + cs->BC_Read_Reg = &ReadHFC; + cs->BC_Write_Reg = &WriteHFC; + cs->cardmsg = &TeleInt_card_msg; + cs->irq_func = &TeleInt_interrupt; + ISACVersion(cs, "TeleInt:"); + return (1); +} diff --git a/drivers/isdn/hisax/teles0.c b/drivers/isdn/hisax/teles0.c new file mode 100644 index 000000000..ce9eabdd2 --- /dev/null +++ b/drivers/isdn/hisax/teles0.c @@ -0,0 +1,364 @@ +/* $Id: teles0.c,v 2.15.2.4 2004/01/13 23:48:39 keil Exp $ + * + * low level stuff for Teles Memory IO isdn cards + * + * Author Karsten Keil + * based on the teles driver from Jan den Ouden + * Copyright by Karsten Keil <keil@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 Jan den Ouden + * Fritz Elfert + * Beat Doebeli + * + */ + +#include <linux/init.h> +#include "hisax.h" +#include "isdnl1.h" +#include "isac.h" +#include "hscx.h" + +static const char *teles0_revision = "$Revision: 2.15.2.4 $"; + +#define TELES_IOMEM_SIZE 0x400 +#define byteout(addr, val) outb(val, addr) +#define bytein(addr) inb(addr) + +static inline u_char +readisac(void __iomem *adr, u_char off) +{ + return readb(adr + ((off & 1) ? 0x2ff : 0x100) + off); +} + +static inline void +writeisac(void __iomem *adr, u_char off, u_char data) +{ + writeb(data, adr + ((off & 1) ? 0x2ff : 0x100) + off); mb(); +} + + +static inline u_char +readhscx(void __iomem *adr, int hscx, u_char off) +{ + return readb(adr + (hscx ? 0x1c0 : 0x180) + + ((off & 1) ? 0x1ff : 0) + off); +} + +static inline void +writehscx(void __iomem *adr, int hscx, u_char off, u_char data) +{ + writeb(data, adr + (hscx ? 0x1c0 : 0x180) + + ((off & 1) ? 0x1ff : 0) + off); mb(); +} + +static inline void +read_fifo_isac(void __iomem *adr, u_char *data, int size) +{ + register int i; + register u_char __iomem *ad = adr + 0x100; + for (i = 0; i < size; i++) + data[i] = readb(ad); +} + +static inline void +write_fifo_isac(void __iomem *adr, u_char *data, int size) +{ + register int i; + register u_char __iomem *ad = adr + 0x100; + for (i = 0; i < size; i++) { + writeb(data[i], ad); mb(); + } +} + +static inline void +read_fifo_hscx(void __iomem *adr, int hscx, u_char *data, int size) +{ + register int i; + register u_char __iomem *ad = adr + (hscx ? 0x1c0 : 0x180); + for (i = 0; i < size; i++) + data[i] = readb(ad); +} + +static inline void +write_fifo_hscx(void __iomem *adr, int hscx, u_char *data, int size) +{ + int i; + register u_char __iomem *ad = adr + (hscx ? 0x1c0 : 0x180); + for (i = 0; i < size; i++) { + writeb(data[i], ad); mb(); + } +} + +/* Interface functions */ + +static u_char +ReadISAC(struct IsdnCardState *cs, u_char offset) +{ + return (readisac(cs->hw.teles0.membase, offset)); +} + +static void +WriteISAC(struct IsdnCardState *cs, u_char offset, u_char value) +{ + writeisac(cs->hw.teles0.membase, offset, value); +} + +static void +ReadISACfifo(struct IsdnCardState *cs, u_char *data, int size) +{ + read_fifo_isac(cs->hw.teles0.membase, data, size); +} + +static void +WriteISACfifo(struct IsdnCardState *cs, u_char *data, int size) +{ + write_fifo_isac(cs->hw.teles0.membase, data, size); +} + +static u_char +ReadHSCX(struct IsdnCardState *cs, int hscx, u_char offset) +{ + return (readhscx(cs->hw.teles0.membase, hscx, offset)); +} + +static void +WriteHSCX(struct IsdnCardState *cs, int hscx, u_char offset, u_char value) +{ + writehscx(cs->hw.teles0.membase, hscx, offset, value); +} + +/* + * fast interrupt HSCX stuff goes here + */ + +#define READHSCX(cs, nr, reg) readhscx(cs->hw.teles0.membase, nr, reg) +#define WRITEHSCX(cs, nr, reg, data) writehscx(cs->hw.teles0.membase, nr, reg, data) +#define READHSCXFIFO(cs, nr, ptr, cnt) read_fifo_hscx(cs->hw.teles0.membase, nr, ptr, cnt) +#define WRITEHSCXFIFO(cs, nr, ptr, cnt) write_fifo_hscx(cs->hw.teles0.membase, nr, ptr, cnt) + +#include "hscx_irq.c" + +static irqreturn_t +teles0_interrupt(int intno, void *dev_id) +{ + struct IsdnCardState *cs = dev_id; + u_char val; + u_long flags; + int count = 0; + + spin_lock_irqsave(&cs->lock, flags); + val = readhscx(cs->hw.teles0.membase, 1, HSCX_ISTA); +Start_HSCX: + if (val) + hscx_int_main(cs, val); + val = readisac(cs->hw.teles0.membase, ISAC_ISTA); +Start_ISAC: + if (val) + isac_interrupt(cs, val); + count++; + val = readhscx(cs->hw.teles0.membase, 1, HSCX_ISTA); + if (val && count < 5) { + if (cs->debug & L1_DEB_HSCX) + debugl1(cs, "HSCX IntStat after IntRoutine"); + goto Start_HSCX; + } + val = readisac(cs->hw.teles0.membase, ISAC_ISTA); + if (val && count < 5) { + if (cs->debug & L1_DEB_ISAC) + debugl1(cs, "ISAC IntStat after IntRoutine"); + goto Start_ISAC; + } + writehscx(cs->hw.teles0.membase, 0, HSCX_MASK, 0xFF); + writehscx(cs->hw.teles0.membase, 1, HSCX_MASK, 0xFF); + writeisac(cs->hw.teles0.membase, ISAC_MASK, 0xFF); + writeisac(cs->hw.teles0.membase, ISAC_MASK, 0x0); + writehscx(cs->hw.teles0.membase, 0, HSCX_MASK, 0x0); + writehscx(cs->hw.teles0.membase, 1, HSCX_MASK, 0x0); + spin_unlock_irqrestore(&cs->lock, flags); + return IRQ_HANDLED; +} + +static void +release_io_teles0(struct IsdnCardState *cs) +{ + if (cs->hw.teles0.cfg_reg) + release_region(cs->hw.teles0.cfg_reg, 8); + iounmap(cs->hw.teles0.membase); + release_mem_region(cs->hw.teles0.phymem, TELES_IOMEM_SIZE); +} + +static int +reset_teles0(struct IsdnCardState *cs) +{ + u_char cfval; + + if (cs->hw.teles0.cfg_reg) { + switch (cs->irq) { + case 2: + case 9: + cfval = 0x00; + break; + case 3: + cfval = 0x02; + break; + case 4: + cfval = 0x04; + break; + case 5: + cfval = 0x06; + break; + case 10: + cfval = 0x08; + break; + case 11: + cfval = 0x0A; + break; + case 12: + cfval = 0x0C; + break; + case 15: + cfval = 0x0E; + break; + default: + return (1); + } + cfval |= ((cs->hw.teles0.phymem >> 9) & 0xF0); + byteout(cs->hw.teles0.cfg_reg + 4, cfval); + HZDELAY(HZ / 10 + 1); + byteout(cs->hw.teles0.cfg_reg + 4, cfval | 1); + HZDELAY(HZ / 10 + 1); + } + writeb(0, cs->hw.teles0.membase + 0x80); mb(); + HZDELAY(HZ / 5 + 1); + writeb(1, cs->hw.teles0.membase + 0x80); mb(); + HZDELAY(HZ / 5 + 1); + return (0); +} + +static int +Teles_card_msg(struct IsdnCardState *cs, int mt, void *arg) +{ + u_long flags; + + switch (mt) { + case CARD_RESET: + spin_lock_irqsave(&cs->lock, flags); + reset_teles0(cs); + spin_unlock_irqrestore(&cs->lock, flags); + return (0); + case CARD_RELEASE: + release_io_teles0(cs); + return (0); + case CARD_INIT: + spin_lock_irqsave(&cs->lock, flags); + inithscxisac(cs, 3); + spin_unlock_irqrestore(&cs->lock, flags); + return (0); + case CARD_TEST: + return (0); + } + return (0); +} + +int setup_teles0(struct IsdnCard *card) +{ + u_char val; + struct IsdnCardState *cs = card->cs; + char tmp[64]; + + strcpy(tmp, teles0_revision); + printk(KERN_INFO "HiSax: Teles 8.0/16.0 driver Rev. %s\n", HiSax_getrev(tmp)); + if ((cs->typ != ISDN_CTYPE_16_0) && (cs->typ != ISDN_CTYPE_8_0)) + return (0); + + if (cs->typ == ISDN_CTYPE_16_0) + cs->hw.teles0.cfg_reg = card->para[2]; + else /* 8.0 */ + cs->hw.teles0.cfg_reg = 0; + + if (card->para[1] < 0x10000) { + card->para[1] <<= 4; + printk(KERN_INFO + "Teles0: membase configured DOSish, assuming 0x%lx\n", + (unsigned long) card->para[1]); + } + cs->irq = card->para[0]; + if (cs->hw.teles0.cfg_reg) { + if (!request_region(cs->hw.teles0.cfg_reg, 8, "teles cfg")) { + printk(KERN_WARNING + "HiSax: %s config port %x-%x already in use\n", + CardType[card->typ], + cs->hw.teles0.cfg_reg, + cs->hw.teles0.cfg_reg + 8); + return (0); + } + } + if (cs->hw.teles0.cfg_reg) { + if ((val = bytein(cs->hw.teles0.cfg_reg + 0)) != 0x51) { + printk(KERN_WARNING "Teles0: 16.0 Byte at %x is %x\n", + cs->hw.teles0.cfg_reg + 0, val); + release_region(cs->hw.teles0.cfg_reg, 8); + return (0); + } + if ((val = bytein(cs->hw.teles0.cfg_reg + 1)) != 0x93) { + printk(KERN_WARNING "Teles0: 16.0 Byte at %x is %x\n", + cs->hw.teles0.cfg_reg + 1, val); + release_region(cs->hw.teles0.cfg_reg, 8); + return (0); + } + val = bytein(cs->hw.teles0.cfg_reg + 2); /* 0x1e=without AB + * 0x1f=with AB + * 0x1c 16.3 ??? + */ + if (val != 0x1e && val != 0x1f) { + printk(KERN_WARNING "Teles0: 16.0 Byte at %x is %x\n", + cs->hw.teles0.cfg_reg + 2, val); + release_region(cs->hw.teles0.cfg_reg, 8); + return (0); + } + } + /* 16.0 and 8.0 designed for IOM1 */ + test_and_set_bit(HW_IOM1, &cs->HW_Flags); + cs->hw.teles0.phymem = card->para[1]; + if (!request_mem_region(cs->hw.teles0.phymem, TELES_IOMEM_SIZE, "teles iomem")) { + printk(KERN_WARNING + "HiSax: %s memory region %lx-%lx already in use\n", + CardType[card->typ], + cs->hw.teles0.phymem, + cs->hw.teles0.phymem + TELES_IOMEM_SIZE); + if (cs->hw.teles0.cfg_reg) + release_region(cs->hw.teles0.cfg_reg, 8); + return (0); + } + cs->hw.teles0.membase = ioremap(cs->hw.teles0.phymem, TELES_IOMEM_SIZE); + printk(KERN_INFO + "HiSax: %s config irq:%d mem:%p cfg:0x%X\n", + CardType[cs->typ], cs->irq, + cs->hw.teles0.membase, cs->hw.teles0.cfg_reg); + if (reset_teles0(cs)) { + printk(KERN_WARNING "Teles0: wrong IRQ\n"); + release_io_teles0(cs); + return (0); + } + setup_isac(cs); + cs->readisac = &ReadISAC; + cs->writeisac = &WriteISAC; + cs->readisacfifo = &ReadISACfifo; + cs->writeisacfifo = &WriteISACfifo; + cs->BC_Read_Reg = &ReadHSCX; + cs->BC_Write_Reg = &WriteHSCX; + cs->BC_Send_Data = &hscx_fill_fifo; + cs->cardmsg = &Teles_card_msg; + cs->irq_func = &teles0_interrupt; + ISACVersion(cs, "Teles0:"); + if (HscxVersion(cs, "Teles0:")) { + printk(KERN_WARNING + "Teles0: wrong HSCX versions check IO/MEM addresses\n"); + release_io_teles0(cs); + return (0); + } + return (1); +} diff --git a/drivers/isdn/hisax/teles3.c b/drivers/isdn/hisax/teles3.c new file mode 100644 index 000000000..1eef693f0 --- /dev/null +++ b/drivers/isdn/hisax/teles3.c @@ -0,0 +1,498 @@ +/* $Id: teles3.c,v 2.19.2.4 2004/01/13 23:48:39 keil Exp $ + * + * low level stuff for Teles 16.3 & PNP isdn cards + * + * Author Karsten Keil + * Copyright by Karsten Keil <keil@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 Jan den Ouden + * Fritz Elfert + * Beat Doebeli + * + */ +#include <linux/init.h> +#include <linux/isapnp.h> +#include "hisax.h" +#include "isac.h" +#include "hscx.h" +#include "isdnl1.h" + +static const char *teles3_revision = "$Revision: 2.19.2.4 $"; + +#define byteout(addr, val) outb(val, addr) +#define bytein(addr) inb(addr) + +static inline u_char +readreg(unsigned int adr, u_char off) +{ + return (bytein(adr + off)); +} + +static inline void +writereg(unsigned int adr, u_char off, u_char data) +{ + byteout(adr + off, data); +} + + +static inline void +read_fifo(unsigned int adr, u_char *data, int size) +{ + insb(adr, data, size); +} + +static void +write_fifo(unsigned int adr, u_char *data, int size) +{ + outsb(adr, data, size); +} + +/* Interface functions */ + +static u_char +ReadISAC(struct IsdnCardState *cs, u_char offset) +{ + return (readreg(cs->hw.teles3.isac, offset)); +} + +static void +WriteISAC(struct IsdnCardState *cs, u_char offset, u_char value) +{ + writereg(cs->hw.teles3.isac, offset, value); +} + +static void +ReadISACfifo(struct IsdnCardState *cs, u_char *data, int size) +{ + read_fifo(cs->hw.teles3.isacfifo, data, size); +} + +static void +WriteISACfifo(struct IsdnCardState *cs, u_char *data, int size) +{ + write_fifo(cs->hw.teles3.isacfifo, data, size); +} + +static u_char +ReadHSCX(struct IsdnCardState *cs, int hscx, u_char offset) +{ + return (readreg(cs->hw.teles3.hscx[hscx], offset)); +} + +static void +WriteHSCX(struct IsdnCardState *cs, int hscx, u_char offset, u_char value) +{ + writereg(cs->hw.teles3.hscx[hscx], offset, value); +} + +/* + * fast interrupt HSCX stuff goes here + */ + +#define READHSCX(cs, nr, reg) readreg(cs->hw.teles3.hscx[nr], reg) +#define WRITEHSCX(cs, nr, reg, data) writereg(cs->hw.teles3.hscx[nr], reg, data) +#define READHSCXFIFO(cs, nr, ptr, cnt) read_fifo(cs->hw.teles3.hscxfifo[nr], ptr, cnt) +#define WRITEHSCXFIFO(cs, nr, ptr, cnt) write_fifo(cs->hw.teles3.hscxfifo[nr], ptr, cnt) + +#include "hscx_irq.c" + +static irqreturn_t +teles3_interrupt(int intno, void *dev_id) +{ +#define MAXCOUNT 5 + struct IsdnCardState *cs = dev_id; + u_char val; + u_long flags; + int count = 0; + + spin_lock_irqsave(&cs->lock, flags); + val = readreg(cs->hw.teles3.hscx[1], HSCX_ISTA); +Start_HSCX: + if (val) + hscx_int_main(cs, val); + val = readreg(cs->hw.teles3.isac, ISAC_ISTA); +Start_ISAC: + if (val) + isac_interrupt(cs, val); + count++; + val = readreg(cs->hw.teles3.hscx[1], HSCX_ISTA); + if (val && count < MAXCOUNT) { + if (cs->debug & L1_DEB_HSCX) + debugl1(cs, "HSCX IntStat after IntRoutine"); + goto Start_HSCX; + } + val = readreg(cs->hw.teles3.isac, ISAC_ISTA); + if (val && count < MAXCOUNT) { + if (cs->debug & L1_DEB_ISAC) + debugl1(cs, "ISAC IntStat after IntRoutine"); + goto Start_ISAC; + } + if (count >= MAXCOUNT) + printk(KERN_WARNING "Teles3: more than %d loops in teles3_interrupt\n", count); + writereg(cs->hw.teles3.hscx[0], HSCX_MASK, 0xFF); + writereg(cs->hw.teles3.hscx[1], HSCX_MASK, 0xFF); + writereg(cs->hw.teles3.isac, ISAC_MASK, 0xFF); + writereg(cs->hw.teles3.isac, ISAC_MASK, 0x0); + writereg(cs->hw.teles3.hscx[0], HSCX_MASK, 0x0); + writereg(cs->hw.teles3.hscx[1], HSCX_MASK, 0x0); + spin_unlock_irqrestore(&cs->lock, flags); + return IRQ_HANDLED; +} + +static inline void +release_ioregs(struct IsdnCardState *cs, int mask) +{ + if (mask & 1) + release_region(cs->hw.teles3.isac + 32, 32); + if (mask & 2) + release_region(cs->hw.teles3.hscx[0] + 32, 32); + if (mask & 4) + release_region(cs->hw.teles3.hscx[1] + 32, 32); +} + +static void +release_io_teles3(struct IsdnCardState *cs) +{ + if (cs->typ == ISDN_CTYPE_TELESPCMCIA) { + release_region(cs->hw.teles3.hscx[1], 96); + } else { + if (cs->hw.teles3.cfg_reg) { + if (cs->typ == ISDN_CTYPE_COMPAQ_ISA) { + release_region(cs->hw.teles3.cfg_reg, 1); + } else { + release_region(cs->hw.teles3.cfg_reg, 8); + } + } + release_ioregs(cs, 0x7); + } +} + +static int +reset_teles3(struct IsdnCardState *cs) +{ + u_char irqcfg; + + if (cs->typ != ISDN_CTYPE_TELESPCMCIA) { + if ((cs->hw.teles3.cfg_reg) && (cs->typ != ISDN_CTYPE_COMPAQ_ISA)) { + switch (cs->irq) { + case 2: + case 9: + irqcfg = 0x00; + break; + case 3: + irqcfg = 0x02; + break; + case 4: + irqcfg = 0x04; + break; + case 5: + irqcfg = 0x06; + break; + case 10: + irqcfg = 0x08; + break; + case 11: + irqcfg = 0x0A; + break; + case 12: + irqcfg = 0x0C; + break; + case 15: + irqcfg = 0x0E; + break; + default: + return (1); + } + byteout(cs->hw.teles3.cfg_reg + 4, irqcfg); + HZDELAY(HZ / 10 + 1); + byteout(cs->hw.teles3.cfg_reg + 4, irqcfg | 1); + HZDELAY(HZ / 10 + 1); + } else if (cs->typ == ISDN_CTYPE_COMPAQ_ISA) { + byteout(cs->hw.teles3.cfg_reg, 0xff); + HZDELAY(2); + byteout(cs->hw.teles3.cfg_reg, 0x00); + HZDELAY(2); + } else { + /* Reset off for 16.3 PnP , thanks to Georg Acher */ + byteout(cs->hw.teles3.isac + 0x3c, 0); + HZDELAY(2); + byteout(cs->hw.teles3.isac + 0x3c, 1); + HZDELAY(2); + } + } + return (0); +} + +static int +Teles_card_msg(struct IsdnCardState *cs, int mt, void *arg) +{ + u_long flags; + + switch (mt) { + case CARD_RESET: + spin_lock_irqsave(&cs->lock, flags); + reset_teles3(cs); + spin_unlock_irqrestore(&cs->lock, flags); + return (0); + case CARD_RELEASE: + release_io_teles3(cs); + return (0); + case CARD_INIT: + spin_lock_irqsave(&cs->lock, flags); + inithscxisac(cs, 3); + spin_unlock_irqrestore(&cs->lock, flags); + return (0); + case CARD_TEST: + return (0); + } + return (0); +} + +#ifdef __ISAPNP__ + +static struct isapnp_device_id teles_ids[] = { + { ISAPNP_VENDOR('T', 'A', 'G'), ISAPNP_FUNCTION(0x2110), + ISAPNP_VENDOR('T', 'A', 'G'), ISAPNP_FUNCTION(0x2110), + (unsigned long) "Teles 16.3 PnP" }, + { ISAPNP_VENDOR('C', 'T', 'X'), ISAPNP_FUNCTION(0x0), + ISAPNP_VENDOR('C', 'T', 'X'), ISAPNP_FUNCTION(0x0), + (unsigned long) "Creatix 16.3 PnP" }, + { ISAPNP_VENDOR('C', 'P', 'Q'), ISAPNP_FUNCTION(0x1002), + ISAPNP_VENDOR('C', 'P', 'Q'), ISAPNP_FUNCTION(0x1002), + (unsigned long) "Compaq ISDN S0" }, + { 0, } +}; + +static struct isapnp_device_id *ipid = &teles_ids[0]; +static struct pnp_card *pnp_c = NULL; +#endif + +int setup_teles3(struct IsdnCard *card) +{ + u_char val; + struct IsdnCardState *cs = card->cs; + char tmp[64]; + + strcpy(tmp, teles3_revision); + printk(KERN_INFO "HiSax: Teles IO driver Rev. %s\n", HiSax_getrev(tmp)); + if ((cs->typ != ISDN_CTYPE_16_3) && (cs->typ != ISDN_CTYPE_PNP) + && (cs->typ != ISDN_CTYPE_TELESPCMCIA) && (cs->typ != ISDN_CTYPE_COMPAQ_ISA)) + return (0); + +#ifdef __ISAPNP__ + if (!card->para[1] && isapnp_present()) { + struct pnp_dev *pnp_d; + while (ipid->card_vendor) { + if ((pnp_c = pnp_find_card(ipid->card_vendor, + ipid->card_device, pnp_c))) { + pnp_d = NULL; + if ((pnp_d = pnp_find_dev(pnp_c, + ipid->vendor, ipid->function, pnp_d))) { + int err; + + printk(KERN_INFO "HiSax: %s detected\n", + (char *)ipid->driver_data); + pnp_disable_dev(pnp_d); + err = pnp_activate_dev(pnp_d); + if (err < 0) { + printk(KERN_WARNING "%s: pnp_activate_dev ret(%d)\n", + __func__, err); + return (0); + } + card->para[3] = pnp_port_start(pnp_d, 2); + card->para[2] = pnp_port_start(pnp_d, 1); + card->para[1] = pnp_port_start(pnp_d, 0); + card->para[0] = pnp_irq(pnp_d, 0); + if (card->para[0] == -1 || !card->para[1] || !card->para[2]) { + printk(KERN_ERR "Teles PnP:some resources are missing %ld/%lx/%lx\n", + card->para[0], card->para[1], card->para[2]); + pnp_disable_dev(pnp_d); + return (0); + } + break; + } else { + printk(KERN_ERR "Teles PnP: PnP error card found, no device\n"); + } + } + ipid++; + pnp_c = NULL; + } + if (!ipid->card_vendor) { + printk(KERN_INFO "Teles PnP: no ISAPnP card found\n"); + return (0); + } + } +#endif + if (cs->typ == ISDN_CTYPE_16_3) { + cs->hw.teles3.cfg_reg = card->para[1]; + switch (cs->hw.teles3.cfg_reg) { + case 0x180: + case 0x280: + case 0x380: + cs->hw.teles3.cfg_reg |= 0xc00; + break; + } + cs->hw.teles3.isac = cs->hw.teles3.cfg_reg - 0x420; + cs->hw.teles3.hscx[0] = cs->hw.teles3.cfg_reg - 0xc20; + cs->hw.teles3.hscx[1] = cs->hw.teles3.cfg_reg - 0x820; + } else if (cs->typ == ISDN_CTYPE_TELESPCMCIA) { + cs->hw.teles3.cfg_reg = 0; + cs->hw.teles3.hscx[0] = card->para[1] - 0x20; + cs->hw.teles3.hscx[1] = card->para[1]; + cs->hw.teles3.isac = card->para[1] + 0x20; + } else if (cs->typ == ISDN_CTYPE_COMPAQ_ISA) { + cs->hw.teles3.cfg_reg = card->para[3]; + cs->hw.teles3.isac = card->para[2] - 32; + cs->hw.teles3.hscx[0] = card->para[1] - 32; + cs->hw.teles3.hscx[1] = card->para[1]; + } else { /* PNP */ + cs->hw.teles3.cfg_reg = 0; + cs->hw.teles3.isac = card->para[1] - 32; + cs->hw.teles3.hscx[0] = card->para[2] - 32; + cs->hw.teles3.hscx[1] = card->para[2]; + } + cs->irq = card->para[0]; + cs->hw.teles3.isacfifo = cs->hw.teles3.isac + 0x3e; + cs->hw.teles3.hscxfifo[0] = cs->hw.teles3.hscx[0] + 0x3e; + cs->hw.teles3.hscxfifo[1] = cs->hw.teles3.hscx[1] + 0x3e; + if (cs->typ == ISDN_CTYPE_TELESPCMCIA) { + if (!request_region(cs->hw.teles3.hscx[1], 96, "HiSax Teles PCMCIA")) { + printk(KERN_WARNING + "HiSax: %s ports %x-%x already in use\n", + CardType[cs->typ], + cs->hw.teles3.hscx[1], + cs->hw.teles3.hscx[1] + 96); + return (0); + } + cs->irq_flags |= IRQF_SHARED; /* cardbus can share */ + } else { + if (cs->hw.teles3.cfg_reg) { + if (cs->typ == ISDN_CTYPE_COMPAQ_ISA) { + if (!request_region(cs->hw.teles3.cfg_reg, 1, "teles3 cfg")) { + printk(KERN_WARNING + "HiSax: %s config port %x already in use\n", + CardType[card->typ], + cs->hw.teles3.cfg_reg); + return (0); + } + } else { + if (!request_region(cs->hw.teles3.cfg_reg, 8, "teles3 cfg")) { + printk(KERN_WARNING + "HiSax: %s config port %x-%x already in use\n", + CardType[card->typ], + cs->hw.teles3.cfg_reg, + cs->hw.teles3.cfg_reg + 8); + return (0); + } + } + } + if (!request_region(cs->hw.teles3.isac + 32, 32, "HiSax isac")) { + printk(KERN_WARNING + "HiSax: %s isac ports %x-%x already in use\n", + CardType[cs->typ], + cs->hw.teles3.isac + 32, + cs->hw.teles3.isac + 64); + if (cs->hw.teles3.cfg_reg) { + if (cs->typ == ISDN_CTYPE_COMPAQ_ISA) { + release_region(cs->hw.teles3.cfg_reg, 1); + } else { + release_region(cs->hw.teles3.cfg_reg, 8); + } + } + return (0); + } + if (!request_region(cs->hw.teles3.hscx[0] + 32, 32, "HiSax hscx A")) { + printk(KERN_WARNING + "HiSax: %s hscx A ports %x-%x already in use\n", + CardType[cs->typ], + cs->hw.teles3.hscx[0] + 32, + cs->hw.teles3.hscx[0] + 64); + if (cs->hw.teles3.cfg_reg) { + if (cs->typ == ISDN_CTYPE_COMPAQ_ISA) { + release_region(cs->hw.teles3.cfg_reg, 1); + } else { + release_region(cs->hw.teles3.cfg_reg, 8); + } + } + release_ioregs(cs, 1); + return (0); + } + if (!request_region(cs->hw.teles3.hscx[1] + 32, 32, "HiSax hscx B")) { + printk(KERN_WARNING + "HiSax: %s hscx B ports %x-%x already in use\n", + CardType[cs->typ], + cs->hw.teles3.hscx[1] + 32, + cs->hw.teles3.hscx[1] + 64); + if (cs->hw.teles3.cfg_reg) { + if (cs->typ == ISDN_CTYPE_COMPAQ_ISA) { + release_region(cs->hw.teles3.cfg_reg, 1); + } else { + release_region(cs->hw.teles3.cfg_reg, 8); + } + } + release_ioregs(cs, 3); + return (0); + } + } + if ((cs->hw.teles3.cfg_reg) && (cs->typ != ISDN_CTYPE_COMPAQ_ISA)) { + if ((val = bytein(cs->hw.teles3.cfg_reg + 0)) != 0x51) { + printk(KERN_WARNING "Teles: 16.3 Byte at %x is %x\n", + cs->hw.teles3.cfg_reg + 0, val); + release_io_teles3(cs); + return (0); + } + if ((val = bytein(cs->hw.teles3.cfg_reg + 1)) != 0x93) { + printk(KERN_WARNING "Teles: 16.3 Byte at %x is %x\n", + cs->hw.teles3.cfg_reg + 1, val); + release_io_teles3(cs); + return (0); + } + val = bytein(cs->hw.teles3.cfg_reg + 2);/* 0x1e=without AB + * 0x1f=with AB + * 0x1c 16.3 ??? + * 0x39 16.3 1.1 + * 0x38 16.3 1.3 + * 0x46 16.3 with AB + Video (Teles-Vision) + */ + if (val != 0x46 && val != 0x39 && val != 0x38 && val != 0x1c && val != 0x1e && val != 0x1f) { + printk(KERN_WARNING "Teles: 16.3 Byte at %x is %x\n", + cs->hw.teles3.cfg_reg + 2, val); + release_io_teles3(cs); + return (0); + } + } + printk(KERN_INFO + "HiSax: %s config irq:%d isac:0x%X cfg:0x%X\n", + CardType[cs->typ], cs->irq, + cs->hw.teles3.isac + 32, cs->hw.teles3.cfg_reg); + printk(KERN_INFO + "HiSax: hscx A:0x%X hscx B:0x%X\n", + cs->hw.teles3.hscx[0] + 32, cs->hw.teles3.hscx[1] + 32); + + setup_isac(cs); + if (reset_teles3(cs)) { + printk(KERN_WARNING "Teles3: wrong IRQ\n"); + release_io_teles3(cs); + return (0); + } + cs->readisac = &ReadISAC; + cs->writeisac = &WriteISAC; + cs->readisacfifo = &ReadISACfifo; + cs->writeisacfifo = &WriteISACfifo; + cs->BC_Read_Reg = &ReadHSCX; + cs->BC_Write_Reg = &WriteHSCX; + cs->BC_Send_Data = &hscx_fill_fifo; + cs->cardmsg = &Teles_card_msg; + cs->irq_func = &teles3_interrupt; + ISACVersion(cs, "Teles3:"); + if (HscxVersion(cs, "Teles3:")) { + printk(KERN_WARNING + "Teles3: wrong HSCX versions check IO address\n"); + release_io_teles3(cs); + return (0); + } + return (1); +} diff --git a/drivers/isdn/hisax/teles_cs.c b/drivers/isdn/hisax/teles_cs.c new file mode 100644 index 000000000..b8dd14958 --- /dev/null +++ b/drivers/isdn/hisax/teles_cs.c @@ -0,0 +1,200 @@ +/* $Id: teles_cs.c,v 1.1.2.2 2004/01/25 15:07:06 keil Exp $ */ +/*====================================================================== + + A teles S0 PCMCIA client driver + + Based on skeleton by David Hinds, dhinds@allegro.stanford.edu + Written by Christof Petig, christof.petig@wtal.de + + Also inspired by ELSA PCMCIA driver + by Klaus Lichtenwalder <Lichtenwalder@ACM.org> + + Extensions to new hisax_pcmcia by Karsten Keil + + minor changes to be compatible with kernel 2.4.x + by Jan.Schubert@GMX.li + + ======================================================================*/ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/ptrace.h> +#include <linux/slab.h> +#include <linux/string.h> +#include <linux/timer.h> +#include <linux/ioport.h> +#include <asm/io.h> + +#include <pcmcia/cistpl.h> +#include <pcmcia/cisreg.h> +#include <pcmcia/ds.h> +#include "hisax_cfg.h" + +MODULE_DESCRIPTION("ISDN4Linux: PCMCIA client driver for Teles PCMCIA cards"); +MODULE_AUTHOR("Christof Petig, christof.petig@wtal.de, Karsten Keil, kkeil@suse.de"); +MODULE_LICENSE("GPL"); + + +/*====================================================================*/ + +/* Parameters that can be set with 'insmod' */ + +static int protocol = 2; /* EURO-ISDN Default */ +module_param(protocol, int, 0); + +static int teles_cs_config(struct pcmcia_device *link); +static void teles_cs_release(struct pcmcia_device *link); +static void teles_detach(struct pcmcia_device *p_dev); + +typedef struct local_info_t { + struct pcmcia_device *p_dev; + int busy; + int cardnr; +} local_info_t; + +static int teles_probe(struct pcmcia_device *link) +{ + local_info_t *local; + + dev_dbg(&link->dev, "teles_attach()\n"); + + /* Allocate space for private device-specific data */ + local = kzalloc(sizeof(local_info_t), GFP_KERNEL); + if (!local) return -ENOMEM; + local->cardnr = -1; + + local->p_dev = link; + link->priv = local; + + link->config_flags |= CONF_ENABLE_IRQ | CONF_AUTO_SET_IO; + + return teles_cs_config(link); +} /* teles_attach */ + +static void teles_detach(struct pcmcia_device *link) +{ + local_info_t *info = link->priv; + + dev_dbg(&link->dev, "teles_detach(0x%p)\n", link); + + info->busy = 1; + teles_cs_release(link); + + kfree(info); +} /* teles_detach */ + +static int teles_cs_configcheck(struct pcmcia_device *p_dev, void *priv_data) +{ + int j; + + p_dev->io_lines = 5; + p_dev->resource[0]->end = 96; + p_dev->resource[0]->flags &= IO_DATA_PATH_WIDTH; + p_dev->resource[0]->flags |= IO_DATA_PATH_WIDTH_AUTO; + + if ((p_dev->resource[0]->end) && p_dev->resource[0]->start) { + printk(KERN_INFO "(teles_cs: looks like the 96 model)\n"); + if (!pcmcia_request_io(p_dev)) + return 0; + } else { + printk(KERN_INFO "(teles_cs: looks like the 97 model)\n"); + for (j = 0x2f0; j > 0x100; j -= 0x10) { + p_dev->resource[0]->start = j; + if (!pcmcia_request_io(p_dev)) + return 0; + } + } + return -ENODEV; +} + +static int teles_cs_config(struct pcmcia_device *link) +{ + int i; + IsdnCard_t icard; + + dev_dbg(&link->dev, "teles_config(0x%p)\n", link); + + i = pcmcia_loop_config(link, teles_cs_configcheck, NULL); + if (i != 0) + goto cs_failed; + + if (!link->irq) + goto cs_failed; + + i = pcmcia_enable_device(link); + if (i != 0) + goto cs_failed; + + icard.para[0] = link->irq; + icard.para[1] = link->resource[0]->start; + icard.protocol = protocol; + icard.typ = ISDN_CTYPE_TELESPCMCIA; + + i = hisax_init_pcmcia(link, &(((local_info_t *)link->priv)->busy), &icard); + if (i < 0) { + printk(KERN_ERR "teles_cs: failed to initialize Teles PCMCIA %d at i/o %#x\n", + i, (unsigned int) link->resource[0]->start); + teles_cs_release(link); + return -ENODEV; + } + + ((local_info_t *)link->priv)->cardnr = i; + return 0; + +cs_failed: + teles_cs_release(link); + return -ENODEV; +} /* teles_cs_config */ + +static void teles_cs_release(struct pcmcia_device *link) +{ + local_info_t *local = link->priv; + + dev_dbg(&link->dev, "teles_cs_release(0x%p)\n", link); + + if (local) { + if (local->cardnr >= 0) { + /* no unregister function with hisax */ + HiSax_closecard(local->cardnr); + } + } + + pcmcia_disable_device(link); +} /* teles_cs_release */ + +static int teles_suspend(struct pcmcia_device *link) +{ + local_info_t *dev = link->priv; + + dev->busy = 1; + + return 0; +} + +static int teles_resume(struct pcmcia_device *link) +{ + local_info_t *dev = link->priv; + + dev->busy = 0; + + return 0; +} + + +static const struct pcmcia_device_id teles_ids[] = { + PCMCIA_DEVICE_PROD_ID12("TELES", "S0/PC", 0x67b50eae, 0xe9e70119), + PCMCIA_DEVICE_NULL, +}; +MODULE_DEVICE_TABLE(pcmcia, teles_ids); + +static struct pcmcia_driver teles_cs_driver = { + .owner = THIS_MODULE, + .name = "teles_cs", + .probe = teles_probe, + .remove = teles_detach, + .id_table = teles_ids, + .suspend = teles_suspend, + .resume = teles_resume, +}; +module_pcmcia_driver(teles_cs_driver); diff --git a/drivers/isdn/hisax/telespci.c b/drivers/isdn/hisax/telespci.c new file mode 100644 index 000000000..33eeb4602 --- /dev/null +++ b/drivers/isdn/hisax/telespci.c @@ -0,0 +1,349 @@ +/* $Id: telespci.c,v 2.23.2.3 2004/01/13 14:31:26 keil Exp $ + * + * low level stuff for Teles PCI isdn cards + * + * Author Ton van Rosmalen + * Karsten Keil + * Copyright by Ton van Rosmalen + * by Karsten Keil <keil@isdn4linux.de> + * + * This software may be used and distributed according to the terms + * of the GNU General Public License, incorporated herein by reference. + * + */ + +#include <linux/init.h> +#include "hisax.h" +#include "isac.h" +#include "hscx.h" +#include "isdnl1.h" +#include <linux/pci.h> + +static const char *telespci_revision = "$Revision: 2.23.2.3 $"; + +#define ZORAN_PO_RQ_PEN 0x02000000 +#define ZORAN_PO_WR 0x00800000 +#define ZORAN_PO_GID0 0x00000000 +#define ZORAN_PO_GID1 0x00100000 +#define ZORAN_PO_GREG0 0x00000000 +#define ZORAN_PO_GREG1 0x00010000 +#define ZORAN_PO_DMASK 0xFF + +#define WRITE_ADDR_ISAC (ZORAN_PO_WR | ZORAN_PO_GID0 | ZORAN_PO_GREG0) +#define READ_DATA_ISAC (ZORAN_PO_GID0 | ZORAN_PO_GREG1) +#define WRITE_DATA_ISAC (ZORAN_PO_WR | ZORAN_PO_GID0 | ZORAN_PO_GREG1) +#define WRITE_ADDR_HSCX (ZORAN_PO_WR | ZORAN_PO_GID1 | ZORAN_PO_GREG0) +#define READ_DATA_HSCX (ZORAN_PO_GID1 | ZORAN_PO_GREG1) +#define WRITE_DATA_HSCX (ZORAN_PO_WR | ZORAN_PO_GID1 | ZORAN_PO_GREG1) + +#define ZORAN_WAIT_NOBUSY do { \ + portdata = readl(adr + 0x200); \ + } while (portdata & ZORAN_PO_RQ_PEN) + +static inline u_char +readisac(void __iomem *adr, u_char off) +{ + register unsigned int portdata; + + ZORAN_WAIT_NOBUSY; + + /* set address for ISAC */ + writel(WRITE_ADDR_ISAC | off, adr + 0x200); + ZORAN_WAIT_NOBUSY; + + /* read data from ISAC */ + writel(READ_DATA_ISAC, adr + 0x200); + ZORAN_WAIT_NOBUSY; + return ((u_char)(portdata & ZORAN_PO_DMASK)); +} + +static inline void +writeisac(void __iomem *adr, u_char off, u_char data) +{ + register unsigned int portdata; + + ZORAN_WAIT_NOBUSY; + + /* set address for ISAC */ + writel(WRITE_ADDR_ISAC | off, adr + 0x200); + ZORAN_WAIT_NOBUSY; + + /* write data to ISAC */ + writel(WRITE_DATA_ISAC | data, adr + 0x200); + ZORAN_WAIT_NOBUSY; +} + +static inline u_char +readhscx(void __iomem *adr, int hscx, u_char off) +{ + register unsigned int portdata; + + ZORAN_WAIT_NOBUSY; + /* set address for HSCX */ + writel(WRITE_ADDR_HSCX | ((hscx ? 0x40 : 0) + off), adr + 0x200); + ZORAN_WAIT_NOBUSY; + + /* read data from HSCX */ + writel(READ_DATA_HSCX, adr + 0x200); + ZORAN_WAIT_NOBUSY; + return ((u_char)(portdata & ZORAN_PO_DMASK)); +} + +static inline void +writehscx(void __iomem *adr, int hscx, u_char off, u_char data) +{ + register unsigned int portdata; + + ZORAN_WAIT_NOBUSY; + /* set address for HSCX */ + writel(WRITE_ADDR_HSCX | ((hscx ? 0x40 : 0) + off), adr + 0x200); + ZORAN_WAIT_NOBUSY; + + /* write data to HSCX */ + writel(WRITE_DATA_HSCX | data, adr + 0x200); + ZORAN_WAIT_NOBUSY; +} + +static inline void +read_fifo_isac(void __iomem *adr, u_char *data, int size) +{ + register unsigned int portdata; + register int i; + + ZORAN_WAIT_NOBUSY; + /* read data from ISAC */ + for (i = 0; i < size; i++) { + /* set address for ISAC fifo */ + writel(WRITE_ADDR_ISAC | 0x1E, adr + 0x200); + ZORAN_WAIT_NOBUSY; + writel(READ_DATA_ISAC, adr + 0x200); + ZORAN_WAIT_NOBUSY; + data[i] = (u_char)(portdata & ZORAN_PO_DMASK); + } +} + +static void +write_fifo_isac(void __iomem *adr, u_char *data, int size) +{ + register unsigned int portdata; + register int i; + + ZORAN_WAIT_NOBUSY; + /* write data to ISAC */ + for (i = 0; i < size; i++) { + /* set address for ISAC fifo */ + writel(WRITE_ADDR_ISAC | 0x1E, adr + 0x200); + ZORAN_WAIT_NOBUSY; + writel(WRITE_DATA_ISAC | data[i], adr + 0x200); + ZORAN_WAIT_NOBUSY; + } +} + +static inline void +read_fifo_hscx(void __iomem *adr, int hscx, u_char *data, int size) +{ + register unsigned int portdata; + register int i; + + ZORAN_WAIT_NOBUSY; + /* read data from HSCX */ + for (i = 0; i < size; i++) { + /* set address for HSCX fifo */ + writel(WRITE_ADDR_HSCX | (hscx ? 0x5F : 0x1F), adr + 0x200); + ZORAN_WAIT_NOBUSY; + writel(READ_DATA_HSCX, adr + 0x200); + ZORAN_WAIT_NOBUSY; + data[i] = (u_char) (portdata & ZORAN_PO_DMASK); + } +} + +static inline void +write_fifo_hscx(void __iomem *adr, int hscx, u_char *data, int size) +{ + unsigned int portdata; + register int i; + + ZORAN_WAIT_NOBUSY; + /* write data to HSCX */ + for (i = 0; i < size; i++) { + /* set address for HSCX fifo */ + writel(WRITE_ADDR_HSCX | (hscx ? 0x5F : 0x1F), adr + 0x200); + ZORAN_WAIT_NOBUSY; + writel(WRITE_DATA_HSCX | data[i], adr + 0x200); + ZORAN_WAIT_NOBUSY; + udelay(10); + } +} + +/* Interface functions */ + +static u_char +ReadISAC(struct IsdnCardState *cs, u_char offset) +{ + return (readisac(cs->hw.teles0.membase, offset)); +} + +static void +WriteISAC(struct IsdnCardState *cs, u_char offset, u_char value) +{ + writeisac(cs->hw.teles0.membase, offset, value); +} + +static void +ReadISACfifo(struct IsdnCardState *cs, u_char *data, int size) +{ + read_fifo_isac(cs->hw.teles0.membase, data, size); +} + +static void +WriteISACfifo(struct IsdnCardState *cs, u_char *data, int size) +{ + write_fifo_isac(cs->hw.teles0.membase, data, size); +} + +static u_char +ReadHSCX(struct IsdnCardState *cs, int hscx, u_char offset) +{ + return (readhscx(cs->hw.teles0.membase, hscx, offset)); +} + +static void +WriteHSCX(struct IsdnCardState *cs, int hscx, u_char offset, u_char value) +{ + writehscx(cs->hw.teles0.membase, hscx, offset, value); +} + +/* + * fast interrupt HSCX stuff goes here + */ + +#define READHSCX(cs, nr, reg) readhscx(cs->hw.teles0.membase, nr, reg) +#define WRITEHSCX(cs, nr, reg, data) writehscx(cs->hw.teles0.membase, nr, reg, data) +#define READHSCXFIFO(cs, nr, ptr, cnt) read_fifo_hscx(cs->hw.teles0.membase, nr, ptr, cnt) +#define WRITEHSCXFIFO(cs, nr, ptr, cnt) write_fifo_hscx(cs->hw.teles0.membase, nr, ptr, cnt) + +#include "hscx_irq.c" + +static irqreturn_t +telespci_interrupt(int intno, void *dev_id) +{ + struct IsdnCardState *cs = dev_id; + u_char hval, ival; + u_long flags; + + spin_lock_irqsave(&cs->lock, flags); + hval = readhscx(cs->hw.teles0.membase, 1, HSCX_ISTA); + if (hval) + hscx_int_main(cs, hval); + ival = readisac(cs->hw.teles0.membase, ISAC_ISTA); + if ((hval | ival) == 0) { + spin_unlock_irqrestore(&cs->lock, flags); + return IRQ_NONE; + } + if (ival) + isac_interrupt(cs, ival); + /* Clear interrupt register for Zoran PCI controller */ + writel(0x70000000, cs->hw.teles0.membase + 0x3C); + + writehscx(cs->hw.teles0.membase, 0, HSCX_MASK, 0xFF); + writehscx(cs->hw.teles0.membase, 1, HSCX_MASK, 0xFF); + writeisac(cs->hw.teles0.membase, ISAC_MASK, 0xFF); + writeisac(cs->hw.teles0.membase, ISAC_MASK, 0x0); + writehscx(cs->hw.teles0.membase, 0, HSCX_MASK, 0x0); + writehscx(cs->hw.teles0.membase, 1, HSCX_MASK, 0x0); + spin_unlock_irqrestore(&cs->lock, flags); + return IRQ_HANDLED; +} + +static void +release_io_telespci(struct IsdnCardState *cs) +{ + iounmap(cs->hw.teles0.membase); +} + +static int +TelesPCI_card_msg(struct IsdnCardState *cs, int mt, void *arg) +{ + u_long flags; + + switch (mt) { + case CARD_RESET: + return (0); + case CARD_RELEASE: + release_io_telespci(cs); + return (0); + case CARD_INIT: + spin_lock_irqsave(&cs->lock, flags); + inithscxisac(cs, 3); + spin_unlock_irqrestore(&cs->lock, flags); + return (0); + case CARD_TEST: + return (0); + } + return (0); +} + +static struct pci_dev *dev_tel = NULL; + +int setup_telespci(struct IsdnCard *card) +{ + struct IsdnCardState *cs = card->cs; + char tmp[64]; + + strcpy(tmp, telespci_revision); + printk(KERN_INFO "HiSax: Teles/PCI driver Rev. %s\n", HiSax_getrev(tmp)); + if (cs->typ != ISDN_CTYPE_TELESPCI) + return (0); + + if ((dev_tel = hisax_find_pci_device(PCI_VENDOR_ID_ZORAN, PCI_DEVICE_ID_ZORAN_36120, dev_tel))) { + if (pci_enable_device(dev_tel)) + return (0); + cs->irq = dev_tel->irq; + if (!cs->irq) { + printk(KERN_WARNING "Teles: No IRQ for PCI card found\n"); + return (0); + } + cs->hw.teles0.membase = ioremap(pci_resource_start(dev_tel, 0), + PAGE_SIZE); + printk(KERN_INFO "Found: Zoran, base-address: 0x%llx, irq: 0x%x\n", + (unsigned long long)pci_resource_start(dev_tel, 0), + dev_tel->irq); + } else { + printk(KERN_WARNING "TelesPCI: No PCI card found\n"); + return (0); + } + + /* Initialize Zoran PCI controller */ + writel(0x00000000, cs->hw.teles0.membase + 0x28); + writel(0x01000000, cs->hw.teles0.membase + 0x28); + writel(0x01000000, cs->hw.teles0.membase + 0x28); + writel(0x7BFFFFFF, cs->hw.teles0.membase + 0x2C); + writel(0x70000000, cs->hw.teles0.membase + 0x3C); + writel(0x61000000, cs->hw.teles0.membase + 0x40); + /* writel(0x00800000, cs->hw.teles0.membase + 0x200); */ + + printk(KERN_INFO + "HiSax: Teles PCI config irq:%d mem:%p\n", + cs->irq, + cs->hw.teles0.membase); + + setup_isac(cs); + cs->readisac = &ReadISAC; + cs->writeisac = &WriteISAC; + cs->readisacfifo = &ReadISACfifo; + cs->writeisacfifo = &WriteISACfifo; + cs->BC_Read_Reg = &ReadHSCX; + cs->BC_Write_Reg = &WriteHSCX; + cs->BC_Send_Data = &hscx_fill_fifo; + cs->cardmsg = &TelesPCI_card_msg; + cs->irq_func = &telespci_interrupt; + cs->irq_flags |= IRQF_SHARED; + ISACVersion(cs, "TelesPCI:"); + if (HscxVersion(cs, "TelesPCI:")) { + printk(KERN_WARNING + "TelesPCI: wrong HSCX versions check IO/MEM addresses\n"); + release_io_telespci(cs); + return (0); + } + return (1); +} diff --git a/drivers/isdn/hisax/w6692.c b/drivers/isdn/hisax/w6692.c new file mode 100644 index 000000000..c4be1644f --- /dev/null +++ b/drivers/isdn/hisax/w6692.c @@ -0,0 +1,1085 @@ +/* $Id: w6692.c,v 1.18.2.4 2004/02/11 13:21:34 keil Exp $ + * + * Winbond W6692 specific routines + * + * Author Petr Novak + * Copyright by Petr Novak <petr.novak@i.cz> + * + * This software may be used and distributed according to the terms + * of the GNU General Public License, incorporated herein by reference. + * + */ + +#include <linux/init.h> +#include "hisax.h" +#include "w6692.h" +#include "isdnl1.h" +#include <linux/interrupt.h> +#include <linux/pci.h> +#include <linux/slab.h> + +/* table entry in the PCI devices list */ +typedef struct { + int vendor_id; + int device_id; + char *vendor_name; + char *card_name; +} PCI_ENTRY; + +static const PCI_ENTRY id_list[] = +{ + {PCI_VENDOR_ID_WINBOND2, PCI_DEVICE_ID_WINBOND2_6692, "Winbond", "W6692"}, + {PCI_VENDOR_ID_DYNALINK, PCI_DEVICE_ID_DYNALINK_IS64PH, "Dynalink/AsusCom", "IS64PH"}, + {0, 0, "U.S.Robotics", "ISDN PCI Card TA"} +}; + +#define W6692_SV_USR 0x16ec +#define W6692_SD_USR 0x3409 +#define W6692_WINBOND 0 +#define W6692_DYNALINK 1 +#define W6692_USR 2 + +static const char *w6692_revision = "$Revision: 1.18.2.4 $"; + +#define DBUSY_TIMER_VALUE 80 + +static char *W6692Ver[] = +{"W6692 V00", "W6692 V01", "W6692 V10", + "W6692 V11"}; + +static void +W6692Version(struct IsdnCardState *cs, char *s) +{ + int val; + + val = cs->readW6692(cs, W_D_RBCH); + printk(KERN_INFO "%s Winbond W6692 version (%x): %s\n", s, val, W6692Ver[(val >> 6) & 3]); +} + +static void +ph_command(struct IsdnCardState *cs, unsigned int command) +{ + if (cs->debug & L1_DEB_ISAC) + debugl1(cs, "ph_command %x", command); + cs->writeisac(cs, W_CIX, command); +} + + +static void +W6692_new_ph(struct IsdnCardState *cs) +{ + switch (cs->dc.w6692.ph_state) { + case (W_L1CMD_RST): + ph_command(cs, W_L1CMD_DRC); + l1_msg(cs, HW_RESET | INDICATION, NULL); + /* fallthru */ + case (W_L1IND_CD): + l1_msg(cs, HW_DEACTIVATE | CONFIRM, NULL); + break; + case (W_L1IND_DRD): + l1_msg(cs, HW_DEACTIVATE | INDICATION, NULL); + break; + case (W_L1IND_CE): + l1_msg(cs, HW_POWERUP | CONFIRM, NULL); + break; + case (W_L1IND_LD): + l1_msg(cs, HW_RSYNC | INDICATION, NULL); + break; + case (W_L1IND_ARD): + l1_msg(cs, HW_INFO2 | INDICATION, NULL); + break; + case (W_L1IND_AI8): + l1_msg(cs, HW_INFO4_P8 | INDICATION, NULL); + break; + case (W_L1IND_AI10): + l1_msg(cs, HW_INFO4_P10 | INDICATION, NULL); + break; + default: + break; + } +} + +static void +W6692_bh(struct work_struct *work) +{ + struct IsdnCardState *cs = + container_of(work, struct IsdnCardState, tqueue); + struct PStack *stptr; + + if (test_and_clear_bit(D_CLEARBUSY, &cs->event)) { + if (cs->debug) + debugl1(cs, "D-Channel Busy cleared"); + stptr = cs->stlist; + while (stptr != NULL) { + stptr->l1.l1l2(stptr, PH_PAUSE | CONFIRM, NULL); + stptr = stptr->next; + } + } + if (test_and_clear_bit(D_L1STATECHANGE, &cs->event)) + W6692_new_ph(cs); + if (test_and_clear_bit(D_RCVBUFREADY, &cs->event)) + DChannel_proc_rcv(cs); + if (test_and_clear_bit(D_XMTBUFREADY, &cs->event)) + DChannel_proc_xmt(cs); +/* + if (test_and_clear_bit(D_RX_MON1, &cs->event)) + arcofi_fsm(cs, ARCOFI_RX_END, NULL); + if (test_and_clear_bit(D_TX_MON1, &cs->event)) + arcofi_fsm(cs, ARCOFI_TX_END, NULL); +*/ +} + +static void +W6692_empty_fifo(struct IsdnCardState *cs, int count) +{ + u_char *ptr; + + if ((cs->debug & L1_DEB_ISAC) && !(cs->debug & L1_DEB_ISAC_FIFO)) + debugl1(cs, "W6692_empty_fifo"); + + if ((cs->rcvidx + count) >= MAX_DFRAME_LEN_L1) { + if (cs->debug & L1_DEB_WARN) + debugl1(cs, "W6692_empty_fifo overrun %d", + cs->rcvidx + count); + cs->writeW6692(cs, W_D_CMDR, W_D_CMDR_RACK); + cs->rcvidx = 0; + return; + } + ptr = cs->rcvbuf + cs->rcvidx; + cs->rcvidx += count; + cs->readW6692fifo(cs, ptr, count); + cs->writeW6692(cs, W_D_CMDR, W_D_CMDR_RACK); + if (cs->debug & L1_DEB_ISAC_FIFO) { + char *t = cs->dlog; + + t += sprintf(t, "W6692_empty_fifo cnt %d", count); + QuickHex(t, ptr, count); + debugl1(cs, "%s", cs->dlog); + } +} + +static void +W6692_fill_fifo(struct IsdnCardState *cs) +{ + int count, more; + u_char *ptr; + + if ((cs->debug & L1_DEB_ISAC) && !(cs->debug & L1_DEB_ISAC_FIFO)) + debugl1(cs, "W6692_fill_fifo"); + + if (!cs->tx_skb) + return; + + count = cs->tx_skb->len; + if (count <= 0) + return; + + more = 0; + if (count > W_D_FIFO_THRESH) { + more = !0; + count = W_D_FIFO_THRESH; + } + ptr = cs->tx_skb->data; + skb_pull(cs->tx_skb, count); + cs->tx_cnt += count; + cs->writeW6692fifo(cs, ptr, count); + cs->writeW6692(cs, W_D_CMDR, more ? W_D_CMDR_XMS : (W_D_CMDR_XMS | W_D_CMDR_XME)); + if (test_and_set_bit(FLG_DBUSY_TIMER, &cs->HW_Flags)) { + debugl1(cs, "W6692_fill_fifo dbusytimer running"); + del_timer(&cs->dbusytimer); + } + cs->dbusytimer.expires = jiffies + ((DBUSY_TIMER_VALUE * HZ) / 1000); + add_timer(&cs->dbusytimer); + if (cs->debug & L1_DEB_ISAC_FIFO) { + char *t = cs->dlog; + + t += sprintf(t, "W6692_fill_fifo cnt %d", count); + QuickHex(t, ptr, count); + debugl1(cs, "%s", cs->dlog); + } +} + +static void +W6692B_empty_fifo(struct BCState *bcs, int count) +{ + u_char *ptr; + struct IsdnCardState *cs = bcs->cs; + + if ((cs->debug & L1_DEB_HSCX) && !(cs->debug & L1_DEB_HSCX_FIFO)) + debugl1(cs, "W6692B_empty_fifo"); + + if (bcs->hw.w6692.rcvidx + count > HSCX_BUFMAX) { + if (cs->debug & L1_DEB_WARN) + debugl1(cs, "W6692B_empty_fifo: incoming packet too large"); + cs->BC_Write_Reg(cs, bcs->channel, W_B_CMDR, W_B_CMDR_RACK | W_B_CMDR_RACT); + bcs->hw.w6692.rcvidx = 0; + return; + } + ptr = bcs->hw.w6692.rcvbuf + bcs->hw.w6692.rcvidx; + bcs->hw.w6692.rcvidx += count; + READW6692BFIFO(cs, bcs->channel, ptr, count); + cs->BC_Write_Reg(cs, bcs->channel, W_B_CMDR, W_B_CMDR_RACK | W_B_CMDR_RACT); + if (cs->debug & L1_DEB_HSCX_FIFO) { + char *t = bcs->blog; + + t += sprintf(t, "W6692B_empty_fifo %c cnt %d", + bcs->channel + '1', count); + QuickHex(t, ptr, count); + debugl1(cs, "%s", bcs->blog); + } +} + +static void +W6692B_fill_fifo(struct BCState *bcs) +{ + struct IsdnCardState *cs = bcs->cs; + int more, count; + u_char *ptr; + + if (!bcs->tx_skb) + return; + if (bcs->tx_skb->len <= 0) + return; + + more = (bcs->mode == L1_MODE_TRANS) ? 1 : 0; + if (bcs->tx_skb->len > W_B_FIFO_THRESH) { + more = 1; + count = W_B_FIFO_THRESH; + } else + count = bcs->tx_skb->len; + + if ((cs->debug & L1_DEB_HSCX) && !(cs->debug & L1_DEB_HSCX_FIFO)) + debugl1(cs, "W6692B_fill_fifo%s%d", (more ? " " : " last "), count); + + ptr = bcs->tx_skb->data; + skb_pull(bcs->tx_skb, count); + bcs->tx_cnt -= count; + bcs->hw.w6692.count += count; + WRITEW6692BFIFO(cs, bcs->channel, ptr, count); + cs->BC_Write_Reg(cs, bcs->channel, W_B_CMDR, W_B_CMDR_RACT | W_B_CMDR_XMS | (more ? 0 : W_B_CMDR_XME)); + if (cs->debug & L1_DEB_HSCX_FIFO) { + char *t = bcs->blog; + + t += sprintf(t, "W6692B_fill_fifo %c cnt %d", + bcs->channel + '1', count); + QuickHex(t, ptr, count); + debugl1(cs, "%s", bcs->blog); + } +} + +static void +W6692B_interrupt(struct IsdnCardState *cs, u_char bchan) +{ + u_char val; + u_char r; + struct BCState *bcs; + struct sk_buff *skb; + int count; + + bcs = (cs->bcs->channel == bchan) ? cs->bcs : (cs->bcs + 1); + val = cs->BC_Read_Reg(cs, bchan, W_B_EXIR); + debugl1(cs, "W6692B chan %d B_EXIR 0x%02X", bchan, val); + + if (!test_bit(BC_FLG_INIT, &bcs->Flag)) { + debugl1(cs, "W6692B not INIT yet"); + return; + } + if (val & W_B_EXI_RME) { /* RME */ + r = cs->BC_Read_Reg(cs, bchan, W_B_STAR); + if (r & (W_B_STAR_RDOV | W_B_STAR_CRCE | W_B_STAR_RMB)) { + if (cs->debug & L1_DEB_WARN) + debugl1(cs, "W6692 B STAR %x", r); + if ((r & W_B_STAR_RDOV) && bcs->mode) + if (cs->debug & L1_DEB_WARN) + debugl1(cs, "W6692 B RDOV mode=%d", + bcs->mode); + if (r & W_B_STAR_CRCE) + if (cs->debug & L1_DEB_WARN) + debugl1(cs, "W6692 B CRC error"); + cs->BC_Write_Reg(cs, bchan, W_B_CMDR, W_B_CMDR_RACK | W_B_CMDR_RRST | W_B_CMDR_RACT); + } else { + count = cs->BC_Read_Reg(cs, bchan, W_B_RBCL) & (W_B_FIFO_THRESH - 1); + if (count == 0) + count = W_B_FIFO_THRESH; + W6692B_empty_fifo(bcs, count); + if ((count = bcs->hw.w6692.rcvidx) > 0) { + if (cs->debug & L1_DEB_HSCX_FIFO) + debugl1(cs, "W6692 Bchan Frame %d", count); + if (!(skb = dev_alloc_skb(count))) + printk(KERN_WARNING "W6692: Bchan receive out of memory\n"); + else { + skb_put_data(skb, + bcs->hw.w6692.rcvbuf, + count); + skb_queue_tail(&bcs->rqueue, skb); + } + } + } + bcs->hw.w6692.rcvidx = 0; + schedule_event(bcs, B_RCVBUFREADY); + } + if (val & W_B_EXI_RMR) { /* RMR */ + W6692B_empty_fifo(bcs, W_B_FIFO_THRESH); + r = cs->BC_Read_Reg(cs, bchan, W_B_STAR); + if (r & W_B_STAR_RDOV) { + if (cs->debug & L1_DEB_WARN) + debugl1(cs, "W6692 B RDOV(RMR) mode=%d", bcs->mode); + cs->BC_Write_Reg(cs, bchan, W_B_CMDR, W_B_CMDR_RACK | W_B_CMDR_RRST | W_B_CMDR_RACT); + if (bcs->mode != L1_MODE_TRANS) + bcs->hw.w6692.rcvidx = 0; + } + if (bcs->mode == L1_MODE_TRANS) { + /* receive audio data */ + if (!(skb = dev_alloc_skb(W_B_FIFO_THRESH))) + printk(KERN_WARNING "HiSax: receive out of memory\n"); + else { + skb_put_data(skb, bcs->hw.w6692.rcvbuf, + W_B_FIFO_THRESH); + skb_queue_tail(&bcs->rqueue, skb); + } + bcs->hw.w6692.rcvidx = 0; + schedule_event(bcs, B_RCVBUFREADY); + } + } + if (val & W_B_EXI_XDUN) { /* XDUN */ + cs->BC_Write_Reg(cs, bchan, W_B_CMDR, W_B_CMDR_XRST | W_B_CMDR_RACT); + if (cs->debug & L1_DEB_WARN) + debugl1(cs, "W6692 B EXIR %x Lost TX", val); + if (bcs->mode == 1) + W6692B_fill_fifo(bcs); + else { + /* Here we lost an TX interrupt, so + * restart transmitting the whole frame. + */ + if (bcs->tx_skb) { + skb_push(bcs->tx_skb, bcs->hw.w6692.count); + bcs->tx_cnt += bcs->hw.w6692.count; + bcs->hw.w6692.count = 0; + } + } + return; + } + if (val & W_B_EXI_XFR) { /* XFR */ + r = cs->BC_Read_Reg(cs, bchan, W_B_STAR); + if (r & W_B_STAR_XDOW) { + if (cs->debug & L1_DEB_WARN) + debugl1(cs, "W6692 B STAR %x XDOW", r); + cs->BC_Write_Reg(cs, bchan, W_B_CMDR, W_B_CMDR_XRST | W_B_CMDR_RACT); + if (bcs->tx_skb && (bcs->mode != 1)) { + skb_push(bcs->tx_skb, bcs->hw.w6692.count); + bcs->tx_cnt += bcs->hw.w6692.count; + bcs->hw.w6692.count = 0; + } + } + if (bcs->tx_skb) { + if (bcs->tx_skb->len) { + W6692B_fill_fifo(bcs); + return; + } else { + if (test_bit(FLG_LLI_L1WAKEUP, &bcs->st->lli.flag) && + (PACKET_NOACK != bcs->tx_skb->pkt_type)) { + u_long flags; + spin_lock_irqsave(&bcs->aclock, flags); + bcs->ackcnt += bcs->hw.w6692.count; + spin_unlock_irqrestore(&bcs->aclock, flags); + schedule_event(bcs, B_ACKPENDING); + } + dev_kfree_skb_irq(bcs->tx_skb); + bcs->hw.w6692.count = 0; + bcs->tx_skb = NULL; + } + } + if ((bcs->tx_skb = skb_dequeue(&bcs->squeue))) { + bcs->hw.w6692.count = 0; + test_and_set_bit(BC_FLG_BUSY, &bcs->Flag); + W6692B_fill_fifo(bcs); + } else { + test_and_clear_bit(BC_FLG_BUSY, &bcs->Flag); + schedule_event(bcs, B_XMTBUFREADY); + } + } +} + +static irqreturn_t +W6692_interrupt(int intno, void *dev_id) +{ + struct IsdnCardState *cs = dev_id; + u_char val, exval, v1; + struct sk_buff *skb; + u_int count; + u_long flags; + int icnt = 5; + + spin_lock_irqsave(&cs->lock, flags); + val = cs->readW6692(cs, W_ISTA); + if (!val) { + spin_unlock_irqrestore(&cs->lock, flags); + return IRQ_NONE; + } +StartW6692: + if (cs->debug & L1_DEB_ISAC) + debugl1(cs, "W6692 ISTA %x", val); + + if (val & W_INT_D_RME) { /* RME */ + exval = cs->readW6692(cs, W_D_RSTA); + if (exval & (W_D_RSTA_RDOV | W_D_RSTA_CRCE | W_D_RSTA_RMB)) { + if (exval & W_D_RSTA_RDOV) + if (cs->debug & L1_DEB_WARN) + debugl1(cs, "W6692 RDOV"); + if (exval & W_D_RSTA_CRCE) + if (cs->debug & L1_DEB_WARN) + debugl1(cs, "W6692 D-channel CRC error"); + if (exval & W_D_RSTA_RMB) + if (cs->debug & L1_DEB_WARN) + debugl1(cs, "W6692 D-channel ABORT"); + cs->writeW6692(cs, W_D_CMDR, W_D_CMDR_RACK | W_D_CMDR_RRST); + } else { + count = cs->readW6692(cs, W_D_RBCL) & (W_D_FIFO_THRESH - 1); + if (count == 0) + count = W_D_FIFO_THRESH; + W6692_empty_fifo(cs, count); + if ((count = cs->rcvidx) > 0) { + cs->rcvidx = 0; + if (!(skb = alloc_skb(count, GFP_ATOMIC))) + printk(KERN_WARNING "HiSax: D receive out of memory\n"); + else { + skb_put_data(skb, cs->rcvbuf, count); + skb_queue_tail(&cs->rq, skb); + } + } + } + cs->rcvidx = 0; + schedule_event(cs, D_RCVBUFREADY); + } + if (val & W_INT_D_RMR) { /* RMR */ + W6692_empty_fifo(cs, W_D_FIFO_THRESH); + } + if (val & W_INT_D_XFR) { /* XFR */ + if (test_and_clear_bit(FLG_DBUSY_TIMER, &cs->HW_Flags)) + del_timer(&cs->dbusytimer); + if (test_and_clear_bit(FLG_L1_DBUSY, &cs->HW_Flags)) + schedule_event(cs, D_CLEARBUSY); + if (cs->tx_skb) { + if (cs->tx_skb->len) { + W6692_fill_fifo(cs); + goto afterXFR; + } else { + dev_kfree_skb_irq(cs->tx_skb); + cs->tx_cnt = 0; + cs->tx_skb = NULL; + } + } + if ((cs->tx_skb = skb_dequeue(&cs->sq))) { + cs->tx_cnt = 0; + W6692_fill_fifo(cs); + } else + schedule_event(cs, D_XMTBUFREADY); + } +afterXFR: + if (val & (W_INT_XINT0 | W_INT_XINT1)) { /* XINT0/1 - never */ + if (cs->debug & L1_DEB_ISAC) + debugl1(cs, "W6692 spurious XINT!"); + } + if (val & W_INT_D_EXI) { /* EXI */ + exval = cs->readW6692(cs, W_D_EXIR); + if (cs->debug & L1_DEB_WARN) + debugl1(cs, "W6692 D_EXIR %02x", exval); + if (exval & (W_D_EXI_XDUN | W_D_EXI_XCOL)) { /* Transmit underrun/collision */ + debugl1(cs, "W6692 D-chan underrun/collision"); + printk(KERN_WARNING "HiSax: W6692 XDUN/XCOL\n"); + if (test_and_clear_bit(FLG_DBUSY_TIMER, &cs->HW_Flags)) + del_timer(&cs->dbusytimer); + if (test_and_clear_bit(FLG_L1_DBUSY, &cs->HW_Flags)) + schedule_event(cs, D_CLEARBUSY); + if (cs->tx_skb) { /* Restart frame */ + skb_push(cs->tx_skb, cs->tx_cnt); + cs->tx_cnt = 0; + W6692_fill_fifo(cs); + } else { + printk(KERN_WARNING "HiSax: W6692 XDUN/XCOL no skb\n"); + debugl1(cs, "W6692 XDUN/XCOL no skb"); + cs->writeW6692(cs, W_D_CMDR, W_D_CMDR_XRST); + } + } + if (exval & W_D_EXI_RDOV) { /* RDOV */ + debugl1(cs, "W6692 D-channel RDOV"); + printk(KERN_WARNING "HiSax: W6692 D-RDOV\n"); + cs->writeW6692(cs, W_D_CMDR, W_D_CMDR_RRST); + } + if (exval & W_D_EXI_TIN2) { /* TIN2 - never */ + debugl1(cs, "W6692 spurious TIN2 interrupt"); + } + if (exval & W_D_EXI_MOC) { /* MOC - not supported */ + debugl1(cs, "W6692 spurious MOC interrupt"); + v1 = cs->readW6692(cs, W_MOSR); + debugl1(cs, "W6692 MOSR %02x", v1); + } + if (exval & W_D_EXI_ISC) { /* ISC - Level1 change */ + v1 = cs->readW6692(cs, W_CIR); + if (cs->debug & L1_DEB_ISAC) + debugl1(cs, "W6692 ISC CIR=0x%02X", v1); + if (v1 & W_CIR_ICC) { + cs->dc.w6692.ph_state = v1 & W_CIR_COD_MASK; + if (cs->debug & L1_DEB_ISAC) + debugl1(cs, "ph_state_change %x", cs->dc.w6692.ph_state); + schedule_event(cs, D_L1STATECHANGE); + } + if (v1 & W_CIR_SCC) { + v1 = cs->readW6692(cs, W_SQR); + debugl1(cs, "W6692 SCC SQR=0x%02X", v1); + } + } + if (exval & W_D_EXI_WEXP) { + debugl1(cs, "W6692 spurious WEXP interrupt!"); + } + if (exval & W_D_EXI_TEXP) { + debugl1(cs, "W6692 spurious TEXP interrupt!"); + } + } + if (val & W_INT_B1_EXI) { + debugl1(cs, "W6692 B channel 1 interrupt"); + W6692B_interrupt(cs, 0); + } + if (val & W_INT_B2_EXI) { + debugl1(cs, "W6692 B channel 2 interrupt"); + W6692B_interrupt(cs, 1); + } + val = cs->readW6692(cs, W_ISTA); + if (val && icnt) { + icnt--; + goto StartW6692; + } + if (!icnt) { + printk(KERN_WARNING "W6692 IRQ LOOP\n"); + cs->writeW6692(cs, W_IMASK, 0xff); + } + spin_unlock_irqrestore(&cs->lock, flags); + return IRQ_HANDLED; +} + +static void +W6692_l1hw(struct PStack *st, int pr, void *arg) +{ + struct IsdnCardState *cs = (struct IsdnCardState *) st->l1.hardware; + struct sk_buff *skb = arg; + u_long flags; + int val; + + switch (pr) { + case (PH_DATA | REQUEST): + if (cs->debug & DEB_DLOG_HEX) + LogFrame(cs, skb->data, skb->len); + if (cs->debug & DEB_DLOG_VERBOSE) + dlogframe(cs, skb, 0); + spin_lock_irqsave(&cs->lock, flags); + if (cs->tx_skb) { + skb_queue_tail(&cs->sq, skb); +#ifdef L2FRAME_DEBUG /* psa */ + if (cs->debug & L1_DEB_LAPD) + Logl2Frame(cs, skb, "PH_DATA Queued", 0); +#endif + } else { + cs->tx_skb = skb; + cs->tx_cnt = 0; +#ifdef L2FRAME_DEBUG /* psa */ + if (cs->debug & L1_DEB_LAPD) + Logl2Frame(cs, skb, "PH_DATA", 0); +#endif + W6692_fill_fifo(cs); + } + spin_unlock_irqrestore(&cs->lock, flags); + break; + case (PH_PULL | INDICATION): + spin_lock_irqsave(&cs->lock, flags); + if (cs->tx_skb) { + if (cs->debug & L1_DEB_WARN) + debugl1(cs, " l2l1 tx_skb exist this shouldn't happen"); + skb_queue_tail(&cs->sq, skb); + spin_unlock_irqrestore(&cs->lock, flags); + break; + } + if (cs->debug & DEB_DLOG_HEX) + LogFrame(cs, skb->data, skb->len); + if (cs->debug & DEB_DLOG_VERBOSE) + dlogframe(cs, skb, 0); + cs->tx_skb = skb; + cs->tx_cnt = 0; +#ifdef L2FRAME_DEBUG /* psa */ + if (cs->debug & L1_DEB_LAPD) + Logl2Frame(cs, skb, "PH_DATA_PULLED", 0); +#endif + W6692_fill_fifo(cs); + spin_unlock_irqrestore(&cs->lock, flags); + break; + case (PH_PULL | REQUEST): +#ifdef L2FRAME_DEBUG /* psa */ + if (cs->debug & L1_DEB_LAPD) + debugl1(cs, "-> PH_REQUEST_PULL"); +#endif + if (!cs->tx_skb) { + test_and_clear_bit(FLG_L1_PULL_REQ, &st->l1.Flags); + st->l1.l1l2(st, PH_PULL | CONFIRM, NULL); + } else + test_and_set_bit(FLG_L1_PULL_REQ, &st->l1.Flags); + break; + case (HW_RESET | REQUEST): + spin_lock_irqsave(&cs->lock, flags); + if ((cs->dc.w6692.ph_state == W_L1IND_DRD)) { + ph_command(cs, W_L1CMD_ECK); + spin_unlock_irqrestore(&cs->lock, flags); + } else { + ph_command(cs, W_L1CMD_RST); + cs->dc.w6692.ph_state = W_L1CMD_RST; + spin_unlock_irqrestore(&cs->lock, flags); + W6692_new_ph(cs); + } + break; + case (HW_ENABLE | REQUEST): + spin_lock_irqsave(&cs->lock, flags); + ph_command(cs, W_L1CMD_ECK); + spin_unlock_irqrestore(&cs->lock, flags); + break; + case (HW_INFO3 | REQUEST): + spin_lock_irqsave(&cs->lock, flags); + ph_command(cs, W_L1CMD_AR8); + spin_unlock_irqrestore(&cs->lock, flags); + break; + case (HW_TESTLOOP | REQUEST): + val = 0; + if (1 & (long) arg) + val |= 0x0c; + if (2 & (long) arg) + val |= 0x3; + /* !!! not implemented yet */ + break; + case (HW_DEACTIVATE | RESPONSE): + skb_queue_purge(&cs->rq); + skb_queue_purge(&cs->sq); + if (cs->tx_skb) { + dev_kfree_skb_any(cs->tx_skb); + cs->tx_skb = NULL; + } + if (test_and_clear_bit(FLG_DBUSY_TIMER, &cs->HW_Flags)) + del_timer(&cs->dbusytimer); + if (test_and_clear_bit(FLG_L1_DBUSY, &cs->HW_Flags)) + schedule_event(cs, D_CLEARBUSY); + break; + default: + if (cs->debug & L1_DEB_WARN) + debugl1(cs, "W6692_l1hw unknown %04x", pr); + break; + } +} + +static void +setstack_W6692(struct PStack *st, struct IsdnCardState *cs) +{ + st->l1.l1hw = W6692_l1hw; +} + +static void +DC_Close_W6692(struct IsdnCardState *cs) +{ +} + +static void +dbusy_timer_handler(struct timer_list *t) +{ + struct IsdnCardState *cs = from_timer(cs, t, dbusytimer); + struct PStack *stptr; + int rbch, star; + u_long flags; + + spin_lock_irqsave(&cs->lock, flags); + if (test_bit(FLG_DBUSY_TIMER, &cs->HW_Flags)) { + rbch = cs->readW6692(cs, W_D_RBCH); + star = cs->readW6692(cs, W_D_STAR); + if (cs->debug) + debugl1(cs, "D-Channel Busy D_RBCH %02x D_STAR %02x", + rbch, star); + if (star & W_D_STAR_XBZ) { /* D-Channel Busy */ + test_and_set_bit(FLG_L1_DBUSY, &cs->HW_Flags); + stptr = cs->stlist; + while (stptr != NULL) { + stptr->l1.l1l2(stptr, PH_PAUSE | INDICATION, NULL); + stptr = stptr->next; + } + } else { + /* discard frame; reset transceiver */ + test_and_clear_bit(FLG_DBUSY_TIMER, &cs->HW_Flags); + if (cs->tx_skb) { + dev_kfree_skb_any(cs->tx_skb); + cs->tx_cnt = 0; + cs->tx_skb = NULL; + } else { + printk(KERN_WARNING "HiSax: W6692 D-Channel Busy no skb\n"); + debugl1(cs, "D-Channel Busy no skb"); + } + cs->writeW6692(cs, W_D_CMDR, W_D_CMDR_XRST); /* Transmitter reset */ + spin_unlock_irqrestore(&cs->lock, flags); + cs->irq_func(cs->irq, cs); + return; + } + } + spin_unlock_irqrestore(&cs->lock, flags); +} + +static void +W6692Bmode(struct BCState *bcs, int mode, int bchan) +{ + struct IsdnCardState *cs = bcs->cs; + + if (cs->debug & L1_DEB_HSCX) + debugl1(cs, "w6692 %c mode %d ichan %d", + '1' + bchan, mode, bchan); + bcs->mode = mode; + bcs->channel = bchan; + bcs->hw.w6692.bchan = bchan; + + switch (mode) { + case (L1_MODE_NULL): + cs->BC_Write_Reg(cs, bchan, W_B_MODE, 0); + break; + case (L1_MODE_TRANS): + cs->BC_Write_Reg(cs, bchan, W_B_MODE, W_B_MODE_MMS); + break; + case (L1_MODE_HDLC): + cs->BC_Write_Reg(cs, bchan, W_B_MODE, W_B_MODE_ITF); + cs->BC_Write_Reg(cs, bchan, W_B_ADM1, 0xff); + cs->BC_Write_Reg(cs, bchan, W_B_ADM2, 0xff); + break; + } + if (mode) + cs->BC_Write_Reg(cs, bchan, W_B_CMDR, W_B_CMDR_RRST | + W_B_CMDR_RACT | W_B_CMDR_XRST); + cs->BC_Write_Reg(cs, bchan, W_B_EXIM, 0x00); +} + +static void +W6692_l2l1(struct PStack *st, int pr, void *arg) +{ + struct sk_buff *skb = arg; + struct BCState *bcs = st->l1.bcs; + u_long flags; + + switch (pr) { + case (PH_DATA | REQUEST): + spin_lock_irqsave(&bcs->cs->lock, flags); + if (bcs->tx_skb) { + skb_queue_tail(&bcs->squeue, skb); + } else { + bcs->tx_skb = skb; + test_and_set_bit(BC_FLG_BUSY, &bcs->Flag); + bcs->hw.w6692.count = 0; + bcs->cs->BC_Send_Data(bcs); + } + spin_unlock_irqrestore(&bcs->cs->lock, flags); + break; + case (PH_PULL | INDICATION): + if (bcs->tx_skb) { + printk(KERN_WARNING "W6692_l2l1: this shouldn't happen\n"); + break; + } + spin_lock_irqsave(&bcs->cs->lock, flags); + test_and_set_bit(BC_FLG_BUSY, &bcs->Flag); + bcs->tx_skb = skb; + bcs->hw.w6692.count = 0; + bcs->cs->BC_Send_Data(bcs); + spin_unlock_irqrestore(&bcs->cs->lock, flags); + break; + case (PH_PULL | REQUEST): + if (!bcs->tx_skb) { + test_and_clear_bit(FLG_L1_PULL_REQ, &st->l1.Flags); + st->l1.l1l2(st, PH_PULL | CONFIRM, NULL); + } else + test_and_set_bit(FLG_L1_PULL_REQ, &st->l1.Flags); + break; + case (PH_ACTIVATE | REQUEST): + spin_lock_irqsave(&bcs->cs->lock, flags); + test_and_set_bit(BC_FLG_ACTIV, &bcs->Flag); + W6692Bmode(bcs, st->l1.mode, st->l1.bc); + spin_unlock_irqrestore(&bcs->cs->lock, flags); + l1_msg_b(st, pr, arg); + break; + case (PH_DEACTIVATE | REQUEST): + l1_msg_b(st, pr, arg); + break; + case (PH_DEACTIVATE | CONFIRM): + spin_lock_irqsave(&bcs->cs->lock, flags); + test_and_clear_bit(BC_FLG_ACTIV, &bcs->Flag); + test_and_clear_bit(BC_FLG_BUSY, &bcs->Flag); + W6692Bmode(bcs, 0, st->l1.bc); + spin_unlock_irqrestore(&bcs->cs->lock, flags); + st->l1.l1l2(st, PH_DEACTIVATE | CONFIRM, NULL); + break; + } +} + +static void +close_w6692state(struct BCState *bcs) +{ + W6692Bmode(bcs, 0, bcs->channel); + if (test_and_clear_bit(BC_FLG_INIT, &bcs->Flag)) { + kfree(bcs->hw.w6692.rcvbuf); + bcs->hw.w6692.rcvbuf = NULL; + kfree(bcs->blog); + bcs->blog = NULL; + skb_queue_purge(&bcs->rqueue); + skb_queue_purge(&bcs->squeue); + if (bcs->tx_skb) { + dev_kfree_skb_any(bcs->tx_skb); + bcs->tx_skb = NULL; + test_and_clear_bit(BC_FLG_BUSY, &bcs->Flag); + } + } +} + +static int +open_w6692state(struct IsdnCardState *cs, struct BCState *bcs) +{ + if (!test_and_set_bit(BC_FLG_INIT, &bcs->Flag)) { + if (!(bcs->hw.w6692.rcvbuf = kmalloc(HSCX_BUFMAX, GFP_ATOMIC))) { + printk(KERN_WARNING + "HiSax: No memory for w6692.rcvbuf\n"); + test_and_clear_bit(BC_FLG_INIT, &bcs->Flag); + return (1); + } + if (!(bcs->blog = kmalloc(MAX_BLOG_SPACE, GFP_ATOMIC))) { + printk(KERN_WARNING + "HiSax: No memory for bcs->blog\n"); + test_and_clear_bit(BC_FLG_INIT, &bcs->Flag); + kfree(bcs->hw.w6692.rcvbuf); + bcs->hw.w6692.rcvbuf = NULL; + return (2); + } + skb_queue_head_init(&bcs->rqueue); + skb_queue_head_init(&bcs->squeue); + } + bcs->tx_skb = NULL; + test_and_clear_bit(BC_FLG_BUSY, &bcs->Flag); + bcs->event = 0; + bcs->hw.w6692.rcvidx = 0; + bcs->tx_cnt = 0; + return (0); +} + +static int +setstack_w6692(struct PStack *st, struct BCState *bcs) +{ + bcs->channel = st->l1.bc; + if (open_w6692state(st->l1.hardware, bcs)) + return (-1); + st->l1.bcs = bcs; + st->l2.l2l1 = W6692_l2l1; + setstack_manager(st); + bcs->st = st; + setstack_l1_B(st); + return (0); +} + +static void resetW6692(struct IsdnCardState *cs) +{ + cs->writeW6692(cs, W_D_CTL, W_D_CTL_SRST); + mdelay(10); + cs->writeW6692(cs, W_D_CTL, 0x00); + mdelay(10); + cs->writeW6692(cs, W_IMASK, 0xff); + cs->writeW6692(cs, W_D_SAM, 0xff); + cs->writeW6692(cs, W_D_TAM, 0xff); + cs->writeW6692(cs, W_D_EXIM, 0x00); + cs->writeW6692(cs, W_D_MODE, W_D_MODE_RACT); + cs->writeW6692(cs, W_IMASK, 0x18); + if (cs->subtyp == W6692_USR) { + /* seems that USR implemented some power control features + * Pin 79 is connected to the oscilator circuit so we + * have to handle it here + */ + cs->writeW6692(cs, W_PCTL, 0x80); + cs->writeW6692(cs, W_XDATA, 0x00); + } +} + +static void initW6692(struct IsdnCardState *cs, int part) +{ + if (part & 1) { + cs->setstack_d = setstack_W6692; + cs->DC_Close = DC_Close_W6692; + timer_setup(&cs->dbusytimer, dbusy_timer_handler, 0); + resetW6692(cs); + ph_command(cs, W_L1CMD_RST); + cs->dc.w6692.ph_state = W_L1CMD_RST; + W6692_new_ph(cs); + ph_command(cs, W_L1CMD_ECK); + + cs->bcs[0].BC_SetStack = setstack_w6692; + cs->bcs[1].BC_SetStack = setstack_w6692; + cs->bcs[0].BC_Close = close_w6692state; + cs->bcs[1].BC_Close = close_w6692state; + W6692Bmode(cs->bcs, 0, 0); + W6692Bmode(cs->bcs + 1, 0, 0); + } + if (part & 2) { + /* Reenable all IRQ */ + cs->writeW6692(cs, W_IMASK, 0x18); + cs->writeW6692(cs, W_D_EXIM, 0x00); + cs->BC_Write_Reg(cs, 0, W_B_EXIM, 0x00); + cs->BC_Write_Reg(cs, 1, W_B_EXIM, 0x00); + /* Reset D-chan receiver and transmitter */ + cs->writeW6692(cs, W_D_CMDR, W_D_CMDR_RRST | W_D_CMDR_XRST); + } +} + +/* Interface functions */ + +static u_char +ReadW6692(struct IsdnCardState *cs, u_char offset) +{ + return (inb(cs->hw.w6692.iobase + offset)); +} + +static void +WriteW6692(struct IsdnCardState *cs, u_char offset, u_char value) +{ + outb(value, cs->hw.w6692.iobase + offset); +} + +static void +ReadISACfifo(struct IsdnCardState *cs, u_char *data, int size) +{ + insb(cs->hw.w6692.iobase + W_D_RFIFO, data, size); +} + +static void +WriteISACfifo(struct IsdnCardState *cs, u_char *data, int size) +{ + outsb(cs->hw.w6692.iobase + W_D_XFIFO, data, size); +} + +static u_char +ReadW6692B(struct IsdnCardState *cs, int bchan, u_char offset) +{ + return (inb(cs->hw.w6692.iobase + (bchan ? 0x40 : 0) + offset)); +} + +static void +WriteW6692B(struct IsdnCardState *cs, int bchan, u_char offset, u_char value) +{ + outb(value, cs->hw.w6692.iobase + (bchan ? 0x40 : 0) + offset); +} + +static int +w6692_card_msg(struct IsdnCardState *cs, int mt, void *arg) +{ + switch (mt) { + case CARD_RESET: + resetW6692(cs); + return (0); + case CARD_RELEASE: + cs->writeW6692(cs, W_IMASK, 0xff); + release_region(cs->hw.w6692.iobase, 256); + if (cs->subtyp == W6692_USR) { + cs->writeW6692(cs, W_XDATA, 0x04); + } + return (0); + case CARD_INIT: + initW6692(cs, 3); + return (0); + case CARD_TEST: + return (0); + } + return (0); +} + +static int id_idx; + +static struct pci_dev *dev_w6692 = NULL; + +int setup_w6692(struct IsdnCard *card) +{ + struct IsdnCardState *cs = card->cs; + char tmp[64]; + u_char found = 0; + u_char pci_irq = 0; + u_int pci_ioaddr = 0; + + strcpy(tmp, w6692_revision); + printk(KERN_INFO "HiSax: W6692 driver Rev. %s\n", HiSax_getrev(tmp)); + if (cs->typ != ISDN_CTYPE_W6692) + return (0); + + while (id_list[id_idx].vendor_id) { + dev_w6692 = hisax_find_pci_device(id_list[id_idx].vendor_id, + id_list[id_idx].device_id, + dev_w6692); + if (dev_w6692) { + if (pci_enable_device(dev_w6692)) + continue; + cs->subtyp = id_idx; + break; + } + id_idx++; + } + if (dev_w6692) { + found = 1; + pci_irq = dev_w6692->irq; + /* I think address 0 is allways the configuration area */ + /* and address 1 is the real IO space KKe 03.09.99 */ + pci_ioaddr = pci_resource_start(dev_w6692, 1); + /* USR ISDN PCI card TA need some special handling */ + if (cs->subtyp == W6692_WINBOND) { + if ((W6692_SV_USR == dev_w6692->subsystem_vendor) && + (W6692_SD_USR == dev_w6692->subsystem_device)) { + cs->subtyp = W6692_USR; + } + } + } + if (!found) { + printk(KERN_WARNING "W6692: No PCI card found\n"); + return (0); + } + cs->irq = pci_irq; + if (!cs->irq) { + printk(KERN_WARNING "W6692: No IRQ for PCI card found\n"); + return (0); + } + if (!pci_ioaddr) { + printk(KERN_WARNING "W6692: NO I/O Base Address found\n"); + return (0); + } + cs->hw.w6692.iobase = pci_ioaddr; + printk(KERN_INFO "Found: %s %s, I/O base: 0x%x, irq: %d\n", + id_list[cs->subtyp].vendor_name, id_list[cs->subtyp].card_name, + pci_ioaddr, pci_irq); + if (!request_region(cs->hw.w6692.iobase, 256, id_list[cs->subtyp].card_name)) { + printk(KERN_WARNING + "HiSax: %s I/O ports %x-%x already in use\n", + id_list[cs->subtyp].card_name, + cs->hw.w6692.iobase, + cs->hw.w6692.iobase + 255); + return (0); + } + + printk(KERN_INFO + "HiSax: %s config irq:%d I/O:%x\n", + id_list[cs->subtyp].card_name, cs->irq, + cs->hw.w6692.iobase); + + INIT_WORK(&cs->tqueue, W6692_bh); + cs->readW6692 = &ReadW6692; + cs->writeW6692 = &WriteW6692; + cs->readisacfifo = &ReadISACfifo; + cs->writeisacfifo = &WriteISACfifo; + cs->BC_Read_Reg = &ReadW6692B; + cs->BC_Write_Reg = &WriteW6692B; + cs->BC_Send_Data = &W6692B_fill_fifo; + cs->cardmsg = &w6692_card_msg; + cs->irq_func = &W6692_interrupt; + cs->irq_flags |= IRQF_SHARED; + W6692Version(cs, "W6692:"); + printk(KERN_INFO "W6692 ISTA=0x%X\n", ReadW6692(cs, W_ISTA)); + printk(KERN_INFO "W6692 IMASK=0x%X\n", ReadW6692(cs, W_IMASK)); + printk(KERN_INFO "W6692 D_EXIR=0x%X\n", ReadW6692(cs, W_D_EXIR)); + printk(KERN_INFO "W6692 D_EXIM=0x%X\n", ReadW6692(cs, W_D_EXIM)); + printk(KERN_INFO "W6692 D_RSTA=0x%X\n", ReadW6692(cs, W_D_RSTA)); + return (1); +} diff --git a/drivers/isdn/hisax/w6692.h b/drivers/isdn/hisax/w6692.h new file mode 100644 index 000000000..024b04d33 --- /dev/null +++ b/drivers/isdn/hisax/w6692.h @@ -0,0 +1,184 @@ +/* $Id: w6692.h,v 1.4.2.2 2004/01/12 22:52:29 keil Exp $ + * + * Winbond W6692 specific defines + * + * Author Petr Novak + * Copyright by Petr Novak <petr.novak@i.cz> + * + * This software may be used and distributed according to the terms + * of the GNU General Public License, incorporated herein by reference. + * + */ + +/* map W6692 functions to ISAC functions */ +#define readW6692 readisac +#define writeW6692 writeisac +#define readW6692fifo readisacfifo +#define writeW6692fifo writeisacfifo + +/* B-channel FIFO read/write routines */ + +#define READW6692BFIFO(cs, bchan, ptr, count) \ + insb(cs->hw.w6692.iobase + W_B_RFIFO + (bchan ? 0x40 : 0), ptr, count) + +#define WRITEW6692BFIFO(cs, bchan, ptr, count) \ + outsb(cs->hw.w6692.iobase + W_B_XFIFO + (bchan ? 0x40 : 0), ptr, count) + +/* Specifications of W6692 registers */ + +#define W_D_RFIFO 0x00 /* R */ +#define W_D_XFIFO 0x04 /* W */ +#define W_D_CMDR 0x08 /* W */ +#define W_D_MODE 0x0c /* R/W */ +#define W_D_TIMR 0x10 /* R/W */ +#define W_ISTA 0x14 /* R_clr */ +#define W_IMASK 0x18 /* R/W */ +#define W_D_EXIR 0x1c /* R_clr */ +#define W_D_EXIM 0x20 /* R/W */ +#define W_D_STAR 0x24 /* R */ +#define W_D_RSTA 0x28 /* R */ +#define W_D_SAM 0x2c /* R/W */ +#define W_D_SAP1 0x30 /* R/W */ +#define W_D_SAP2 0x34 /* R/W */ +#define W_D_TAM 0x38 /* R/W */ +#define W_D_TEI1 0x3c /* R/W */ +#define W_D_TEI2 0x40 /* R/W */ +#define W_D_RBCH 0x44 /* R */ +#define W_D_RBCL 0x48 /* R */ +#define W_TIMR2 0x4c /* W */ +#define W_L1_RC 0x50 /* R/W */ +#define W_D_CTL 0x54 /* R/W */ +#define W_CIR 0x58 /* R */ +#define W_CIX 0x5c /* W */ +#define W_SQR 0x60 /* R */ +#define W_SQX 0x64 /* W */ +#define W_PCTL 0x68 /* R/W */ +#define W_MOR 0x6c /* R */ +#define W_MOX 0x70 /* R/W */ +#define W_MOSR 0x74 /* R_clr */ +#define W_MOCR 0x78 /* R/W */ +#define W_GCR 0x7c /* R/W */ + +#define W_B_RFIFO 0x80 /* R */ +#define W_B_XFIFO 0x84 /* W */ +#define W_B_CMDR 0x88 /* W */ +#define W_B_MODE 0x8c /* R/W */ +#define W_B_EXIR 0x90 /* R_clr */ +#define W_B_EXIM 0x94 /* R/W */ +#define W_B_STAR 0x98 /* R */ +#define W_B_ADM1 0x9c /* R/W */ +#define W_B_ADM2 0xa0 /* R/W */ +#define W_B_ADR1 0xa4 /* R/W */ +#define W_B_ADR2 0xa8 /* R/W */ +#define W_B_RBCL 0xac /* R */ +#define W_B_RBCH 0xb0 /* R */ + +#define W_XADDR 0xf4 /* R/W */ +#define W_XDATA 0xf8 /* R/W */ +#define W_EPCTL 0xfc /* W */ + +/* W6692 register bits */ + +#define W_D_CMDR_XRST 0x01 +#define W_D_CMDR_XME 0x02 +#define W_D_CMDR_XMS 0x08 +#define W_D_CMDR_STT 0x10 +#define W_D_CMDR_RRST 0x40 +#define W_D_CMDR_RACK 0x80 + +#define W_D_MODE_RLP 0x01 +#define W_D_MODE_DLP 0x02 +#define W_D_MODE_MFD 0x04 +#define W_D_MODE_TEE 0x08 +#define W_D_MODE_TMS 0x10 +#define W_D_MODE_RACT 0x40 +#define W_D_MODE_MMS 0x80 + +#define W_INT_B2_EXI 0x01 +#define W_INT_B1_EXI 0x02 +#define W_INT_D_EXI 0x04 +#define W_INT_XINT0 0x08 +#define W_INT_XINT1 0x10 +#define W_INT_D_XFR 0x20 +#define W_INT_D_RME 0x40 +#define W_INT_D_RMR 0x80 + +#define W_D_EXI_WEXP 0x01 +#define W_D_EXI_TEXP 0x02 +#define W_D_EXI_ISC 0x04 +#define W_D_EXI_MOC 0x08 +#define W_D_EXI_TIN2 0x10 +#define W_D_EXI_XCOL 0x20 +#define W_D_EXI_XDUN 0x40 +#define W_D_EXI_RDOV 0x80 + +#define W_D_STAR_DRDY 0x10 +#define W_D_STAR_XBZ 0x20 +#define W_D_STAR_XDOW 0x80 + +#define W_D_RSTA_RMB 0x10 +#define W_D_RSTA_CRCE 0x20 +#define W_D_RSTA_RDOV 0x40 + +#define W_D_CTL_SRST 0x20 + +#define W_CIR_SCC 0x80 +#define W_CIR_ICC 0x40 +#define W_CIR_COD_MASK 0x0f + +#define W_B_CMDR_XRST 0x01 +#define W_B_CMDR_XME 0x02 +#define W_B_CMDR_XMS 0x04 +#define W_B_CMDR_RACT 0x20 +#define W_B_CMDR_RRST 0x40 +#define W_B_CMDR_RACK 0x80 + +#define W_B_MODE_FTS0 0x01 +#define W_B_MODE_FTS1 0x02 +#define W_B_MODE_SW56 0x04 +#define W_B_MODE_BSW0 0x08 +#define W_B_MODE_BSW1 0x10 +#define W_B_MODE_EPCM 0x20 +#define W_B_MODE_ITF 0x40 +#define W_B_MODE_MMS 0x80 + +#define W_B_EXI_XDUN 0x01 +#define W_B_EXI_XFR 0x02 +#define W_B_EXI_RDOV 0x10 +#define W_B_EXI_RME 0x20 +#define W_B_EXI_RMR 0x40 + +#define W_B_STAR_XBZ 0x01 +#define W_B_STAR_XDOW 0x04 +#define W_B_STAR_RMB 0x10 +#define W_B_STAR_CRCE 0x20 +#define W_B_STAR_RDOV 0x40 + +#define W_B_RBCH_LOV 0x20 + +/* W6692 Layer1 commands */ + +#define W_L1CMD_ECK 0x00 +#define W_L1CMD_RST 0x01 +#define W_L1CMD_SCP 0x04 +#define W_L1CMD_SSP 0x02 +#define W_L1CMD_AR8 0x08 +#define W_L1CMD_AR10 0x09 +#define W_L1CMD_EAL 0x0a +#define W_L1CMD_DRC 0x0f + +/* W6692 Layer1 indications */ + +#define W_L1IND_CE 0x07 +#define W_L1IND_DRD 0x00 +#define W_L1IND_LD 0x04 +#define W_L1IND_ARD 0x08 +#define W_L1IND_TI 0x0a +#define W_L1IND_ATI 0x0b +#define W_L1IND_AI8 0x0c +#define W_L1IND_AI10 0x0d +#define W_L1IND_CD 0x0f + +/* FIFO thresholds */ +#define W_D_FIFO_THRESH 64 +#define W_B_FIFO_THRESH 64 |