diff options
Diffstat (limited to 'drivers/staging/sbe-2t3e3/module.c')
-rw-r--r-- | drivers/staging/sbe-2t3e3/module.c | 210 |
1 files changed, 210 insertions, 0 deletions
diff --git a/drivers/staging/sbe-2t3e3/module.c b/drivers/staging/sbe-2t3e3/module.c new file mode 100644 index 0000000..e87fe81 --- /dev/null +++ b/drivers/staging/sbe-2t3e3/module.c @@ -0,0 +1,210 @@ +/* + * SBE 2T3E3 synchronous serial card driver for Linux + * + * Copyright (C) 2009-2010 Krzysztof Halasa <khc@pm.waw.pl> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of version 2 of the GNU General Public License + * as published by the Free Software Foundation. + * + * This code is based on a driver written by SBE Inc. + */ + +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/delay.h> +#include <linux/netdevice.h> +#include <linux/pci.h> +#include <linux/hdlc.h> +#include <linux/if_arp.h> +#include <linux/interrupt.h> +#include "2t3e3.h" + +static void check_leds(unsigned long arg) +{ + struct card *card = (struct card *)arg; + struct channel *channel0 = &card->channels[0]; + static int blinker; + + update_led(channel0, ++blinker); + if (has_two_ports(channel0->pdev)) + update_led(&card->channels[1], blinker); + + card->timer.expires = jiffies + HZ / 10; + add_timer(&card->timer); +} + +static void t3e3_remove_channel(struct channel *channel) +{ + struct pci_dev *pdev = channel->pdev; + struct net_device *dev = channel->dev; + + /* system hangs if board asserts irq while module is unloaded */ + cpld_stop_intr(channel); + free_irq(dev->irq, dev); + dc_drop_descriptor_list(channel); + unregister_hdlc_device(dev); + free_netdev(dev); + pci_release_regions(pdev); + pci_disable_device(pdev); + pci_set_drvdata(pdev, NULL); +} + +static int __devinit t3e3_init_channel(struct channel *channel, struct pci_dev *pdev, struct card *card) +{ + struct net_device *dev; + unsigned int val; + int err; + + err = pci_enable_device(pdev); + if (err) + return err; + + err = pci_request_regions(pdev, "SBE 2T3E3"); + if (err) + goto disable; + + dev = alloc_hdlcdev(channel); + if (!dev) { + printk(KERN_ERR "SBE 2T3E3" ": Out of memory\n"); + goto free_regions; + } + + t3e3_sc_init(channel); + dev_to_priv(dev) = channel; + + channel->pdev = pdev; + channel->dev = dev; + channel->card = card; + channel->addr = pci_resource_start(pdev, 0); + if (pdev->subsystem_device == PCI_SUBDEVICE_ID_SBE_2T3E3_P1) + channel->h.slot = 1; + else + channel->h.slot = 0; + + if (setup_device(dev, channel)) + goto free_regions; + + pci_read_config_dword(channel->pdev, 0x40, &val); /* mask sleep mode */ + pci_write_config_dword(channel->pdev, 0x40, val & 0x3FFFFFFF); + + pci_read_config_byte(channel->pdev, PCI_CACHE_LINE_SIZE, &channel->h.cache_size); + pci_read_config_dword(channel->pdev, PCI_COMMAND, &channel->h.command); + t3e3_init(channel); + + if (request_irq(dev->irq, &t3e3_intr, IRQF_SHARED, dev->name, dev)) { + printk(KERN_WARNING "%s: could not get irq: %d\n", dev->name, dev->irq); + goto free_regions; + } + + pci_set_drvdata(pdev, channel); + return 0; + +free_regions: + pci_release_regions(pdev); +disable: + pci_disable_device(pdev); + return err; +} + +static void __devexit t3e3_remove_card(struct pci_dev *pdev) +{ + struct channel *channel0 = pci_get_drvdata(pdev); + struct card *card = channel0->card; + + del_timer(&card->timer); + if (has_two_ports(channel0->pdev)) { + t3e3_remove_channel(&card->channels[1]); + pci_dev_put(card->channels[1].pdev); + } + t3e3_remove_channel(channel0); + kfree(card); +} + +static int __devinit t3e3_init_card(struct pci_dev *pdev, const struct pci_device_id *ent) +{ + /* pdev points to channel #0 */ + struct pci_dev *pdev1 = NULL; + struct card *card; + int channels = 1, err; + + if (has_two_ports(pdev)) { + while ((pdev1 = pci_get_subsys(PCI_VENDOR_ID_DEC, PCI_DEVICE_ID_DEC_21142, + PCI_VENDOR_ID_SBE, PCI_SUBDEVICE_ID_SBE_2T3E3_P1, + pdev1))) + if (pdev1->bus == pdev->bus && + pdev1->devfn == pdev->devfn + 8 /* next device on the same bus */) + break; /* found the second channel */ + + if (!pdev1) { + printk(KERN_ERR "SBE 2T3E3" ": Can't find the second channel\n"); + return -EFAULT; + } + channels = 2; + /* holds the reference for pdev1 */ + } + + card = kzalloc(sizeof(struct card) + channels * sizeof(struct channel), GFP_KERNEL); + if (!card) { + printk(KERN_ERR "SBE 2T3E3" ": Out of memory\n"); + return -ENOBUFS; + } + + spin_lock_init(&card->bootrom_lock); + card->bootrom_addr = pci_resource_start(pdev, 0); + + err = t3e3_init_channel(&card->channels[0], pdev, card); + if (err) + goto free_card; + + if (channels == 2) { + err = t3e3_init_channel(&card->channels[1], pdev1, card); + if (err) { + t3e3_remove_channel(&card->channels[0]); + goto free_card; + } + } + + /* start LED timer */ + init_timer(&card->timer); + card->timer.function = check_leds; + card->timer.expires = jiffies + HZ / 10; + card->timer.data = (unsigned long)card; + add_timer(&card->timer); + return 0; + +free_card: + kfree(card); + return err; +} + +static struct pci_device_id t3e3_pci_tbl[] __devinitdata = { + { PCI_VENDOR_ID_DEC, PCI_DEVICE_ID_DEC_21142, + PCI_VENDOR_ID_SBE, PCI_SUBDEVICE_ID_SBE_T3E3, 0, 0, 0 }, + { PCI_VENDOR_ID_DEC, PCI_DEVICE_ID_DEC_21142, + PCI_VENDOR_ID_SBE, PCI_SUBDEVICE_ID_SBE_2T3E3_P0, 0, 0, 0 }, + /* channel 1 will be initialized after channel 0 */ + { 0, } +}; + +static struct pci_driver t3e3_pci_driver = { + .name = "SBE T3E3", + .id_table = t3e3_pci_tbl, + .probe = t3e3_init_card, + .remove = t3e3_remove_card, +}; + +static int __init t3e3_init_module(void) +{ + return pci_register_driver(&t3e3_pci_driver); +} + +static void __exit t3e3_cleanup_module(void) +{ + pci_unregister_driver(&t3e3_pci_driver); +} + +module_init(t3e3_init_module); +module_exit(t3e3_cleanup_module); +MODULE_LICENSE("GPL"); +MODULE_DEVICE_TABLE(pci, t3e3_pci_tbl); |