/* iBurst (TM) compatible driver for Linux kernel 2.6
 * based on the original ArrayComm (TM) iBurst (TM) driver.
 * Nicholas Jefferson <xanthophile@gmail.com>
 * 11 May 2005
 *
 * Ported to 2.6 Linux kernel by Nik Trevallyn-Jones.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 */

#include "ib-net.h"

#include <asm/io.h>
#include <linux/version.h>
#include <pcmcia/cs_types.h>
#include <pcmcia/cs.h>
#include <pcmcia/cisreg.h>
#include <pcmcia/cistpl.h>
#include <pcmcia/ds.h>

static int debug = 0;
module_param(debug, int, 0);
#define DEBUG(n, args...) if ((n) < debug) printk(KERN_INFO args)

static int irq_list[4] = { -1 };
static int irq_nlist;
#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,10)
module_param_array(irq_list, int, irq_nlist, 0);
#else
module_param_array(irq_list, int, &irq_nlist, 0);
#endif /* KERNEL_VERSION(2,6,10) */

static int irq_mask = 0xdeb8;
module_param(irq_mask, int, 0);

/**
 * ib_pcmcia_interval - poll interval (in milliseconds).
 */
static int interval = 200;
module_param(interval, int, 0);

/**
 * ib_pcmcia_io16 - PCMCIA 16 bit mode.
 */
static int io16 = 0;
module_param(io16, int, 0);

/**
 * enum ib_pcmcia_ifstate - hardware interface state.
 */
enum ib_pcmcia_ifstate
{
	IFSTATE_RESET = 0,
	IFSTATE_WAIT = 1,
	IFSTATE_NORMAL = 2,
};

/**
 * ib_pcmcia_offset - i/o mapped memory offsets.
 */
enum ib_pcmcia_offset
{
	OFFSET_UT_MAGIC1 = 0x00,
	OFFSET_UT_MAGIC2 = 0x01,
	OFFSET_UT_SEQUENCE = 0x02,
	OFFSET_UT_FEEDBACK = 0x03,
	OFFSET_UT_JRX = 0x04,
	OFFSET_UT_ITX = 0x05,
	OFFSET_UT_PACKET = 0x06,
	OFFSET_UT_STATUS = 0x07,
	OFFSET_PC_MAGIC1 = 0x08,
	OFFSET_PC_MAGIC2 = 0x09,
	OFFSET_PC_SEQUENCE = 0x0a,
	OFFSET_PC_FEEDBACK = 0x0b,
	OFFSET_PC_JTX = 0x0c,
	OFFSET_PC_IRX = 0x0d,
	OFFSET_PC_PACKET = 0x0e,
	OFFSET_PC_STATUS = 0x0f,
	OFFSET_UT_ICHUNK = 0x10,
	OFFSET_UT_NCHUNK = 0x11,
	OFFSET_PC_JCHUNK = 0x12,
	OFFSET_PC_NCHUNK = 0x13,
	OFFSET_UT_ETHERNET = 0x1a,
};

/**
 * ib_pcmcia_local_t - PCMCIA device private state.
 * @link: PCMCIA device state.
 * @node: network node.
 * @mem: i/o mapped memory.
 * @ctl: control register in mem.
 * @modem: corresponding modem state.
 * @timer: poll hardware.
 * @release: release configuration timer.
 * @ifstate: hardware state.
 * @sequence: hardware sync protocol.
 * @feedback: hardware sync protocol.
 * @rx_echunk: expected chunks (32 bytes) to receive.
 * @rx_ibuf: next index into rx_buf to use.
 * @rx_nbuf: packet length.
 * @tx_echunk: expected chunks (32 bytes) to transmit.
 * @tx_jbuf: next index into tx_buf to use.
 * @irx: most recent index into i/o mapped receive buffer.
 * @jtx: most recent index into i/o mapped transmit buffer.
 * @nreset: number of hardware resets.
 */
struct ib_pcmcia_local_t {
	struct dev_link_t link;
	struct dev_node_t node;
	u_char *mem;
	u_char *ctl;
	struct ib_net_modem_t *modem;
	struct timer_list timer;
	struct timer_list release;
	enum ib_pcmcia_ifstate ifstate;
	u_int sequence, feedback;
	int rx_echunk, rx_ibuf, rx_nbuf;
	int tx_echunk, tx_jbuf;
	u_int irx, jtx;
	u_int nreset;
};

/**
 * ib_pcmcia_dev_info - identifier for this driver.
 */
