diff options
Diffstat (limited to 'drivers/net/pcmcia/com20020_cs.c')
-rw-r--r-- | drivers/net/pcmcia/com20020_cs.c | 509 |
1 files changed, 509 insertions, 0 deletions
diff --git a/drivers/net/pcmcia/com20020_cs.c b/drivers/net/pcmcia/com20020_cs.c new file mode 100644 index 000000000000..4294e1e3f156 --- /dev/null +++ b/drivers/net/pcmcia/com20020_cs.c @@ -0,0 +1,509 @@ +/* + * 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 + * + * ********************** + */ +#include <linux/kernel.h> +#include <linux/init.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/arcdevice.h> +#include <linux/com20020.h> + +#include <pcmcia/version.h> +#include <pcmcia/cs_types.h> +#include <pcmcia/cs.h> +#include <pcmcia/cistpl.h> +#include <pcmcia/ds.h> + +#include <asm/io.h> +#include <asm/system.h> + +#define VERSION "arcnet: COM20020 PCMCIA support loaded.\n" + +#ifdef PCMCIA_DEBUG + +static int pc_debug = PCMCIA_DEBUG; +module_param(pc_debug, int, 0); +#define DEBUG(n, args...) if (pc_debug>(n)) printk(KERN_DEBUG args) + +static void regdump(struct net_device *dev) +{ + int ioaddr = dev->base_addr; + int count; + + printk("com20020 register dump:\n"); + for (count = ioaddr; count < ioaddr + 16; count++) + { + if (!(count % 16)) + printk("\n%04X: ", count); + printk("%02X ", inb(count)); + } + printk("\n"); + + printk("buffer0 dump:\n"); + /* set up the address register */ + count = 0; + outb((count >> 8) | RDDATAflag | AUTOINCflag, _ADDR_HI); + outb(count & 0xff, _ADDR_LO); + + for (count = 0; count < 256+32; count++) + { + if (!(count % 16)) + printk("\n%04X: ", count); + + /* copy the data */ + printk("%02X ", inb(_MEMDATA)); + } + printk("\n"); +} + +#else + +#define DEBUG(n, args...) do { } while (0) +static inline void regdump(struct net_device *dev) { } + +#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 void com20020_config(dev_link_t *link); +static void com20020_release(dev_link_t *link); +static int com20020_event(event_t event, int priority, + event_callback_args_t *args); + +static dev_info_t dev_info = "com20020_cs"; + +static dev_link_t *com20020_attach(void); +static void com20020_detach(dev_link_t *); + +static dev_link_t *dev_list; + +/*====================================================================*/ + +typedef struct com20020_dev_t { + struct net_device *dev; + dev_node_t node; +} com20020_dev_t; + +/*====================================================================== + + com20020_attach() creates an "instance" of the driver, allocating + local data structures for one device. The device is registered + with Card Services. + +======================================================================*/ + +static dev_link_t *com20020_attach(void) +{ + client_reg_t client_reg; + dev_link_t *link; + com20020_dev_t *info; + struct net_device *dev; + int ret; + struct arcnet_local *lp; + + DEBUG(0, "com20020_attach()\n"); + + /* Create new network device */ + link = kmalloc(sizeof(struct dev_link_t), GFP_KERNEL); + if (!link) + return NULL; + + info = kmalloc(sizeof(struct com20020_dev_t), GFP_KERNEL); + if (!info) + goto fail_alloc_info; + + dev = alloc_arcdev(""); + if (!dev) + goto fail_alloc_dev; + + memset(info, 0, sizeof(struct com20020_dev_t)); + memset(link, 0, sizeof(struct dev_link_t)); + lp = dev->priv; + 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; + + link->io.Attributes1 = IO_DATA_PATH_WIDTH_8; + link->io.NumPorts1 = 16; + link->io.IOAddrLines = 16; + link->irq.Attributes = IRQ_TYPE_EXCLUSIVE; + link->irq.IRQInfo1 = IRQ_LEVEL_ID; + link->conf.Attributes = CONF_ENABLE_IRQ; + link->conf.Vcc = 50; + link->conf.IntType = INT_MEMORY_AND_IO; + link->conf.Present = PRESENT_OPTION; + + + link->irq.Instance = info->dev = dev; + link->priv = info; + + /* Register with Card Services */ + link->next = dev_list; + dev_list = link; + client_reg.dev_info = &dev_info; + client_reg.EventMask = + CS_EVENT_CARD_INSERTION | CS_EVENT_CARD_REMOVAL | + CS_EVENT_RESET_PHYSICAL | CS_EVENT_CARD_RESET | + CS_EVENT_PM_SUSPEND | CS_EVENT_PM_RESUME; + client_reg.event_handler = &com20020_event; + client_reg.Version = 0x0210; + client_reg.event_callback_args.client_data = link; + ret = pcmcia_register_client(&link->handle, &client_reg); + if (ret != 0) { + cs_error(link->handle, RegisterClient, ret); + com20020_detach(link); + return NULL; + } + + return link; + +fail_alloc_dev: + kfree(info); +fail_alloc_info: + kfree(link); + return NULL; +} /* com20020_attach */ + +/*====================================================================== + + This deletes a driver "instance". The device is de-registered + with Card Services. If it has been released, all local data + structures are freed. Otherwise, the structures will be freed + when the device is released. + +======================================================================*/ + +static void com20020_detach(dev_link_t *link) +{ + struct com20020_dev_t *info = link->priv; + dev_link_t **linkp; + struct net_device *dev; + + DEBUG(1,"detach...\n"); + + DEBUG(0, "com20020_detach(0x%p)\n", link); + + /* Locate device structure */ + for (linkp = &dev_list; *linkp; linkp = &(*linkp)->next) + if (*linkp == link) break; + if (*linkp == NULL) + return; + + dev = info->dev; + + if (link->dev) { + DEBUG(1,"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); + } + + if (link->state & DEV_CONFIG) + com20020_release(link); + + if (link->handle) + pcmcia_deregister_client(link->handle); + + /* Unlink device structure, free bits */ + DEBUG(1,"unlinking...\n"); + *linkp = link->next; + if (link->priv) + { + dev = info->dev; + if (dev) + { + DEBUG(1,"kfree...\n"); + free_netdev(dev); + } + DEBUG(1,"kfree2...\n"); + kfree(info); + } + DEBUG(1,"kfree3...\n"); + kfree(link); + +} /* com20020_detach */ + +/*====================================================================== + + com20020_config() is scheduled to run after a CARD_INSERTION event + is received, to configure the PCMCIA socket, and to make the + device available to the system. + +======================================================================*/ + +#define CS_CHECK(fn, ret) \ +do { last_fn = (fn); if ((last_ret = (ret)) != 0) goto cs_failed; } while (0) + +static void com20020_config(dev_link_t *link) +{ + struct arcnet_local *lp; + client_handle_t handle; + tuple_t tuple; + cisparse_t parse; + com20020_dev_t *info; + struct net_device *dev; + int i, last_ret, last_fn; + u_char buf[64]; + int ioaddr; + + handle = link->handle; + info = link->priv; + dev = info->dev; + + DEBUG(1,"config...\n"); + + DEBUG(0, "com20020_config(0x%p)\n", link); + + tuple.Attributes = 0; + tuple.TupleData = buf; + tuple.TupleDataMax = 64; + tuple.TupleOffset = 0; + tuple.DesiredTuple = CISTPL_CONFIG; + CS_CHECK(GetFirstTuple, pcmcia_get_first_tuple(handle, &tuple)); + CS_CHECK(GetTupleData, pcmcia_get_tuple_data(handle, &tuple)); + CS_CHECK(ParseTuple, pcmcia_parse_tuple(handle, &tuple, &parse)); + link->conf.ConfigBase = parse.config.base; + + /* Configure card */ + link->state |= DEV_CONFIG; + + DEBUG(1,"arcnet: baseport1 is %Xh\n", link->io.BasePort1); + i = !CS_SUCCESS; + if (!link->io.BasePort1) + { + for (ioaddr = 0x100; ioaddr < 0x400; ioaddr += 0x10) + { + link->io.BasePort1 = ioaddr; + i = pcmcia_request_io(link->handle, &link->io); + if (i == CS_SUCCESS) + break; + } + } + else + i = pcmcia_request_io(link->handle, &link->io); + + if (i != CS_SUCCESS) + { + DEBUG(1,"arcnet: requestIO failed totally!\n"); + goto failed; + } + + ioaddr = dev->base_addr = link->io.BasePort1; + DEBUG(1,"arcnet: got ioaddr %Xh\n", ioaddr); + + DEBUG(1,"arcnet: request IRQ %d (%Xh/%Xh)\n", + link->irq.AssignedIRQ, + link->irq.IRQInfo1, link->irq.IRQInfo2); + i = pcmcia_request_irq(link->handle, &link->irq); + if (i != CS_SUCCESS) + { + DEBUG(1,"arcnet: requestIRQ failed totally!\n"); + goto failed; + } + + dev->irq = link->irq.AssignedIRQ; + + CS_CHECK(RequestConfiguration, pcmcia_request_configuration(link->handle, &link->conf)); + + if (com20020_check(dev)) + { + regdump(dev); + goto failed; + } + + lp = dev->priv; + lp->card_name = "PCMCIA COM20020"; + lp->card_flags = ARC_CAN_10MBIT; /* pretend all of them can 10Mbit */ + + link->dev = &info->node; + link->state &= ~DEV_CONFIG_PENDING; + SET_NETDEV_DEV(dev, &handle_to_dev(handle)); + + i = com20020_found(dev, 0); /* calls register_netdev */ + + if (i != 0) { + DEBUG(1,KERN_NOTICE "com20020_cs: com20020_found() failed\n"); + link->dev = NULL; + goto failed; + } + + strcpy(info->node.dev_name, dev->name); + + DEBUG(1,KERN_INFO "%s: port %#3lx, irq %d\n", + dev->name, dev->base_addr, dev->irq); + return; + +cs_failed: + cs_error(link->handle, last_fn, last_ret); +failed: + DEBUG(1,"com20020_config failed...\n"); + com20020_release(link); +} /* com20020_config */ + +/*====================================================================== + + After a card is removed, com20020_release() will unregister the net + device, and release the PCMCIA configuration. If the device is + still open, this will be postponed until it is closed. + +======================================================================*/ + +static void com20020_release(dev_link_t *link) +{ + + DEBUG(1,"release...\n"); + + DEBUG(0, "com20020_release(0x%p)\n", link); + + pcmcia_release_configuration(link->handle); + pcmcia_release_io(link->handle, &link->io); + pcmcia_release_irq(link->handle, &link->irq); + + link->state &= ~(DEV_CONFIG | DEV_RELEASE_PENDING); +} + +/*====================================================================== + + The card status event handler. Mostly, this schedules other + stuff to run after an event is received. A CARD_REMOVAL event + also sets some flags to discourage the net drivers from trying + to talk to the card any more. + +======================================================================*/ + +static int com20020_event(event_t event, int priority, + event_callback_args_t *args) +{ + dev_link_t *link = args->client_data; + com20020_dev_t *info = link->priv; + struct net_device *dev = info->dev; + + DEBUG(1, "com20020_event(0x%06x)\n", event); + + switch (event) { + case CS_EVENT_CARD_REMOVAL: + link->state &= ~DEV_PRESENT; + if (link->state & DEV_CONFIG) + netif_device_detach(dev); + break; + case CS_EVENT_CARD_INSERTION: + link->state |= DEV_PRESENT; + com20020_config(link); + break; + case CS_EVENT_PM_SUSPEND: + link->state |= DEV_SUSPEND; + /* Fall through... */ + case CS_EVENT_RESET_PHYSICAL: + if (link->state & DEV_CONFIG) { + if (link->open) { + netif_device_detach(dev); + } + pcmcia_release_configuration(link->handle); + } + break; + case CS_EVENT_PM_RESUME: + link->state &= ~DEV_SUSPEND; + /* Fall through... */ + case CS_EVENT_CARD_RESET: + if (link->state & DEV_CONFIG) { + pcmcia_request_configuration(link->handle, &link->conf); + if (link->open) { + int ioaddr = dev->base_addr; + struct arcnet_local *lp = dev->priv; + ARCRESET; + } + } + break; + } + return 0; +} /* com20020_event */ + + + +static struct pcmcia_driver com20020_cs_driver = { + .owner = THIS_MODULE, + .drv = { + .name = "com20020_cs", + }, + .attach = com20020_attach, + .detach = com20020_detach, +}; + +static int __init init_com20020_cs(void) +{ + return pcmcia_register_driver(&com20020_cs_driver); +} + +static void __exit exit_com20020_cs(void) +{ + pcmcia_unregister_driver(&com20020_cs_driver); + BUG_ON(dev_list != NULL); +} + +module_init(init_com20020_cs); +module_exit(exit_com20020_cs); |