795 lines
20 KiB
C
795 lines
20 KiB
C
/* 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);
|
|
}
|