static dev_info_t ib_pcmcia_dev_info = "iburst_cs";

/**
 * ib_pcmcia_dev_list - linked list of PCMCIA devices.
 */
static struct dev_link_t *ib_pcmcia_dev_list = NULL;

/**
 * ib_pcmcia_blit - the selected blit function.
 */
static void (*ib_pcmcia_blit)(u_char*, u_char*, int) = NULL;

/**
 * ib_pcmcia_dump - dump chunks to syslog.
 * @msg: output prefix.
 * @buf: chunk buffer.
 * @nchunk: chunk count.
static void ib_pcmcia_dump(u_char *msg, u_char *buf, int nchunk)
{
	int i, j;

	for (i = 0; i < nchunk; i += 1) {
		DEBUG(1, "ib-pcmcia: %s", msg);
		for (j = 0; j < 32; j += 1)
			printk(" %02x", buf[i * 32 + j]);
		printk("\n");
	}
}
 */

/**
 * ib_pcmcia_timer - poll hardware.
 * @_local: device private state.
 */
static void ib_pcmcia_timer(u_long _local)
{
	struct ib_pcmcia_local_t *local = (struct ib_pcmcia_local_t*) _local;

	DEBUG(3,"ib-pcmcia: periodic timer start %lu\n", jiffies);
	ib_net_schedule(local->modem);
	DEBUG(3,"ib-pcmcia: periodic timer middle %lu\n", jiffies);
	local->timer.expires = jiffies + (HZ*interval)/1000;
	local->timer.function = ib_pcmcia_timer;
	local->timer.data = _local;
	add_timer(&local->timer);
	DEBUG(3,"ib-pcmcia: periodic timer end %lu\n", jiffies);
}

/**
 * ib_pcmcia_intr - hardware interrupt; schedule bottom half handler.
 * @irq:
 * @_local: device private state.
 * @pt_regs:
 */
static int ib_pcmcia_intr(int irq, void *_local, struct pt_regs *regs)
{
	struct ib_pcmcia_local_t *local = _local;

	DEBUG(3,"ib-pcmcia: ack irupt from modem\n");
	((unsigned short *)local->ctl)[0] = 1;  /* ack interrupt */
	ib_net_schedule(local->modem);
	DEBUG(3,"ib-pcmcia: ack'd irupt from modem\n");
	return IRQ_HANDLED;
}

/**
 * ib_pcmcia_blit16 - blit chunks (32 bytes) 16 bits at a time.
 * @_dst: destination buffer.
 * @_src: source buffer.
 * @nchunk: chunk count.
 */
static void ib_pcmcia_blit16(u_char *_dst, u_char *_src, int nchunk)
{
	uint16_t *dst = (uint16_t*) _dst;
	uint16_t *src = (uint16_t*) _src;

	while (nchunk--) {
		dst[0] = src[0];
		dst[1] = src[1];
		dst[2] = src[2];
		dst[3] = src[3];
		dst[4] = src[4];
		dst[5] = src[5];
		dst[6] = src[6];
		dst[7] = src[7];
		dst[8] = src[8];
		dst[9] = src[9];
		dst[10] = src[10];
		dst[11] = src[11];
		dst[12] = src[12];
		dst[13] = src[13];
		dst[14] = src[14];
		dst[15] = src[15];
		dst += 16;
		src += 16;
	}
}

/**
 * ib_pcmcia_blit32 - blit chunks (32 bytes) 32 bits at a time.
 * @_dst: destination buffer.
 * @_src: source buffer.
 * @nchunk: chunk count.
 */
static void ib_pcmcia_blit32(u_char *_dst, u_char *_src, int nchunk)
{
	uint32_t *dst = (uint32_t*) _dst;
	uint32_t *src = (uint32_t*) _src;

	while (1 < nchunk) {
		dst[0] = src[0];
		dst[1] = src[1];
		dst[2] = src[2];
		dst[3] = src[3];
		dst[4] = src[4];
		dst[5] = src[5];
		dst[6] = src[6];
		dst[7] = src[7];
		dst[8] = src[8];
		dst[9] = src[9];
		dst[10] = src[10];
		dst[11] = src[11];
		dst[12] = src[12];
		dst[13] = src[13];
		dst[14] = src[14];
		dst[15] = src[15];
		dst += 16;
		src += 16;
		nchunk -= 2;
	}
	if (nchunk) {
		dst[0] = src[0];
		dst[1] = src[1];
		dst[2] = src[2];
		dst[3] = src[3];
		dst[4] = src[4];
		dst[5] = src[5];
		dst[6] = src[6];
		dst[7] = src[7];
	}
}

