/*
 * HP Omnibook 600C/CT PCMCIA Socket layer (VL82C717 chipset)
 * Copyright 2001 Grant Grundler
 * Copyright 2002 Michael Teske
 *
 * This driver is currently tested on an OB600C with kernel 2.2.19 and 
 * pcmcia-cs-3.1.33 (because that are the versions flifl, the
 * one-disk-router uses); I had to change the  do_mem_probe() 
 * function in rsrc_mgr.c to check in steps of 0x4000 instead of
 * 0x2000 because the OB can set mem windows only in this stepping
 * (else do_mem_probe fails fo almost all regions, leaving nothing left).
 * 
 * It should be no big problem to port it to kernel 2.4.x builtin pcmcia.
 *
 * Of course: USE THIS DRIVE AT YOUR OWN RISK. Nobody mentioned here is 
 * responsible if you burn your computer or a pc-card inserted in a slot
 * while using this driver.
 * 
 * I tested the driver with a Xircom 2ps network card and a SCM modem card,
 * both need only 5V Vpp, I don't know if 12 V or memory cards work.
 *
 * The code is sometimes a bit unclear, mostly because I've got my
 * information from disassembling and debugging the Win95 driver _only_.
 * Especially the bios_addr stuff is not completely understood. Please
 * read also the comment before the module parm bios_rev, you might need
 * to set this value.
 *
 * Michael Teske (mteske@csksoftware.com)
 * Noveber 2002
 *
 * P.S.:This driver is based on the work if Grant Grundler, who almost 
 * got it right. Thanks, it made the work much easier!
 * His original comments are below:
 * 
 *
 *
 * USE AT YOUR OWN RISK ON OB530/600/600C MACHINES.
 * They are supposed to use the same chipset but I don't know
 * which machines have which revisions of the chipset.
 * (I'll remove this notice when I believe it works)
 *
 * DISCLAIMER: This is ugly code. It's a hack. Send me patches - not gripes.
 *
 * FIXME: need to tell linux that IRQ 9 is edge triggered? (request_irq flag?)
 * FIXME: where is SS_WRPROT bit?	(set_socket) A: it's unsupported
 * FIXME: where is SS_OUTPUT_ENA bit?	(set_socket) A: it's unsupported
 * FIXME: where is SS_SPKR_ENA bit?	(set_socket) A: it's unsupported
 * FIXME: where is SS_DMA_MODE bit?	(set_socket) A: it's unsupported
 *
 *
 * Q: how to determine card type? (ie Memory card vs IO/Mem card)
 * A: "askpcmcia" says:  The current value was chosen to allow powering
 *	the PC Card's interface and Attribute Memory so the card type
 *	and operating current could be determined (from the card's tuples).
 *
 * Posted to omnilist@elektro.cmhnet.org on 23 Sep 1995:
 *    The VL82C717 is the system controller which is modified version of
 *    the VLSI SCAMP IV system controller. Part of the PCMCIA logic is on
 *    it and part is on the VL82C715 which is a custom "glue" chip for
 *    HP. The PCMCIA implantation is unique to HP and not based on anything
 *    by VLSI or anyone else. Both ICs are considered confidential by VLSI
 *    so even within HP will probably need signing non-disclosure
 *    agreements to get the documentation.
 *
 * None of this code is based on confidential info.
 *
 * Olivier Florent had gotten source (while working for HP) and
 * started working on a PCMCIA driver for linux in Feb 1999.  He got
 * laid off around April 1999 and was forced to abandon the OB600C and
 * related confidential information. All of his code and documents on his
 * HP internal website were deleted shortly after - 4 monthes before
 * I was given an OB600CT.
 *
 * Thanks to drivers/pcmcia/tcic.c [Copyright 1999 David A. Hinds]
 * I used it's "frame work", none of it's code survived.
 *
 * grant
 * August 30, 2001
 *
 * 
 *
 * This software is distributed under the Gnu Public License (GPL) on an
 * "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or implied.
 * See the Gnu Public License for the specific language governing rights and
 * limitations under the License.
 */
#include <pcmcia/config.h>
#include <pcmcia/k_compat.h>


#include <linux/init.h>
#include <linux/sched.h>
#include <linux/interrupt.h>
#include <linux/delay.h>
#include <linux/module.h>
#include <linux/ioport.h>	/* for resource mgt stuff */
#include <linux/proc_fs.h>

#include <pcmcia/version.h>
#include <pcmcia/cs_types.h>
#include <pcmcia/ss.h>
#include <pcmcia/cs.h>		/* for servinfo_t */

#include <asm/io.h>


/* BIOS revision. We cannot detect it like the Windoze driver, because
 * INT 0x15 is overwritten by linux, so we cannot call it.
 * To get your bios revision do the following under MS-DOS:
 *
 * Start the debugger "DEBUG"
 * enter the following commands:
 * A 100
 * mov ax,4dd4
 * int 15
 * (press Ctrl-C)
 * P 100
 * P
 * Q
 *
 * you should see 4850 ("HP") in ax.
 * The value in cl (lower byte of cx) is the bios revision.
 * Allowed values are 3 or greater.
 */
#define INT_MODULE_PARM(n, v) static int n = v; MODULE_PARM(n, "i")

INT_MODULE_PARM(bios_rev, 4);
MODULE_AUTHOR("Grant Grundler/Michael Teske");
MODULE_DESCRIPTION("HP Omnibook 600C/CT PCMCIA (VL82C717 chipset)");
MODULE_LICENSE("GPL");

#define NEWER_BIOS_REV (bios_rev > 3)

#define DEBUG_INIT 0		/* log function call parameters */
#define DEBUG_IO 0		/* log reads/writes to the bus controller */
#define DEBUG_IRQ 0		/* log controller interrupt activity */

#define DEBUG_ACT 1		/* debug what I actually want to debug */

static const char *version =
    "OB600C/CT (VL82C717) Socket Services v0.3 2002/11/25 Michael Teske";

#define NUM_HWREVS_SUPPORTED 3
static unsigned char obss_hwrev;	/* 0-2 */

/* WIndow no 0-4 are mmio-windows, 5-8 ioport wins */
#define NUM_MMIO_WIN	5
#define NUM_IOPORT_WIN	4

