/* orinoco_nortel.c * * Driver for Prism II devices which would usually be driven by orinoco_cs, * but are connected to the PCI bus by a Nortel PCI-PCMCIA-Adapter. * * Copyright (C) 2002 Tobias Hoffmann * (C) 2003 Christoph Jungegger * * Some of this code is borrowed from orinoco_plx.c * Copyright (C) 2001 Daniel Barlow * Some of this code is borrowed from orinoco_pci.c * Copyright (C) 2001 Jean Tourrilhes * Some of this code is "inspired" by linux-wlan-ng-0.1.10, but nothing * has been copied from it. linux-wlan-ng-0.1.10 is originally : * Copyright (C) 1999 AbsoluteValue Systems, Inc. All Rights Reserved. * * The contents of this file are subject to the Mozilla Public License * Version 1.1 (the "License"); you may not use this file except in * compliance with the License. You may obtain a copy of the License * at http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See * the License for the specific language governing rights and * limitations under the License. * * Alternatively, the contents of this file may be used under the * terms of the GNU General Public License version 2 (the "GPL"), in * which case the provisions of the GPL are applicable instead of the * above. If you wish to allow the use of your version of this file * only under the terms of the GPL and not to allow others to use your * version of this file under the MPL, indicate your decision by * deleting the provisions above and replace them with the notice and * other provisions required by the GPL. If you do not delete the * provisions above, a recipient may use your version of this file * under either the MPL or the GPL. */ #define DRIVER_NAME "orinoco_nortel" #define PFX DRIVER_NAME ": " #include #include #include #include #include #include #include #include "orinoco.h" #define COR_OFFSET (0xe0) /* COR attribute offset of Prism2 PC card */ #define COR_VALUE (COR_LEVEL_REQ | COR_FUNC_ENA) /* Enable PC card with interrupt in level trigger */ /* Nortel specific data */ struct nortel_pci_card { unsigned long iobase1; unsigned long iobase2; }; /* * Do a soft reset of the PCI card using the Configuration Option Register * We need this to get going... * This is the part of the code that is strongly inspired from wlan-ng * * Note bis : Don't try to access HERMES_CMD during the reset phase. * It just won't work ! */ static int nortel_pci_cor_reset(struct orinoco_private *priv) { struct nortel_pci_card *card = priv->card; /* Assert the reset until the card notice */ outw_p(8, card->iobase1 + 2); inw(card->iobase2 + COR_OFFSET); outw_p(0x80, card->iobase2 + COR_OFFSET); mdelay(1); /* Give time for the card to recover from this hard effort */ outw_p(0, card->iobase2 + COR_OFFSET); outw_p(0, card->iobase2 + COR_OFFSET); mdelay(1); /* set COR as usual */ outw_p(COR_VALUE, card->iobase2 + COR_OFFSET); outw_p(COR_VALUE, card->iobase2 + COR_OFFSET); mdelay(1); outw_p(0x228, card->iobase1 + 2); return 0; } int nortel_pci_hw_init(struct nortel_pci_card *card) { int i; u32 reg; /* setup bridge */ if (inw(card->iobase1) & 1) { printk(KERN_ERR PFX "brg1 answer1 wrong\n"); return -EBUSY; } outw_p(0x118, card->iobase1 + 2); outw_p(0x108, card->iobase1 + 2); mdelay(30); outw_p(0x8, card->iobase1 + 2); for (i = 0; i < 30; i++) { mdelay(30); if (inw(card->iobase1) & 0x10) { break; } } if (i == 30) { printk(KERN_ERR PFX "brg1 timed out\n"); return -EBUSY; } if (inw(card->iobase2 + 0xe0) & 1) { printk(KERN_ERR PFX "brg2 answer1 wrong\n"); return -EBUSY; } if (inw(card->iobase2 + 0xe2) & 1) { printk(KERN_ERR PFX "brg2 answer2 wrong\n"); return -EBUSY; } if (inw(card->iobase2 + 0xe4) & 1) { printk(KERN_ERR PFX "brg2 answer3 wrong\n"); return -EBUSY; } /* set the PCMCIA COR-Register */ outw_p(COR_VALUE, card->iobase2 + COR_OFFSET); mdelay(1); reg = inw(card->iobase2 + COR_OFFSET); if (reg != COR_VALUE) { printk(KERN_ERR PFX "Error setting COR value (reg=%x)\n", reg); return -EBUSY; } /* set leds */ outw_p(1, card->iobase1 + 10); return 0; } static int nortel_pci_init_one(struct pci_dev *pdev, const struct pci_device_id *ent) { int err; struct orinoco_private *priv; struct nortel_pci_card *card; struct net_device *dev; void __iomem *iomem; err = pci_enable_device(pdev); if (err) { printk(KERN_ERR PFX "Cannot enable PCI device\n"); return err; } err = pci_request_regions(pdev, DRIVER_NAME); if (err != 0) { printk(KERN_ERR PFX "Cannot obtain PCI resources\n"); goto fail_resources; } iomem = pci_iomap(pdev, 3, 0); if (!iomem) { err = -ENOMEM; goto fail_map_io; } /* Allocate network device */ dev = alloc_orinocodev(sizeof(*card), nortel_pci_cor_reset); if (!dev) { printk(KERN_ERR PFX "Cannot allocate network device\n"); err = -ENOMEM; goto fail_alloc; } priv = netdev_priv(dev); card = priv->card; card->iobase1 = pci_resource_start(pdev, 0); card->iobase2 = pci_resource_start(pdev, 1); dev->base_addr = pci_resource_start(pdev, 2); SET_MODULE_OWNER(dev); SET_NETDEV_DEV(dev, &pdev->dev); hermes_struct_init(&priv->hw, iomem, HERMES_16BIT_REGSPACING); printk(KERN_DEBUG PFX "Detected Nortel PCI device at %s irq:%d, " "io addr:0x%lx\n", pci_name(pdev), pdev->irq, dev->base_addr); err = request_irq(pdev->irq, orinoco_interrupt, SA_SHIRQ, dev->name, dev); if (err) { printk(KERN_ERR PFX "Cannot allocate IRQ %d\n", pdev->irq); err = -EBUSY; goto fail_irq; } dev->irq = pdev->irq; err = nortel_pci_hw_init(card); if (err) { printk(KERN_ERR PFX "Hardware initialization failed\n"); goto fail; } err = nortel_pci_cor_reset(priv); if (err) { printk(KERN_ERR PFX "Initial reset failed\n"); goto fail; } err = register_netdev(dev); if (err) { printk(KERN_ERR PFX "Cannot register network device\n"); goto fail; } pci_set_drvdata(pdev, dev); return 0; fail: free_irq(pdev->irq, dev); fail_irq: pci_set_drvdata(pdev, NULL); free_orinocodev(dev); fail_alloc: pci_iounmap(pdev, iomem); fail_map_io: pci_release_regions(pdev); fail_resources: pci_disable_device(pdev); return err; } static void __devexit nortel_pci_remove_one(struct pci_dev *pdev) { struct net_device *dev = pci_get_drvdata(pdev); struct orinoco_private *priv = netdev_priv(dev); struct nortel_pci_card *card = priv->card; /* clear leds */ outw_p(0, card->iobase1 + 10); unregister_netdev(dev); free_irq(dev->irq, dev); pci_set_drvdata(pdev, NULL); free_orinocodev(dev); pci_iounmap(pdev, priv->hw.iobase); pci_release_regions(pdev); pci_disable_device(pdev); } static struct pci_device_id nortel_pci_id_table[] = { /* Nortel emobility PCI */ {0x126c, 0x8030, PCI_ANY_ID, PCI_ANY_ID,}, {0,}, }; MODULE_DEVICE_TABLE(pci, nortel_pci_id_table); static struct pci_driver nortel_pci_driver = { .name = DRIVER_NAME, .id_table = nortel_pci_id_table, .probe = nortel_pci_init_one, .remove = __devexit_p(nortel_pci_remove_one), }; static char version[] __initdata = DRIVER_NAME " " DRIVER_VERSION " (Tobias Hoffmann & Christoph Jungegger )"; MODULE_AUTHOR("Christoph Jungegger "); MODULE_DESCRIPTION ("Driver for wireless LAN cards using the Nortel PCI bridge"); MODULE_LICENSE("Dual MPL/GPL"); static int __init nortel_pci_init(void) { printk(KERN_DEBUG "%s\n", version); return pci_module_init(&nortel_pci_driver); } static void __exit nortel_pci_exit(void) { pci_unregister_driver(&nortel_pci_driver); ssleep(1); } module_init(nortel_pci_init); module_exit(nortel_pci_exit); /* * Local variables: * c-indent-level: 8 * c-basic-offset: 8 * tab-width: 8 * End: */