| /* |
| * 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/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); |
| vsprintf(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; |
| |
| if (!isac->tx_skb) |
| BUG(); |
| |
| count = isac->tx_skb->len; |
| if (count <= 0) |
| BUG(); |
| |
| 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; |
| } |
| if (isac->tx_skb) |
| BUG(); |
| |
| 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); |