diff options
Diffstat (limited to 'drivers/isdn/act2000/act2000_isa.c')
-rw-r--r-- | drivers/isdn/act2000/act2000_isa.c | 449 |
1 files changed, 449 insertions, 0 deletions
diff --git a/drivers/isdn/act2000/act2000_isa.c b/drivers/isdn/act2000/act2000_isa.c new file mode 100644 index 0000000..bc98d77 --- /dev/null +++ b/drivers/isdn/act2000/act2000_isa.c @@ -0,0 +1,449 @@ +/* $Id: act2000_isa.c,v 1.11.6.3 2001/09/23 22:24:32 kai Exp $ + * + * ISDN lowlevel-module for the IBM ISDN-S0 Active 2000 (ISA-Version). + * + * Author Fritz Elfert + * Copyright by Fritz Elfert <fritz@isdn4linux.de> + * + * This software may be used and distributed according to the terms + * of the GNU General Public License, incorporated herein by reference. + * + * Thanks to Friedemann Baitinger and IBM Germany + * + */ + +#include "act2000.h" +#include "act2000_isa.h" +#include "capi.h" + +static act2000_card *irq2card_map[16]; + +/* + * Reset Controller, then try to read the Card's signature. + + Return: + * 1 = Signature found. + * 0 = Signature not found. + */ +static int +act2000_isa_reset(unsigned short portbase) +{ + unsigned char reg; + int i; + int found; + int serial = 0; + + found = 0; + if ((reg = inb(portbase + ISA_COR)) != 0xff) { + outb(reg | ISA_COR_RESET, portbase + ISA_COR); + mdelay(10); + outb(reg, portbase + ISA_COR); + mdelay(10); + + for (i = 0; i < 16; i++) { + if (inb(portbase + ISA_ISR) & ISA_ISR_SERIAL) + serial |= 0x10000; + serial >>= 1; + } + if (serial == ISA_SER_ID) + found++; + } + return found; +} + +int +act2000_isa_detect(unsigned short portbase) +{ + int ret = 0; + + if (request_region(portbase, ACT2000_PORTLEN, "act2000isa")) { + ret = act2000_isa_reset(portbase); + release_region(portbase, ISA_REGION); + } + return ret; +} + +static irqreturn_t +act2000_isa_interrupt(int irq, void *dev_id, struct pt_regs *regs) +{ + act2000_card *card = irq2card_map[irq]; + u_char istatus; + + if (!card) { + printk(KERN_WARNING + "act2000: Spurious interrupt!\n"); + return IRQ_NONE; + } + istatus = (inb(ISA_PORT_ISR) & 0x07); + if (istatus & ISA_ISR_OUT) { + /* RX fifo has data */ + istatus &= ISA_ISR_OUT_MASK; + outb(0, ISA_PORT_SIS); + act2000_isa_receive(card); + outb(ISA_SIS_INT, ISA_PORT_SIS); + } + if (istatus & ISA_ISR_ERR) { + /* Error Interrupt */ + istatus &= ISA_ISR_ERR_MASK; + printk(KERN_WARNING "act2000: errIRQ\n"); + } + if (istatus) + printk(KERN_DEBUG "act2000: ?IRQ %d %02x\n", irq, istatus); + return IRQ_HANDLED; +} + +static void +act2000_isa_select_irq(act2000_card * card) +{ + unsigned char reg; + + reg = (inb(ISA_PORT_COR) & ~ISA_COR_IRQOFF) | ISA_COR_PERR; + switch (card->irq) { + case 3: + reg = ISA_COR_IRQ03; + break; + case 5: + reg = ISA_COR_IRQ05; + break; + case 7: + reg = ISA_COR_IRQ07; + break; + case 10: + reg = ISA_COR_IRQ10; + break; + case 11: + reg = ISA_COR_IRQ11; + break; + case 12: + reg = ISA_COR_IRQ12; + break; + case 15: + reg = ISA_COR_IRQ15; + break; + } + outb(reg, ISA_PORT_COR); +} + +static void +act2000_isa_enable_irq(act2000_card * card) +{ + act2000_isa_select_irq(card); + /* Enable READ irq */ + outb(ISA_SIS_INT, ISA_PORT_SIS); +} + +/* + * Install interrupt handler, enable irq on card. + * If irq is -1, choose next free irq, else irq is given explicitely. + */ +int +act2000_isa_config_irq(act2000_card * card, short irq) +{ + if (card->flags & ACT2000_FLAGS_IVALID) { + free_irq(card->irq, NULL); + irq2card_map[card->irq] = NULL; + } + card->flags &= ~ACT2000_FLAGS_IVALID; + outb(ISA_COR_IRQOFF, ISA_PORT_COR); + if (!irq) + return 0; + + if (!request_irq(irq, &act2000_isa_interrupt, 0, card->regname, NULL)) { + card->irq = irq; + irq2card_map[card->irq] = card; + card->flags |= ACT2000_FLAGS_IVALID; + printk(KERN_WARNING + "act2000: Could not request irq %d\n",irq); + return -EBUSY; + } else { + act2000_isa_select_irq(card); + /* Disable READ and WRITE irq */ + outb(0, ISA_PORT_SIS); + outb(0, ISA_PORT_SOS); + } + return 0; +} + +int +act2000_isa_config_port(act2000_card * card, unsigned short portbase) +{ + if (card->flags & ACT2000_FLAGS_PVALID) { + release_region(card->port, ISA_REGION); + card->flags &= ~ACT2000_FLAGS_PVALID; + } + if (request_region(portbase, ACT2000_PORTLEN, card->regname) == NULL) + return -EBUSY; + else { + card->port = portbase; + card->flags |= ACT2000_FLAGS_PVALID; + return 0; + } +} + +/* + * Release ressources, used by an adaptor. + */ +void +act2000_isa_release(act2000_card * card) +{ + unsigned long flags; + + spin_lock_irqsave(&card->lock, flags); + if (card->flags & ACT2000_FLAGS_IVALID) { + free_irq(card->irq, NULL); + irq2card_map[card->irq] = NULL; + } + card->flags &= ~ACT2000_FLAGS_IVALID; + if (card->flags & ACT2000_FLAGS_PVALID) + release_region(card->port, ISA_REGION); + card->flags &= ~ACT2000_FLAGS_PVALID; + spin_unlock_irqrestore(&card->lock, flags); +} + +static int +act2000_isa_writeb(act2000_card * card, u_char data) +{ + u_char timeout = 40; + + while (timeout) { + if (inb(ISA_PORT_SOS) & ISA_SOS_READY) { + outb(data, ISA_PORT_SDO); + return 0; + } else { + timeout--; + udelay(10); + } + } + return 1; +} + +static int +act2000_isa_readb(act2000_card * card, u_char * data) +{ + u_char timeout = 40; + + while (timeout) { + if (inb(ISA_PORT_SIS) & ISA_SIS_READY) { + *data = inb(ISA_PORT_SDI); + return 0; + } else { + timeout--; + udelay(10); + } + } + return 1; +} + +void +act2000_isa_receive(act2000_card *card) +{ + u_char c; + + if (test_and_set_bit(ACT2000_LOCK_RX, (void *) &card->ilock) != 0) + return; + while (!act2000_isa_readb(card, &c)) { + if (card->idat.isa.rcvidx < 8) { + card->idat.isa.rcvhdr[card->idat.isa.rcvidx++] = c; + if (card->idat.isa.rcvidx == 8) { + int valid = actcapi_chkhdr(card, (actcapi_msghdr *)&card->idat.isa.rcvhdr); + + if (valid) { + card->idat.isa.rcvlen = ((actcapi_msghdr *)&card->idat.isa.rcvhdr)->len; + card->idat.isa.rcvskb = dev_alloc_skb(card->idat.isa.rcvlen); + if (card->idat.isa.rcvskb == NULL) { + card->idat.isa.rcvignore = 1; + printk(KERN_WARNING + "act2000_isa_receive: no memory\n"); + test_and_clear_bit(ACT2000_LOCK_RX, (void *) &card->ilock); + return; + } + memcpy(skb_put(card->idat.isa.rcvskb, 8), card->idat.isa.rcvhdr, 8); + card->idat.isa.rcvptr = skb_put(card->idat.isa.rcvskb, card->idat.isa.rcvlen - 8); + } else { + card->idat.isa.rcvidx = 0; + printk(KERN_WARNING + "act2000_isa_receive: Invalid CAPI msg\n"); + { + int i; __u8 *p; __u8 *c; __u8 tmp[30]; + for (i = 0, p = (__u8 *)&card->idat.isa.rcvhdr, c = tmp; i < 8; i++) + c += sprintf(c, "%02x ", *(p++)); + printk(KERN_WARNING "act2000_isa_receive: %s\n", tmp); + } + } + } + } else { + if (!card->idat.isa.rcvignore) + *card->idat.isa.rcvptr++ = c; + if (++card->idat.isa.rcvidx >= card->idat.isa.rcvlen) { + if (!card->idat.isa.rcvignore) { + skb_queue_tail(&card->rcvq, card->idat.isa.rcvskb); + act2000_schedule_rx(card); + } + card->idat.isa.rcvidx = 0; + card->idat.isa.rcvlen = 8; + card->idat.isa.rcvignore = 0; + card->idat.isa.rcvskb = NULL; + card->idat.isa.rcvptr = card->idat.isa.rcvhdr; + } + } + } + if (!(card->flags & ACT2000_FLAGS_IVALID)) { + /* In polling mode, schedule myself */ + if ((card->idat.isa.rcvidx) && + (card->idat.isa.rcvignore || + (card->idat.isa.rcvidx < card->idat.isa.rcvlen))) + act2000_schedule_poll(card); + } + test_and_clear_bit(ACT2000_LOCK_RX, (void *) &card->ilock); +} + +void +act2000_isa_send(act2000_card * card) +{ + unsigned long flags; + struct sk_buff *skb; + actcapi_msg *msg; + int l; + + if (test_and_set_bit(ACT2000_LOCK_TX, (void *) &card->ilock) != 0) + return; + while (1) { + spin_lock_irqsave(&card->lock, flags); + if (!(card->sbuf)) { + if ((card->sbuf = skb_dequeue(&card->sndq))) { + card->ack_msg = card->sbuf->data; + msg = (actcapi_msg *)card->sbuf->data; + if ((msg->hdr.cmd.cmd == 0x86) && + (msg->hdr.cmd.subcmd == 0) ) { + /* Save flags in message */ + card->need_b3ack = msg->msg.data_b3_req.flags; + msg->msg.data_b3_req.flags = 0; + } + } + } + spin_unlock_irqrestore(&card->lock, flags); + if (!(card->sbuf)) { + /* No more data to send */ + test_and_clear_bit(ACT2000_LOCK_TX, (void *) &card->ilock); + return; + } + skb = card->sbuf; + l = 0; + while (skb->len) { + if (act2000_isa_writeb(card, *(skb->data))) { + /* Fifo is full, but more data to send */ + test_and_clear_bit(ACT2000_LOCK_TX, (void *) &card->ilock); + /* Schedule myself */ + act2000_schedule_tx(card); + return; + } + skb_pull(skb, 1); + l++; + } + msg = (actcapi_msg *)card->ack_msg; + if ((msg->hdr.cmd.cmd == 0x86) && + (msg->hdr.cmd.subcmd == 0) ) { + /* + * If it's user data, reset data-ptr + * and put skb into ackq. + */ + skb->data = card->ack_msg; + /* Restore flags in message */ + msg->msg.data_b3_req.flags = card->need_b3ack; + skb_queue_tail(&card->ackq, skb); + } else + dev_kfree_skb(skb); + card->sbuf = NULL; + } +} + +/* + * Get firmware ID, check for 'ISDN' signature. + */ +static int +act2000_isa_getid(act2000_card * card) +{ + + act2000_fwid fid; + u_char *p = (u_char *) & fid; + int count = 0; + + while (1) { + if (count > 510) + return -EPROTO; + if (act2000_isa_readb(card, p++)) + break; + count++; + } + if (count <= 20) { + printk(KERN_WARNING "act2000: No Firmware-ID!\n"); + return -ETIME; + } + *p = '\0'; + fid.revlen[0] = '\0'; + if (strcmp(fid.isdn, "ISDN")) { + printk(KERN_WARNING "act2000: Wrong Firmware-ID!\n"); + return -EPROTO; + } + if ((p = strchr(fid.revision, '\n'))) + *p = '\0'; + printk(KERN_INFO "act2000: Firmware-ID: %s\n", fid.revision); + if (card->flags & ACT2000_FLAGS_IVALID) { + printk(KERN_DEBUG "Enabling Interrupts ...\n"); + act2000_isa_enable_irq(card); + } + return 0; +} + +/* + * Download microcode into card, check Firmware signature. + */ +int +act2000_isa_download(act2000_card * card, act2000_ddef __user * cb) +{ + unsigned int length; + int l; + int c; + long timeout; + u_char *b; + u_char __user *p; + u_char *buf; + act2000_ddef cblock; + + if (!act2000_isa_reset(card->port)) + return -ENXIO; + msleep_interruptible(500); + if (copy_from_user(&cblock, cb, sizeof(cblock))) + return -EFAULT; + length = cblock.length; + p = cblock.buffer; + if (!access_ok(VERIFY_READ, p, length)) + return -EFAULT; + buf = (u_char *) kmalloc(1024, GFP_KERNEL); + if (!buf) + return -ENOMEM; + timeout = 0; + while (length) { + l = (length > 1024) ? 1024 : length; + c = 0; + b = buf; + if (copy_from_user(buf, p, l)) { + kfree(buf); + return -EFAULT; + } + while (c < l) { + if (act2000_isa_writeb(card, *b++)) { + printk(KERN_WARNING + "act2000: loader timed out" + " len=%d c=%d\n", length, c); + kfree(buf); + return -ETIME; + } + c++; + } + length -= l; + p += l; + } + kfree(buf); + msleep_interruptible(500); + return (act2000_isa_getid(card)); +} |