/* This is a small system and x86 is good with bit ops.
** ergo pack the data pretty tightly to save mem.
**
** Each MMIO or IOPORT window can be assigned to either socket...but linux
** pcmcia assumes each window belongs to a socket. We fake CS out
** and hope we never run out of windows in set_io_map().
*/
struct obss_window {
	u_char busy:1;		/* assigned to a socket/map? */
	u_char attrib_mem:1;	/* mem map is for "Config Space" */
	u_char socket:2;	/* socket assigned to */
	u_char map:4;		/* map assigned to */

	u_char spd_sz;		/* IOPORT size;    MMIO it's the speed idx */
	u_short base;		/* IOPORT uses 10 bits; MMIO uses 12-bits */
	u_short offset;		/* Page offset */
	u_short flags;
};

static struct obss_window obss_ioport[NUM_IOPORT_WIN];
static struct obss_window obss_mmio[NUM_MMIO_WIN];


#if DEBUG_INIT | DEBUG_IO | DEBUG_IRQ | DEBUG_ACT
#define DBG_PRINTK(fmt,args...)	printk(KERN_INFO "%s:%d: " ##fmt, __FUNCTION__, __LINE__, ##args)
#else
#define DBG_PRINTK(fmt,args...)
#endif

#if DEBUG_INIT
#define DBG_INIT	DBG_PRINTK
#else
#define DBG_INIT(fmt,args...)
#endif

#if DEBUG_IO
#define DBG_IO		DBG_PRINTK
#else
#define DBG_IO(fmt,args...)
#endif

#if DEBUG_IRQ
#define DBG_IRQ		DBG_PRINTK
#else
#define DBG_IRQ(fmt,args...)
#endif

#if DEBUG_ACT
#define DBG_ACT		DBG_PRINTK
#else
#define DBG_ACT(fmt,args...)
#endif


#define OBSS_NUM_SOCKETS 2
#define OBSS_ADAPTER_IRQ 9	/* OB PCMCIA controller uses IRQ 9 */

static unsigned int pending_events[OBSS_NUM_SOCKETS];

typedef struct {
	void (*handler) (void *info, u_int events);
	void *info;
#ifdef CONFIG_PROC_FS
	struct proc_dir_entry *proc;
#endif
	u_int pending_events;
	u_int event_mask;	/* csc events SW allows */
	u_int event_state;	/* state from most recent interrupt */
	u_char irq_idx;		/* IRQ assigned to socket */
	u_char is_iocard;	/* SS_IOCARD or zero */
	u_char Vcc;		/* Actual Vcc */
	u_char Vpp;		/* Actual Vpp (Vpp1 == Vpp2) */
} obss_socket_t;

static obss_socket_t obss_socket[OBSS_NUM_SOCKETS];


/* See read/write_mode functions */
#define OBSS_TOGGLE	0xFB	/* only written, never read */
#define OBSS_MODE	0xEC	/* read after writing 0xFB */

/* See read/write reg functions */
#define OBSS_ADDR	0xEC	/* write internal register address here */
#define OBSS_DATA	0xED	/* rd/wr this port to rd/wr internal register */

/*
** interesting offsets into internal register space
*/
#define OBSS_HWREV	0x1A
#define		REV_MASK	0x03	/* lower 2 bits indicate rev */

#define OBSS_POWER	0x34	/* bit 2 is on/off */
#define OBSS_CSCTL1	0x38	/* 38[-socket*2] , IRQ programming, Vpp */
#define OBSS_CSCTL2	0x39	/* 39[-socket*2] , Presence, Vcc/Vpp sense */
#define OBSS_IRQ	0x50	/* adapter IRQ reset */
#define OBSS_CSIRQ	0x5D	/* Socket IRQ enable , card type detect */
#define OBSS_IOWIN	0x5E	/* [i*2] , 4 entries */
#define OBSS_MEMWIN	0x6A	/* [i*3] , 5 entries */

/* OBSS_POWER */
#define POWER_ENABLE	0x04	/* bit 2 is on/off */

/* OBSS_CSCTL1	*/
#define CSCTL1_LVLTRIG	0x40	/* Shared/Level Triggered IRQ */
#define CSCTL1_IRQIDX	0x38	/* Mask for IRQ bits - see irq_lut[] */
#define CSCTL1_VPP12	0x04	/* Set Vpp1/Vpp2 to 12v (vs 5v) */
#define CSCTL1_ENBIRQ	0x02	/* IRQ Enable? */

/* OBSS_IRQ */
#define IRQ_LINE	0x04	/* bit 2 is bus controller active/event */

/* OBSS_CSIRQ */
#define CSIRQ_SOCK0	0x80	/* Socket 0 IRQ Enable */
#define CSIRQ_SOCK1	0x40	/* Socket 1 IRQ Enable */

/* CSCTL2 */
#define CSCTL2_BVD1	0x40	/* STSCHG/BVD1 AND BVD2 signal for memory card? */
#define CSCTL2_WP	0x20	/* WP bit on rd? */
#define CSCTL2_DETECT	0x10	/* card detect */
#define CSCTL2_READY	0x08	/* memory card ready */
#define CSCTL2_INIT	0x02	/* GGG: NFC..set during init if DETECT is set */
#define CSCTL2_RESET	0x01	/* card RESET line control */

/* OBSS_MEMWIN */
#define MEMWIN_16BIT	0x80

#define MEMWIN_SIZE	(16*1024)


static socket_cap_t obss_cap = {
	/* only 8/16-bit cards, memory windows must be size-aligned
	 ** only 24 MMIO address bits (not 32)
	 */
	SS_CAP_PCCARD | SS_CAP_MEM_ALIGN,
	0x0898,			/* ISA IRQs 11 7 4 3  (see obss_irq_lut[]) */
	MEMWIN_SIZE,		/* 16K fixed MMIO window size */
	0,			/* No PCI irq support */
	0,			/* No CardBus support */
	NULL			/* No bus ops needed */
};



/* ----------------------------------------------------------------------------
**   MMIO Access Speed LUT
*/



/* hmm, in driver source it's like this */
#define NS_PER_BIT	  5	/* really? would be values between 170 and 330 ns */
#define NUM_ACCESS_SPEEDS   4

static u_char obss_speed_lut[NUM_HWREVS_SUPPORTED][NUM_ACCESS_SPEEDS] = {
	{0x3a, 0x52, 0x2a, 0x3a},	/* hwrev 0 */
	{0x32, 0x42, 0x22, 0x2a},	/* hwrev 1 */
	{0x32, 0x42, 0x1a, 0x22}	/* hwrev 2 */
};


