| /* | 
|  * Linux ARCnet driver - COM20020 PCMCIA support | 
|  * | 
|  * Written 1994-1999 by Avery Pennarun, | 
|  *    based on an ISA version by David Woodhouse. | 
|  * Derived from ibmtr_cs.c by Steve Kipisz (pcmcia-cs 3.1.4) | 
|  *    which was derived from pcnet_cs.c by David Hinds. | 
|  * Some additional portions derived from skeleton.c by Donald Becker. | 
|  * | 
|  * Special thanks to Contemporary Controls, Inc. (www.ccontrols.com) | 
|  *  for sponsoring the further development of this driver. | 
|  * | 
|  * ********************** | 
|  * | 
|  * The original copyright of skeleton.c was as follows: | 
|  * | 
|  * skeleton.c Written 1993 by Donald Becker. | 
|  * Copyright 1993 United States Government as represented by the | 
|  * Director, National Security Agency.  This software may only be used | 
|  * and distributed according to the terms of the GNU General Public License as | 
|  * modified by SRC, incorporated herein by reference. | 
|  * | 
|  * ********************** | 
|  * Changes: | 
|  * Arnaldo Carvalho de Melo <acme@conectiva.com.br> - 08/08/2000 | 
|  * - reorganize kmallocs in com20020_attach, checking all for failure | 
|  *   and releasing the previous allocations if one fails | 
|  * ********************** | 
|  * | 
|  * For more details, see drivers/net/arcnet.c | 
|  * | 
|  * ********************** | 
|  */ | 
|   | 
| #define pr_fmt(fmt) "arcnet:" KBUILD_MODNAME ": " fmt | 
|   | 
| #include <linux/kernel.h> | 
| #include <linux/ptrace.h> | 
| #include <linux/slab.h> | 
| #include <linux/string.h> | 
| #include <linux/timer.h> | 
| #include <linux/delay.h> | 
| #include <linux/module.h> | 
| #include <linux/netdevice.h> | 
| #include <linux/io.h> | 
| #include <pcmcia/cistpl.h> | 
| #include <pcmcia/ds.h> | 
|   | 
| #include "arcdevice.h" | 
| #include "com20020.h" | 
|   | 
| static void regdump(struct net_device *dev) | 
| { | 
| #ifdef DEBUG | 
|     int ioaddr = dev->base_addr; | 
|     int count; | 
|   | 
|     netdev_dbg(dev, "register dump:\n"); | 
|     for (count = 0; count < 16; count++) { | 
|         if (!(count % 16)) | 
|             pr_cont("%04X:", ioaddr + count); | 
|         pr_cont(" %02X", arcnet_inb(ioaddr, count)); | 
|     } | 
|     pr_cont("\n"); | 
|   | 
|     netdev_dbg(dev, "buffer0 dump:\n"); | 
|     /* set up the address register */ | 
|     count = 0; | 
|     arcnet_outb((count >> 8) | RDDATAflag | AUTOINCflag, | 
|             ioaddr, com20020_REG_W_ADDR_HI); | 
|     arcnet_outb(count & 0xff, ioaddr, COM20020_REG_W_ADDR_LO); | 
|   | 
|     for (count = 0; count < 256 + 32; count++) { | 
|         if (!(count % 16)) | 
|             pr_cont("%04X:", count); | 
|   | 
|         /* copy the data */ | 
|         pr_cont(" %02X", arcnet_inb(ioaddr, COM20020_REG_RW_MEMDATA)); | 
|     } | 
|     pr_cont("\n"); | 
| #endif | 
| } | 
|   | 
| /*====================================================================*/ | 
|   | 
| /* Parameters that can be set with 'insmod' */ | 
|   | 
| static int node; | 
| static int timeout = 3; | 
| static int backplane; | 
| static int clockp; | 
| static int clockm; | 
|   | 
| module_param(node, int, 0); | 
| module_param(timeout, int, 0); | 
| module_param(backplane, int, 0); | 
| module_param(clockp, int, 0); | 
| module_param(clockm, int, 0); | 
|   | 
| MODULE_LICENSE("GPL"); | 
|   | 
| /*====================================================================*/ | 
|   | 
| static int com20020_config(struct pcmcia_device *link); | 
| static void com20020_release(struct pcmcia_device *link); | 
|   | 
| static void com20020_detach(struct pcmcia_device *p_dev); | 
|   | 
| /*====================================================================*/ | 
|   | 
| static int com20020_probe(struct pcmcia_device *p_dev) | 
| { | 
|     struct com20020_dev *info; | 
|     struct net_device *dev; | 
|     struct arcnet_local *lp; | 
|   | 
|     dev_dbg(&p_dev->dev, "com20020_attach()\n"); | 
|   | 
|     /* Create new network device */ | 
|     info = kzalloc(sizeof(*info), GFP_KERNEL); | 
|     if (!info) | 
|         goto fail_alloc_info; | 
|   | 
|     dev = alloc_arcdev(""); | 
|     if (!dev) | 
|         goto fail_alloc_dev; | 
|   | 
|     lp = netdev_priv(dev); | 
|     lp->timeout = timeout; | 
|     lp->backplane = backplane; | 
|     lp->clockp = clockp; | 
|     lp->clockm = clockm & 3; | 
|     lp->hw.owner = THIS_MODULE; | 
|   | 
|     /* fill in our module parameters as defaults */ | 
|     dev->dev_addr[0] = node; | 
|   | 
|     p_dev->resource[0]->flags |= IO_DATA_PATH_WIDTH_8; | 
|     p_dev->resource[0]->end = 16; | 
|     p_dev->config_flags |= CONF_ENABLE_IRQ; | 
|   | 
|     info->dev = dev; | 
|     p_dev->priv = info; | 
|   | 
|     return com20020_config(p_dev); | 
|   | 
| fail_alloc_dev: | 
|     kfree(info); | 
| fail_alloc_info: | 
|     return -ENOMEM; | 
| } /* com20020_attach */ | 
|   | 
| static void com20020_detach(struct pcmcia_device *link) | 
| { | 
|     struct com20020_dev *info = link->priv; | 
|     struct net_device *dev = info->dev; | 
|   | 
|     dev_dbg(&link->dev, "detach...\n"); | 
|   | 
|     dev_dbg(&link->dev, "com20020_detach\n"); | 
|   | 
|     dev_dbg(&link->dev, "unregister...\n"); | 
|   | 
|     unregister_netdev(dev); | 
|   | 
|     /* this is necessary because we register our IRQ separately | 
|      * from card services. | 
|      */ | 
|     if (dev->irq) | 
|         free_irq(dev->irq, dev); | 
|   | 
|     com20020_release(link); | 
|   | 
|     /* Unlink device structure, free bits */ | 
|     dev_dbg(&link->dev, "unlinking...\n"); | 
|     if (link->priv) { | 
|         dev = info->dev; | 
|         if (dev) { | 
|             dev_dbg(&link->dev, "kfree...\n"); | 
|             free_netdev(dev); | 
|         } | 
|         dev_dbg(&link->dev, "kfree2...\n"); | 
|         kfree(info); | 
|     } | 
|   | 
| } /* com20020_detach */ | 
|   | 
| static int com20020_config(struct pcmcia_device *link) | 
| { | 
|     struct arcnet_local *lp; | 
|     struct com20020_dev *info; | 
|     struct net_device *dev; | 
|     int i, ret; | 
|     int ioaddr; | 
|   | 
|     info = link->priv; | 
|     dev = info->dev; | 
|   | 
|     dev_dbg(&link->dev, "config...\n"); | 
|   | 
|     dev_dbg(&link->dev, "com20020_config\n"); | 
|   | 
|     dev_dbg(&link->dev, "baseport1 is %Xh\n", | 
|         (unsigned int)link->resource[0]->start); | 
|   | 
|     i = -ENODEV; | 
|     link->io_lines = 16; | 
|   | 
|     if (!link->resource[0]->start) { | 
|         for (ioaddr = 0x100; ioaddr < 0x400; ioaddr += 0x10) { | 
|             link->resource[0]->start = ioaddr; | 
|             i = pcmcia_request_io(link); | 
|             if (i == 0) | 
|                 break; | 
|         } | 
|     } else { | 
|         i = pcmcia_request_io(link); | 
|     } | 
|   | 
|     if (i != 0) { | 
|         dev_dbg(&link->dev, "requestIO failed totally!\n"); | 
|         goto failed; | 
|     } | 
|   | 
|     ioaddr = dev->base_addr = link->resource[0]->start; | 
|     dev_dbg(&link->dev, "got ioaddr %Xh\n", ioaddr); | 
|   | 
|     dev_dbg(&link->dev, "request IRQ %d\n", | 
|         link->irq); | 
|     if (!link->irq) { | 
|         dev_dbg(&link->dev, "requestIRQ failed totally!\n"); | 
|         goto failed; | 
|     } | 
|   | 
|     dev->irq = link->irq; | 
|   | 
|     ret = pcmcia_enable_device(link); | 
|     if (ret) | 
|         goto failed; | 
|   | 
|     if (com20020_check(dev)) { | 
|         regdump(dev); | 
|         goto failed; | 
|     } | 
|   | 
|     lp = netdev_priv(dev); | 
|     lp->card_name = "PCMCIA COM20020"; | 
|     lp->card_flags = ARC_CAN_10MBIT; /* pretend all of them can 10Mbit */ | 
|   | 
|     SET_NETDEV_DEV(dev, &link->dev); | 
|   | 
|     i = com20020_found(dev, 0);    /* calls register_netdev */ | 
|   | 
|     if (i != 0) { | 
|         dev_notice(&link->dev, | 
|                "com20020_found() failed\n"); | 
|         goto failed; | 
|     } | 
|   | 
|     netdev_dbg(dev, "port %#3lx, irq %d\n", | 
|            dev->base_addr, dev->irq); | 
|     return 0; | 
|   | 
| failed: | 
|     dev_dbg(&link->dev, "com20020_config failed...\n"); | 
|     com20020_release(link); | 
|     return -ENODEV; | 
| } /* com20020_config */ | 
|   | 
| static void com20020_release(struct pcmcia_device *link) | 
| { | 
|     dev_dbg(&link->dev, "com20020_release\n"); | 
|     pcmcia_disable_device(link); | 
| } | 
|   | 
| static int com20020_suspend(struct pcmcia_device *link) | 
| { | 
|     struct com20020_dev *info = link->priv; | 
|     struct net_device *dev = info->dev; | 
|   | 
|     if (link->open) | 
|         netif_device_detach(dev); | 
|   | 
|     return 0; | 
| } | 
|   | 
| static int com20020_resume(struct pcmcia_device *link) | 
| { | 
|     struct com20020_dev *info = link->priv; | 
|     struct net_device *dev = info->dev; | 
|   | 
|     if (link->open) { | 
|         int ioaddr = dev->base_addr; | 
|         struct arcnet_local *lp = netdev_priv(dev); | 
|   | 
|         arcnet_outb(lp->config | 0x80, ioaddr, COM20020_REG_W_CONFIG); | 
|         udelay(5); | 
|         arcnet_outb(lp->config, ioaddr, COM20020_REG_W_CONFIG); | 
|     } | 
|   | 
|     return 0; | 
| } | 
|   | 
| static const struct pcmcia_device_id com20020_ids[] = { | 
|     PCMCIA_DEVICE_PROD_ID12("Contemporary Control Systems, Inc.", | 
|                 "PCM20 Arcnet Adapter", 0x59991666, 0x95dfffaf), | 
|     PCMCIA_DEVICE_PROD_ID12("SoHard AG", | 
|                 "SH ARC PCMCIA", 0xf8991729, 0x69dff0c7), | 
|     PCMCIA_DEVICE_NULL | 
| }; | 
| MODULE_DEVICE_TABLE(pcmcia, com20020_ids); | 
|   | 
| static struct pcmcia_driver com20020_cs_driver = { | 
|     .owner        = THIS_MODULE, | 
|     .name        = "com20020_cs", | 
|     .probe        = com20020_probe, | 
|     .remove        = com20020_detach, | 
|     .id_table    = com20020_ids, | 
|     .suspend    = com20020_suspend, | 
|     .resume        = com20020_resume, | 
| }; | 
| module_pcmcia_driver(com20020_cs_driver); |