| /* 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 */ |
| memcpy(skb_put(skb, cs->rcvidx), 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); |
| } |
| init_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 IsdnCardState *cs) |
| { |
| 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); |
| cs->dbusytimer.function = (void *) dbusy_timer_handler; |
| cs->dbusytimer.data = (long) cs; |
| init_timer(&cs->dbusytimer); |
| } |