static u_char obss_ror13_speed_lut[NUM_HWREVS_SUPPORTED][NUM_ACCESS_SPEEDS
							 * 2] = {
	{0x45, 0x02, 0x47, 0x03, 0x4a, 0x01, 0x4a, 0x01},	/* hwrev 0 */
	{0x44, 0x02, 0x45, 0x03, 0x46, 0x00, 0x48, 0x01},	/* hwrev 1 */
	{0x43, 0x02, 0x44, 0x03, 0x46, 0x00, 0x48, 0x01},	/* hwrev 2 */

};


#define to_ns(idx) (((u_short) obss_speed_lut[obss_hwrev][idx]+2) * NS_PER_BIT)

static u_char to_idx(u_short ns)
{
	int i = 0;
	u_char speed = (u_char) (ns / NS_PER_BIT) - 2;	/* convert to table format */
	u_char *t;

	/*ror 3 the speed */
	speed = (speed >> 3) | ((speed << 5) & 0xff);

	t = obss_ror13_speed_lut[obss_hwrev];

	for (i = 0; i < NUM_ACCESS_SPEEDS; i++) {
		if (speed <= t[2 * i])
			break;
	}

	if (i >= NUM_ACCESS_SPEEDS) {
		printk(KERN_WARNING
		       "OB600_SS: card speed (%d ns) too slow!\n", ns);
		i = 3;		/* try slowest speed */
	}

	return (t[2 * i + 1]);	/* return idx to speed */
}


/*
** Convert OBSS IRQ bit values (3 bits) to ISA IRQ
**
** AFAICT, ISA IRQs are assigned as follows:
**    IRQ  0  timer
**    IRQ  1  keyboard
**    IRQ  2  8237 cascade
**    IRQ  3# ttyS01/S03?
**    IRQ  4# ttyS00/S02 (built-in and port replicator serial port)
**    IRQ  5# (LAN) ISA slot in port replicator (normally sound blaster)
**    IRQ  6# Floppy disk drive controller
I**    IRQ  7# LPT? (25-pin parallel port)
**    IRQ  8  rtc
**    IRQ  9  VL82C717 PCMCIA controller
**    IRQ 10# SCSI-2 adapter on port replicator
**    IRQ 11#
**    IRQ 12
**    IRQ 13  math coproc
**    IRQ 14# IDE0
**
**    (#) decoded by PCMCIA controller and available for either socket
**
**   Index into irq_lut is value to write to CSCTL1 register.
*/
static unsigned char obss_irq_lut[8] = { 3, 4, 5, 6, 7, 10, 11, 14 };


/* ----------------------------------------------------------------------------
** Register initialization data
*/
typedef struct {
	u_char reg;
	u_char data;
} reg_data_t;

static reg_data_t reg_data[] = {
	{0x38, 0x90}, {0x36, 0x90},	/* Sockets */
	{0x6A, 0xC0}, {0x6B, 0xB0}, {0x6C, 0x00},	/* MMIO Windows (5x) */
	{0x6D, 0xC0}, {0x6E, 0xB0}, {0x6F, 0x00},
	{0x70, 0xC0}, {0x71, 0xB0}, {0x72, 0x00},
	{0x73, 0xC0}, {0x74, 0xB0}, {0x75, 0x00},
	{0x76, 0xC0}, {0x77, 0xB0}, {0x78, 0x00},
	{0x5E, 0x01}, {0x5F, 0x00},	/* IO Port Windows (4x) */
	{0x60, 0x01}, {0x61, 0x01},
	{0x62, 0x01}, {0x63, 0x02},
	{0x64, 0x01}, {0x65, 0x03},
	{0, 0}
};


/* word for setting bios bits (probably interrupts etc) */
static volatile void *bios_addr;

static inline void and_bios(unsigned short val)
{
	unsigned short oldval = readw(bios_addr + 0x15a);
	oldval &= val;
	writew(oldval, bios_addr + 0x15a);
}

static inline void or_bios(unsigned short val)
{
	unsigned short oldval = readw(bios_addr + 0x15a);
	oldval |= val;
	writew(oldval, bios_addr + 0x15a);
}

static inline unsigned short read_bios(void)
{
	return readw(bios_addr + 0x15a);
}


#define READ_ADDR()	inb(OBSS_ADDR)
#define WRITE_ADDR(x)	outb(x, OBSS_ADDR)
#define READ_DATA()	inb(OBSS_DATA)
#define WRITE_DATA(x)	outb(x, OBSS_DATA)


/*
** Read (and save) "mode" before poking other internal registers.
** Write the saved value back to OBSS_MODE before bailing. 
*/
static inline u_char read_mode(void)
{
	u_char val;

	outb(0, OBSS_TOGGLE);
	val = inb(OBSS_MODE);
	/*DBG_IO("mode 0x%x\n", val); */

	return val;
}

/* Restore the contents of "mode" register */
static inline void write_mode(u_char val)
{
	/*    DBG_IO("mode 0x%x\n", val); */
	outb(val, OBSS_MODE);
}

/*
 * read an internal register. Use *_mode() calls above first.
 * r/m/w should use read_data() and then WRITE_DATA macro directly.
 */
static inline u_char read_data(u_char reg)
{
	u_char val;
	WRITE_ADDR(reg);
	val = READ_DATA();
	/*      DBG_IO("%02x -> %02x\n", reg, val); */
	return val;
}

/*
 * write an internal register.
 */
static inline void write_data(u_char val, u_char reg)
{
	/*    DBG_IO("%02x <- %02x\n", reg, val); */
	WRITE_ADDR(reg);
	WRITE_DATA(val);
}


/*---------------------------------------------------------------------------*/


/*---------------------------------------------------------------------------*/


static void obss_bh(void *dummy)
{
	u_int events;
	int i;

	DBG_IRQ(" events : 0x%x 0x%x\n", pending_events[0],
		pending_events[1]);

	for (i = 0; i < OBSS_NUM_SOCKETS; i++) {
		u_long flags;

		local_irq_save(flags);
		events = pending_events[i];
		pending_events[i] = 0;
		local_irq_restore(flags);

		if (events && obss_socket[i].handler) {
			DBG_IRQ(" calling handler : %p %p\n",
				obss_socket[i].handler,
				obss_socket[i].info);
			obss_socket[i].handler(obss_socket[i].info,
					       events);
		}
	}
}