/**
 * ib_pcmcia_deregister - deregister pcmcia device.
 * @link: device state.
 */
static void ib_pcmcia_deregister(struct dev_link_t *link)
{
	struct dev_link_t **linkp;
	struct ib_pcmcia_local_t *local = link->priv;

	if (local->modem)
		ib_net_deregister(local->modem);
	pcmcia_deregister_client(link->handle);
	for (linkp = &ib_pcmcia_dev_list; *linkp; linkp = &(*linkp)->next)
		if (*linkp == link)
			break;
	if (*linkp == NULL) {
		/* can't happen? */
		DEBUG(1, "ib-pcmcia: deregister *linkp null\n");
		return;
	}
	*linkp = link->next;
	kfree(link->priv);
}

/**
 * ib_pcmcia_release - release configuration.
 * @_link: device state.
 */
static void ib_pcmcia_release(u_long _link)
{
	struct dev_link_t *link = (dev_link_t*) _link;
	struct ib_pcmcia_local_t *local = link->priv;

	if (link->open) {
		DEBUG(1, "ib-pcmcia: release postponed, '%s' still open\n",
				link->dev->dev_name);
		link->state |= DEV_STALE_CONFIG;
		return;
	}
	pcmcia_release_window(link->win);
	pcmcia_release_configuration(link->handle);
	pcmcia_release_irq(link->handle, &link->irq);
	iounmap(local->mem);
	del_timer_sync(&local->timer);
	link->state &= ~DEV_CONFIG;
}

/**
 * ib_pcmcia_detach - detach device.
 * @link: device state.
 */
static void ib_pcmcia_detach(dev_link_t *link)
{
	struct ib_pcmcia_local_t *local = link->priv;

	del_timer(&local->release);
	if (link->state & DEV_CONFIG) {
		ib_pcmcia_release((u_long) link);
		if (link->state & DEV_STALE_CONFIG) {
			link->state |= DEV_STALE_LINK;
			return;
		}
	}
	ib_pcmcia_deregister(link);
}

/**
 * ib_pcmcia_delay - reset hardware protocol delay.
 * @local: device private state.
 */
static void ib_pcmcia_delay(struct ib_pcmcia_local_t *local)
{
	struct ib_net_modem_t *modem = local->modem;
	u_char *mem = local->mem;
	u_int sequence, feedback;

	mem[OFFSET_PC_MAGIC1] = 0xac;
	mem[OFFSET_PC_MAGIC2] = 0x02;
	sequence = mem[OFFSET_UT_SEQUENCE];
	feedback = mem[OFFSET_UT_FEEDBACK];
	if (local->nreset)
		local->sequence = (feedback + 1) & 0xff;
	else
		local->sequence = (feedback + 7) & 0xff;
	local->feedback = sequence;
	mem[OFFSET_PC_SEQUENCE] = local->sequence;
	mem[OFFSET_PC_FEEDBACK] = local->feedback;
	mem[OFFSET_PC_IRX] = local->irx = 0;
	mem[OFFSET_PC_JTX] = local->jtx = 0;
	mem[OFFSET_PC_PACKET] = modem->stats.tx_packets & 0xff;
	mem[OFFSET_PC_STATUS] = modem->status;
	local->ifstate = IFSTATE_WAIT;
	DEBUG(2,"ib-pcmcia: waiting for modem to finish sw connect\n");
}

/**
 * ib_pcmcia_reset - reset hardware protocol.
 * @local: device private state.
 */
static void ib_pcmcia_reset(struct ib_pcmcia_local_t *local)
{
	u_char *mem = local->mem;

	DEBUG(2,"ib-pcmcia: s/w connection reset \n");
	local->nreset += 1;
	mem[OFFSET_PC_MAGIC1] = 0x00;
	mem[OFFSET_PC_MAGIC2] = 0x00;
	ib_pcmcia_delay(local);
	DEBUG(2,"ib-pcmcia: pcmcia reset exit\n");
}

/**
 * ib_pcmcia_check - check hardware consistent.
 * @local: device private state.
 */
