| /* $Id: tpam_main.c,v 1.1.2.3 2001/09/23 22:25:03 kai Exp $ |
| * |
| * Turbo PAM ISDN driver for Linux. (Kernel Driver - main routines) |
| * |
| * Copyright 2001 Stelian Pop <stelian.pop@fr.alcove.com>, AlcĂ´ve |
| * |
| * This software may be used and distributed according to the terms |
| * of the GNU General Public License, incorporated herein by reference. |
| * |
| * For all support questions please contact: <support@auvertech.fr> |
| * |
| */ |
| |
| #include <linux/module.h> |
| #include <linux/pci.h> |
| #include <linux/sched.h> |
| #include <linux/interrupt.h> |
| |
| #include <linux/init.h> |
| #include <asm/io.h> |
| |
| #include "tpam.h" |
| |
| /* Local functions prototypes */ |
| static int __devinit tpam_probe(struct pci_dev *, const struct pci_device_id *); |
| static void __devexit tpam_unregister_card(struct pci_dev *, tpam_card *); |
| static void __devexit tpam_remove(struct pci_dev *); |
| static int __init tpam_init(void); |
| static void __exit tpam_exit(void); |
| |
| /* List of boards */ |
| static tpam_card *cards; /* = NULL; */ |
| /* Number of cards */ |
| static int cards_num; |
| /* Configurable id of the driver */ |
| static char *id = "tpam\0\0\0\0\0\0\0\0\0\0\0\0"; |
| |
| MODULE_DESCRIPTION("ISDN4Linux: Driver for TurboPAM ISDN cards"); |
| MODULE_AUTHOR("Stelian Pop"); |
| MODULE_LICENSE("GPL"); |
| MODULE_PARM_DESC(id,"ID-String of the driver"); |
| module_param(id, charp, 0); |
| |
| /* |
| * Finds a board by its driver ID. |
| * |
| * driverId: driver ID (as referenced by the IDSN link layer) |
| * |
| * Return: the tpam_card structure if found, NULL on error. |
| */ |
| tpam_card *tpam_findcard(int driverid) { |
| tpam_card *p = cards; |
| |
| while (p) { |
| if (p->id == driverid) |
| return p; |
| p = p->next; |
| } |
| return NULL; |
| } |
| |
| /* |
| * Finds a channel number by its ncoid. |
| * |
| * card: the board |
| * ncoid: the NCO id |
| * |
| * Return: the channel number if found, TPAM_CHANNEL_INVALID if not. |
| */ |
| u32 tpam_findchannel(tpam_card *card, u32 ncoid) { |
| int i; |
| |
| for (i = 0; i < TPAM_NBCHANNEL; ++i) |
| if (card->channels[i].ncoid == ncoid) |
| return card->channels[i].num; |
| return TPAM_CHANNEL_INVALID; |
| } |
| |
| /* |
| * Initializes and registers a new TurboPAM card. |
| * |
| * dev: the PCI device |
| * num: the board number |
| * |
| * Return: 0 if OK, <0 if error |
| */ |
| static int __devinit tpam_probe(struct pci_dev *dev, const struct pci_device_id *pci_id) { |
| tpam_card *card, *c; |
| int i, err; |
| |
| if ((err = pci_enable_device(dev))) { |
| printk(KERN_ERR "TurboPAM: can't enable PCI device at %s\n", |
| pci_name(dev)); |
| return err; |
| } |
| |
| /* allocate memory for the board structure */ |
| if (!(card = (tpam_card *)kmalloc(sizeof(tpam_card), GFP_KERNEL))) { |
| printk(KERN_ERR "TurboPAM: tpam_register_card: " |
| "kmalloc failed!\n"); |
| err = -ENOMEM; |
| goto err_out_disable_dev; |
| } |
| |
| memset((char *)card, 0, sizeof(tpam_card)); |
| |
| card->irq = dev->irq; |
| spin_lock_init(&card->lock); |
| sprintf(card->interface.id, "%s%d", id, cards_num); |
| |
| /* request interrupt */ |
| if (request_irq(card->irq, &tpam_irq, SA_INTERRUPT | SA_SHIRQ, |
| card->interface.id, card)) { |
| printk(KERN_ERR "TurboPAM: tpam_register_card: " |
| "could not request irq %d\n", card->irq); |
| err = -EIO; |
| goto err_out_free_card; |
| } |
| |
| /* remap board memory */ |
| if (!(card->bar0 = ioremap(pci_resource_start(dev, 0), |
| 0x800000))) { |
| printk(KERN_ERR "TurboPAM: tpam_register_card: " |
| "unable to remap bar0\n"); |
| err = -EIO; |
| goto err_out_free_irq; |
| } |
| |
| /* reset the board */ |
| readl(card->bar0 + TPAM_RESETPAM_REGISTER); |
| |
| /* initialisation magic :-( */ |
| copy_to_pam_dword(card, 0x01800008, 0x00000030); |
| copy_to_pam_dword(card, 0x01800010, 0x00000030); |
| copy_to_pam_dword(card, 0x01800014, 0x42240822); |
| copy_to_pam_dword(card, 0x01800018, 0x07114000); |
| copy_to_pam_dword(card, 0x0180001c, 0x00000400); |
| copy_to_pam_dword(card, 0x01840070, 0x00000010); |
| |
| /* fill the ISDN link layer structure */ |
| card->interface.owner = THIS_MODULE; |
| card->interface.channels = TPAM_NBCHANNEL; |
| card->interface.maxbufsize = TPAM_MAXBUFSIZE; |
| card->interface.features = |
| ISDN_FEATURE_P_EURO | |
| ISDN_FEATURE_L2_HDLC | |
| ISDN_FEATURE_L2_MODEM | |
| ISDN_FEATURE_L3_TRANS; |
| card->interface.hl_hdrlen = 0; |
| card->interface.command = tpam_command; |
| card->interface.writebuf_skb = tpam_writebuf_skb; |
| card->interface.writecmd = NULL; |
| card->interface.readstat = NULL; |
| |
| /* register wrt the ISDN link layer */ |
| if (!register_isdn(&card->interface)) { |
| printk(KERN_ERR "TurboPAM: tpam_register_card: " |
| "unable to register %s\n", card->interface.id); |
| err = -EIO; |
| goto err_out_iounmap; |
| } |
| card->id = card->interface.channels; |
| |
| /* initialize all channels */ |
| for (i = 0; i < TPAM_NBCHANNEL; ++i) { |
| card->channels[i].num = i; |
| card->channels[i].card = card; |
| card->channels[i].ncoid = TPAM_NCOID_INVALID; |
| card->channels[i].hdlc = 0; |
| card->channels[i].realhdlc = 0; |
| card->channels[i].hdlcshift = 0; |
| skb_queue_head_init(&card->channels[i].sendq); |
| } |
| |
| /* initialize the rest of board structure */ |
| card->channels_used = 0; |
| card->channels_tested = 0; |
| card->running = 0; |
| card->busy = 0; |
| card->roundrobin = 0; |
| card->loopmode = 0; |
| skb_queue_head_init(&card->sendq); |
| skb_queue_head_init(&card->recvq); |
| INIT_WORK(&card->recv_tq, (void *) (void *) tpam_recv_tq, card); |
| INIT_WORK(&card->send_tq, (void *) (void *) tpam_send_tq, card); |
| |
| /* add the board at the end of the list of boards */ |
| card->next = NULL; |
| if (cards) { |
| c = cards; |
| while (c->next) |
| c = c->next; |
| c->next = card; |
| } |
| else |
| cards = card; |
| |
| ++cards_num; |
| pci_set_drvdata(dev, card); |
| |
| return 0; |
| |
| err_out_iounmap: |
| iounmap(card->bar0); |
| |
| err_out_free_irq: |
| free_irq(card->irq, card); |
| |
| err_out_free_card: |
| kfree(card); |
| |
| err_out_disable_dev: |
| pci_disable_device(dev); |
| return err; |
| } |
| |
| /* |
| * Unregisters a TurboPAM board by releasing all its ressources (irq, mem etc). |
| * |
| * card: the board. |
| */ |
| static void __devexit tpam_unregister_card(struct pci_dev *pcidev, tpam_card *card) { |
| isdn_ctrl cmd; |
| |
| /* prevent the ISDN link layer that the driver will be unloaded */ |
| cmd.command = ISDN_STAT_UNLOAD; |
| cmd.driver = card->id; |
| (* card->interface.statcallb)(&cmd); |
| |
| /* release interrupt */ |
| free_irq(card->irq, card); |
| |
| /* release mapped memory */ |
| iounmap(card->bar0); |
| |
| pci_disable_device(pcidev); |
| } |
| |
| /* |
| * Stops the driver. |
| */ |
| static void __devexit tpam_remove(struct pci_dev *pcidev) { |
| tpam_card *card = pci_get_drvdata(pcidev); |
| tpam_card *c; |
| |
| /* remove from the list of cards */ |
| if (card == cards) |
| cards = cards->next; |
| else { |
| c = cards; |
| while (c->next != card) |
| c = c->next; |
| c->next = c->next->next; |
| } |
| |
| /* unregister each board */ |
| tpam_unregister_card(pcidev, card); |
| |
| /* and free the board structure itself */ |
| kfree(card); |
| } |
| |
| static struct pci_device_id tpam_pci_tbl[] = { |
| { PCI_VENDOR_ID_XILINX, PCI_DEVICE_ID_TURBOPAM, |
| PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0 }, |
| { } |
| }; |
| |
| MODULE_DEVICE_TABLE(pci, tpam_pci_tbl); |
| |
| static struct pci_driver tpam_driver = { |
| .name = "tpam", |
| .id_table = tpam_pci_tbl, |
| .probe = tpam_probe, |
| .remove = __devexit_p(tpam_remove), |
| }; |
| |
| static int __init tpam_init(void) { |
| int ret; |
| |
| ret = pci_module_init(&tpam_driver); |
| if (ret) |
| return ret; |
| printk(KERN_INFO "TurboPAM: %d card%s found, driver loaded.\n", |
| cards_num, (cards_num > 1) ? "s" : ""); |
| return 0; |
| } |
| |
| static void __exit tpam_exit(void) { |
| pci_unregister_driver(&tpam_driver); |
| printk(KERN_INFO "TurboPAM: driver unloaded\n"); |
| } |
| |
| /* Module entry points */ |
| module_init(tpam_init); |
| module_exit(tpam_exit); |
| |