static struct tq_struct obss_task = {
	routine:obss_bh
};


static /* __inline__ */ u_int decode_csctl2(u_char psock, u_char x)
{
	u_int state = 0;

	if (x & CSCTL2_RESET)
		state |= SS_RESET;
	if (x & CSCTL2_DETECT)
		state |= SS_DETECT;

	if (x & CSCTL2_READY)
		state |= SS_READY;

	if (obss_socket[psock].is_iocard) {
		if (x & CSCTL2_BVD1)
			state |= SS_STSCHG;
		if (x & CSCTL2_WP)
			state |= SS_WRPROT;
	} else {
		if (x & CSCTL2_BVD1)
			state |= SS_BATDEAD | SS_BATWARN;
		/* if (x & CSCTL2_BVD2) state |= SS_BATWARN ; */
	}
	return state;
}


static void obss_interrupt(int irq, void *dev, struct pt_regs *regs)
{
	u_char i;
	u_char saved_mode, val;
	unsigned short special_val;

#ifdef DEBUG_INTR
	static volatile int active = 0;

	if (active) {
		printk(KERN_NOTICE "obss: reentered interrupt handler!\n");
		return;
	} else
		active = 1;
#endif

	DBG_IRQ(" irq 0x%x 0x%p\n", irq, dev);

	saved_mode = read_mode();
	val = read_data(OBSS_IRQ);
	val &= 0xFB;		/* clear controller IRQ line */
	WRITE_DATA(val);
	DBG_IO("%02x <- %02x\n", OBSS_IRQ, val);
	write_mode(saved_mode);	/* restore value */

	special_val = read_bios();

	for (i = 0; i < OBSS_NUM_SOCKETS; i++) {
		u_int stat = 0;
		/* check special val. bit 4 socket 0, bit 3 socket 1 */
		if (special_val & (0x0010 >> i)) {
			/* clear bit! */
			and_bios(~(0x0010 >> i));
			stat = SS_DETECT;
		} else {
			/* nothing for this socket! */
			continue;
		}

		if (obss_socket[i].handler) {
			/* handle event off the interrupt stack */
			pending_events[i] |= stat;
			schedule_task(&obss_task);
		}
	}

#ifdef DEBUG_INTR
	active = 0;
#endif
}				/* obss_interrupt */


/*
** ----------------------------------------------------------------------------
** struct pccard_operations functions
** ----------------------------------------------------------------------------
*/
/*====================================================================*/

static int obss_register_callback(u_short lsock, ss_callback_t * call)
{
	if (call) {
		DBG_INIT(" socket %d 0x%p 0x%p\n", lsock, call->handler,
			 call->info);
		obss_socket[lsock].handler = call->handler;
		obss_socket[lsock].info = call->info;
		MOD_INC_USE_COUNT;
	} else {
		DBG_INIT(" socket %d 0x%p\n", lsock, call);
		obss_socket[lsock].handler = NULL;
		obss_socket[lsock].info = NULL;
		MOD_DEC_USE_COUNT;
	}
	return 0;
}


/*====================================================================*/

static int obss_get_status(unsigned int psock, u_int * value)
{
	u_char val, valx, saved_mode;
	u_long flags;

	local_irq_save(flags);
	saved_mode = read_mode();

	valx = read_data(OBSS_CSIRQ);
	if (valx & (0x80 >> psock)) {
		obss_socket[psock].is_iocard = 1;
	} else {
		obss_socket[psock].is_iocard = 0;
	}

	val = read_data(OBSS_CSCTL2 - psock * 2);
	DBG_INIT("(%d)-> %x   %x\n", psock, valx, val);

	*value = decode_csctl2(psock, val);

	val = read_data(OBSS_POWER);
	if (val & POWER_ENABLE)
		*value |= SS_POWERON;

	write_mode(saved_mode);	/* restore value */
	local_irq_restore(flags);

	DBG_INIT("0x%04x\n", *value);
	return 0;
}


/*====================================================================*/

static int obss_inquire_socket(unsigned int psock, socket_cap_t * cap)
{
	*cap = obss_cap;
	DBG_INIT("(%d)\n", psock);
	return 0;
}				/* obss_inquire_socket */


/*====================================================================*/

static int obss_get_socket(unsigned int psock, socket_state_t * state)
{
	u_char reg, val, saved_mode;
	obss_socket_t *s = &obss_socket[psock];
	u_long flags;

	DBG_INIT("(%d)\n", psock);

	state->flags = s->is_iocard ? SS_IOCARD : 0;	/* FIXME IOCARD */
	state->io_irq = obss_irq_lut[s->irq_idx];

	local_irq_save(flags);
	saved_mode = read_mode();

	reg = OBSS_CSCTL2 - (psock * 2);
	val = read_data(reg);	/* OBSS_CSCTL2 */

	state->flags |= decode_csctl2(psock, val);

	state->Vcc = s->Vcc;
	state->Vpp = s->Vpp;
#if 0
	val = read_data(OBSS_POWER);
	if (val & POWER_ENABLE) {
		state->Vcc = state->Vpp = 50;	/* can only be on or off */

		reg--;
		val = read_data(reg);	/* OBSS_CSCTL1 */
		if (val & CSCTL1_VPP12)
			state->Vpp = 120;
	}
#endif

	write_mode(saved_mode);
	local_irq_restore(flags);

	state->csc_mask = obss_socket[psock].event_mask;

	DBG_INIT("(%d) = flags %#3.3x, Vcc %d, Vpp %d, "
		 "io_irq %d, csc_mask %#2.2x\n", psock, state->flags,
		 state->Vcc, state->Vpp, state->io_irq, state->csc_mask);
	return 0;
}				/* obcmp_get_socket */

/*====================================================================*/
static void obss_busy_loop(int howoften)
{
	u_char oldreg = READ_ADDR();
	int i = 0;

	WRITE_ADDR(0);
	for (i = 0; i < howoften; i++) {
		volatile u_char dummy;
		dummy = READ_DATA();
		dummy = READ_DATA();
		dummy = READ_DATA();
		dummy = READ_DATA();
		dummy = READ_DATA();
		dummy = READ_DATA();
		dummy = READ_DATA();
		dummy = READ_DATA();
		dummy = READ_DATA();
		dummy = READ_DATA();
		dummy = READ_DATA();
		dummy = READ_DATA();
		dummy = READ_DATA();
		dummy = READ_DATA();
		dummy = READ_DATA();
	}

	WRITE_ADDR(oldreg);
}