static int ib_pcmcia_check(struct ib_pcmcia_local_t *local)
{
	u_char *mem = local->mem;

	if (mem[OFFSET_PC_MAGIC1] != 0xac || mem[OFFSET_PC_MAGIC2] != 0x02
			|| mem[OFFSET_PC_SEQUENCE] != local->sequence
			|| mem[OFFSET_PC_FEEDBACK] != local->feedback
			|| mem[OFFSET_PC_IRX] != local->irx
			|| mem[OFFSET_PC_JTX] != local->jtx) {
		DEBUG(2, "ib-pcmcia: check %02X %02X %02X %02X "
				"%02X %02X %02X %02X %02X %02X %02X %02X\n",
				mem[OFFSET_PC_MAGIC1], mem[OFFSET_PC_MAGIC2],
				mem[OFFSET_PC_PACKET], mem[OFFSET_PC_STATUS],
				mem[OFFSET_PC_SEQUENCE], local->sequence,
				mem[OFFSET_PC_FEEDBACK], local->feedback,
				mem[OFFSET_PC_IRX], local->irx,
				mem[OFFSET_PC_JTX], local->jtx);
		ib_pcmcia_reset(local);
		DEBUG(2, "ib-pcmcia: check fix exit\n");
		return 1;
	}
	DEBUG(3, "ib-pcmcia: s/w connection check ok\n");
	return 0;
}

/**
 * ib_pcmcia_normal - normal operation; transfer chunks.
 * @local: device private state.
 */
