summaryrefslogtreecommitdiffstats
path: root/drivers/staging/sbe-2t3e3/module.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/staging/sbe-2t3e3/module.c')
-rw-r--r--drivers/staging/sbe-2t3e3/module.c210
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);
OpenPOWER on IntegriCloud