static void obss_reset(void)
{
	u_char valx, valsave, saved_mode;
	unsigned short bios_val;
	u_long flags;

#if 0
	if (NEWER_BIOS_REV)
		return;
#endif
	DBG_INIT("RESETXXXX %s", "XXX");

	local_irq_save(flags);
	saved_mode = read_mode();

	valsave = read_data(OBSS_POWER);
	valx = valsave & ~0x04;	/*clear bit 2 */
	bios_val = read_bios();
	if (bios_val & 0xe000)
		valx |= 0x04;
	WRITE_DATA(valx);	/* write it back */
	if (valsave & 0x04) {
		/* was already set */
	} else {
		if (valx & 0x04) {
			DBG_INIT("VBUSY_LOOP %s", "XXX");
			/* bit was just set, we need to wait */
			obss_busy_loop(0x29b);
		}
	}

	write_mode(saved_mode);
	local_irq_restore(flags);
	mdelay(50);
}


static void obss_reset_socket(int psock)
{
	u_char reg, val, saved_mode;
	u_long flags;

	local_irq_save(flags);
	saved_mode = read_mode();

	reg = OBSS_CSCTL2 - (psock * 2);
	val = read_data(reg);	/* OBSS_CSCTL2 */

	if ((val & CSCTL2_DETECT)) {

		/* SOcket reset sequence */
		u_char socketmask = ~(0x80 >> psock);
		u_char valx;

		valx = read_data(OBSS_CSIRQ);
		valx &= socketmask;
		WRITE_DATA(valx);

		write_data(0x90, OBSS_CSCTL1 - (psock * 2));


		if (!NEWER_BIOS_REV) {
			/*done only on bios rev. 3 */
			/* clear bit 13 (socket 0) or bit 14 (socket 1) at special bios addr */
			/* these are needed for 12 V Vpp */
			and_bios(~(1 << (13 + psock)));

			obss_reset();

		}
		/* read reg again */
		val = read_data(reg);
		val &= 0xfe;	/* clear bit 1 */
		val |= 0x02;	/* set bit 1 */
		WRITE_DATA(val);
		obss_busy_loop(4);
		val |= 0x03;	/* set both bits */
		write_data(val, reg);
		obss_busy_loop(4);
		WRITE_ADDR(0);
		val &= 0xfe;	/* clear bit 1 again */
		write_data(val, reg);
	}

	write_mode(saved_mode);
	local_irq_restore(flags);
}


/*====================================================================*/

static int obss_set_socket(unsigned int psock, socket_state_t * state)
{
	u_char reg, val, saved_mode;
	int i;
	u_long flags;

	DBG_INIT("(%d, flags %#3.3x, Vcc %d, Vpp %d, "
		 "io_irq %d, csc_mask %#2.2x) bios:%x\n", psock,
		 state->flags, state->Vcc, state->Vpp, state->io_irq,
		 state->csc_mask, read_bios());

	obss_socket[psock].is_iocard = (state->flags & SS_IOCARD) ? 1 : 0;

	/* We only try to detect these events */
	obss_socket[psock].event_mask = state->csc_mask &
	    (SS_DETECT | SS_WRPROT | SS_STSCHG |
	     SS_BATDEAD | SS_BATWARN | SS_READY);

	if (state->flags & SS_RESET) {

		obss_reset_socket(psock);
	} else {
		/*        WRITE_DATA(val);  DBG_IO("%02x <- %02x\n", reg, val); */
	}

	/*enable irqs in socket */
	if (state->csc_mask & SS_DETECT)
		or_bios(1 << (0xc - psock));

	local_irq_save(flags);
	saved_mode = read_mode();

	reg = OBSS_CSCTL2 - (psock * 2);
	val = read_data(reg);	/* OBSS_CSCTL2 */

	obss_socket[psock].event_state = decode_csctl2(psock, val);

	if (state->flags & SS_IOCARD) {
		if (state->io_irq == 0)
			state->io_irq = psock ? 11 : 3;

		for (i = 0; (state->io_irq != obss_irq_lut[i]) && (i < 8);
		     i++);
		if (i >= 8)
			goto err_inval;

		val = i << 3;	/* start assembling OBSS_CSCTL1 */
		val |= CSCTL1_ENBIRQ | 0x40;	/* I think 0x40 is enable IRQ */
	} else {
		val = 0x90;
	}

	/* clear bit 13 (socket 0) or bit 14 (socket 1) at special bios addr */
	/* these are needed for 12 V Vpp */
	if (!NEWER_BIOS_REV)
		and_bios(~(1 << (13 + psock)));

	/* it doesn't seem to matter if vpp/vcc are set to 0 or 5V */

	if (state->Vcc == 50) {
		switch (state->Vpp) {
		case 0:
			break;
		case 50:
			break;
		case 120:
			val |= CSCTL1_VPP12;
			if (!NEWER_BIOS_REV)
				or_bios(1 << (13 + psock));
			break;
		default:
			goto err_inval;
		}
	} else if (state->Vcc != 0)
		goto err_inval;

	obss_socket[psock].Vcc = state->Vcc;
	obss_socket[psock].Vpp = state->Vpp;

	reg = OBSS_CSCTL1 - (psock * 2);

	write_data(val, reg);	/* OBSS_CSCTL1 */

	val = read_data(OBSS_CSIRQ);
	if (state->flags & SS_IOCARD) {
		val |= (CSIRQ_SOCK0 >> psock);
	} else {
		val &= ~(CSIRQ_SOCK0 >> psock);
	}
	WRITE_DATA(val);
	DBG_IO("%02x <- %02x\n", OBSS_CSIRQ, val);

	/* hmm, need to wait a longer time, here! */
	obss_reset();

	/* FIXME SS_OUTPUT_ENA (or is CSIRQ register really OUTPUT? */
	/* FIXME SS_IOCARD   */
	/* FIXME SS_SPKR_ENA */
	/* FIXME SS_DMA_MODE */

	write_mode(saved_mode);
	local_irq_restore(flags);
	return 0;

      err_inval:
	write_mode(saved_mode);
	return -EINVAL;
}				/* obss_set_socket */