static void ib_pcmcia_normal(struct ib_pcmcia_local_t *local)
{
	struct ib_net_modem_t *modem = local->modem;
	struct ib_net_cell_t *cell = (struct ib_net_cell_t*) modem->rx_buf;
	u_char *mem = local->mem;
	int irx, jrx, rx_ichunk, rx_nchunk;
	int itx, jtx, tx_jchunk, tx_nchunk;
	int nchunk, nbuf;
	int len;

	DEBUG(3,"ib-pcmcia: normal top\n");
	if (ib_pcmcia_check(local))
	{
		DEBUG(2,"ib-pcmcia: normal mode restart exit\n");
		return;
	}
	if (mem[OFFSET_UT_MAGIC1] != 0xac || mem[OFFSET_UT_MAGIC2] != 0x02
			|| mem[OFFSET_UT_SEQUENCE] != local->feedback
			|| mem[OFFSET_UT_FEEDBACK] != local->sequence) {
		DEBUG(2, "ib-pcmcia: normal mode %02X %02X "
				"%02X %02X %02X %02X\n",
				mem[OFFSET_UT_MAGIC1], mem[OFFSET_UT_MAGIC2],
				mem[OFFSET_UT_SEQUENCE], local->feedback,
				mem[OFFSET_UT_FEEDBACK], local->sequence);
		mem[OFFSET_PC_IRX] = local->irx = 0;
		mem[OFFSET_PC_JTX] = local->jtx = 0;
		DEBUG(2,"ib-pcmcia: normal mode waiting for modem\n");
		local->ifstate = IFSTATE_WAIT;
		return;
	}
	if (mem[OFFSET_PC_PACKET] != (modem->stats.tx_packets & 0xff)) {
		mem[OFFSET_PC_PACKET] = modem->stats.tx_packets & 0xff;
	}
	if (mem[OFFSET_PC_STATUS] != modem->status) {
		mem[OFFSET_PC_STATUS] = modem->status;
	}
	rx_ichunk = mem[OFFSET_UT_ICHUNK];
	rx_nchunk = mem[OFFSET_UT_NCHUNK];
	tx_jchunk = mem[OFFSET_PC_JCHUNK];
	tx_nchunk = mem[OFFSET_PC_NCHUNK];
	if (rx_ichunk == 0 || rx_nchunk == 0)
		goto failed;
	if (tx_jchunk == 0 || tx_nchunk == 0)
		goto failed;
	if (tx_jchunk <= rx_ichunk && rx_ichunk < tx_jchunk + tx_nchunk)
		goto failed;
	if (rx_ichunk <= tx_jchunk && tx_jchunk < rx_ichunk + rx_nchunk)
		goto failed;
	if (0x80 < rx_ichunk + rx_nchunk || 0x80 < tx_jchunk + tx_nchunk)
		goto failed;
	while (1) {
		DEBUG(5,"ib-pcmcia: poll rx loop top\n");
		irx = local->irx;
		if (mem[OFFSET_PC_IRX] != irx)
			goto failed;
		jrx = mem[OFFSET_UT_JRX];
		if (2 * rx_nchunk <= jrx)
			goto failed;
		if (irx == jrx)
			break;
		if (jrx < irx)
			nchunk = jrx - irx + 2 * rx_nchunk;
		else
			nchunk = jrx - irx;
		if (rx_nchunk < nchunk)
			goto failed;
		if (rx_nchunk <= irx)
			irx -= rx_nchunk;
		if (rx_nchunk - irx < nchunk)
			nchunk = rx_nchunk - irx;
		if (local->rx_echunk == 0) {
			DEBUG(4,"ib-pcmcia: copy chunk\n");
			ib_pcmcia_blit(modem->rx_buf,
					mem + (rx_ichunk + irx) * 32, 1);
//			ib_pcmcia_dump("rx", modem->rx_buf, 1);
			if (cell->word[0] & 0x80)
				goto failed;
			nbuf = (cell->word[0] & 0x07) * 256 + cell->word[1];
			if (IB_NET_CELL_SIZE < nbuf)
				goto failed;
			local->irx += 1;
			if (local->irx == 2 * rx_nchunk)
				local->irx = 0;
			mem[OFFSET_PC_IRX] = local->irx;
			if (nbuf <= 32) {
				ib_net_parse(modem, nbuf);
				continue;
			}
			local->rx_echunk = ((nbuf + 31) >> 5) - 1;
			local->rx_ibuf = 32;
			local->rx_nbuf = nbuf;
			continue;
		}
		if (local->rx_echunk < nchunk)
			nchunk = local->rx_echunk;
		DEBUG(4,"ib-pcmcia: copy %d chunks\n", nchunk);
		ib_pcmcia_blit(modem->rx_buf + local->rx_ibuf,
				mem + (rx_ichunk + irx) * 32, nchunk);
//		ib_pcmcia_dump("rx", modem->rx_buf + local->rx_ibuf, nchunk);
		local->irx += nchunk;
		if (2 * rx_nchunk <= local->irx)
			local->irx -= 2 * rx_nchunk;
		mem[OFFSET_PC_IRX] = local->irx;
		local->rx_echunk -= nchunk;
		local->rx_ibuf += nchunk * 32;
		if (local->rx_echunk == 0)
			ib_net_parse(modem, local->rx_nbuf);
	}
	while (1) {
		DEBUG(5,"ib-pcmcia: poll tx loop top\n");
		if (local->tx_echunk == 0) {
			len = ib_net_fill(modem);
			if (len == 0)
				break;
			modem->stats.tx_bytes += len;
			modem->stats.tx_packets += 1;
			local->tx_echunk = (len + 31) >> 5;
			local->tx_jbuf = 0;
		}
		itx = mem[OFFSET_UT_ITX];
		if (2 * tx_nchunk <= itx)
			goto failed;
		jtx = local->jtx;
		if (mem[OFFSET_PC_JTX] != jtx)
			goto failed;
		if (jtx < itx)
			nchunk = itx - jtx - tx_nchunk;
		else
			nchunk = itx - jtx + tx_nchunk;
		if (nchunk < 0)
			goto failed;
		if (nchunk == 0)
			break;
		if (tx_nchunk <= jtx)
			jtx -= tx_nchunk;
		if (tx_nchunk - jtx < nchunk)
			nchunk = tx_nchunk - jtx;
		if (local->tx_echunk < nchunk)
			nchunk = local->tx_echunk;
//		ib_pcmcia_dump("tx", modem->tx_buf + local->tx_jbuf, nchunk);
		ib_pcmcia_blit(mem + (tx_jchunk + jtx) * 32,
				modem->tx_buf + local->tx_jbuf, nchunk);
		local->tx_echunk -= nchunk;
		local->tx_jbuf += nchunk * 32;
		local->jtx += nchunk;
		if (2 * tx_nchunk <= local->jtx)
			local->jtx -= 2 * tx_nchunk;
		mem[OFFSET_PC_JTX] = local->jtx;
	}
	ib_net_wake(modem);
	DEBUG(3,"ib-pcmcia: poll done ok\n");
	return;
failed:
	DEBUG(2, "ib-pcmcia: normal failed\n");
	ib_pcmcia_reset(local);
	DEBUG(2, "ib-pcmcia: normal failed exit\n");
}

/**
 * ib_pcmcia_wait - hardware reset wait; get hardware address.
 * @local: device private state.
 */
