162306a36Sopenharmony_ci/*
262306a36Sopenharmony_ci * Linux ARCnet driver - COM20020 PCMCIA support
362306a36Sopenharmony_ci *
462306a36Sopenharmony_ci * Written 1994-1999 by Avery Pennarun,
562306a36Sopenharmony_ci *    based on an ISA version by David Woodhouse.
662306a36Sopenharmony_ci * Derived from ibmtr_cs.c by Steve Kipisz (pcmcia-cs 3.1.4)
762306a36Sopenharmony_ci *    which was derived from pcnet_cs.c by David Hinds.
862306a36Sopenharmony_ci * Some additional portions derived from skeleton.c by Donald Becker.
962306a36Sopenharmony_ci *
1062306a36Sopenharmony_ci * Special thanks to Contemporary Controls, Inc. (www.ccontrols.com)
1162306a36Sopenharmony_ci *  for sponsoring the further development of this driver.
1262306a36Sopenharmony_ci *
1362306a36Sopenharmony_ci * **********************
1462306a36Sopenharmony_ci *
1562306a36Sopenharmony_ci * The original copyright of skeleton.c was as follows:
1662306a36Sopenharmony_ci *
1762306a36Sopenharmony_ci * skeleton.c Written 1993 by Donald Becker.
1862306a36Sopenharmony_ci * Copyright 1993 United States Government as represented by the
1962306a36Sopenharmony_ci * Director, National Security Agency.  This software may only be used
2062306a36Sopenharmony_ci * and distributed according to the terms of the GNU General Public License as
2162306a36Sopenharmony_ci * modified by SRC, incorporated herein by reference.
2262306a36Sopenharmony_ci *
2362306a36Sopenharmony_ci * **********************
2462306a36Sopenharmony_ci * Changes:
2562306a36Sopenharmony_ci * Arnaldo Carvalho de Melo <acme@conectiva.com.br> - 08/08/2000
2662306a36Sopenharmony_ci * - reorganize kmallocs in com20020_attach, checking all for failure
2762306a36Sopenharmony_ci *   and releasing the previous allocations if one fails
2862306a36Sopenharmony_ci * **********************
2962306a36Sopenharmony_ci *
3062306a36Sopenharmony_ci * For more details, see drivers/net/arcnet.c
3162306a36Sopenharmony_ci *
3262306a36Sopenharmony_ci * **********************
3362306a36Sopenharmony_ci */
3462306a36Sopenharmony_ci
3562306a36Sopenharmony_ci#define pr_fmt(fmt) "arcnet:" KBUILD_MODNAME ": " fmt
3662306a36Sopenharmony_ci
3762306a36Sopenharmony_ci#include <linux/kernel.h>
3862306a36Sopenharmony_ci#include <linux/ptrace.h>
3962306a36Sopenharmony_ci#include <linux/slab.h>
4062306a36Sopenharmony_ci#include <linux/string.h>
4162306a36Sopenharmony_ci#include <linux/timer.h>
4262306a36Sopenharmony_ci#include <linux/delay.h>
4362306a36Sopenharmony_ci#include <linux/module.h>
4462306a36Sopenharmony_ci#include <linux/netdevice.h>
4562306a36Sopenharmony_ci#include <linux/io.h>
4662306a36Sopenharmony_ci#include <pcmcia/cistpl.h>
4762306a36Sopenharmony_ci#include <pcmcia/ds.h>
4862306a36Sopenharmony_ci
4962306a36Sopenharmony_ci#include "arcdevice.h"
5062306a36Sopenharmony_ci#include "com20020.h"
5162306a36Sopenharmony_ci
5262306a36Sopenharmony_cistatic void regdump(struct net_device *dev)
5362306a36Sopenharmony_ci{
5462306a36Sopenharmony_ci#ifdef DEBUG
5562306a36Sopenharmony_ci	int ioaddr = dev->base_addr;
5662306a36Sopenharmony_ci	int count;
5762306a36Sopenharmony_ci
5862306a36Sopenharmony_ci	netdev_dbg(dev, "register dump:\n");
5962306a36Sopenharmony_ci	for (count = 0; count < 16; count++) {
6062306a36Sopenharmony_ci		if (!(count % 16))
6162306a36Sopenharmony_ci			pr_cont("%04X:", ioaddr + count);
6262306a36Sopenharmony_ci		pr_cont(" %02X", arcnet_inb(ioaddr, count));
6362306a36Sopenharmony_ci	}
6462306a36Sopenharmony_ci	pr_cont("\n");
6562306a36Sopenharmony_ci
6662306a36Sopenharmony_ci	netdev_dbg(dev, "buffer0 dump:\n");
6762306a36Sopenharmony_ci	/* set up the address register */
6862306a36Sopenharmony_ci	count = 0;
6962306a36Sopenharmony_ci	arcnet_outb((count >> 8) | RDDATAflag | AUTOINCflag,
7062306a36Sopenharmony_ci		    ioaddr, COM20020_REG_W_ADDR_HI);
7162306a36Sopenharmony_ci	arcnet_outb(count & 0xff, ioaddr, COM20020_REG_W_ADDR_LO);
7262306a36Sopenharmony_ci
7362306a36Sopenharmony_ci	for (count = 0; count < 256 + 32; count++) {
7462306a36Sopenharmony_ci		if (!(count % 16))
7562306a36Sopenharmony_ci			pr_cont("%04X:", count);
7662306a36Sopenharmony_ci
7762306a36Sopenharmony_ci		/* copy the data */
7862306a36Sopenharmony_ci		pr_cont(" %02X", arcnet_inb(ioaddr, COM20020_REG_RW_MEMDATA));
7962306a36Sopenharmony_ci	}
8062306a36Sopenharmony_ci	pr_cont("\n");
8162306a36Sopenharmony_ci#endif
8262306a36Sopenharmony_ci}
8362306a36Sopenharmony_ci
8462306a36Sopenharmony_ci/*====================================================================*/
8562306a36Sopenharmony_ci
8662306a36Sopenharmony_ci/* Parameters that can be set with 'insmod' */
8762306a36Sopenharmony_ci
8862306a36Sopenharmony_cistatic int node;
8962306a36Sopenharmony_cistatic int timeout = 3;
9062306a36Sopenharmony_cistatic int backplane;
9162306a36Sopenharmony_cistatic int clockp;
9262306a36Sopenharmony_cistatic int clockm;
9362306a36Sopenharmony_ci
9462306a36Sopenharmony_cimodule_param(node, int, 0);
9562306a36Sopenharmony_cimodule_param(timeout, int, 0);
9662306a36Sopenharmony_cimodule_param(backplane, int, 0);
9762306a36Sopenharmony_cimodule_param(clockp, int, 0);
9862306a36Sopenharmony_cimodule_param(clockm, int, 0);
9962306a36Sopenharmony_ci
10062306a36Sopenharmony_ciMODULE_LICENSE("GPL");
10162306a36Sopenharmony_ci
10262306a36Sopenharmony_ci/*====================================================================*/
10362306a36Sopenharmony_ci
10462306a36Sopenharmony_cistatic int com20020_config(struct pcmcia_device *link);
10562306a36Sopenharmony_cistatic void com20020_release(struct pcmcia_device *link);
10662306a36Sopenharmony_ci
10762306a36Sopenharmony_cistatic void com20020_detach(struct pcmcia_device *p_dev);
10862306a36Sopenharmony_ci
10962306a36Sopenharmony_ci/*====================================================================*/
11062306a36Sopenharmony_ci
11162306a36Sopenharmony_cistatic int com20020_probe(struct pcmcia_device *p_dev)
11262306a36Sopenharmony_ci{
11362306a36Sopenharmony_ci	struct com20020_dev *info;
11462306a36Sopenharmony_ci	struct net_device *dev;
11562306a36Sopenharmony_ci	struct arcnet_local *lp;
11662306a36Sopenharmony_ci	int ret = -ENOMEM;
11762306a36Sopenharmony_ci
11862306a36Sopenharmony_ci	dev_dbg(&p_dev->dev, "com20020_attach()\n");
11962306a36Sopenharmony_ci
12062306a36Sopenharmony_ci	/* Create new network device */
12162306a36Sopenharmony_ci	info = kzalloc(sizeof(*info), GFP_KERNEL);
12262306a36Sopenharmony_ci	if (!info)
12362306a36Sopenharmony_ci		goto fail_alloc_info;
12462306a36Sopenharmony_ci
12562306a36Sopenharmony_ci	dev = alloc_arcdev("");
12662306a36Sopenharmony_ci	if (!dev)
12762306a36Sopenharmony_ci		goto fail_alloc_dev;
12862306a36Sopenharmony_ci
12962306a36Sopenharmony_ci	lp = netdev_priv(dev);
13062306a36Sopenharmony_ci	lp->timeout = timeout;
13162306a36Sopenharmony_ci	lp->backplane = backplane;
13262306a36Sopenharmony_ci	lp->clockp = clockp;
13362306a36Sopenharmony_ci	lp->clockm = clockm & 3;
13462306a36Sopenharmony_ci	lp->hw.owner = THIS_MODULE;
13562306a36Sopenharmony_ci
13662306a36Sopenharmony_ci	/* fill in our module parameters as defaults */
13762306a36Sopenharmony_ci	arcnet_set_addr(dev, node);
13862306a36Sopenharmony_ci
13962306a36Sopenharmony_ci	p_dev->resource[0]->flags |= IO_DATA_PATH_WIDTH_8;
14062306a36Sopenharmony_ci	p_dev->resource[0]->end = 16;
14162306a36Sopenharmony_ci	p_dev->config_flags |= CONF_ENABLE_IRQ;
14262306a36Sopenharmony_ci
14362306a36Sopenharmony_ci	info->dev = dev;
14462306a36Sopenharmony_ci	p_dev->priv = info;
14562306a36Sopenharmony_ci
14662306a36Sopenharmony_ci	ret = com20020_config(p_dev);
14762306a36Sopenharmony_ci	if (ret)
14862306a36Sopenharmony_ci		goto fail_config;
14962306a36Sopenharmony_ci
15062306a36Sopenharmony_ci	return 0;
15162306a36Sopenharmony_ci
15262306a36Sopenharmony_cifail_config:
15362306a36Sopenharmony_ci	free_arcdev(dev);
15462306a36Sopenharmony_cifail_alloc_dev:
15562306a36Sopenharmony_ci	kfree(info);
15662306a36Sopenharmony_cifail_alloc_info:
15762306a36Sopenharmony_ci	return ret;
15862306a36Sopenharmony_ci} /* com20020_attach */
15962306a36Sopenharmony_ci
16062306a36Sopenharmony_cistatic void com20020_detach(struct pcmcia_device *link)
16162306a36Sopenharmony_ci{
16262306a36Sopenharmony_ci	struct com20020_dev *info = link->priv;
16362306a36Sopenharmony_ci	struct net_device *dev = info->dev;
16462306a36Sopenharmony_ci
16562306a36Sopenharmony_ci	dev_dbg(&link->dev, "detach...\n");
16662306a36Sopenharmony_ci
16762306a36Sopenharmony_ci	dev_dbg(&link->dev, "com20020_detach\n");
16862306a36Sopenharmony_ci
16962306a36Sopenharmony_ci	dev_dbg(&link->dev, "unregister...\n");
17062306a36Sopenharmony_ci
17162306a36Sopenharmony_ci	unregister_netdev(dev);
17262306a36Sopenharmony_ci
17362306a36Sopenharmony_ci	/* this is necessary because we register our IRQ separately
17462306a36Sopenharmony_ci	 * from card services.
17562306a36Sopenharmony_ci	 */
17662306a36Sopenharmony_ci	if (dev->irq)
17762306a36Sopenharmony_ci		free_irq(dev->irq, dev);
17862306a36Sopenharmony_ci
17962306a36Sopenharmony_ci	com20020_release(link);
18062306a36Sopenharmony_ci
18162306a36Sopenharmony_ci	/* Unlink device structure, free bits */
18262306a36Sopenharmony_ci	dev_dbg(&link->dev, "unlinking...\n");
18362306a36Sopenharmony_ci	if (link->priv) {
18462306a36Sopenharmony_ci		dev = info->dev;
18562306a36Sopenharmony_ci		if (dev) {
18662306a36Sopenharmony_ci			dev_dbg(&link->dev, "kfree...\n");
18762306a36Sopenharmony_ci			free_arcdev(dev);
18862306a36Sopenharmony_ci		}
18962306a36Sopenharmony_ci		dev_dbg(&link->dev, "kfree2...\n");
19062306a36Sopenharmony_ci		kfree(info);
19162306a36Sopenharmony_ci	}
19262306a36Sopenharmony_ci
19362306a36Sopenharmony_ci} /* com20020_detach */
19462306a36Sopenharmony_ci
19562306a36Sopenharmony_cistatic int com20020_config(struct pcmcia_device *link)
19662306a36Sopenharmony_ci{
19762306a36Sopenharmony_ci	struct arcnet_local *lp;
19862306a36Sopenharmony_ci	struct com20020_dev *info;
19962306a36Sopenharmony_ci	struct net_device *dev;
20062306a36Sopenharmony_ci	int i, ret;
20162306a36Sopenharmony_ci	int ioaddr;
20262306a36Sopenharmony_ci
20362306a36Sopenharmony_ci	info = link->priv;
20462306a36Sopenharmony_ci	dev = info->dev;
20562306a36Sopenharmony_ci
20662306a36Sopenharmony_ci	dev_dbg(&link->dev, "config...\n");
20762306a36Sopenharmony_ci
20862306a36Sopenharmony_ci	dev_dbg(&link->dev, "com20020_config\n");
20962306a36Sopenharmony_ci
21062306a36Sopenharmony_ci	dev_dbg(&link->dev, "baseport1 is %Xh\n",
21162306a36Sopenharmony_ci		(unsigned int)link->resource[0]->start);
21262306a36Sopenharmony_ci
21362306a36Sopenharmony_ci	i = -ENODEV;
21462306a36Sopenharmony_ci	link->io_lines = 16;
21562306a36Sopenharmony_ci
21662306a36Sopenharmony_ci	if (!link->resource[0]->start) {
21762306a36Sopenharmony_ci		for (ioaddr = 0x100; ioaddr < 0x400; ioaddr += 0x10) {
21862306a36Sopenharmony_ci			link->resource[0]->start = ioaddr;
21962306a36Sopenharmony_ci			i = pcmcia_request_io(link);
22062306a36Sopenharmony_ci			if (i == 0)
22162306a36Sopenharmony_ci				break;
22262306a36Sopenharmony_ci		}
22362306a36Sopenharmony_ci	} else {
22462306a36Sopenharmony_ci		i = pcmcia_request_io(link);
22562306a36Sopenharmony_ci	}
22662306a36Sopenharmony_ci
22762306a36Sopenharmony_ci	if (i != 0) {
22862306a36Sopenharmony_ci		dev_dbg(&link->dev, "requestIO failed totally!\n");
22962306a36Sopenharmony_ci		goto failed;
23062306a36Sopenharmony_ci	}
23162306a36Sopenharmony_ci
23262306a36Sopenharmony_ci	ioaddr = dev->base_addr = link->resource[0]->start;
23362306a36Sopenharmony_ci	dev_dbg(&link->dev, "got ioaddr %Xh\n", ioaddr);
23462306a36Sopenharmony_ci
23562306a36Sopenharmony_ci	dev_dbg(&link->dev, "request IRQ %d\n",
23662306a36Sopenharmony_ci		link->irq);
23762306a36Sopenharmony_ci	if (!link->irq) {
23862306a36Sopenharmony_ci		dev_dbg(&link->dev, "requestIRQ failed totally!\n");
23962306a36Sopenharmony_ci		goto failed;
24062306a36Sopenharmony_ci	}
24162306a36Sopenharmony_ci
24262306a36Sopenharmony_ci	dev->irq = link->irq;
24362306a36Sopenharmony_ci
24462306a36Sopenharmony_ci	ret = pcmcia_enable_device(link);
24562306a36Sopenharmony_ci	if (ret)
24662306a36Sopenharmony_ci		goto failed;
24762306a36Sopenharmony_ci
24862306a36Sopenharmony_ci	if (com20020_check(dev)) {
24962306a36Sopenharmony_ci		regdump(dev);
25062306a36Sopenharmony_ci		goto failed;
25162306a36Sopenharmony_ci	}
25262306a36Sopenharmony_ci
25362306a36Sopenharmony_ci	lp = netdev_priv(dev);
25462306a36Sopenharmony_ci	lp->card_name = "PCMCIA COM20020";
25562306a36Sopenharmony_ci	lp->card_flags = ARC_CAN_10MBIT; /* pretend all of them can 10Mbit */
25662306a36Sopenharmony_ci
25762306a36Sopenharmony_ci	SET_NETDEV_DEV(dev, &link->dev);
25862306a36Sopenharmony_ci
25962306a36Sopenharmony_ci	i = com20020_found(dev, 0);	/* calls register_netdev */
26062306a36Sopenharmony_ci
26162306a36Sopenharmony_ci	if (i != 0) {
26262306a36Sopenharmony_ci		dev_notice(&link->dev,
26362306a36Sopenharmony_ci			   "com20020_found() failed\n");
26462306a36Sopenharmony_ci		goto failed;
26562306a36Sopenharmony_ci	}
26662306a36Sopenharmony_ci
26762306a36Sopenharmony_ci	netdev_dbg(dev, "port %#3lx, irq %d\n",
26862306a36Sopenharmony_ci		   dev->base_addr, dev->irq);
26962306a36Sopenharmony_ci	return 0;
27062306a36Sopenharmony_ci
27162306a36Sopenharmony_cifailed:
27262306a36Sopenharmony_ci	dev_dbg(&link->dev, "com20020_config failed...\n");
27362306a36Sopenharmony_ci	com20020_release(link);
27462306a36Sopenharmony_ci	return -ENODEV;
27562306a36Sopenharmony_ci} /* com20020_config */
27662306a36Sopenharmony_ci
27762306a36Sopenharmony_cistatic void com20020_release(struct pcmcia_device *link)
27862306a36Sopenharmony_ci{
27962306a36Sopenharmony_ci	dev_dbg(&link->dev, "com20020_release\n");
28062306a36Sopenharmony_ci	pcmcia_disable_device(link);
28162306a36Sopenharmony_ci}
28262306a36Sopenharmony_ci
28362306a36Sopenharmony_cistatic int com20020_suspend(struct pcmcia_device *link)
28462306a36Sopenharmony_ci{
28562306a36Sopenharmony_ci	struct com20020_dev *info = link->priv;
28662306a36Sopenharmony_ci	struct net_device *dev = info->dev;
28762306a36Sopenharmony_ci
28862306a36Sopenharmony_ci	if (link->open)
28962306a36Sopenharmony_ci		netif_device_detach(dev);
29062306a36Sopenharmony_ci
29162306a36Sopenharmony_ci	return 0;
29262306a36Sopenharmony_ci}
29362306a36Sopenharmony_ci
29462306a36Sopenharmony_cistatic int com20020_resume(struct pcmcia_device *link)
29562306a36Sopenharmony_ci{
29662306a36Sopenharmony_ci	struct com20020_dev *info = link->priv;
29762306a36Sopenharmony_ci	struct net_device *dev = info->dev;
29862306a36Sopenharmony_ci
29962306a36Sopenharmony_ci	if (link->open) {
30062306a36Sopenharmony_ci		int ioaddr = dev->base_addr;
30162306a36Sopenharmony_ci		struct arcnet_local *lp = netdev_priv(dev);
30262306a36Sopenharmony_ci
30362306a36Sopenharmony_ci		arcnet_outb(lp->config | 0x80, ioaddr, COM20020_REG_W_CONFIG);
30462306a36Sopenharmony_ci		udelay(5);
30562306a36Sopenharmony_ci		arcnet_outb(lp->config, ioaddr, COM20020_REG_W_CONFIG);
30662306a36Sopenharmony_ci	}
30762306a36Sopenharmony_ci
30862306a36Sopenharmony_ci	return 0;
30962306a36Sopenharmony_ci}
31062306a36Sopenharmony_ci
31162306a36Sopenharmony_cistatic const struct pcmcia_device_id com20020_ids[] = {
31262306a36Sopenharmony_ci	PCMCIA_DEVICE_PROD_ID12("Contemporary Control Systems, Inc.",
31362306a36Sopenharmony_ci				"PCM20 Arcnet Adapter", 0x59991666, 0x95dfffaf),
31462306a36Sopenharmony_ci	PCMCIA_DEVICE_PROD_ID12("SoHard AG",
31562306a36Sopenharmony_ci				"SH ARC PCMCIA", 0xf8991729, 0x69dff0c7),
31662306a36Sopenharmony_ci	PCMCIA_DEVICE_NULL
31762306a36Sopenharmony_ci};
31862306a36Sopenharmony_ciMODULE_DEVICE_TABLE(pcmcia, com20020_ids);
31962306a36Sopenharmony_ci
32062306a36Sopenharmony_cistatic struct pcmcia_driver com20020_cs_driver = {
32162306a36Sopenharmony_ci	.owner		= THIS_MODULE,
32262306a36Sopenharmony_ci	.name		= "com20020_cs",
32362306a36Sopenharmony_ci	.probe		= com20020_probe,
32462306a36Sopenharmony_ci	.remove		= com20020_detach,
32562306a36Sopenharmony_ci	.id_table	= com20020_ids,
32662306a36Sopenharmony_ci	.suspend	= com20020_suspend,
32762306a36Sopenharmony_ci	.resume		= com20020_resume,
32862306a36Sopenharmony_ci};
32962306a36Sopenharmony_cimodule_pcmcia_driver(com20020_cs_driver);
330