/*====================================================================*/

#define LOOKUP_WINDOW(p, a, b, s, m) { \
	for (a=0; a<b; a++, p++) { \
		if (p->busy && (p->socket == s) && (p->map == m)) \
			break;	\
	} \
}


static int obss_get_io_map(unsigned int psock, struct pccard_io_map *io)
{
	struct obss_window *p;
	u_char win = io->map + 2 * psock;

	DBG_INIT("(%d) map %d\n", psock, win);
	if ((win >= NUM_IOPORT_WIN))
		return -EINVAL;

	p = &(obss_ioport[win]);
	io->speed = 13 * NS_PER_BIT;	/* fixed speed */

	io->start = p->base;
	io->stop = p->base + p->spd_sz;
	io->flags = p->flags;

#if 0
	u_char reg = (win * 2) + OBSS_IOWIN;

	u_char saved_mode = read_mode();

	valh = read_data(reg);
	vall = read_data(reg + 1);
	write_mode(saved_mode);

	io->start = vall | ((valh << 8) | 0x300);
	io->stop = io->start + (valh >> 4);

#endif

	return 0;
}


/*====================================================================*/

static int obss_set_io_map(unsigned int psock, struct pccard_io_map *io)
{
	unsigned short dx;
	struct obss_window *p;
	u_long flags;
	u_char win = io->map + psock * 2;

	/* len is infact the len -1! */
	int len = io->stop - io->start;	/* signed - could be minus */

	DBG_INIT("(%d, %d, %#2.2x, %d ns, %#4.4x-%#4.4x)\n",
		 psock, io->map, io->flags, io->speed, io->start,
		 io->stop);

	if ((win >= NUM_IOPORT_WIN) || (io->map > NUM_IOPORT_WIN / 2)
	    || (io->start > 0x3ff) || (io->stop > 0x3ff)
	    || (len & ~0xF)	/* Check Min/Max ioport window size */
	    )
		return -EINVAL;

	/* Check to see that len+1 is power of two, etc */
	if ((len & (len + 1)) || (io->start & len))
		return -EINVAL;


	dx = (len << 12) | io->start;


#if 0	/* That's how the windoze driver saves the state */
	/* rol feee around the window # */
	ax = (0xfeee << win) | (0xfeee >> (16 - win));
	ax &= 0xffff;

	da5 &= ax;

	al = (io->flags & MAP_16BIT) ? 1 : 0;
	al |= (io->flags & MAP_ACTIVE) ? 0x10 : 0;
	al |= psock ? 0x100 : 0;
	al = al << win;

	da5 |= al;
#endif
	p = &(obss_ioport[win]);
	p->flags = io->flags;
	p->busy = 0;
	p->spd_sz = len;
	p->base = io->start;

	{
		u_char saved_mode;
		u_char reg;

		local_irq_save(flags);
		saved_mode = read_mode();

		reg = (win * 2) + OBSS_IOWIN;
		write_data((dx >> 8), reg);
		write_data(dx & 0xff, reg + 1);

		if (io->flags & MAP_ACTIVE) {
			u_char tmp;
			/* enable window for socket */
			tmp = (psock << 2) ^ 0xc;
			tmp |= (dx >> 8);
			write_data(tmp, reg);

			p->socket = psock;
			p->busy = 1;
		}

		write_mode(saved_mode);
		local_irq_restore(flags);
	}

	return 0;
}				/* obss_set_io_map */


/*====================================================================*/
/*
** Linux PCMCIA CS can assign up to 4 MMIO windows a socket.
** VL82C717 has 5 MMIO windows which can be assigned to either socket.
** Statically allocate 2 MMIO windows to each socket and ignore the 5th.
*/
static int obss_get_mem_map(unsigned int psock, struct pccard_mem_map *mem)
{
	struct obss_window *p;
	u_char win = psock * 3 + mem->map;

	DBG_INIT("(%d) map %d\n", psock, mem->map);

	if (win >= NUM_MMIO_WIN || mem->map > 2)
		return -EINVAL;

	p = &(obss_mmio[win]);
	mem->flags = p->flags;
	mem->sys_start = p->base << 14;
	mem->card_start = (p->base + p->offset) << 14;
	mem->speed = to_ns(p->spd_sz);	/* return speed in ns */
	mem->sys_stop = mem->sys_start + 16 * 1024 - 1;

	return 0;
}				/* obss_get_mem_map */


/*====================================================================*/