static void ib_pcmcia_wait(struct ib_pcmcia_local_t *local)
{
	struct ib_net_modem_t *modem = local->modem;
	u_char *mem = local->mem;

	if (ib_pcmcia_check(local))
		return;
	local->rx_echunk = 0;
	local->tx_echunk = 0;
	local->feedback = mem[OFFSET_UT_SEQUENCE];
	mem[OFFSET_PC_FEEDBACK] = local->feedback;
	if (mem[OFFSET_UT_MAGIC1] != 0xac || mem[OFFSET_UT_MAGIC2] != 0x02
			|| mem[OFFSET_UT_FEEDBACK] != local->sequence) {
		DEBUG(2, "ib-pcmcia: wait %02X %02X %02X %02X\n",
				mem[OFFSET_UT_MAGIC1], mem[OFFSET_UT_MAGIC2],
				mem[OFFSET_UT_FEEDBACK], local->sequence);
		return;
	}
	ib_net_addr(modem, mem + OFFSET_UT_ETHERNET);
	local->ifstate = IFSTATE_NORMAL;
	ib_pcmcia_normal(local);
}

/**
 * ib_pcmcia_poll - poll hardware according to protocol.
 * @_local: device private state.
 */
static void ib_pcmcia_poll(void *_local)
{
	struct ib_pcmcia_local_t *local = _local;

	DEBUG(3,"ib-pcmcia: polling start %lu\n", jiffies);
	switch (local->ifstate) {
	case IFSTATE_RESET:
		ib_pcmcia_reset(local);
		break;
	case IFSTATE_WAIT:
		ib_pcmcia_wait(local);
		break;
	case IFSTATE_NORMAL:
		ib_pcmcia_normal(local);
		break;
	}
	DEBUG(3,"ib-pcmcia: polling exit %lu\n", jiffies);
}

/**
 * ib_pcmcia_stop - stop device; release in a momemt.
 * @_local: device private state.
 */
static void ib_pcmcia_stop(void *_local)
{
	struct ib_pcmcia_local_t *local = _local;
	struct dev_link_t *link = &local->link;

	if (link->state & DEV_STALE_CONFIG)
		mod_timer(&local->release, jiffies + HZ / 20);
}

/**
 * ib_pcmcia_net_driver - PCMCIA driver callback functions.
 */
static struct ib_net_driver_t ib_pcmcia_net_driver = {
	.name = "pcmcia",
	.poll = ib_pcmcia_poll,
	.stop = ib_pcmcia_stop,
	.tx_timeout = NULL,
};

/**
 * ib_pcmcia_config - PCMCIA config.
 * @link: device state.
 */
