/* * Linux Support Services, Inc. Wildcard T100P T1/PRI card Driver * * Written by Mark Spencer * Matthew Fredrickson * William Meadows * * Copyright (C) 2001, Linux Support Services, Inc. * * All rights reserved. * * 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 of the License, 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 #include #include #include #include #include #include #ifdef STANDALONE_ZAPATA #include "zaptel.h" #else #include #endif #define DELAY 0x30 /* 30 = 15 cycles, 10 = 8 cycles, 0 = 3 cycles */ #define WC_CNTL 0x00 #define WC_OPER 0x01 #define WC_AUXC 0x02 #define WC_AUXD 0x03 #define WC_MASK0 0x04 #define WC_MASK1 0x05 #define WC_INTSTAT 0x06 #define WC_DMAWS 0x08 #define WC_DMAWI 0x0c #define WC_DMAWE 0x10 #define WC_DMARS 0x18 #define WC_DMARI 0x1c #define WC_DMARE 0x20 #define WC_CURPOS 0x24 #define WC_SERC 0x2d #define WC_USERREG 0xc0 #define WC_CLOCK 0x0 #define WC_LEDTEST 0x1 #define WC_VERSION 0x2 #define BIT_CS (1 << 5) #define BIT_OK (1 << 0) #define BIT_TEST (1 << 1) #define BIT_ERROR (1 << 2) #define BIT_ALARM (1 << 3) struct t1xxp { struct pci_dev *dev; char *variety; int usecount; int dead; int alarmtimer; int alarm; int alarmthreshold; unsigned char ledtestreg; unsigned char outbyte; unsigned long ioaddr; dma_addr_t readdma; dma_addr_t writedma; volatile int *writechunk; /* Double-word aligned write memory */ volatile int *readchunk; /* Double-word aligned read memory */ struct zt_span span; /* Span */ struct zt_chan chans[24]; /* Channels */ }; static inline void select_framer(struct t1xxp *wc) { if (wc->outbyte & BIT_CS) { wc->outbyte &= ~BIT_CS; outb(wc->outbyte, wc->ioaddr + WC_AUXD); } } static inline void select_control(struct t1xxp *wc) { if (!(wc->outbyte & BIT_CS)) { wc->outbyte |= BIT_CS; outb(wc->outbyte, wc->ioaddr + WC_AUXD); } } static inline void select_page(struct t1xxp *wc, unsigned char reg) { int page = (reg & 0x30) << 2; if (wc->outbyte != page) { /* Clear high bits */ wc->outbyte &= 0x3f; /* Set page */ wc->outbyte |= page; /* Make sure we've turned on the framer */ wc->outbyte &= ~BIT_CS; outb(wc->outbyte, wc->ioaddr + WC_AUXD); } else if (wc->outbyte & BIT_CS) { /* Check to be sure we don't still have to enable the framer */ select_framer(wc); } } static int t1xxp_open(struct zt_chan *chan) { struct t1xxp *wc = chan->pvt; if (wc->dead) return -ENODEV; wc->usecount++; MOD_INC_USE_COUNT; return 0; } static int t1_get_reg(struct t1xxp *wc, int reg) { unsigned char res; select_page(wc, reg); res = inb(wc->ioaddr + WC_USERREG + ((reg & 0xf) << 2)); return res; } static int t1_set_reg(struct t1xxp *wc, int reg, unsigned char val) { select_page(wc, reg); outb(val, wc->ioaddr + WC_USERREG + ((reg & 0xf) << 2)); return 0; } static int control_set_reg(struct t1xxp *wc, int reg, unsigned char val) { select_control(wc); outb(val, wc->ioaddr + WC_USERREG + ((reg & 0xf) << 2)); return 0; } static int control_get_reg(struct t1xxp *wc, int reg) { unsigned char res; select_control(wc); res = inb(wc->ioaddr + WC_USERREG + ((reg & 0xf) << 2)); return res; } static void t1xxp_release(struct t1xxp *wc) { kfree(wc); printk("Freed a Wildcard\n"); } static int t1xxp_close(struct zt_chan *chan) { struct t1xxp *wc = chan->pvt; wc->usecount--; MOD_DEC_USE_COUNT; /* If we're dead, release us now */ if (!wc->usecount && wc->dead) t1xxp_release(wc); return 0; } static inline void handle_leds(struct t1xxp *wc) { if (wc->alarm) { wc->alarmtimer++; if (wc->alarmtimer == 40) { wc->ledtestreg = wc->ledtestreg | BIT_ALARM; control_set_reg(wc, WC_LEDTEST, wc->ledtestreg); } else if (wc->alarmtimer == 160) { wc->ledtestreg = wc->ledtestreg & ~BIT_ALARM; control_set_reg(wc, WC_LEDTEST, wc->ledtestreg); wc->alarmtimer = 0; } } #if 0 if (wc->alarmtimer == wc->alarmthreshold >> 2) { wc->ledtestreg = wc->ledtestreg ^ BIT_ALARM; control_set_reg(wc, WC_LEDTEST, wc->ledtestreg); } if (wc->alarmtimer == 0xf) { wc->ledtestreg = wc->ledtestreg ^ BIT_ALARM; control_set_reg(wc, WC_LEDTEST, wc->ledtestreg); wc->alarmtimer = -1; wc->alarmthreshold = (wc->alarmthreshold + 1 & 0x1f); } #endif } static void t1xxp_interrupt(int irq, void *dev_id, struct pt_regs *regs) { struct t1xxp *wc = dev_id; unsigned char ints; unsigned char b; static int gotint = 0; ints = inb(wc->ioaddr + WC_INTSTAT); outb(ints, wc->ioaddr + WC_INTSTAT); if (!gotint) { printk("Got interrupt: 0x%04x\n", ints); gotint++; } /* Initialize Write/Buffers to all blank data */ memset((void *)wc->writechunk,0x55,ZT_MAX_CHUNKSIZE * 2 * 2 * 24 * 4); handle_leds(wc); #if 0 if (ints & 0x0f) { tjmodem_transmitprep(wc, ints); tjmodem_receiveprep(wc, ints); } #endif if (ints & 0x10) printk("PCI Master abort\n"); if (ints & 0x20) printk("PCI Target abort\n"); } static int t1xxp_reset(struct t1xxp *wc) { char c; c = t1_get_reg(wc, 0x30); /* Reset the Line Interface */ t1_set_reg(wc, 0x30, c | 0x1); /* Wait 50ms for this to finish */ mdelay(50); /* Clear it */ t1_set_reg(wc, 0x30, c); /* Reset the elastic stores */ t1_set_reg(wc, 0x30, c | 0x40); mdelay(1); t1_set_reg(wc, 0x30, c); return 0; } static int t1xxp_hardware_init(struct t1xxp *wc) { int x; /* Hardware Tigerjet stuff */ /* Reset TJ chip and registers */ outb(DELAY | 0x0e, wc->ioaddr + WC_CNTL); /* Set all outputs to 0 */ outb(0x00, wc->ioaddr + WC_AUXD); /* Set all to outputs except AUX1 (TDO). */ outb(0xfd, wc->ioaddr + WC_AUXC); /* Configure the serial port: double clock, 20ns width, no inversion, MSB first */ outb(0xc8, wc->ioaddr + WC_SERC); /* Internally delay FSC by one */ outb(0x01, wc->ioaddr + 0x2f); /* Back to normal, with automatic DMA wrap around */ outb(DELAY | 0x01, wc->ioaddr + WC_CNTL); /* Make sure serial port and DMA are out of reset */ outb(inb(wc->ioaddr + WC_CNTL) & 0xf9, WC_CNTL); /* Setup DMA Addresses */ outl(wc->writedma, wc->ioaddr + WC_DMAWS); /* Write start */ outl(wc->writedma + ZT_CHUNKSIZE * 96, wc->ioaddr + WC_DMAWI); /* Middle (interrupt) */ outl(wc->writedma + ZT_CHUNKSIZE * 192 - 4, wc->ioaddr + WC_DMAWE); /* End */ outl(wc->readdma, wc->ioaddr + WC_DMARS); /* Read start */ outl(wc->readdma + ZT_CHUNKSIZE * 96, wc->ioaddr + WC_DMARI); /* Middle (interrupt) */ outl(wc->readdma + ZT_CHUNKSIZE * 192 - 4, wc->ioaddr + WC_DMARE); /* End */ printk("Setting up DMA (write/read = %08lx/%08lx)\n", wc->writedma, wc->readdma); /* Clear interrupts */ outb(0xff, wc->ioaddr + WC_INTSTAT); /* Check out the controller */ printk("Controller version: %02x\n", control_get_reg(wc, WC_VERSION)); control_set_reg(wc, WC_LEDTEST, 0xff); control_set_reg(wc, WC_CLOCK, 0x00); /* Pretend we're in alarm */ wc->alarm = 1; #if 0 printk("LED/Test Reg test: expected %02x, got %02x\n", 0x55, control_get_reg(wc, WC_LEDTEST)); control_set_reg(wc, WC_LEDTEST, 0xAA); printk("LED/Test Reg test: expected %02x, got %02x\n", 0xAA, control_get_reg(wc, WC_LEDTEST)); control_set_reg(wc, WC_CLOCK, 0x55); printk("Clock Reg test: expected %02x, got %02x\n", 0x55, control_get_reg(wc, WC_CLOCK)); control_set_reg(wc, WC_CLOCK, 0xAA); printk("Clock Reg test: expected %02x, got %02x\n", 0xAA, control_get_reg(wc, WC_CLOCK)); #endif return 0; #if 0 /* Reset all registers on the 2151 */ for (x=0x20;x<0x40;x++) t1_set_reg(wc, x, 0); for (x=0x60;x<0x80;x++) t1_set_reg(wc, x, 0); /* Sanity check the T1 */ if (t1_get_reg(wc, 0x7c) != 0) { printk("Unable to talk to T1 part\n"); return -ENOSYS; } /* Enable elastic store for transmit and receive, 2.048 Mhz bus */ t1_set_reg(wc, 0x37, 0x8c | 0x01); /* TSync is an output */ t1_set_reg(wc, 0x36, 0x04); /* RSync is an input (Elastic Store) */ t1_set_reg(wc, 0x2c, 0x08); /* ESF/B8ZS */ t1_set_reg(wc, 0x38, 0xcc); { /* XXX Take me out XXX */ t1_set_reg(wc, 0x2d, 0xff); t1_set_reg(wc, 0x2e, 0xff); t1_set_reg(wc, 0x2f, 0xff); } return t1xxp_reset(wc); #endif } static void t1xxp_enable_interrupts(struct t1xxp *wc) { /* Enable interrupts (we care about all of them) */ outb(0x3f, wc->ioaddr + WC_MASK0); /* No external interrupts */ outb(0x00, wc->ioaddr + WC_MASK1); } static void t1xxp_start_dma(struct t1xxp *wc) { /* Reset Master and TDM */ outb(DELAY | 0x0f, wc->ioaddr + WC_CNTL); set_current_state(TASK_INTERRUPTIBLE); schedule_timeout(1); outb(DELAY | 0x01, wc->ioaddr + WC_CNTL); outb(0x01, wc->ioaddr + WC_OPER); printk("Started DMA\n"); { int x; for (x=0;x<10;x++) { printk("%d) Read at %08lx\n", x, inl(wc->ioaddr + WC_CURPOS)); udelay(10000); } } printk("0x0: %02x\n", (unsigned int)inb(wc->ioaddr + 0x0)); printk("0x1: %02x\n", (unsigned int)inb(wc->ioaddr + 0x1)); printk("0x2b: %02x\n", (unsigned int)inb(wc->ioaddr + 0x2b)); printk("0x2c: %02x\n", (unsigned int)inb(wc->ioaddr + 0x2c)); printk("0x2d: %02x\n", (unsigned int)inb(wc->ioaddr + 0x2d)); printk("0x2e: %02x\n", (unsigned int)inb(wc->ioaddr + 0x2e)); printk("0x2f: %02x\n", (unsigned int)inb(wc->ioaddr + 0x2f)); } static void t1xxp_stop_dma(struct t1xxp *wc) { outb(0x00, wc->ioaddr + WC_OPER); } static void t1xxp_disable_interrupts(struct t1xxp *wc) { outb(0x00, wc->ioaddr + WC_MASK0); outb(0x00, wc->ioaddr + WC_MASK1); } static int __devinit t1xxp_init_one(struct pci_dev *pdev, const struct pci_device_id *ent) { int res; struct t1xxp *wc; if (pci_enable_device(pdev)) { res = -EIO; } else { wc = kmalloc(sizeof(struct t1xxp), GFP_KERNEL); if (wc) { memset(wc, 0x0, sizeof(struct t1xxp)); wc->ioaddr = pci_resource_start(pdev, 0); wc->dev = pdev; wc->variety = (char *)(ent->driver_data); wc->writechunk = (int *)pci_alloc_consistent(pdev, ZT_MAX_CHUNKSIZE * 2 * 2 * 24 * 4, &wc->writedma); if (!wc->writechunk) { printk("wcmodem: Unable to allocate DMA-able memory\n"); return -ENOMEM; } /* Read is after the whole write piece (in double words) */ wc->readchunk = wc->writechunk + ZT_CHUNKSIZE * 48; /* Same thing, but in bytes */ wc->readdma = wc->writedma + ZT_CHUNKSIZE * 192; /* Initialize Write/Buffers to all blank data */ memset((void *)wc->writechunk,0xff,ZT_MAX_CHUNKSIZE * 2 * 2 * 24 * 4); /* Enable bus mastering */ pci_set_master(pdev); /* Keep track of which device we are */ pci_set_drvdata(pdev, wc); if (request_irq(pdev->irq, t1xxp_interrupt, SA_SHIRQ, "t1xxp", wc)) { printk("t1xxp: Unable to request IRQ %d\n", pdev->irq); kfree(wc); return -EIO; } t1xxp_hardware_init(wc); /* Enable interrupts */ t1xxp_enable_interrupts(wc); /* Initialize Write/Buffers to all blank data */ memset((void *)wc->writechunk,0,ZT_MAX_CHUNKSIZE * 2 * 2 * 24 * 4); /* Start DMA */ t1xxp_start_dma(wc); printk("Found a Wildcard: %s\n", wc->variety); res = 0; } else res = -ENOMEM; } return res; } static void __devexit t1xxp_remove_one(struct pci_dev *pdev) { struct t1xxp *wc = pci_get_drvdata(pdev); if (wc) { /* Stop any DMA */ t1xxp_stop_dma(wc); /* In case hardware is still there */ t1xxp_disable_interrupts(wc); /* Immediately free resources */ pci_free_consistent(pdev, ZT_MAX_CHUNKSIZE * 2 * 2 * 24 * 4, (void *)wc->writechunk, wc->writedma); free_irq(pdev->irq, wc); /* Reset TJ chip and registers */ outb(DELAY | 0x0e, wc->ioaddr + WC_CNTL); /* Release span, possibly delayed */ if (!wc->usecount) t1xxp_release(wc); else wc->dead = 1; } } static struct pci_device_id t1xxp_pci_tbl[] __devinitdata = { { 0xe159, 0x0001, PCI_ANY_ID, PCI_ANY_ID, 0, 0, (unsigned long) "LSS Wildcard T100P T1/PRI Board" }, }; static struct pci_driver t1xxp_driver = { name: "t1xxp", probe: t1xxp_init_one, remove: t1xxp_remove_one, suspend: NULL, resume: NULL, id_table: t1xxp_pci_tbl, }; static int __init t1xxp_init(void) { int res; res = pci_module_init(&t1xxp_driver); if (res) return -ENODEV; return 0; } static void __exit t1xxp_cleanup(void) { pci_unregister_driver(&t1xxp_driver); } MODULE_PARM(debug, "i"); MODULE_DESCRIPTION("Wildcard T100P Zaptel Driver"); MODULE_AUTHOR("Mark Spencer "); module_init(t1xxp_init); module_exit(t1xxp_cleanup);