mISDN/drivers/isdn/hardware/mISDN/w6692.c

1622 lines
43 KiB
C
Executable File

/* $Id$
* w6692.c low level driver for CCD's hfc-pci based cards
*
* Author Karsten Keil <kkeil@suse.de>
* based on the w6692 I4L driver from Petr Novak <petr.novak@i.cz>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2, 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.
*
*/
#include <linux/module.h>
#include <linux/pci.h>
#include <linux/delay.h>
#include "core.h"
#include "channel.h"
#include "layer1.h"
#include "helper.h"
#include "debug.h"
#include "w6692.h"
#include <linux/isdn_compat.h>
extern const char *CardType[];
const char *w6692_rev = "$Revision$";
#define DBUSY_TIMER_VALUE 80
enum {
W6692_ASUS,
W6692_WINBOND,
W6692_USR
};
/* private data in the PCI devices list */
typedef struct _w6692_map{
u_int subtype;
char *name;
} w6692_map_t;
static w6692_map_t w6692_map[] =
{
{W6692_ASUS, "Dynalink/AsusCom IS64PH"},
{W6692_WINBOND, "Winbond W6692"},
{W6692_USR, "USR W6692"}
};
#ifndef PCI_VENDOR_ID_USR2
#define PCI_VENDOR_ID_USR2 0x16ec
#define PCI_DEVICE_ID_USR2_6692 0x3409
#endif
typedef struct _w6692_bc {
struct timer_list timer;
u_char b_mode;
} w6692_bc;
typedef struct _w6692pci {
struct list_head list;
struct pci_dev *pdev;
u_int subtype;
u_int irq;
u_int irqcnt;
u_int addr;
int pots;
int led;
spinlock_t lock;
u_char imask;
u_char pctl;
u_char xaddr;
u_char xdata;
w6692_bc wbc[2];
channel_t dch;
channel_t bch[2];
} w6692pci;
#define W_LED1_ON 1
#define W_LED1_S0STATUS 2
static __inline__ u_char
ReadW6692(w6692pci *card, u_char offset)
{
return (inb(card->addr + offset));
}
static __inline__ void
WriteW6692(w6692pci *card, u_char offset, u_char value)
{
outb(value, card->addr + offset);
}
static __inline__ u_char
ReadW6692B(w6692pci *card, int bchan, u_char offset)
{
return (inb(card->addr + (bchan ? 0x40 : 0) + offset));
}
static __inline__ void
WriteW6692B(w6692pci *card, int bchan, u_char offset, u_char value)
{
outb(value, card->addr + (bchan ? 0x40 : 0) + offset);
}
static void
enable_hwirq(w6692pci *card)
{
WriteW6692(card, W_IMASK, card->imask);
}
static void
disable_hwirq(w6692pci *card)
{
WriteW6692(card, W_IMASK, 0xff);
}
static char *W6692Ver[] __initdata =
{"W6692 V00", "W6692 V01", "W6692 V10",
"W6692 V11"};
static void
W6692Version(w6692pci *card, char *s)
{
int val;
val = ReadW6692(card, W_D_RBCH);
printk(KERN_INFO "%s Winbond W6692 version (%x): %s\n", s, val, W6692Ver[(val >> 6) & 3]);
}
static void
w6692_led_handler(w6692pci *card, int on)
{
if (!card->led || card->subtype == W6692_USR)
return;
if (on) {
card->xdata &= 0xfb; /* LED ON */
WriteW6692(card, W_XDATA, card->xdata);
} else {
card->xdata |= 0x04; /* LED OFF */
WriteW6692(card, W_XDATA, card->xdata);
}
}
static void
ph_command(w6692pci *card, u_char command)
{
if (card->dch.debug & L1_DEB_ISAC)
mISDN_debugprint(&card->dch.inst, "ph_command %x", command);
WriteW6692(card, W_CIX, command);
}
static void
W6692_new_ph(channel_t *dch)
{
u_int prim = PH_SIGNAL | INDICATION;
u_int para = 0;
switch (dch->state) {
case W_L1CMD_RST:
ph_command(dch->hw, W_L1CMD_DRC);
mISDN_queue_data(&dch->inst, FLG_MSG_UP, PH_CONTROL | INDICATION, HW_RESET, 0, NULL, 0);
/* fall trough */
case W_L1IND_CD:
prim = PH_CONTROL | CONFIRM;
para = HW_DEACTIVATE;
test_and_clear_bit(FLG_ACTIVE, &dch->Flags);
break;
case W_L1IND_DRD:
prim = PH_CONTROL | INDICATION;
para = HW_DEACTIVATE;
test_and_clear_bit(FLG_ACTIVE, &dch->Flags);
break;
case W_L1IND_CE:
prim = PH_CONTROL | INDICATION;
para = HW_POWERUP;
break;
case W_L1IND_LD:
para = ANYSIGNAL;
break;
case W_L1IND_ARD:
para = INFO2;
break;
case W_L1IND_AI8:
para = INFO4_P8;
test_and_set_bit(FLG_ACTIVE, &dch->Flags);
break;
case W_L1IND_AI10:
para = INFO4_P10;
test_and_set_bit(FLG_ACTIVE, &dch->Flags);
break;
default:
return;
}
mISDN_queue_data(&dch->inst, FLG_MSG_UP, prim, para, 0, NULL, 0);
}
static void
W6692_empty_Dfifo(w6692pci *card, int count)
{
channel_t *dch = &card->dch;
u_char *ptr;
if ((dch->debug & L1_DEB_ISAC) && !(dch->debug & L1_DEB_ISAC_FIFO))
mISDN_debugprint(&dch->inst, "empty_Dfifo");
if (!dch->rx_skb) {
if (!(dch->rx_skb = alloc_stack_skb(dch->maxlen, dch->up_headerlen))) {
printk(KERN_WARNING "mISDN: D receive out of memory\n");
WriteW6692(card, W_D_CMDR, W_D_CMDR_RACK);
return;
}
}
if ((dch->rx_skb->len + count) >= dch->maxlen) {
if (dch->debug & L1_DEB_WARN)
mISDN_debugprint(&dch->inst, "empty_Dfifo overrun %d",
dch->rx_skb->len + count);
WriteW6692(card, W_D_CMDR, W_D_CMDR_RACK);
return;
}
ptr = skb_put(dch->rx_skb, count);
insb(card->addr + W_D_RFIFO, ptr, count);
WriteW6692(card, W_D_CMDR, W_D_CMDR_RACK);
if (dch->debug & L1_DEB_ISAC_FIFO) {
char *t = dch->log;
t += sprintf(t, "empty_Dfifo cnt %d", count);
mISDN_QuickHex(t, ptr, count);
mISDN_debugprint(&dch->inst, dch->log);
}
}
static void
W6692_fill_Dfifo(w6692pci *card)
{
channel_t *dch = &card->dch;
int count;
u_char *ptr;
u_char cmd = W_D_CMDR_XMS;
if ((dch->debug & L1_DEB_ISAC) && !(dch->debug & L1_DEB_ISAC_FIFO))
mISDN_debugprint(&dch->inst, "fill_Dfifo");
if (!dch->tx_skb)
return;
count = dch->tx_skb->len - dch->tx_idx;
if (count <= 0)
return;
if (count > 32)
count = 32;
else
cmd |= W_D_CMDR_XME;
ptr = dch->tx_skb->data + dch->tx_idx;
dch->tx_idx += count;
outsb(card->addr + W_D_XFIFO, ptr, count);
WriteW6692(card, W_D_CMDR, cmd);
if (test_and_set_bit(FLG_BUSY_TIMER, &dch->Flags)) {
mISDN_debugprint(&dch->inst, "fill_Dfifo dbusytimer running");
del_timer(&dch->timer);
}
init_timer(&dch->timer);
dch->timer.expires = jiffies + ((DBUSY_TIMER_VALUE * HZ)/1000);
add_timer(&dch->timer);
if (dch->debug & L1_DEB_ISAC_FIFO) {
char *t = dch->log;
t += sprintf(t, "fill_Dfifo cnt %d", count);
mISDN_QuickHex(t, ptr, count);
mISDN_debugprint(&dch->inst, dch->log);
}
}
static void
d_retransmit(w6692pci *card)
{
channel_t *dch = &card->dch;
if (test_and_clear_bit(FLG_BUSY_TIMER, &dch->Flags))
del_timer(&dch->timer);
#ifdef FIXME
if (test_and_clear_bit(FLG_L1_BUSY, &dch->Flags))
dchannel_sched_event(dch, D_CLEARBUSY);
#endif
if (test_bit(FLG_TX_BUSY, &dch->Flags)) {
/* Restart frame */
dch->tx_idx = 0;
W6692_fill_Dfifo(card);
} else if (dch->tx_skb) { /* should not happen */
int_error();
test_and_set_bit(FLG_TX_BUSY, &dch->Flags);
dch->tx_idx = 0;
W6692_fill_Dfifo(card);
} else {
printk(KERN_WARNING "mISDN: w6692 XDU no TX_BUSY\n");
mISDN_debugprint(&dch->inst, "XDU no TX_BUSY");
if (test_bit(FLG_TX_NEXT, &dch->Flags)) {
dch->tx_skb = dch->next_skb;
if (dch->tx_skb) {
mISDN_head_t *hh = mISDN_HEAD_P(dch->tx_skb);
dch->next_skb = NULL;
test_and_clear_bit(FLG_TX_NEXT, &dch->Flags);
dch->tx_idx = 0;
queue_ch_frame(dch, CONFIRM, hh->dinfo, NULL);
W6692_fill_Dfifo(card);
} else {
printk(KERN_WARNING "w6692 xdu irq TX_NEXT without skb\n");
test_and_clear_bit(FLG_TX_NEXT, &dch->Flags);
}
}
}
}
static void
handle_rxD(w6692pci *card) {
u_char stat;
int count;
stat = ReadW6692(card, W_D_RSTA);
if (stat & (W_D_RSTA_RDOV | W_D_RSTA_CRCE | W_D_RSTA_RMB)) {
if (stat & W_D_RSTA_RDOV) {
if (card->dch.debug & L1_DEB_WARN)
mISDN_debugprint(&card->dch.inst, "D-channel RDOV");
#ifdef ERROR_STATISTIC
card->dch.err_rx++;
#endif
}
if (stat & W_D_RSTA_CRCE) {
if (card->dch.debug & L1_DEB_WARN)
mISDN_debugprint(&card->dch.inst, "D-channel CRC error");
#ifdef ERROR_STATISTIC
card->dch.err_crc++;
#endif
}
if (stat & W_D_RSTA_RMB) {
if (card->dch.debug & L1_DEB_WARN)
mISDN_debugprint(&card->dch.inst, "D-channel ABORT");
#ifdef ERROR_STATISTIC
card->dch.err_rx++;
#endif
}
if (card->dch.rx_skb)
dev_kfree_skb(card->dch.rx_skb);
WriteW6692(card, W_D_CMDR, W_D_CMDR_RACK | W_D_CMDR_RRST);
} else {
count = ReadW6692(card, W_D_RBCL) & (W_D_FIFO_THRESH - 1);
if (count == 0)
count = W_D_FIFO_THRESH;
W6692_empty_Dfifo(card, count);
if (card->dch.rx_skb)
mISDN_queueup_newhead(&card->dch.inst, 0, PH_DATA_IND,
MISDN_ID_ANY, card->dch.rx_skb);
}
card->dch.rx_skb = NULL;
}
static void
handle_txD(w6692pci *card) {
register channel_t *dch = &card->dch;
if (test_and_clear_bit(FLG_BUSY_TIMER, &dch->Flags))
del_timer(&dch->timer);
#ifdef FIXME
if (test_and_clear_bit(FLG_L1_BUSY, &dch->Flags))
dchannel_sched_event(dch, D_CLEARBUSY);
#endif
if (dch->tx_skb && dch->tx_idx < dch->tx_skb->len) {
W6692_fill_Dfifo(card);
} else {
if (dch->tx_skb)
dev_kfree_skb(dch->tx_skb);
if (test_bit(FLG_TX_NEXT, &dch->Flags)) {
dch->tx_skb = dch->next_skb;
if (dch->tx_skb) {
mISDN_head_t *hh = mISDN_HEAD_P(dch->tx_skb);
dch->next_skb = NULL;
test_and_clear_bit(FLG_TX_NEXT, &dch->Flags);
dch->tx_idx = 0;
queue_ch_frame(dch, CONFIRM, hh->dinfo, NULL);
W6692_fill_Dfifo(card);
} else {
printk(KERN_WARNING "w6692 txD irq TX_NEXT without skb\n");
test_and_clear_bit(FLG_TX_NEXT, &dch->Flags);
test_and_clear_bit(FLG_TX_BUSY, &dch->Flags);
}
} else {
dch->tx_skb = NULL;
test_and_clear_bit(FLG_TX_BUSY, &dch->Flags);
}
}
}
static void
handle_statusD(w6692pci *card)
{
register channel_t *dch = &card->dch;
u_char exval, v1, cir;
exval = ReadW6692(card, W_D_EXIR);
if (card->dch.debug & L1_DEB_ISAC)
mISDN_debugprint(&card->dch.inst, "D_EXIR %02x", exval);
if (exval & (W_D_EXI_XDUN | W_D_EXI_XCOL)) { /* Transmit underrun/collision */
if (card->dch.debug & L1_DEB_WARN)
mISDN_debugprint(&card->dch.inst, "D-channel underrun/collision");
#ifdef ERROR_STATISTIC
dch->err_tx++;
#endif
d_retransmit(card);
}
if (exval & W_D_EXI_RDOV) { /* RDOV */
if (card->dch.debug & L1_DEB_WARN)
mISDN_debugprint(&card->dch.inst, "D-channel RDOV");
WriteW6692(card, W_D_CMDR, W_D_CMDR_RRST);
}
if (exval & W_D_EXI_TIN2) /* TIN2 - never */
if (card->dch.debug & L1_DEB_WARN)
mISDN_debugprint(&card->dch.inst, "spurious TIN2 interrupt");
if (exval & W_D_EXI_MOC) { /* MOC - not supported */
v1 = ReadW6692(card, W_MOSR);
if (card->dch.debug & L1_DEB_ISAC) {
mISDN_debugprint(&card->dch.inst, "spurious MOC interrupt");
mISDN_debugprint(&card->dch.inst, "MOSR %02x", v1);
}
}
if (exval & W_D_EXI_ISC) { /* ISC - Level1 change */
cir = ReadW6692(card, W_CIR);
if (card->dch.debug & L1_DEB_ISAC)
mISDN_debugprint(&card->dch.inst, "ISC CIR=0x%02X", cir);
if (cir & W_CIR_ICC) {
v1 = cir & W_CIR_COD_MASK;
if (card->dch.debug & L1_DEB_ISAC)
mISDN_debugprint(&card->dch.inst, "ph_state_change %x -> %x",
dch->state, v1);
dch->state = v1;
if (card->led & W_LED1_S0STATUS) {
switch (v1) {
case W_L1IND_AI8:
case W_L1IND_AI10:
w6692_led_handler(card, 1);
break;
default:
w6692_led_handler(card, 0);
break;
}
}
W6692_new_ph(dch);
}
if (cir & W_CIR_SCC) {
v1 = ReadW6692(card, W_SQR);
if (card->dch.debug & L1_DEB_ISAC)
mISDN_debugprint(&card->dch.inst, "SCC SQR=0x%02X", v1);
}
}
if (exval & W_D_EXI_WEXP) {
if (card->dch.debug & L1_DEB_WARN)
mISDN_debugprint(&card->dch.inst, "spurious WEXP interrupt!");
}
if (exval & W_D_EXI_TEXP) {
if (card->dch.debug & L1_DEB_WARN)
mISDN_debugprint(&card->dch.inst, "spurious TEXP interrupt!");
}
}
static void
W6692_empty_Bfifo(channel_t *bch, int count)
{
u_char *ptr;
w6692pci *card = bch->inst.privat;
if ((bch->debug & L1_DEB_HSCX) && !(bch->debug & L1_DEB_HSCX_FIFO))
mISDN_debugprint(&bch->inst, "empty_Bfifo %d", count);
if (unlikely(bch->state == ISDN_PID_NONE)) {
if (bch->debug & L1_DEB_WARN)
mISDN_debugprint(&bch->inst, "empty_Bfifo ISDN_PID_NONE");
WriteW6692B(card, bch->channel, W_B_CMDR, W_B_CMDR_RACK | W_B_CMDR_RACT);
if (bch->rx_skb)
skb_trim(bch->rx_skb, 0);
return;
}
if (!bch->rx_skb) {
bch->rx_skb = alloc_stack_skb(bch->maxlen, bch->up_headerlen);
if (unlikely(!bch->rx_skb)) {
printk(KERN_WARNING "mISDN: B receive out of memory\n");
WriteW6692B(card, bch->channel, W_B_CMDR, W_B_CMDR_RACK | W_B_CMDR_RACT);
return;
}
}
if (bch->rx_skb->len + count > bch->maxlen) {
if (bch->debug & L1_DEB_WARN)
mISDN_debugprint(&bch->inst, "empty_Bfifo incoming packet too large");
WriteW6692B(card, bch->channel, W_B_CMDR, W_B_CMDR_RACK | W_B_CMDR_RACT);
skb_trim(bch->rx_skb, 0);
return;
}
ptr = skb_put(bch->rx_skb, count);
insb(card->addr + W_B_RFIFO + (bch->channel ? 0x40 : 0), ptr, count);
WriteW6692B(card, bch->channel, W_B_CMDR, W_B_CMDR_RACK | W_B_CMDR_RACT);
if (bch->debug & L1_DEB_HSCX_FIFO) {
char *t = bch->log;
t += sprintf(t, "empty_Bfifo B%d cnt %d", bch->channel, count);
mISDN_QuickHex(t, ptr, count);
mISDN_debugprint(&bch->inst, bch->log);
}
}
static void
W6692_fill_Bfifo(channel_t *bch)
{
w6692pci *card = bch->inst.privat;
int count;
u_char *ptr, cmd = W_B_CMDR_RACT | W_B_CMDR_XMS;
if ((bch->debug & L1_DEB_HSCX) && !(bch->debug & L1_DEB_HSCX_FIFO))
mISDN_debugprint(&bch->inst, "%s", __FUNCTION__);
if (!bch->tx_skb)
return;
count = bch->tx_skb->len - bch->tx_idx;
if (count <= 0)
return;
ptr = bch->tx_skb->data + bch->tx_idx;
if (count > W_B_FIFO_THRESH) {
count = W_B_FIFO_THRESH;
} else {
if (test_bit(FLG_HDLC, &bch->Flags))
cmd |= W_B_CMDR_XME;
}
if ((bch->debug & L1_DEB_HSCX) && !(bch->debug & L1_DEB_HSCX_FIFO))
mISDN_debugprint(&bch->inst, "%s: %d/%d", __FUNCTION__,
count, bch->tx_idx);
bch->tx_idx += count;
outsb(card->addr + W_B_XFIFO + (bch->channel ? 0x40 : 0), ptr, count);
WriteW6692B(card, bch->channel, W_B_CMDR, cmd);
if (bch->debug & L1_DEB_HSCX_FIFO) {
char *t = bch->log;
t += sprintf(t, "fill_Bfifo B%d cnt %d",
bch->channel, count);
mISDN_QuickHex(t, ptr, count);
mISDN_debugprint(&bch->inst, bch->log);
}
}
static int
setvolume(channel_t *bch, int mic, struct sk_buff *skb)
{
w6692pci *card = bch->inst.privat;
u16 *vol = (u16 *)skb->data;
u_char val;
if ((card->pots == 0) || !test_bit(FLG_TRANSPARENT, &bch->Flags))
return(-ENODEV);
if (skb->len < 2)
return(-EINVAL);
if (*vol > 7)
return(-EINVAL);
val = *vol & 7;
val = 7 - val;
if (mic) {
val <<= 3;
card->xaddr &= 0xc7;
} else {
card->xaddr &= 0xf8;
}
card->xaddr |= val;
WriteW6692(card, W_XADDR, card->xaddr);
return(0);
}
static int
enable_pots(channel_t *bch)
{
w6692_bc *bhw = bch->hw;
w6692pci *card = bch->inst.privat;
if ((card->pots == 0) || !test_bit(FLG_TRANSPARENT, &bch->Flags))
return(-ENODEV);
bhw->b_mode |= W_B_MODE_EPCM | W_B_MODE_BSW0;
WriteW6692B(card, bch->channel, W_B_MODE, bhw->b_mode);
WriteW6692B(card, bch->channel, W_B_CMDR, W_B_CMDR_RRST | W_B_CMDR_XRST);
card->pctl |= (bch->channel ? W_PCTL_PCX : 0);
WriteW6692(card, W_PCTL, card->pctl);
return(0);
}
static int
disable_pots(channel_t *bch)
{
w6692_bc *bhw = bch->hw;
w6692pci *card = bch->inst.privat;
if (card->pots == 0)
return(-ENODEV);
bhw->b_mode &= ~(W_B_MODE_EPCM | W_B_MODE_BSW0);
WriteW6692B(card, bch->channel, W_B_MODE, bhw->b_mode);
WriteW6692B(card, bch->channel, W_B_CMDR, W_B_CMDR_RRST | W_B_CMDR_RACT | W_B_CMDR_XRST);
return(0);
}
static int
mode_w6692(channel_t *bch, int bc, int protocol)
{
w6692pci *card = bch->inst.privat;
w6692_bc *bhw = bch->hw;
if (bch->debug & L1_DEB_HSCX)
mISDN_debugprint(&bch->inst, "B%d protocol %x-->%x ch %d-->%d",
bch->channel, bch->state, protocol, bch->channel, bc);
switch (protocol) {
case (-1): /* used for init */
bch->state = -1;
bch->channel = bc;
case (ISDN_PID_NONE):
if (bch->state == ISDN_PID_NONE)
break;
bch->state = ISDN_PID_NONE;
if (card->pots && (bhw->b_mode & W_B_MODE_EPCM))
disable_pots(bch);
bhw->b_mode = 0;
bch->tx_idx = 0;
if (bch->next_skb) {
dev_kfree_skb(bch->next_skb);
bch->next_skb = 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;
}
WriteW6692B(card, bch->channel, W_B_MODE, bhw->b_mode);
WriteW6692B(card, bch->channel, W_B_CMDR, W_B_CMDR_RRST | W_B_CMDR_XRST);
test_and_clear_bit(FLG_HDLC, &bch->Flags);
test_and_clear_bit(FLG_TRANSPARENT, &bch->Flags);
break;
case (ISDN_PID_L1_B_64TRANS):
bch->state = protocol;
bhw->b_mode = W_B_MODE_MMS;
WriteW6692B(card, bch->channel, W_B_MODE, bhw->b_mode);
WriteW6692B(card, bch->channel, W_B_EXIM, 0);
WriteW6692B(card, bch->channel, W_B_CMDR, W_B_CMDR_RRST | W_B_CMDR_RACT | W_B_CMDR_XRST);
test_and_set_bit(FLG_TRANSPARENT, &bch->Flags);
break;
case (ISDN_PID_L1_B_64HDLC):
bch->state = protocol;
bhw->b_mode = W_B_MODE_ITF;
WriteW6692B(card, bch->channel, W_B_MODE, bhw->b_mode);
WriteW6692B(card, bch->channel, W_B_ADM1, 0xff);
WriteW6692B(card, bch->channel, W_B_ADM2, 0xff);
WriteW6692B(card, bch->channel, W_B_EXIM, 0);
WriteW6692B(card, bch->channel, W_B_CMDR, W_B_CMDR_RRST | W_B_CMDR_RACT | W_B_CMDR_XRST);
test_and_set_bit(FLG_HDLC, &bch->Flags);
break;
default:
mISDN_debugprint(&bch->inst, "prot not known %x", protocol);
return(-ENOPROTOOPT);
}
return(0);
}
static void
send_next(channel_t *bch)
{
if (test_bit(FLG_ACTIVE, &bch->Flags))
return;
if (bch->tx_skb && bch->tx_idx < bch->tx_skb->len)
W6692_fill_Bfifo(bch);
else {
if (bch->tx_skb)
dev_kfree_skb(bch->tx_skb);
bch->tx_idx = 0;
if (test_bit(FLG_TX_NEXT, &bch->Flags)) {
bch->tx_skb = bch->next_skb;
if (bch->tx_skb) {
mISDN_head_t *hh = mISDN_HEAD_P(bch->tx_skb);
bch->next_skb = NULL;
test_and_clear_bit(FLG_TX_NEXT, &bch->Flags);
queue_ch_frame(bch, CONFIRM, hh->dinfo, NULL);
W6692_fill_Bfifo(bch);
} else {
test_and_clear_bit(FLG_TX_NEXT, &bch->Flags);
printk(KERN_WARNING "W6692 tx irq TX_NEXT without skb\n");
test_and_clear_bit(FLG_TX_BUSY, &bch->Flags);
}
} else {
bch->tx_skb = NULL;
test_and_clear_bit(FLG_TX_BUSY, &bch->Flags);
}
}
}
static void
W6692B_interrupt(w6692pci *card, int ch)
{
channel_t *bch = &card->bch[ch];
int count;
u_char stat, star = 0;
struct sk_buff *skb;
stat = ReadW6692B(card, ch, W_B_EXIR);
if (bch->debug & L1_DEB_HSCX)
mISDN_debugprint(&bch->inst, "ch%d stat %#x", bch->channel, stat);
if (stat & W_B_EXI_RME) {
star = ReadW6692B(card, ch, W_B_STAR);
if (star & (W_B_STAR_RDOV | W_B_STAR_CRCE | W_B_STAR_RMB)) {
if ((star & W_B_STAR_RDOV) && test_bit(FLG_ACTIVE, &bch->Flags)) {
if (bch->debug & L1_DEB_WARN)
mISDN_debugprint(&bch->inst, "B%d RDOV protocol=%x",
ch +1, bch->state);
#ifdef ERROR_STATISTIC
bch->err_rdo++;
#endif
}
if (test_bit(FLG_HDLC, &bch->Flags)) {
if (star & W_B_STAR_CRCE) {
if (bch->debug & L1_DEB_WARN)
mISDN_debugprint(&bch->inst,
"B%d CRC error", ch+1);
#ifdef ERROR_STATISTIC
bch->err_crc++;
#endif
}
if (star & W_B_STAR_RMB) {
if (bch->debug & L1_DEB_WARN)
mISDN_debugprint(&bch->inst,
"B%d message abort", ch+1);
#ifdef ERROR_STATISTIC
bch->err_inv++;
#endif
}
}
WriteW6692B(card, ch, W_B_CMDR, W_B_CMDR_RACK | W_B_CMDR_RRST | W_B_CMDR_RACT);
if (bch->rx_skb)
skb_trim(bch->rx_skb, 0);
} else {
count = ReadW6692B(card, ch, W_B_RBCL) & (W_B_FIFO_THRESH - 1);
if (count == 0)
count = W_B_FIFO_THRESH;
W6692_empty_Bfifo(bch, count);
if (bch->rx_skb && bch->rx_skb->len > 0) {
if (bch->debug & L1_DEB_HSCX)
mISDN_debugprint(&bch->inst, "Bchan Frame %d", bch->rx_skb->len);
if (bch->rx_skb->len < MISDN_COPY_SIZE) {
skb = alloc_stack_skb(bch->rx_skb->len, bch->up_headerlen);
if (skb) {
memcpy(skb_put(skb, bch->rx_skb->len),
bch->rx_skb->data, bch->rx_skb->len);
skb_trim(bch->rx_skb, 0);
} else {
skb = bch->rx_skb;
bch->rx_skb = NULL;
}
} else {
skb = bch->rx_skb;
bch->rx_skb = NULL;
}
queue_ch_frame(bch, INDICATION, MISDN_ID_ANY, skb);
}
}
}
if (stat & W_B_EXI_RMR) {
if (!(stat & W_B_EXI_RME))
star = ReadW6692B(card, ch, W_B_STAR);
if (star & W_B_STAR_RDOV) {
if (bch->debug & L1_DEB_WARN)
mISDN_debugprint(&bch->inst, "B%d RDOV protocol=%x",
ch +1, bch->state);
#ifdef ERROR_STATISTIC
bch->err_rdo++;
#endif
WriteW6692B(card, ch, W_B_CMDR, W_B_CMDR_RACK | W_B_CMDR_RRST | W_B_CMDR_RACT);
} else {
W6692_empty_Bfifo(bch, W_B_FIFO_THRESH);
if (test_bit(FLG_TRANSPARENT, &bch->Flags) &&
bch->rx_skb && (bch->rx_skb->len > 0)) {
/* receive audio data */
if (bch->debug & L1_DEB_HSCX)
mISDN_debugprint(&bch->inst,
"Bchan Frame %d", bch->rx_skb->len);
queue_ch_frame(bch, INDICATION, MISDN_ID_ANY, bch->rx_skb);
bch->rx_skb = NULL;
}
}
}
if (stat & W_B_EXI_RDOV) {
if (!(star & W_B_STAR_RDOV)) { /* only if it is not handled yet */
if (bch->debug & L1_DEB_WARN)
mISDN_debugprint(&bch->inst, "B%d RDOV IRQ protocol=%x",
ch +1, bch->state);
#ifdef ERROR_STATISTIC
bch->err_rdo++;
#endif
WriteW6692B(card, ch, W_B_CMDR, W_B_CMDR_RACK | W_B_CMDR_RRST | W_B_CMDR_RACT);
}
}
if (stat & W_B_EXI_XFR) {
if (!(stat & (W_B_EXI_RME | W_B_EXI_RMR))) {
star = ReadW6692B(card, ch, W_B_STAR);
if (bch->debug & L1_DEB_HSCX)
mISDN_debugprint(&bch->inst, "B%d star %02x", ch +1, star);
}
if (star & W_B_STAR_XDOW) {
if (bch->debug & L1_DEB_WARN)
mISDN_debugprint(&bch->inst, "B%d XDOW protocol=%x",
ch +1, bch->state);
#ifdef ERROR_STATISTIC
bch->err_xdu++;
#endif
WriteW6692B(card, ch, W_B_CMDR, W_B_CMDR_XRST | W_B_CMDR_RACT);
/* resend */
if (bch->tx_skb) {
if (!test_bit(FLG_TRANSPARENT, &bch->Flags))
bch->tx_idx = 0;
}
}
send_next(bch);
if (stat & W_B_EXI_XDUN)
return; /* handle XDOW only once */
}
if (stat & W_B_EXI_XDUN) {
if (bch->debug & L1_DEB_WARN)
mISDN_debugprint(&bch->inst, "B%d XDUN protocol=%x",
ch +1, bch->state);
#ifdef ERROR_STATISTIC
bch->err_xdu++;
#endif
WriteW6692B(card, ch, W_B_CMDR, W_B_CMDR_XRST | W_B_CMDR_RACT);
/* resend */
if (bch->tx_skb) {
if (!test_bit(FLG_TRANSPARENT, &bch->Flags))
bch->tx_idx = 0;
}
send_next(bch);
}
}
static irqreturn_t
w6692_interrupt(int intno, void *dev_id, struct pt_regs *regs)
{
w6692pci *card = dev_id;
u_char ista;
spin_lock(&card->lock);
ista = ReadW6692(card, W_ISTA);
if ((ista | card->imask) == card->imask) {
/* possible a shared IRQ reqest */
spin_unlock(&card->lock);
return IRQ_NONE;
}
card->irqcnt++;
/* Begin IRQ handler */
if (card->dch.debug & L1_DEB_ISAC)
mISDN_debugprint(&card->dch.inst, "ista %02x", ista);
ista &= ~card->imask;
if (ista & W_INT_B1_EXI)
W6692B_interrupt(card, 0);
if (ista & W_INT_B2_EXI)
W6692B_interrupt(card, 1);
if (ista & W_INT_D_RME)
handle_rxD(card);
if (ista & W_INT_D_RMR)
W6692_empty_Dfifo(card, W_D_FIFO_THRESH);
if (ista & W_INT_D_XFR)
handle_txD(card);
if (ista & W_INT_D_EXI)
handle_statusD(card);
if (ista & (W_INT_XINT0 | W_INT_XINT1)) /* XINT0/1 - never */
mISDN_debugprint(&card->dch.inst, "W6692 spurious XINT!");
/* End IRQ Handler */
spin_unlock(&card->lock);
return IRQ_HANDLED;
}
static void
dbusy_timer_handler(channel_t *dch)
{
w6692pci *card = dch->hw;
int rbch, star;
u_long flags;
if (test_bit(FLG_BUSY_TIMER, &dch->Flags)) {
spin_lock_irqsave(dch->inst.hwlock, flags);
rbch = ReadW6692(card, W_D_RBCH);
star = ReadW6692(card, W_D_STAR);
if (dch->debug)
mISDN_debugprint(&dch->inst, "D-Channel Busy RBCH %02x STAR %02x",
rbch, star);
if (star & W_D_STAR_XBZ) { /* D-Channel Busy */
test_and_set_bit(FLG_L1_BUSY, &dch->Flags);
} else {
/* discard frame; reset transceiver */
test_and_clear_bit(FLG_BUSY_TIMER, &dch->Flags);
if (dch->tx_idx) {
dch->tx_idx = 0;
} else {
printk(KERN_WARNING "mISDN: W6692 D-Channel Busy no tx_idx\n");
mISDN_debugprint(&dch->inst, "D-Channel Busy no tx_idx");
}
/* Transmitter reset */
WriteW6692(card, W_D_CMDR, W_D_CMDR_XRST); /* Transmitter reset */
}
spin_unlock_irqrestore(dch->inst.hwlock, flags);
}
}
void initW6692(w6692pci *card)
{
u_char val;
card->dch.timer.function = (void *) dbusy_timer_handler;
card->dch.timer.data = (u_long) &card->dch;
init_timer(&card->dch.timer);
mode_w6692(&card->bch[0], 0, -1);
mode_w6692(&card->bch[1], 1, -1);
WriteW6692(card, W_D_CTL, 0x00);
disable_hwirq(card);
WriteW6692(card, W_D_SAM, 0xff);
WriteW6692(card, W_D_TAM, 0xff);
WriteW6692(card, W_D_MODE, W_D_MODE_RACT);
card->dch.state = W_L1CMD_RST;
ph_command(card, W_L1CMD_RST);
ph_command(card, W_L1CMD_ECK);
/* enable all IRQ but extern */
card->imask = 0x18;
WriteW6692(card, W_D_EXIM, 0x00);
WriteW6692B(card, 0, W_B_EXIM, 0);
WriteW6692B(card, 1, W_B_EXIM, 0);
/* Reset D-chan receiver and transmitter */
WriteW6692(card, W_D_CMDR, W_D_CMDR_RRST | W_D_CMDR_XRST);
/* Reset B-chan receiver and transmitter */
WriteW6692B(card, 0, W_B_CMDR, W_B_CMDR_RRST | W_B_CMDR_XRST);
WriteW6692B(card, 1, W_B_CMDR, W_B_CMDR_RRST | W_B_CMDR_XRST);
/* enable peripheral */
if (card->subtype == 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
*/
card->pctl = 0x80;
card->xdata = 0;
WriteW6692(card, W_PCTL, card->pctl);
WriteW6692(card, W_XDATA, card->xdata);
} else {
card->pctl = W_PCTL_OE5 | W_PCTL_OE4 | W_PCTL_OE2 | W_PCTL_OE1 | W_PCTL_OE0;
if (card->pots) {
card->xaddr = 0x00; /* all sw off */
card->xdata |= 0x06; /* POWER UP/ LED OFF / ALAW */
WriteW6692(card, W_PCTL, card->pctl);
WriteW6692(card, W_XADDR, card->xaddr);
WriteW6692(card, W_XDATA, card->xdata);
val = ReadW6692(card, W_XADDR);
if (card->dch.debug & L1_DEB_ISAC)
mISDN_debugprint(&card->dch.inst, "W_XADDR: %02x", val);
if (card->led & W_LED1_ON)
w6692_led_handler(card, 1);
else
w6692_led_handler(card, 0);
}
}
}
static void reset_w6692(w6692pci *card)
{
WriteW6692(card, W_D_CTL, W_D_CTL_SRST);
mdelay(10);
WriteW6692(card, W_D_CTL, 0);
}
static int init_card(w6692pci *card)
{
int cnt = 3;
u_long flags;
spin_lock_irqsave(&card->lock, flags);
disable_hwirq(card);
spin_unlock_irqrestore(&card->lock, flags);
if (request_irq(card->irq, w6692_interrupt, SA_SHIRQ, "w6692", card)) {
printk(KERN_WARNING "mISDN: couldn't get interrupt %d\n", card->irq);
return(-EIO);
}
spin_lock_irqsave(&card->lock, flags);
while (cnt) {
initW6692(card);
enable_hwirq(card);
spin_unlock_irqrestore(&card->lock, flags);
/* Timeout 10ms */
current->state = TASK_UNINTERRUPTIBLE;
schedule_timeout((10*HZ)/1000);
printk(KERN_INFO "w6692: IRQ %d count %d\n",
card->irq, card->irqcnt);
if (!card->irqcnt) {
printk(KERN_WARNING
"w6692: IRQ(%d) getting no interrupts during init %d\n",
card->irq, 4 - cnt);
if (cnt == 1) {
return (-EIO);
} else {
spin_lock_irqsave(&card->lock, flags);
reset_w6692(card);
cnt--;
}
} else {
return(0);
}
}
spin_unlock_irqrestore(&card->lock, flags);
return(-EIO);
}
#define MAX_CARDS 4
static int w6692_cnt;
static u_int protocol[MAX_CARDS];
static int layermask[MAX_CARDS];
static mISDNobject_t w6692;
static int debug;
static int pots[MAX_CARDS];
static int led[MAX_CARDS];
#ifdef MODULE
MODULE_AUTHOR("Karsten Keil");
#ifdef MODULE_LICENSE
MODULE_LICENSE("GPL");
#endif
#ifdef OLD_MODULE_PARAM
MODULE_PARM(debug, "1i");
#define MODULE_PARM_T "1-4i"
MODULE_PARM(protocol, MODULE_PARM_T);
MODULE_PARM(layermask, MODULE_PARM_T);
#else
module_param(debug, uint, S_IRUGO | S_IWUSR);
#ifdef OLD_MODULE_PARAM_ARRAY
static int num_led=0, num_pots=0, num_protocol=0, num_layermask=0;
module_param_array(led, uint, num_led, S_IRUGO | S_IWUSR);
module_param_array(pots, uint, num_pots, S_IRUGO | S_IWUSR);
module_param_array(protocol, uint, num_protocol, S_IRUGO | S_IWUSR);
module_param_array(layermask, uint, num_layermask, S_IRUGO | S_IWUSR);
#else
module_param_array(led, uint, NULL, S_IRUGO | S_IWUSR);
module_param_array(pots, uint, NULL, S_IRUGO | S_IWUSR);
module_param_array(protocol, uint, NULL, S_IRUGO | S_IWUSR);
module_param_array(layermask, uint, NULL, S_IRUGO | S_IWUSR);
#endif
#endif
#endif
/******************************/
/* Layer2 -> Layer 1 Transfer */
/******************************/
static int
w6692_bmsg(channel_t *bch, struct sk_buff *skb)
{
int ret = 0;
mISDN_head_t *hh = mISDN_HEAD_P(skb);
u_long flags;
if ((hh->prim == (PH_ACTIVATE | REQUEST)) ||
(hh->prim == (DL_ESTABLISH | REQUEST))) {
if (!test_and_set_bit(FLG_ACTIVE, &bch->Flags)) {
spin_lock_irqsave(bch->inst.hwlock, flags);
ret = mode_w6692(bch, bch->channel, bch->inst.pid.protocol[1]);
spin_unlock_irqrestore(bch->inst.hwlock, flags);
}
#ifdef FIXME
if (bch->inst.pid.protocol[2] == ISDN_PID_L2_B_RAWDEV)
if (bch->dev)
if_link(&bch->dev->rport.pif,
hh->prim | CONFIRM, 0, 0, NULL, 0);
#endif
skb_trim(skb, 0);
return(mISDN_queueup_newhead(&bch->inst, 0,
hh->prim | CONFIRM, ret, skb));
} else if ((hh->prim == (PH_DEACTIVATE | REQUEST)) ||
(hh->prim == (DL_RELEASE | REQUEST)) ||
((hh->prim == (PH_CONTROL | REQUEST) &&
(hh->dinfo == HW_DEACTIVATE)))) {
spin_lock_irqsave(bch->inst.hwlock, flags);
if (test_and_clear_bit(FLG_TX_NEXT, &bch->Flags)) {
dev_kfree_skb(bch->next_skb);
bch->next_skb = 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;
}
test_and_clear_bit(FLG_TX_BUSY, &bch->Flags);
mode_w6692(bch, bch->channel, ISDN_PID_NONE);
test_and_clear_bit(FLG_ACTIVE, &bch->Flags);
spin_unlock_irqrestore(bch->inst.hwlock, flags);
skb_trim(skb, 0);
if (hh->prim != (PH_CONTROL | REQUEST)) {
#ifdef FIXME
if (bch->inst.pid.protocol[2] == ISDN_PID_L2_B_RAWDEV)
if (bch->dev)
if_link(&bch->dev->rport.pif,
hh->prim | CONFIRM, 0, 0, NULL, 0);
#endif
if (!mISDN_queueup_newhead(&bch->inst, 0,
hh->prim | CONFIRM, 0, skb))
return(0);
}
} else if (hh->prim == (PH_CONTROL | REQUEST)) {
spin_lock_irqsave(bch->inst.hwlock, flags);
if (hh->dinfo == HW_POTS_ON) {
ret = enable_pots(bch);
} else if (hh->dinfo == HW_POTS_OFF) {
ret = disable_pots(bch);
} else if (hh->dinfo == HW_POTS_SETMICVOL) {
ret = setvolume(bch, 1, skb);
} else if (hh->dinfo == HW_POTS_SETSPKVOL) {
ret = setvolume(bch, 0, skb);
} else
ret = -EINVAL;
spin_unlock_irqrestore(bch->inst.hwlock, flags);
} else
ret = -EAGAIN;
if (!ret)
dev_kfree_skb(skb);
return(ret);
}
static int
w6692_dmsg(channel_t *dch, struct sk_buff *skb)
{
int ret = 0;
mISDN_head_t *hh = mISDN_HEAD_P(skb);
u_long flags;
if (hh->prim == (PH_SIGNAL | REQUEST)) {
spin_lock_irqsave(dch->inst.hwlock, flags);
if (hh->dinfo == INFO3_P8)
ph_command(dch->hw, W_L1CMD_AR8);
else if (hh->dinfo == INFO3_P10)
ph_command(dch->hw, W_L1CMD_AR10);
else
ret = -EINVAL;
spin_unlock_irqrestore(dch->inst.hwlock, flags);
} else if (hh->prim == (PH_CONTROL | REQUEST)) {
spin_lock_irqsave(dch->inst.hwlock, flags);
if (hh->dinfo == HW_RESET) {
if (dch->state != W_L1IND_DRD)
ph_command(dch->hw, W_L1CMD_RST);
ph_command(dch->hw, W_L1CMD_ECK);
} else if (hh->dinfo == HW_POWERUP) {
ph_command(dch->hw, W_L1CMD_ECK);
} else if (hh->dinfo == HW_DEACTIVATE) {
if (dch->next_skb) {
dev_kfree_skb(dch->next_skb);
dch->next_skb = NULL;
}
if (dch->tx_skb) {
dev_kfree_skb(dch->tx_skb);
dch->tx_skb = NULL;
}
if (dch->rx_skb) {
dev_kfree_skb(dch->rx_skb);
dch->rx_skb = NULL;
}
test_and_clear_bit(FLG_ACTIVE, &dch->Flags);
test_and_clear_bit(FLG_TX_NEXT, &dch->Flags);
test_and_clear_bit(FLG_TX_BUSY, &dch->Flags);
if (test_and_clear_bit(FLG_BUSY_TIMER, &dch->Flags))
del_timer(&dch->timer);
#ifdef FIXME
if (test_and_clear_bit(FLG_L1_BUSY, &dch->Flags))
dchannel_sched_event(dch, D_CLEARBUSY);
#endif
} else if ((hh->dinfo & HW_TESTLOOP) == HW_TESTLOOP) {
u_char val = 0;
if (1 & hh->dinfo)
val |= 0x0c;
if (2 & hh->dinfo)
val |= 0x3;
/* !!! not implemented yet */
} else {
if (dch->debug & L1_DEB_WARN)
mISDN_debugprint(&dch->inst, "w6692_dmsg unknown ctrl %x",
hh->dinfo);
ret = -EINVAL;
}
spin_unlock_irqrestore(dch->inst.hwlock, flags);
} else
ret = -EAGAIN;
if (!ret)
dev_kfree_skb(skb);
return(ret);
}
static int
w6692_l2l1(mISDNinstance_t *inst, struct sk_buff *skb)
{
channel_t *chan = container_of(inst, channel_t, inst);
int ret = 0;
mISDN_head_t *hh = mISDN_HEAD_P(skb);
u_long flags;
if ((hh->prim == PH_DATA_REQ) || (hh->prim == DL_DATA_REQ)) {
spin_lock_irqsave(inst->hwlock, flags);
ret = channel_senddata(chan, hh->dinfo, skb);
if (ret > 0) { /* direct TX */
if (test_bit(FLG_BCHANNEL, &chan->Flags))
W6692_fill_Dfifo(chan->hw);
else if (test_bit(FLG_DCHANNEL, &chan->Flags))
W6692_fill_Bfifo(chan);
else
int_error();
ret = 0;
}
spin_unlock_irqrestore(inst->hwlock, flags);
return(ret);
}
if (test_bit(FLG_DCHANNEL, &chan->Flags)) {
ret = w6692_dmsg(chan, skb);
if (ret != -EAGAIN)
return(ret);
ret = -EINVAL;
}
if (test_bit(FLG_BCHANNEL, &chan->Flags)) {
ret = w6692_bmsg(chan, skb);
if (ret != -EAGAIN)
return(ret);
ret = -EINVAL;
}
if (!ret)
dev_kfree_skb(skb);
return(ret);
}
int __init
setup_w6692(w6692pci *card)
{
u_int val;
if (!request_region(card->addr, 256, "w6692")) {
printk(KERN_WARNING
"mISDN: %s config port %x-%x already in use\n",
"w6692",
card->addr,
card->addr + 255);
return(-EIO);
}
W6692Version(card, "W6692:");
val = ReadW6692(card, W_ISTA);
if (debug)
printk(KERN_DEBUG "W6692 ISTA=0x%X\n", val);
val = ReadW6692(card, W_IMASK);
if (debug)
printk(KERN_DEBUG "W6692 IMASK=0x%X\n", val);
val = ReadW6692(card, W_D_EXIR);
if (debug)
printk(KERN_DEBUG "W6692 D_EXIR=0x%X\n", val);
val = ReadW6692(card, W_D_EXIM);
if (debug)
printk(KERN_DEBUG "W6692 D_EXIM=0x%X\n", val);
val = ReadW6692(card, W_D_RSTA);
if (debug)
printk(KERN_DEBUG "W6692 D_RSTA=0x%X\n", val);
return (0);
}
static void
release_card(w6692pci *card)
{
u_long flags;
spin_lock_irqsave(&card->lock, flags);
disable_hwirq(card);
spin_unlock_irqrestore(&card->lock, flags);
free_irq(card->irq, card);
spin_lock_irqsave(&card->lock, flags);
mode_w6692(&card->bch[0], 0, ISDN_PID_NONE);
mode_w6692(&card->bch[1], 1, ISDN_PID_NONE);
if (card->led || card->subtype == W6692_USR) {
card->xdata |= 0x04; /* LED OFF */
WriteW6692(card, W_XDATA, card->xdata);
}
release_region(card->addr, 256);
mISDN_freechannel(&card->bch[1]);
mISDN_freechannel(&card->bch[0]);
mISDN_freechannel(&card->dch);
spin_unlock_irqrestore(&card->lock, flags);
mISDN_ctrl(&card->dch.inst, MGR_UNREGLAYER | REQUEST, NULL);
spin_lock_irqsave(&w6692.lock, flags);
list_del(&card->list);
spin_unlock_irqrestore(&w6692.lock, flags);
pci_disable_device(card->pdev);
pci_set_drvdata(card->pdev, NULL);
kfree(card);
}
static int
w6692_manager(void *data, u_int prim, void *arg) {
w6692pci *card;
mISDNinstance_t *inst = data;
struct sk_buff *skb;
int channel = -1;
u_long flags;
if (debug & 0x10000)
printk(KERN_DEBUG "%s: data(%p) prim(%x) arg(%p)\n",
__FUNCTION__, data, prim, arg);
if (!data) {
MGR_HASPROTOCOL_HANDLER(prim,arg,&w6692)
printk(KERN_ERR "%s: no data prim %x arg %p\n",
__FUNCTION__, prim, arg);
return(-EINVAL);
}
spin_lock_irqsave(&w6692.lock, flags);
list_for_each_entry(card, &w6692.ilist, list) {
if (&card->dch.inst == inst) {
channel = 2;
break;
}
if (&card->bch[0].inst == inst) {
channel = 0;
break;
}
if (&card->bch[1].inst == inst) {
channel = 1;
break;
}
}
spin_unlock_irqrestore(&w6692.lock, flags);
if (channel<0) {
printk(KERN_WARNING "%s: no channel data %p prim %x arg %p\n",
__FUNCTION__, data, prim, arg);
return(-EINVAL);
}
switch(prim) {
case MGR_REGLAYER | CONFIRM:
if (channel == 2)
mISDN_setpara(&card->dch, &inst->st->para);
else
mISDN_setpara(&card->bch[channel], &inst->st->para);
break;
case MGR_UNREGLAYER | REQUEST:
if ((skb = create_link_skb(PH_CONTROL | REQUEST,
HW_DEACTIVATE, 0, NULL, 0))) {
if (w6692_l2l1(inst, skb))
dev_kfree_skb(skb);
} else
printk(KERN_WARNING "no SKB in %s MGR_UNREGLAYER | REQUEST\n", __FUNCTION__);
mISDN_ctrl(inst, MGR_UNREGLAYER | REQUEST, NULL);
break;
case MGR_CLRSTPARA | INDICATION:
arg = NULL;
case MGR_ADDSTPARA | INDICATION:
if (channel == 2)
mISDN_setpara(&card->dch, arg);
else
mISDN_setpara(&card->bch[channel], arg);
break;
case MGR_RELEASE | INDICATION:
if (channel == 2) {
release_card(card);
} else {
w6692.refcnt--;
}
break;
case MGR_SETSTACK | INDICATION:
if ((channel!=2) && (inst->pid.global == 2)) {
if ((skb = create_link_skb(PH_ACTIVATE | REQUEST,
0, 0, NULL, 0))) {
if (w6692_l2l1(inst, skb))
dev_kfree_skb(skb);
}
if (inst->pid.protocol[2] == ISDN_PID_L2_B_TRANS)
mISDN_queue_data(inst, FLG_MSG_UP, DL_ESTABLISH | INDICATION,
0, 0, NULL, 0);
else
mISDN_queue_data(inst, FLG_MSG_UP, PH_ACTIVATE | INDICATION,
0, 0, NULL, 0);
}
break;
case MGR_GLOBALOPT | REQUEST:
if (arg) {
/* FIXME: detect cards with HEADSET */
u_int *gopt = arg;
*gopt = GLOBALOPT_INTERNAL_CTRL |
GLOBALOPT_EXTERNAL_EQUIPMENT |
GLOBALOPT_HANDSET;
} else
return(-EINVAL);
break;
case MGR_SELCHANNEL | REQUEST:
/* no special procedure */
return(-EINVAL);
PRIM_NOT_HANDLED(MGR_CTRLREADY | INDICATION);
default:
printk(KERN_WARNING "%s: prim %x not handled\n",
__FUNCTION__, prim);
return(-EINVAL);
}
return(0);
}
static int setup_instance(w6692pci *card)
{
int i, err;
mISDN_pid_t pid;
u_long flags;
spin_lock_irqsave(&w6692.lock, flags);
list_add_tail(&card->list, &w6692.ilist);
spin_unlock_irqrestore(&w6692.lock, flags);
card->dch.debug = debug;
spin_lock_init(&card->lock);
card->dch.inst.hwlock = &card->lock;
card->dch.inst.class_dev.dev = &card->pdev->dev;
card->dch.inst.pid.layermask = ISDN_LAYER(0);
card->dch.inst.pid.protocol[0] = ISDN_PID_L0_TE_S0;
mISDN_init_instance(&card->dch.inst, &w6692, card, w6692_l2l1);
sprintf(card->dch.inst.name, "W6692_%d", w6692_cnt+1);
mISDN_set_dchannel_pid(&pid, protocol[w6692_cnt], layermask[w6692_cnt]);
mISDN_initchannel(&card->dch, MSK_INIT_DCHANNEL, MAX_DFRAME_LEN_L1);
card->dch.hw = card;
for (i=0; i<2; i++) {
card->bch[i].channel = i;
mISDN_init_instance(&card->bch[i].inst, &w6692, card, w6692_l2l1);
card->bch[i].inst.pid.layermask = ISDN_LAYER(0);
card->bch[i].inst.hwlock = &card->lock;
card->bch[i].inst.class_dev.dev = &card->pdev->dev;
card->bch[i].debug = debug;
sprintf(card->bch[i].inst.name, "%s B%d", card->dch.inst.name, i+1);
mISDN_initchannel(&card->bch[i], MSK_INIT_BCHANNEL, MAX_DATA_MEM);
card->bch[i].hw = &card->wbc[i];
}
if (debug)
printk(KERN_DEBUG "w6692 card %p dch %p bch1 %p bch2 %p\n",
card, &card->dch, &card->bch[0], &card->bch[1]);
err = setup_w6692(card);
if (err) {
mISDN_freechannel(&card->dch);
mISDN_freechannel(&card->bch[1]);
mISDN_freechannel(&card->bch[0]);
list_del(&card->list);
kfree(card);
return(err);
}
card->pots = pots[w6692_cnt];
card->led = led[w6692_cnt];
w6692_cnt++;
err = mISDN_ctrl(NULL, MGR_NEWSTACK | REQUEST, &card->dch.inst);
if (err) {
release_card(card);
return(err);
}
for (i=0; i<2; i++) {
err = mISDN_ctrl(card->dch.inst.st, MGR_NEWSTACK | REQUEST, &card->bch[i].inst);
if (err) {
printk(KERN_ERR "MGR_ADDSTACK bchan error %d\n", err);
mISDN_ctrl(card->dch.inst.st, MGR_DELSTACK | REQUEST, NULL);
return(err);
}
}
err = mISDN_ctrl(card->dch.inst.st, MGR_SETSTACK | REQUEST, &pid);
if (err) {
printk(KERN_ERR "MGR_SETSTACK REQUEST dch err(%d)\n", err);
mISDN_ctrl(card->dch.inst.st, MGR_DELSTACK | REQUEST, NULL);
return(err);
}
err = init_card(card);
if (err) {
mISDN_ctrl(card->dch.inst.st, MGR_DELSTACK | REQUEST, NULL);
return(err);
}
mISDN_ctrl(card->dch.inst.st, MGR_CTRLREADY | INDICATION, NULL);
printk(KERN_INFO "w6692 %d cards installed\n", w6692_cnt);
return(0);
}
static int __devinit w6692_probe(struct pci_dev *pdev, const struct pci_device_id *ent)
{
int err = -ENOMEM;
w6692pci *card;
w6692_map_t *m = (w6692_map_t *)ent->driver_data;
if (!(card = kmalloc(sizeof(w6692pci), GFP_ATOMIC))) {
printk(KERN_ERR "No kmem for w6692 card\n");
return(err);
}
memset(card, 0, sizeof(w6692pci));
card->pdev = pdev;
card->subtype = m->subtype;
err = pci_enable_device(pdev);
if (err) {
kfree(card);
return(err);
}
printk(KERN_INFO "mISDN_w6692: found adapter %s at %s\n",
m->name, pci_name(pdev));
card->addr = pci_resource_start(pdev, 1);
card->irq = pdev->irq;
pci_set_drvdata(pdev, card);
err = setup_instance(card);
return(err);
}
static void __devexit w6692_remove_pci(struct pci_dev *pdev)
{
w6692pci *card = pci_get_drvdata(pdev);
if (card)
mISDN_ctrl(card->dch.inst.st, MGR_DELSTACK | REQUEST, NULL);
else
printk(KERN_WARNING "%s: drvdata allready removed\n", __FUNCTION__);
}
static struct pci_device_id w6692_ids[] = {
{ PCI_VENDOR_ID_DYNALINK, PCI_DEVICE_ID_DYNALINK_IS64PH, PCI_ANY_ID, PCI_ANY_ID,
0, 0, (unsigned long) &w6692_map[0] },
{ PCI_VENDOR_ID_WINBOND2, PCI_DEVICE_ID_WINBOND2_6692, PCI_VENDOR_ID_USR2, PCI_DEVICE_ID_USR2_6692,
0, 0, (unsigned long) &w6692_map[2] },
{ PCI_VENDOR_ID_WINBOND2, PCI_DEVICE_ID_WINBOND2_6692, PCI_ANY_ID, PCI_ANY_ID,
0, 0, (unsigned long) &w6692_map[1] },
{ }
};
MODULE_DEVICE_TABLE(pci, w6692_ids);
static struct pci_driver w6692_driver = {
name: "w6692",
probe: w6692_probe,
remove: __devexit_p(w6692_remove_pci),
id_table: w6692_ids,
};
static char W6692Name[] = "W6692";
static int __init w6692_init(void)
{
int err;
printk(KERN_INFO "Winbond W6692 PCI driver Rev. %s\n", mISDN_getrev(w6692_rev));
#ifdef MODULE
w6692.owner = THIS_MODULE;
#endif
spin_lock_init(&w6692.lock);
INIT_LIST_HEAD(&w6692.ilist);
w6692.name = W6692Name;
w6692.own_ctrl = w6692_manager;
w6692.DPROTO.protocol[0] = ISDN_PID_L0_TE_S0;
w6692.BPROTO.protocol[1] = ISDN_PID_L1_B_64TRANS |
ISDN_PID_L1_B_64HDLC;
w6692.BPROTO.protocol[2] = ISDN_PID_L2_B_TRANS;
if ((err = mISDN_register(&w6692))) {
printk(KERN_ERR "Can't register Winbond W6692 PCI error(%d)\n", err);
return(err);
}
err = pci_register_driver(&w6692_driver);
if (err < 0)
goto out;
#ifdef OLD_PCI_REGISTER_DRIVER
if (err == 0) {
err = -ENODEV;
pci_unregister_driver(&w6692_driver);
goto out;
}
#endif
mISDN_module_register(THIS_MODULE);
return 0;
out:
mISDN_unregister(&w6692);
return err;
}
static void __exit w6692_cleanup(void)
{
int err;
w6692pci *card, *next;
mISDN_module_unregister(THIS_MODULE);
if ((err = mISDN_unregister(&w6692))) {
printk(KERN_ERR "Can't unregister Winbond W6692 PCI error(%d)\n", err);
}
list_for_each_entry_safe(card, next, &w6692.ilist, list) {
printk(KERN_ERR "Winbond W6692 PCI card struct not empty refs %d\n",
w6692.refcnt);
release_card(card);
}
pci_unregister_driver(&w6692_driver);
}
module_init(w6692_init);
module_exit(w6692_cleanup);