static void ib_pcmcia_config(struct dev_link_t *link)
{
	client_handle_t handle = link->handle;
	struct ib_pcmcia_local_t *local = link->priv;
	struct tuple_t tuple;
	union cisparse_t parse;
	u_char buf[64];
	int version;
	struct config_info_t conf;
	struct cistpl_cftable_entry_t *cfg;
	struct cistpl_cftable_entry_t dflt = { 0 };
	struct win_req_t req;

	tuple.DesiredTuple = CISTPL_MANFID;
	tuple.Attributes = 0;
	tuple.TupleData = buf;
	tuple.TupleDataMax = sizeof(buf);
	tuple.TupleOffset = 0;
	if (pcmcia_get_first_tuple(handle, &tuple))
		goto failed0;
	if (pcmcia_get_tuple_data(handle, &tuple))
		goto failed0;
	if (pcmcia_parse_tuple(handle, &tuple, &parse))
		goto failed0;
	version = parse.manfid.card;
	DEBUG(1,"ib-pcmcia: ib version %d\n", version);
	tuple.DesiredTuple = CISTPL_CONFIG;
	tuple.Attributes = 0;
	tuple.TupleData = buf;
	tuple.TupleDataMax = sizeof(buf);
	tuple.TupleOffset = 0;
	if (pcmcia_get_first_tuple(handle, &tuple))
		goto failed0;
	if (pcmcia_get_tuple_data(handle, &tuple))
		goto failed0;
	if (pcmcia_parse_tuple(handle, &tuple, &parse))
		goto failed0;
	link->conf.ConfigBase = parse.config.base;
	link->conf.Present = parse.config.rmask[0];
	if (pcmcia_get_configuration_info(handle, &conf))
		goto failed0;
	link->conf.Vcc = conf.Vcc;
	tuple.DesiredTuple = CISTPL_CFTABLE_ENTRY;
	if (pcmcia_get_first_tuple(handle, &tuple))
		goto failed0;
	while (1) {
		DEBUG(2, "ib-pcmcia: config loop...\n");
		if (pcmcia_get_tuple_data(handle, &tuple))
			goto next_entry;
		if (pcmcia_parse_tuple(handle, &tuple, &parse))
			goto next_entry;
		cfg = &parse.cftable_entry;
		if (cfg->flags & CISTPL_CFTABLE_DEFAULT)
			dflt = *cfg;
		if (cfg->index == 0)
			goto next_entry;
		link->conf.ConfigIndex = cfg->index;
		if (cfg->flags & CISTPL_CFTABLE_AUDIO) {
			link->conf.Attributes |= CONF_ENABLE_SPKR;
			link->conf.Status = CCSR_AUDIO_ENA;
		}
		if (cfg->vcc.present & (1 << CISTPL_POWER_VNOM)) {
			if (conf.Vcc != cfg->vcc.param[CISTPL_POWER_VNOM]
					/ 10000)
				goto next_entry;
		} else if (dflt.vcc.present & (1 << CISTPL_POWER_VNOM)) {
			if (conf.Vcc != dflt.vcc.param[CISTPL_POWER_VNOM]
					/ 10000)
				goto next_entry;
		}
		if (cfg->vpp1.present & (1 << CISTPL_POWER_VNOM))
			link->conf.Vpp1 = link->conf.Vpp2
					= cfg->vpp1.param[CISTPL_POWER_VNOM]
						/ 10000;
		else if (dflt.vpp1.present & (1 << CISTPL_POWER_VNOM))
			link->conf.Vpp1 = link->conf.Vpp2
					= dflt.vpp1.param[CISTPL_POWER_VNOM]
						/ 10000;
		if (cfg->irq.IRQInfo1 || dflt.irq.IRQInfo1)
			link->conf.Attributes |= CONF_ENABLE_IRQ;
		break;
next_entry:
		if (pcmcia_get_next_tuple(handle, &tuple))
			goto failed0;
	}
	if (link->conf.Attributes & CONF_ENABLE_IRQ)
		if (pcmcia_request_irq(link->handle, &link->irq))
			goto failed0;
	if (pcmcia_request_configuration(link->handle, &link->conf))
		goto failed1;
	req.AccessSpeed = 0;
	req.Attributes = WIN_MEMORY_TYPE_CM | WIN_DATA_WIDTH_16 | WIN_ENABLE;
	req.Base = 0;
	req.Size = version < 2 ? 0x1000 : 0x2000;
	link->win = (window_handle_t) link->handle;
	if (pcmcia_request_window(&link->handle, &req, &link->win))
		goto failed2;
	local->mem = (u_char*) ioremap(req.Base, req.Size);
	if (local->mem == NULL)
		goto failed3;
	local->ctl = local->mem + (version < 2 ? 0x0800 : 0x1000);
	local->modem = ib_net_register(local->node.dev_name);
	if (local->modem == NULL)
		goto failed4;
	ib_net_addr(local->modem, local->mem + OFFSET_UT_ETHERNET);
	local->modem->driver = &ib_pcmcia_net_driver;
	local->modem->instance = local;
	local->ifstate = IFSTATE_RESET;
	local->nreset = 0;
	init_timer(&local->timer);
	local->timer.expires = jiffies + HZ / 4;
	local->timer.function = ib_pcmcia_timer;
	local->timer.data = (u_long) local;
	add_timer(&local->timer);
	local->node.major = local->node.minor = 0;
	local->node.next = NULL;
	link->dev = &local->node;
	link->state |= DEV_CONFIG;
	DEBUG(1, "ib-pcmcia: config finished\n");
	return;
failed4:
	iounmap(local->mem);
failed3:
	pcmcia_release_window(link->win);
failed2:
	pcmcia_release_configuration(link->handle);
failed1:
	pcmcia_release_irq(link->handle, &link->irq);
failed0:
	DEBUG(0, "ib-pcmcia: config failed\n");
}

/**
 * ib_pcmcia_event - pcmcia event handler.
 * @event:
 * @priority:
 * @args:
 */
static int ib_pcmcia_event(event_t event, int priority,
		struct event_callback_args_t *args)
{
	struct dev_link_t *link = args->client_data;
	struct ib_pcmcia_local_t *local = link->priv;

	switch (event) {
	case CS_EVENT_CARD_REMOVAL:
		link->state &= ~DEV_PRESENT;
		if (link->state & DEV_CONFIG) {
			ib_net_detach(local->modem);
			mod_timer(&local->release, jiffies + HZ / 20);
		}
		break;
	case CS_EVENT_CARD_INSERTION:
		link->state |= DEV_PRESENT | DEV_CONFIG_PENDING;
		ib_pcmcia_config(link);
		link->state &= ~DEV_CONFIG_PENDING;
		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)
				ib_net_detach(local->modem);
			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)
				ib_net_attach(local->modem);
		}
		break;
	}
	return 0;
}