static int obss_set_mem_map(unsigned int psock, struct pccard_mem_map *mem)
{
	struct obss_window *p = &(obss_mmio[0]);
	u_char reg, val, val2, valx, saved_mode;
	u_char speed_idx = to_idx(mem->speed);
	u_long flags;

	u_char win = psock * 3 + mem->map;

	/* static int xxx = 0; */
	static int last_speed = 0, last_base = 0;

	int dump_regs = 1;
#if 0
	/* don't output too often or console will hang */
	if (mem->speed != last_speed) {
		DBG_INIT
		    ("speed change: %d, %#2.2x, %d ns (%d), 0x%lx-0x%lx, 0x%x\n",
		     win, mem->flags, mem->speed, speed_idx,
		     mem->sys_start, mem->sys_stop, mem->card_start);
	} else if (mem->sys_start != last_base) {
		DBG_INIT
		    ("base change: %d, %#2.2x, %d ns (%d), 0x%lx-0x%lx, 0x%x\n",
		     win, mem->flags, mem->speed, speed_idx,
		     mem->sys_start, mem->sys_stop, mem->card_start);
	} else {
		dump_regs = 0;
	}
#else
	dump_regs = 0;
#endif

	last_speed = mem->speed;
	last_base = mem->sys_start;


	if (win >= NUM_MMIO_WIN || mem->map > 2)
		return -EINVAL;

	if (mem->sys_start & 0x3fff) {	/* not properly aligned */
		/* DBG_IO("socket %d map %d : invalid alignment\n", psock, mem->map); */
		return -EINVAL;
	}
	if ((mem->sys_stop - mem->sys_start) != MEMWIN_SIZE) {
		/*DBG_IO("socket %d map %d : invalid size\n", psock, mem->map); */
		/* return -EINVAL; */
	}
	if (mem->flags & (MAP_USE_WAIT | MAP_AUTOSZ | MAP_0WS | MAP_WRPROT)) {
		DBG_IO("socket %d map %d : invalid flag\n", psock,
		       mem->map);
		/* return -EINVAL; */
	}

	p = &obss_mmio[win];
	p->busy = (mem->flags & MAP_ACTIVE) ? 1 : 0;
	p->base = (mem->flags & MAP_ACTIVE) ? (mem->sys_start >> 14) : 0;
	p->map = mem->map;
	p->spd_sz = speed_idx;
	p->socket = psock;
	p->flags = mem->flags;
	if (mem->card_start == 0) {
		p->offset = 0;
	} else {
		p->offset = ((mem->card_start - mem->sys_start) >> 14);
	}

	reg = OBSS_MEMWIN + win * 3;
	val = (mem->flags & MAP_16BIT) ? MEMWIN_16BIT : 0;
	val |= p->base;
	val |= ((speed_idx & 1) << 6);

	val2 = ((psock << 4) ^ 0x30) | ((speed_idx & 2) << 6);

	local_irq_save(flags);
	saved_mode = read_mode();

	write_data(val, reg);	/* OBSS_MEMWIN + 0 */
	reg++;

	valx = 0;		/*read_data(reg); */
	valx &= 0x8f;
	val2 |= valx | ((mem->flags & MAP_ATTRIB) ? 0x0 : 0x80);

	write_data(val2, reg);	/* OBSS_MEMWIN + 1 */

	reg++;
	write_data(p->offset, reg);

	write_mode(saved_mode);
	local_irq_restore(flags);

	if (dump_regs) {
		DBG_INIT("%x %x %x\n", val, val2, p->offset);
	}

	return 0;
}				/* obss_set_mem_map */

/*====================================================================*/

#ifdef CONFIG_PROC_FS
static int obss_read_info(char *buf, char **start, off_t pos,
			  int count, int *eof, void *data)
{
/* 	obss_socket_t *s = data; */
	char *p = buf;
	p += sprintf(p, "type:     VL82C717 v%d\npsock:    %d\n",
		     obss_hwrev, (int) data);
	return (p - buf);
}


static int obss_read_regs(char *buf, char **start, off_t pos,
			  int count, int *eof, void *data)
{
	int psock = (int) data;
	char *p = buf;
	int i;
	u_long flags;

	/* IRQ assigned to socket */

	for (i = 0; i < NUM_IOPORT_WIN; i++) {
		struct obss_window *s = &(obss_ioport[i]);
		if (!s->busy || s->socket != psock)
			continue;
		p += sprintf(p,
			     "IO Port Window %d, map %d,  address 0x%x-0x%x speed %d ns\n",
			     i, s->map, s->base, s->base + s->spd_sz,
			     13 * NS_PER_BIT);
	}

	for (i = 0; i < NUM_MMIO_WIN; i++) {
		struct obss_window *s = &obss_mmio[i];
		if (!s->busy || s->socket != psock)
			continue;
		p += sprintf(p,
			     "MMIO Window %d, map %d,  address 0x%x-0x%x speed %d ns\n",
			     i, s->map, s->base << 14,
			     (s->base << 14) + (16 * 1024 - 1),
			     s->spd_sz * NS_PER_BIT);
	}

	{
		u_char saved_mode;
		u_char reg, val;

		local_irq_save(flags);
		saved_mode = read_mode();

		reg = OBSS_CSCTL1 - psock * 2;
		val = read_data(reg);
		p += sprintf(p, "0x%x 0x%x (IRQ %d Vpp %d)\n",
			     reg, val,
			     obss_irq_lut[(val >> 3) & 7],
			     (val & 0x04) ? 12 : 5);

		reg++;
		val = read_data(reg);
		p += sprintf(p,
			     "0x%x 0x%x (BatWarn %d  Card %d  Rdy %d  RST %d)\n",
			     reg, val, (val >> 6) & 1, (val >> 4) & 1,
			     (val >> 3) & 1, val & 1);

		write_mode(saved_mode);
		local_irq_restore(flags);
	}
	return (p - buf);
}


static int obss_proc_setup(unsigned int psock, struct proc_dir_entry *base)
{
	DBG_INIT("(%d, %p)\n", psock, base);

	create_proc_read_entry("info", 0, base, obss_read_info,
			       (void *) psock);
	create_proc_read_entry("regs", 0, base, obss_read_regs,
			       (void *) psock);
	obss_socket[psock].proc = base;
	return 0;
}


static void obss_proc_remove(u_short sock)
{
	struct proc_dir_entry *base = obss_socket[sock].proc;
	if (base == NULL)
		return;
	remove_proc_entry("info", base);
	remove_proc_entry("regs", base);
}

#else

#define obss_proc_setup NULL

#endif


typedef int (*subfn_t) (u_short, void *);

static subfn_t service_table[] = {
	(subfn_t) & obss_register_callback,
	(subfn_t) & obss_inquire_socket,
	(subfn_t) & obss_get_status,
	(subfn_t) & obss_get_socket,
	(subfn_t) & obss_set_socket,
	(subfn_t) & obss_get_io_map,
	(subfn_t) & obss_set_io_map,
	(subfn_t) & obss_get_mem_map,
	(subfn_t) & obss_set_mem_map,
};

#define NFUNC (sizeof(service_table)/sizeof(subfn_t))

static int obss_service(u_int lsock, u_int cmd, void *arg)
{
	int res = -EINVAL;
	res = (cmd < NFUNC) ? service_table[cmd] (lsock, arg) : -EINVAL;
	if (res == -EINVAL && cmd == SS_ProcSetup)
		res = obss_proc_setup(lsock, arg);

	return res;
}




/*====================================================================*/

static void obss_power(int power)
{
	u_char saved_mode;
	u_long flags;
	u_char val;

	local_irq_save(flags);
	saved_mode = read_mode();

	val = read_data(OBSS_POWER);
	WRITE_DATA(val | (power << 2));
	DBG_IO("%02x <- %02x\n", OBSS_POWER, val);
	write_mode(saved_mode);

	/* If we are turning power on, delay a bit */
	if (power && !(val & POWER_ENABLE))
		mdelay(5);
	local_irq_restore(flags);
}





