Add the rt linux 4.1.3-rt3 as base
[kvmfornfv.git] / kernel / drivers / isdn / hisax / hisax_isac.c
diff --git a/kernel/drivers/isdn/hisax/hisax_isac.c b/kernel/drivers/isdn/hisax/hisax_isac.c
new file mode 100644 (file)
index 0000000..5154c25
--- /dev/null
@@ -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;
+       }
+       memcpy(skb_put(skb, count), 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;
+       }
+       memcpy(skb_put(skb, count), 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);