/**
 * ib_pcmcia_flush - deregister stale links.
 */
static void ib_pcmcia_flush(void)
{
	struct dev_link_t *link, *next;

	for (link = ib_pcmcia_dev_list; link; link = next) {
		next = link->next;
		if (link->state & DEV_STALE_LINK)
			ib_pcmcia_deregister(link);
	}
}

/**
 * ib_pcmcia_register - register client.
 * @link: device state.
 */
static int ib_pcmcia_register(struct dev_link_t *link)
{
	struct client_reg_t client_reg;

	link->handle = NULL;
	client_reg.dev_info = &ib_pcmcia_dev_info;
	client_reg.Attributes = INFO_IO_CLIENT;
	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_callback_args.client_data = link;
	client_reg.event_handler = &ib_pcmcia_event;
	client_reg.Version = 0x0210;
	if (pcmcia_register_client(&link->handle, &client_reg))
		return 1;
	link->next = ib_pcmcia_dev_list;
	ib_pcmcia_dev_list = link;
	return 0;
}

/**
 * ib_pcmcia_attach - initialize attached device.
 */
static struct dev_link_t *ib_pcmcia_attach(void)
{
	struct ib_pcmcia_local_t *local;
	struct dev_link_t *link;
	int i;

	ib_pcmcia_flush();
	local = kmalloc(sizeof(struct ib_pcmcia_local_t), GFP_KERNEL);
	if (local == NULL)
		return NULL;
	link = &local->link;
	link->priv = local;
	link->dev = NULL;
	link->conf.Attributes = 0;
	link->conf.IntType = INT_MEMORY_AND_IO;
	link->conf.Status = 0;
	link->conf.Vpp1 = 0;
	link->conf.Vpp2 = 0;
	link->irq.AssignedIRQ = 0;
	link->irq.Attributes = IRQ_TYPE_EXCLUSIVE|IRQ_HANDLE_PRESENT;
        #if 0   // The following worked in linux 2.4 but not sure about linux 2.6
	link->irq.Attributes |= IRQ_TYPE_DYNAMIC_SHARING|IRQ_FIRST_SHARED;
        #endif
	link->irq.Handler = ib_pcmcia_intr;
	link->irq.Instance = local;
	link->irq.IRQInfo1 = IRQ_INFO2_VALID | IRQ_LEVEL_ID;
	if (irq_list[0] == -1)
		link->irq.IRQInfo2 = irq_mask;
	else
		for (i = 0; i < 4; i += 1)
			link->irq.IRQInfo2 |= 1 << irq_list[i];
	link->open = 0;
	link->state = 0;
	init_timer(&local->release);
	local->release.function = &ib_pcmcia_release;
	local->release.data = (u_long) link;
	local->modem = NULL;
	if (ib_pcmcia_register(link)) {
		if (link->handle)
			pcmcia_deregister_client(link->handle);
		kfree(local);
		return NULL;
	}
	return link;
}

/**
 * ib_pcmcia_driver - PCMCIA driver struct.
 */
static struct pcmcia_driver ib_pcmcia_driver = {
	.owner = THIS_MODULE,
	.drv = {
		.name = ib_pcmcia_dev_info,
	},
	.attach = ib_pcmcia_attach,
	.detach = ib_pcmcia_detach,
};

/**
 * ib_pcmcia_init - ib-pcmcia module init.
 */
static int __init ib_pcmcia_init(void)
{
	if (io16)
		ib_pcmcia_blit = ib_pcmcia_blit16;
	else
		ib_pcmcia_blit = ib_pcmcia_blit32;
	pcmcia_register_driver(&ib_pcmcia_driver);
	return 0;
}

/**
 * ib_pcmcia_exit - ib-pcmcia module exit.
 */
static void __exit ib_pcmcia_exit(void)
{
	ib_pcmcia_flush();
	if (ib_pcmcia_dev_list)
		/* can't happen? */
		DEBUG(1, "ib-pcmcia: exit dev_list not null\n");
	pcmcia_unregister_driver(&ib_pcmcia_driver);
}

module_init(ib_pcmcia_init);
module_exit(ib_pcmcia_exit);

MODULE_DESCRIPTION("iBurst compatible PCMCIA driver");
MODULE_LICENSE("GPL");