static void obss_hw_init(void)
{
	int i;
	u_char val;
	u_char saved_mode;
	u_long flags;

	local_irq_save(flags);
	saved_mode = read_mode();

	/* blast init values to internal registers */
	for (i = 0; reg_data[i].reg; i++)
		write_data(reg_data[i].data, reg_data[i].reg);

	/* Disable Socket 0 and 1 IRQ */
	val = read_data(OBSS_CSIRQ);
	val &= 0x33;		/* Save BIOS settings? */
	val |= 0x0C;		/* FIXME - why set these bits? */
	WRITE_DATA(val);
	DBG_IO("%02x <- %02x\n", OBSS_CSIRQ, val);

	write_mode(saved_mode);
	local_irq_restore(flags);
}

/* The windoze/DOS driver does it like the code #ifdef'd wih this define. We cannot do that
 * here, because linux overwrites the interrupt vector for int 0x15.
 */
#undef TRY_INT15

static int __init init_obss(void)
{
	int i;
	servinfo_t serv;

#if TRY_INT15
	short ax, bx, cx, dx;
#endif
	volatile char *bword;
	unsigned long special_address;




	printk(KERN_INFO "%s\n", version);

	CardServices(GetCardServicesInfo, &serv);

	/* pcmcia_get_card_services_info(&serv); */
	/* 2.4.7 == 3.1.16 - debian sid wants 3.1.27 */
	if (serv.Revision != CS_RELEASE_CODE) {
		printk(KERN_NOTICE
		       "ob600_ss: Card Services (0x%x) release does not match ob600_ss (0x%x)!\n",
		       serv.Revision, CS_RELEASE_CODE);
		return -1;
	}

#if TRY_INT15
	/* check HP interrupt - used by vxd, but not really needed */

	ax = 0x4dd4;
	__asm__
	    __volatile__
	    ("movw %3,%%ax \n\t int $0x15 \n\t movw %%bx,%0 \n\t movw %%cx,%1 \n\t movw %%dx,%2":"=r"
	     (bx), "=r"(cx), "=r"(dx):"g"(ax):"ebx", "ecx", "edx");

	DBG_ACT("INT 15 result: bx:%x  cx:%x dx:%x\n", bx, cx, dx);
	if (bx != 0x4850)	/* "HP" */
		return -1;

	bios_rev = (cx & 0xff);
	if (bios_rev < 3)
		return -1;
#endif

	/* get bios word register */
	bword = (char *) ioremap(0x400, 0x400);	/* 0x40e;      */
	special_address = readw(bword + 0xe);
	iounmap((void *) bword);

	/* map special bios address space */
	bios_addr = ioremap(((special_address) << 4), 0x400);


	printk(KERN_INFO "BIOS addr %p\n", bios_addr);
	/*clear all bits except 15. Probably means "init" */
	and_bios(0x8000);

	if (0 == check_region(OBSS_ADDR, 2)) {
		u_long flags;
		/*
		 ** no one has claimed IO ports yet, we can carefully
		 ** poke around there - check mode and then HW REV register.
		 */
		u_char saved_mode;
		local_irq_save(flags);
		saved_mode = read_mode();

		if (!saved_mode) {
			u_char extra_info = obss_hwrev =
			    read_data(OBSS_HWREV);
			extra_info &= ~REV_MASK;
			obss_hwrev &= REV_MASK;

			/* OB600CT : 0xAD and 0xD (ie rev 1) */
			if (obss_hwrev == 0x03) {
				printk(KERN_INFO "not found. "
				       "(hwrev %d  extra_info 0x%x)\n",
				       obss_hwrev, extra_info);
				return -ENODEV;
			}

		} else {
			printk(KERN_INFO "(aack! MODE == 0x%x) ",
			       saved_mode);
		}

		write_mode(saved_mode);
		local_irq_restore(flags);
	}

	printk(KERN_INFO "rev %d.\n", obss_hwrev);

	request_region(OBSS_ADDR, 2, "ob600_ss");
	/* FPU owns this : request_region(OBSS_TOGGLE, 1, "ob600_ss"); */

	for (i = 0; i < OBSS_NUM_SOCKETS; i++) {
		obss_socket[i].handler = NULL;
		obss_socket[i].info = NULL;
	}

	memset(obss_mmio, 0, sizeof(struct obss_window) * NUM_MMIO_WIN);
	memset(obss_ioport, 0,
	       sizeof(struct obss_window) * NUM_IOPORT_WIN);

	/* init HW */
	obss_hw_init();
	/*obss_power(1); *//* enable adapter power ? */

	/* FIXME: need to tell the OS that IRQ 9 is edge triggered? */
	if (request_irq
	    (OBSS_ADAPTER_IRQ, obss_interrupt, 0, "ob600_ss",
	     obss_interrupt)) {
		printk(KERN_WARNING "  irq %d not available!\n",
		       OBSS_ADAPTER_IRQ);
		goto err1;
	}
	printk(KERN_INFO "  irq %d\n", OBSS_ADAPTER_IRQ);


	/*enable irq: */
	and_bios(0xfffe);
	or_bios(0x0001);



	if (register_ss_entry(OBSS_NUM_SOCKETS, &obss_service) != 0) {
		printk(KERN_NOTICE
		       "ob600_ss: register_ss_entry() failed\n");
		free_irq(OBSS_ADAPTER_IRQ, obss_interrupt);
	      err1:
		release_region(OBSS_ADDR, 2);
		/* FPU owns this : release_region(OBSS_TOGGLE, 1);  */
		return -ENODEV;
	}

	return 0;
}				/* init_obss */


static void __exit exit_obss(void)
{
	u_long flags;
	int i;

	/*clear all bits except 15, clear interrupts */
	and_bios(0x8000);
	obss_power(0);		/* disable adapter power */

#ifdef CONFIG_PROC_FS
	for (i = 0; i < OBSS_NUM_SOCKETS; i++)
		obss_proc_remove(i);
#endif

	unregister_ss_entry(&obss_service);

	iounmap((void *) bios_addr);

	local_irq_save(flags);
	free_irq(OBSS_ADAPTER_IRQ, obss_interrupt);
	local_irq_restore(flags);

	release_region(OBSS_ADDR, 2);
	/* release_region(OBSS_TOGGLE, 1); */
}				/* exit_obss */


module_init(init_obss);
module_exit(exit_obss);

