/*======================================================================

    Device driver for Intel 82365 and compatible PCMCIA controllers

    Written by David Hinds, dhinds@allegro.stanford.edu
    
======================================================================*/

#include <linux/autoconf.h>

#ifdef MODULE
#include <linux/module.h>
#include <linux/version.h>
#endif

#include <sys/types.h>
#include <linux/fcntl.h>
#include <linux/string.h>

#include <asm/io.h>
#include <asm/bitops.h>
#include <asm/segment.h>
#include <asm/system.h>

#include <linux/kernel.h>
#include <linux/errno.h>
#include <linux/timer.h>
#include <linux/sched.h>
#include <linux/malloc.h>
#include <linux/pci.h>
#include <linux/ioport.h>

#include "version.h"
#include "ss.h"
#include "i82365.h"
#include "pd67xx.h"
#include "vg468.h"
#include "ricoh.h"
#include "cs_types.h"
#include "cs.h"

#ifdef PCMCIA_DEBUG
static int pc_debug = PCMCIA_DEBUG;
static const char *version =
    "i82365.c 1.68 1995/05/15 01:44:37 (David Hinds)\n";
#endif

/*====================================================================*/

/* Parameters that can be set with 'insmod' */

/* Default base address for i82365sl chip */
static int i365_base = 0x3e0;

/* Bit map of interrupts to choose from -- 0 means autoprobe */
static u_long irq_mask = 0;

/* Specify a socket number to ignore */
static int ignore = -1;

/* Poll status interval -- 0 means default to interrupt */
static int poll_interval = 0;

/* External clock time, in nanoseconds.  210 ns = 4.77 MHz */
static int cycle_time = 210;

/* These features may or may not be implemented */
static int has_dma = 0;
static int has_led = 1;
static int has_ring = 1;

/* Timing parameters for Vadem VG468 chips */
static int async_clock = 0;

/* For the Cirrus PD6729 PCI chip -- set if bus > 25 MHz */
#ifdef CONFIG_PCI
static int fast_pci = 0;
#endif

/* Timing parameters for Cirrus PD67XX ISA chips */
static int freq_bypass = 0;
static int setup_time = 1;
static int cmd_time = 6;
static int recov_time = 0;
static int wakeup = 0;

/*====================================================================*/

static struct timer_list poll_timer;

static void i365_interrupt(int irq, struct pt_regs *regs);

static int i365_service(u_int sock, u_int cmd, void *arg);

typedef struct socket_info_t {
    int		type;
    socket_cap_t cap;
    u_short	psock;
    u_short	cs_irq;
    void	(*handler)(void *info, u_long events);
    void	*info;
} socket_info_t;

/* Controller types */
#define CHIP(t)		((t) & 0xf0)
#define ID(t)		((t) & 0x0f)
#define IS_I82365	0x10
#define IS_PD672X	0x20
#define IS_PD6710	0x30
#define IS_PD6729	0x40
#define IS_VLSI		0x50
#define IS_IBM		0x60
#define IS_VG468	0x70
#define IS_RICOH	0x80

/* Where we keep track of our sockets... */
static int sockets = 0;
static socket_info_t socket_table[8] = {
    { 0, { 0 }, 0, 0, NULL, NULL }, /* ... */
};

/* Socket capabilities */
static socket_cap_t i365_cap = {
    0xdeb8,	/* irq 15, 14, 12, 11, 10, 9, 7, 5, 4, 3 */
};

/*====================================================================*/

static u_char i365_get(u_short sock, u_short reg)
{
    u_short port = I365_PORT(sock);
    u_char val = I365_REG(sock, reg);
    cli();
    outb_p(val, port); val = inb_p(port+1);
    sti();
    return val;
}

static void i365_set(u_short sock, u_short reg, u_char data)
{
    u_short port = I365_PORT(sock);
    u_char val = I365_REG(sock, reg);
    cli();
    outb_p(val, port); outb_p(data, port+1);
    sti();
}

static void i365_bset(u_short sock, u_short reg, u_char mask)
{
    u_char d = i365_get(sock, reg);
    d |= mask;
    i365_set(sock, reg, d);
}

static void i365_bclr(u_short sock, u_short reg, u_char mask)
{
    u_char d = i365_get(sock, reg);
    d &= ~mask;
    i365_set(sock, reg, d);
}

static u_short i365_get_pair(u_short sock, u_short reg)
{
    u_short a, b;
    a = i365_get(sock, reg);
    b = i365_get(sock, reg+1);
    return (a + (b<<8));
}

static void i365_set_pair(u_short sock, u_short reg, u_short data)
{
    i365_set(sock, reg, data & 0xff);
    i365_set(sock, reg+1, data >> 8);
}

/*====================================================================*/

static void busy_loop(u_long len)
{
    u_long flags, timeout = jiffies + len;
    save_flags(flags);
    sti();
    while (timeout >= jiffies)
	;
    restore_flags(flags);
} /* busy_loop */

/*====================================================================*/

static volatile u_long irq_hits;

static void irq_count(int irq, struct pt_regs *regs)
{
    irq_hits++;
}

static u_long try_irq(u_short psock, int irq)
{
    u_char csc;
    
    if (request_irq(irq, irq_count, 0, "irq scan") != 0)
	return -1;
    irq_hits = 0;
    busy_loop(2);
    if (irq_hits) {
	free_irq(irq);
	return -1;
    }

    /* Generate one interrupt */
    csc = i365_get(psock, I365_CSCINT);
    i365_set(psock, I365_CSCINT, I365_CSC_DETECT | (irq << 4));
    i365_bclr(psock, I365_INTCTL, I365_INTR_ENA);
    i365_bset(psock, I365_GENCTL, I365_CTL_SW_IRQ);

    busy_loop(2);
    free_irq(irq);

    /* Turn off interrupts */
    i365_set(psock, I365_CSCINT, csc);
    
    return (irq_hits != 1);
}

static u_long irq_scan(u_short lsock, u_long mask0)
{
    u_short psock;
    u_long mask1, mask2;
    int i, ni;
    
    psock = socket_table[lsock].psock;
    mask1 = mask2 = ni = 0;
    for (i = 0; i < 16; i++)
	if ((mask0 & (1 << i)) && (try_irq(psock, i) == 0))
	    mask1 |= (1 << i);
    for (i = 0; i < 16; i++)
	if ((mask0 & (1 << i)) && (try_irq(psock, i) == 0)) {
	    mask2 |= (1 << i);
	    ni++;
	}
    if (ni > 2)
	return (mask1 & mask2);
    else
	return 0;
}

/*====================================================================*/

/* Time conversion functions */

static int to_cycles(int ns)
{
    return ns/cycle_time;
} /* speed_convert */

static int to_ns(int cycles)
{
    return cycle_time*cycles;
}

/*====================================================================*/

static int identify(int sock)
{
    u_char val;
    int type;
    
    val = i365_get(sock, I365_IDENT);
    switch (val) {
    case 0x82: case 0x83:
	type = IS_I82365 | (val & 7); break;
    case 0x84:
	type = IS_VLSI; break;
    case 0x88: case 0x89:
	type = IS_IBM | (val & 7); break;
    case 0x8b:
	type = IS_VG468 | (val & 7);
	i365_bclr(sock, VG468_MISC, VG468_MISC_VADEMREV);
	break;
    default:
	return -1;
    }
    
    /* Check for Vadem VG-468 chips */
    if (CHIP(type) == IS_I82365) {
	outb_p(0x0e, I365_PORT(sock));
	outb_p(0x37, I365_PORT(sock));
	i365_bset(sock, VG468_MISC, VG468_MISC_VADEMREV);
	val = i365_get(sock, I365_IDENT);
	if (val & I365_IDENT_VADEM) {
	    type = IS_VG468 | (val & 7);
	    i365_bclr(sock, VG468_MISC, VG468_MISC_VADEMREV);
	}
    }

    /* Check for Ricoh chips */
    val = i365_get(sock, RF5C_CHIP_ID);
    if ((val == RF5C_CHIP_RF5C296) || (val == RF5C_CHIP_RF5C396))
	type = IS_RICOH | ID(type);
    
    /* Check for Cirrus CL-PD67xx chips */
    i365_set(sock, PD67_CHIP_INFO, 0);
    val = i365_get(sock, PD67_CHIP_INFO);
    if ((val & PD67_INFO_CHIP_ID) == PD67_INFO_CHIP_ID) {
	val = i365_get(sock, PD67_CHIP_INFO);
	if ((val & PD67_INFO_CHIP_ID) == 0) {
	    if (val & PD67_INFO_SLOTS)
		type = IS_PD672X;
	    else
		type = IS_PD6710;
	    type |= 8 - ((val & PD67_INFO_REV) >> 2);
	}
    }
    return type;
} /* identify */

/*====================================================================*/

void add_pcic(int type, int ns)
{
    int i, j, irq;
    u_short base;
    u_long mask, scan;
    
    if (sockets == ns) printk("\n  ");
    switch (CHIP(type)) {
    case IS_VLSI:
	printk("VLSI 82C146"); break;
    case IS_I82365:
	printk("Intel i82365sl"); break;
    case IS_PD672X:
	printk("Cirrus PD672x");
	if (has_dma) printk(", DMA");
	if (has_led) printk(", LED");
	if (has_ring) printk(", RING");
	break;
    case IS_PD6710:
	printk("Cirrus PD6710"); break;
    case IS_PD6729:
	printk("Cirrus PD6729 PCI"); break;
    case IS_IBM:
	printk("IBM Clone"); break;
    case IS_VG468:
	printk("Vadem VG-468"); break;
    case IS_RICOH:
	printk("Ricoh RF5C296/396");
	if (has_led) printk(", LED");
	break;
    }
    base = sockets-ns;
    printk(" rev %d at %#x", ID(type),
	   I365_PORT(socket_table[base].psock));
    printk(", %d socket%s\n", ns, ((ns > 1) ? "s" : ""));

    /* Build interrupt mask; scan if possible */
    printk("  irq mask (");
    mask = (irq_mask) ? irq_mask : i365_cap.irq_mask;
    switch (CHIP(type)) {
    case IS_PD6710:
    case IS_PD6729:
    case IS_PD672X:
	if (has_dma)  mask &= ~0x0600; /* disable irq 9,10 */
	if (has_led)  mask &= ~0x1000; /* disable irq 12 */
	if (has_ring) mask &= ~0x8000; /* disable irq 15 */
	printk("Cirrus");
	break;
    default:
	scan = irq_scan(base, mask);
	if (scan != 0) {
	    mask = scan;
	    printk("scanned");
	}
	else
	    printk("default");
	break;
    }
    printk(") = 0x%lx, ", mask);
    
    /* Pick a card status change interrupt */
    if (poll_interval == 0) {
	for (irq = 15; irq > 0; irq--)
	    if ((mask & (1 << irq)) &&
		(request_irq(irq, i365_interrupt, 0, "i82365") == 0))
		break;
	if (irq == 0) poll_interval = 100;
    }
    else
	irq = 0;
    
    if (irq)
	printk("status change on irq %d\n", irq);
    else
	printk("polled status, interval = %d\n", poll_interval);
    
    /* Turn on interrupts, but with all events masked */
    for (i = base; i < base+ns; i++) {
	socket_table[i].cap.irq_mask = mask;
	socket_table[i].cs_irq = irq;
	if (irq != 0) {
	    j = socket_table[i].psock;
	    i365_set(j, I365_CSCINT, (irq << 4));
	    i365_bclr(j, I365_INTCTL, I365_INTR_ENA);
	    i365_get(j, I365_CSC);
	}
    }

} /* add_pcic */

/*====================================================================*/

int i365_init(void)
{
    int i, j, k, ns;
    int id, type, is_pci = 0;

#ifdef PCMCIA_DEBUG
    if (pc_debug)
	printk(version);
#endif
    
    printk("Intel PCIC probe: ");
    
#ifdef CONFIG_PCI
    if (pcibios_present()) {
	u_char bus, device_fn;
	/* Check for Cirrus PD6729 PCI controller */
	i = pcibios_find_device(PCI_VENDOR_ID_CIRRUS,
				PCI_DEVICE_ID_CIRRUS_6729,
				0, &bus, &device_fn);
	if (i == 0) {
	    unsigned long addr;
	    pcibios_read_config_dword(bus, device_fn, 0x10, &addr);
	    i365_base = addr & ~0x1;
	    pcibios_write_config_dword(bus, device_fn, 0x4, 0x00ff00ff);
	    is_pci = 1;
	}
    }
#endif

    if (check_region(i365_base, 8) != 0) {
	printk("port conflict at %#x\n", i365_base);
	return -ENODEV;
    }

    sockets = 0;

    /* Wake up a sleepy Cirrus controller */
    if (wakeup) {
	i365_bclr(0, PD67_MISC_CTL_2, PD67_MC2_SUSPEND);
	/* Pause at least 50 ms */
	busy_loop(6);
    }
    
    type = identify(0);
    if (CHIP(type) == IS_VLSI) {
	for (i = 0; i < 4; i++) {
	    if (i == ignore) continue;
	    j = ((i & 1) << 3) + (i << 1);
	    if (identify(j) == type) {
		socket_table[sockets].psock = j;
		socket_table[sockets].type = type;
		sockets++;
	    }
	}
	add_pcic(type, sockets);
    }
    else
	for (i = 0; i < 8; i += 4) {
	    id = identify(i);
	    if (id < 0) continue;

	    if (is_pci && (CHIP(id) == IS_PD672X))
		type = ID(id) | IS_PD6729;
	    else
		type = id;
	    
	    ns = 0;
	    for (j = 0; j < 4; j++) {
		/* Does the socket exist? */
		if ((ignore == i+j) || (identify(i+j) != id))
		    continue;
		/* Check for bad socket decode */
		i365_set(i+j, I365_CSCINT, i+j);
		for (k = i; k < i+j; k++) {
		    if (i365_get(k, I365_CSCINT) != k) {
			i365_set(k, I365_CSCINT, k);
			break;
		    }
		}
		if (k == i+j) {
		    socket_table[sockets].psock = i+j;
		    socket_table[sockets].type = type;
		    sockets++;
		    ns++;
		}
	    }
	    add_pcic(type, ns);
	}
    
    if (sockets == 0) {
	printk("not found.\n");
	return -ENODEV;
    }
    
    request_region(i365_base, 8, "i82365");

    /* Set up polling */
    if (poll_interval != 0) {
	poll_timer.function = (void (*)(u_long))i365_interrupt;
	poll_timer.data = 0;
	poll_timer.prev = poll_timer.next = NULL;
    }
    
    if (register_ss_entry(sockets, &i365_service) != 0) {
	printk("i82365: register_ss_entry() failed\n");
	release_region(i365_base, 8);
	for (i = 0; i < sockets; i++)
	    if (((i & 3) == 0) && socket_table[i].cs_irq)
		free_irq(socket_table[i].cs_irq);
	return -ENODEV;
    }

    /* Finally, schedule a polling interrupt */
    if (poll_interval != 0) {
    	poll_timer.expires = poll_interval;
	add_timer(&poll_timer);
    }
    
    return 0;
    
} /* i365_init */
  
/*====================================================================*/

static void i365_finish(void)
{
    int i;
    unregister_ss_entry(&i365_service);
    if (poll_interval != 0)
	del_timer(&poll_timer);
    else
	for (i = 0; i < sockets; i++) {
	    i365_set(socket_table[i].psock, I365_CSCINT, 0);
	    if (((i & 3) == 0) && socket_table[i].cs_irq)
		free_irq(socket_table[i].cs_irq);
	}
    release_region(i365_base, 8);
} /* i365_finish */

/*====================================================================*/

static void i365_interrupt(int irq, struct pt_regs *regs)
{
    int i, csc;
    u_short psock;
    u_long events;
    static volatile int active = 0;

    if (active) {
	printk("i82365: reentered interrupt handler!\n");
	return;
    }
    else
	active = 1;

#ifdef PCMCIA_DEBUG
    if (pc_debug > 1)
	printk("i82365: i365_interrupt()\n");
#endif
    
    for (i = 0; i < sockets; i++) {
	if (socket_table[i].handler == NULL) continue;
	psock = socket_table[i].psock;
	csc = i365_get(psock, I365_CSC);
	if (csc == 0) continue;
	events = (csc & I365_CSC_DETECT) ? SS_DETECT : 0;
	if (i365_get(psock, I365_INTCTL) & I365_PC_IOCARD)
	    events |= (csc & I365_CSC_STSCHG) ? SS_STSCHG : 0;
	else {
	    events |= (csc & I365_CSC_BVD1) ? SS_BATDEAD : 0;
	    events |= (csc & I365_CSC_BVD2) ? SS_BATWARN : 0;
	    events |= (csc & I365_CSC_READY) ? SS_READY : 0;
	}
	if (events)
	    socket_table[i].handler(socket_table[i].info, events);
    }

    /* Schedule next poll, if needed */
    if (poll_interval > 0) {
	poll_timer.expires = poll_interval;
	add_timer(&poll_timer);
    }
    active = 0;
    
#ifdef PCMCIA_DEBUG
    if (pc_debug > 1)
	printk("i82365: done\n");
#endif

} /* i365_interrupt */

/*====================================================================*/

static int i365_register_callback(u_short lsock, ss_callback_t *call)
{
    if (call == NULL) {
	socket_table[lsock].handler = NULL;
#ifdef MODULE
	MOD_DEC_USE_COUNT;
#endif
    }
    else {
#ifdef MODULE
	MOD_INC_USE_COUNT;
#endif
	socket_table[lsock].handler = call->handler;
	socket_table[lsock].info = call->info;
    }
    return 0;
} /* i365_register_callback */

/*====================================================================*/

static int i365_get_status(u_short lsock, u_int *value)
{
    u_short psock = socket_table[lsock].psock;
    u_char status;
    
    status = i365_get(psock, I365_STATUS);
    *value = ((status & I365_CS_DETECT) == I365_CS_DETECT)
	? SS_DETECT : 0;
    if (i365_get(psock, I365_INTCTL) & I365_PC_IOCARD)
	*value |= (status & I365_CS_STSCHG) ? 0 : SS_STSCHG;
    else {
	*value |= (status & I365_CS_BVD1) ? 0 : SS_BATDEAD;
	*value |= (status & I365_CS_BVD2) ? 0 : SS_BATWARN;
    }
    *value |= (status & I365_CS_WRPROT) ? SS_WRPROT : 0;
    *value |= (status & I365_CS_READY) ? SS_READY : 0;
    *value |= (status & I365_CS_POWERON) ? SS_POWERON : 0;
#ifdef PCMCIA_DEBUG
    if (pc_debug > 1)
	printk("i82365: GetStatus(%d) = %#2.2x\n", lsock, *value);
#endif
    return 0;
} /* i365_get_status */

/*====================================================================*/

static int i365_inquire_socket(u_short lsock, socket_cap_t *cap)
{
    *cap = socket_table[lsock].cap;
    return 0;
} /* i365_inquire_socket */

/*====================================================================*/

static int i365_get_socket(u_short lsock, socket_state_t *state)
{
    u_short psock = socket_table[lsock].psock;
    u_char reg, vcc, vpp;
    
    reg = i365_get(psock, I365_POWER);
    state->flags = (reg & I365_PWR_AUTO) ? SS_PWR_AUTO : 0;
    state->flags |= (reg & I365_PWR_OUT) ? SS_OUTPUT_ENA : 0;
    vcc = reg & I365_VCC_MASK; vpp = reg & I365_VPP_MASK;
    state->Vcc = state->Vpp = 0;
    switch (CHIP(socket_table[lsock].type)) {
    case IS_PD672X:
    case IS_PD6710:
    case IS_PD6729:
	if (i365_get(psock, PD67_MISC_CTL_1) & PD67_MC1_VCC_3V) {
	    if (reg & I365_VCC_5V) state->Vcc = 30;
	    if (vpp == I365_VPP_5V) state->Vpp = 30;
	    if (vpp == I365_VPP_12V) state->Vpp = 120;
	}
	else {
	    if (reg & I365_VCC_5V) state->Vcc = 50;
	    if (vpp == I365_VPP_5V) state->Vpp = 50;
	    if (vpp == I365_VPP_12V) state->Vpp = 120;
	}
	break;
    case IS_VG468:
	if (reg & I365_VCC_5V) {
	    state->Vcc = 50;
	    if (vpp == I365_VPP_5V) state->Vpp = 50;
	    if (vpp == I365_VPP_12V) state->Vpp = 120;
	}
	break;
    default:
	if (vcc == I365_VCC_3V) state->Vcc = 33;
	if (vcc == I365_VCC_5V) state->Vcc = 50;
	if (vpp == I365_VPP_5V) state->Vpp = 50;
	if (vpp == I365_VPP_12V) state->Vpp = 120;
	break;
    }
    reg = i365_get(psock, I365_INTCTL);
    state->flags |= (reg & I365_PC_IOCARD) ? SS_IOCARD : 0;
    state->flags |= (reg & I365_PC_RESET) ? 0 : SS_RESET;

    /* IO card, RESET flags, IO interrupt */
    reg = i365_get(psock, I365_INTCTL);
    state->flags |= (reg & I365_PC_RESET) ? 0 : SS_RESET;
    if (reg & I365_PC_IOCARD) state->flags |= SS_IOCARD;
    state->io_irq = reg & I365_IRQ_MASK;
    
    /* DMA mode, speaker control */
    switch (CHIP(socket_table[lsock].type)) {
    case IS_PD672X:
    case IS_PD6710:
    case IS_PD6729:
	if (i365_get(psock, PD67_MISC_CTL_1) & PD67_MC1_SPKR_ENA)
	    state->flags |= SS_SPKR_ENA;
	if (i365_get(psock, PD67_MISC_CTL_2) & PD67_MC2_DMA_MODE)
	    state->flags |= SS_DMA_MODE;
	break;
    default:
    }
    
    /* Card status change mask */
    reg = i365_get(psock, I365_CSCINT);
    state->csc_mask = (reg & I365_CSC_DETECT) ? SS_DETECT : 0;
    if (state->flags & SS_IOCARD)
	state->csc_mask |= (reg & I365_CSC_STSCHG) ? SS_STSCHG : 0;
    else {
	state->csc_mask |= (reg & I365_CSC_BVD1) ? SS_BATDEAD : 0;
	state->csc_mask |= (reg & I365_CSC_BVD2) ? SS_BATWARN : 0;
	state->csc_mask |= (reg & I365_CSC_READY) ? SS_READY : 0;
    }
    
#ifdef PCMCIA_DEBUG
    if (pc_debug > 1)
	printk("i82365: GetSocket(%d) = flags %#3.3lx, Vcc %d, Vpp %d"
	       ", io_irq %d, csc_mask %#2.2lx\n", lsock, state->flags,
	       state->Vcc, state->Vpp, state->io_irq, state->csc_mask);
#endif
    return 0;
} /* i365_get_socket */

/*====================================================================*/

static int i365_set_socket(u_short lsock, socket_state_t *state)
{
    u_short psock = socket_table[lsock].psock;
    u_char reg;
    
#ifdef PCMCIA_DEBUG
    if (pc_debug > 1)
	printk("i82365: SetSocket(%d, flags %#3.3lx, Vcc %d, Vpp %d"
	       ", io_irq %d, csc_mask %#2.2lx)\n", lsock, state->flags,
	       state->Vcc, state->Vpp, state->io_irq, state->csc_mask);
#endif

    /* IO card, RESET flags, IO interrupt */
    reg = i365_get(psock, I365_INTCTL);
    reg &= ~(I365_PC_IOCARD | I365_PC_RESET | I365_IRQ_MASK);
    /* Note that the reset signal is inverted */
    reg |= (state->flags & SS_RESET) ? 0 : I365_PC_RESET;
    reg |= (state->flags & SS_IOCARD) ? I365_PC_IOCARD : 0;
    reg |= state->io_irq;
    i365_set(psock, I365_INTCTL, reg);

    reg = I365_PWR_NORESET;
    if (state->flags & SS_PWR_AUTO) reg |= I365_PWR_AUTO;
    if (state->flags & SS_OUTPUT_ENA) reg |= I365_PWR_OUT;
    
    switch (CHIP(socket_table[lsock].type)) {

    case IS_PD6729:
    case IS_PD672X:
    case IS_PD6710:
	if (state->Vpp != 0) {
	    if (state->Vpp == 120)
		reg |= I365_VPP_12V;
	    else if (state->Vpp == state->Vcc)
		reg |= I365_VPP_5V;
	    else return -EINVAL;
	}
	if (state->Vcc != 0) {
	    if (state->Vcc == 30) {
		reg |= I365_VCC_5V;
		i365_bset(psock, PD67_MISC_CTL_1, PD67_MC1_VCC_3V);
	    }
	    else if (state->Vcc == 50) {
		reg |= I365_VCC_5V;
		i365_bclr(psock, PD67_MISC_CTL_1, PD67_MC1_VCC_3V);
	    }
	    else return -EINVAL;
	}
	break;
	
    case IS_VG468:
	switch (state->Vcc) {
	case 0:		break;
	case 50:	reg |= I365_VCC_5V; break;
	default:	return -EINVAL;
	}
	switch (state->Vpp) {
	case 0:		break;
	case 50:	reg |= I365_VPP_5V; break;
	case 120:	reg |= I365_VPP_12V; break;
	default:	return -EINVAL;
	}
	break;
	
    default:
	switch (state->Vcc) {
	case 0:		break;
	case 30:   	reg |= I365_VCC_3V; break;
	case 50:	reg |= I365_VCC_5V; break;
	default:	return -EINVAL;
	}
	switch (state->Vpp) {
	case 0:		break;
	case 50:   	reg |= I365_VPP_5V; break;
	case 120:	reg |= I365_VPP_12V; break;
	default:	return -EINVAL;
	}
	break;
    }
    
    if (reg != i365_get(psock, I365_POWER))
	i365_set(psock, I365_POWER, reg);

    /* Chipset-specific functions */
    switch (CHIP(socket_table[lsock].type)) {
    case IS_VG468:
	if (async_clock)
	    i365_bset(psock, VG468_CTL, VG468_CTL_ASYNC);
	else
	    i365_bclr(psock, VG468_CTL, VG468_CTL_ASYNC);
	break;
#ifdef CONFIG_PCI
    case IS_PD6729:
	if (fast_pci)
	    i365_bset(psock, PD67_MISC_CTL_2, PD67_MC2_FAST_PCI);
	/* Fall through... */
#endif
    case IS_PD6710:
    case IS_PD672X:
	/* Special pin functions */
	if (has_led)
	    i365_bset(psock, PD67_MISC_CTL_2, PD67_MC2_LED_ENA);
	if (has_ring)
	    i365_bset(psock, PD67_MISC_CTL_2, PD67_MC2_IRQ15_RI);
	else
	    i365_bclr(psock, PD67_MISC_CTL_2, PD67_MC2_IRQ15_RI);
	/* Speaker control */
	if (state->flags & SS_SPKR_ENA)
	    i365_bset(psock, PD67_MISC_CTL_1, PD67_MC1_SPKR_ENA);
	else
	    i365_bclr(psock, PD67_MISC_CTL_1, PD67_MC1_SPKR_ENA);
	/* DMA control */
	i365_set(psock, PD67_EXT_INDEX, PD67_DMA_CTL);
	reg = i365_get(psock, PD67_EXT_DATA) & ~PD67_DMA_MODE;
	if (state->flags & SS_DMA_MODE) {
	    reg |= PD67_DMA_DREQ_BVD2;
	    i365_set(psock, PD67_EXT_DATA, reg);
	    i365_bset(psock, PD67_MISC_CTL_2, PD67_MC2_DMA_MODE);
	}
	else {
	    i365_bclr(psock, PD67_MISC_CTL_2, PD67_MC2_DMA_MODE);
	    i365_set(psock, PD67_EXT_DATA, reg);
	}
	/* Timing parameters */
	if (freq_bypass)
	    i365_bset(psock, PD67_MISC_CTL_2, PD67_MC2_FREQ_BYPASS);
	else
	    i365_bclr(psock, PD67_MISC_CTL_2, PD67_MC2_FREQ_BYPASS);
	i365_set(psock, PD67_TIME_SETUP(0), setup_time);
	i365_set(psock, PD67_TIME_SETUP(1), setup_time);
	i365_set(psock, PD67_TIME_CMD(0), cmd_time);
	i365_set(psock, PD67_TIME_CMD(1), cmd_time + 9);
	i365_set(psock, PD67_TIME_RECOV(0), recov_time);
	i365_set(psock, PD67_TIME_RECOV(1), recov_time);
	break;
    default:
	break;
    }
    
    /* Card status change interrupt mask */
    reg = (socket_table[lsock].cs_irq << 4);
    if (state->csc_mask & SS_DETECT) reg |= I365_CSC_DETECT;
    if (state->flags & SS_IOCARD) {
	if (state->csc_mask & SS_STSCHG) reg |= I365_CSC_STSCHG;
    }
    else {
	if (state->csc_mask & SS_BATDEAD) reg |= I365_CSC_BVD1;
	if (state->csc_mask & SS_BATWARN) reg |= I365_CSC_BVD2;
	if (state->csc_mask & SS_READY) reg |= I365_CSC_READY;
    }
    i365_set(psock, I365_CSCINT, reg);
    i365_get(psock, I365_CSC);
    
    return 0;
} /* i365_set_socket */

/*====================================================================*/

static int i365_get_io_map(u_short lsock, struct pcmcia_io_map *io)
{
    u_short psock = socket_table[lsock].psock;
    u_char map, ioctl, addr;
    
    map = io->map;
    if (map > 1) return -EINVAL;
    io->start = i365_get_pair(psock, I365_IO(map)+I365_W_START);
    io->stop = i365_get_pair(psock, I365_IO(map)+I365_W_STOP);
    ioctl = i365_get(psock, I365_IOCTL);
    addr = i365_get(psock, I365_ADDRWIN);
    io->speed = to_ns(ioctl & I365_IOCTL_WAIT(map)) ? 1 : 0;
    io->flags  = (addr & I365_ENA_IO(map)) ? MAP_ACTIVE : 0;
    io->flags |= (ioctl & I365_IOCTL_0WS(map)) ? MAP_0WS : 0;
    io->flags |= (ioctl & I365_IOCTL_16BIT(map)) ? MAP_16BIT : 0;
    io->flags |= (ioctl & I365_IOCTL_IOCS16(map)) ? MAP_AUTOSZ : 0;
#ifdef PCMCIA_DEBUG
    if (pc_debug > 1)
	printk("i82365: GetIOMap(%d, %d) = %#2.2x, %d ns, "
	       "%#4.4x-%#4.4x\n", lsock, map, io->flags,
	       io->speed, io->start, io->stop);
#endif
    return 0;
} /* i365_get_io_map */

/*====================================================================*/

static int i365_set_io_map(u_short lsock, struct pcmcia_io_map *io)
{
    u_short psock = socket_table[lsock].psock;
    u_char map, ioctl;
    
#ifdef PCMCIA_DEBUG
    if (pc_debug > 1)
	printk("i82365: SetIOMap(%d, %d, %#2.2x, %d ns, "
	       "%#4.4x-%#4.4x)\n", lsock, io->map, io->flags,
	       io->speed, io->start, io->stop);
#endif
    map = io->map;
    if ((map > 1) || (io->start > 0xffff) || (io->stop > 0xffff) ||
	(io->stop < io->start)) return -EINVAL;
    /* Turn off the window before changing anything */
    if (i365_get(psock, I365_ADDRWIN) & I365_ENA_IO(map))
	i365_bclr(psock, I365_ADDRWIN, I365_ENA_IO(map));
    i365_set_pair(psock, I365_IO(map)+I365_W_START, io->start);
    i365_set_pair(psock, I365_IO(map)+I365_W_STOP, io->stop);
    ioctl = i365_get(psock, I365_IOCTL) & ~I365_IOCTL_MASK(map);
    if (io->speed) ioctl |= I365_IOCTL_WAIT(map);
    if (io->flags & MAP_0WS) ioctl |= I365_IOCTL_0WS(map);
    if (io->flags & MAP_16BIT) ioctl |= I365_IOCTL_16BIT(map);
    if (io->flags & MAP_AUTOSZ) ioctl |= I365_IOCTL_IOCS16(map);
    i365_set(psock, I365_IOCTL, ioctl);
    /* Turn on the window if necessary */
    if (io->flags & MAP_ACTIVE)
	i365_bset(psock, I365_ADDRWIN, I365_ENA_IO(map));
    return 0;
} /* i365_set_io_map */

/*====================================================================*/

static int i365_get_mem_map(u_short lsock, struct pcmcia_mem_map *mem)
{
    u_short psock = socket_table[lsock].psock;
    u_short base, i;
    u_char map, addr;
    
    map = mem->map;
    if (map > 4) return -EINVAL;
    addr = i365_get(psock, I365_ADDRWIN);
    mem->flags = (addr & I365_ENA_MEM(map)) ? MAP_ACTIVE : 0;
    base = I365_MEM(map);
    
    i = i365_get_pair(psock, base+I365_W_START);
    mem->flags |= (i & I365_MEM_16BIT) ? MAP_16BIT : 0;
    mem->flags |= (i & I365_MEM_0WS) ? MAP_0WS : 0;
    mem->sys_start = ((long)(i & 0x0fff) << 12);
    
    i = i365_get_pair(psock, base+I365_W_STOP);
    mem->speed  = (i & I365_MEM_WS0) ? 1 : 0;
    mem->speed += (i & I365_MEM_WS1) ? 2 : 0;
    mem->speed = to_ns(mem->speed);
    mem->sys_stop = ((long)(i & 0x0fff) << 12) + 0x0fff;
    
    i = i365_get_pair(psock, base+I365_W_OFF);
    mem->flags |= (i & I365_MEM_WRPROT) ? MAP_WRPROT : 0;
    mem->flags |= (i & I365_MEM_REG) ? MAP_ATTRIB : 0;
    mem->card_start = ((long)(i & 0x3fff) << 12) + mem->sys_start;
    mem->card_start &= 0x3ffffff;
#ifdef PCMCIA_DEBUG
    if (pc_debug > 1)
	printk("i82365: GetMemMap(%d, %d) = %#2.2x, %d ns, %#5.5lx-%#5.5lx,"
	       " %#5.5lx\n", lsock, mem->map, mem->flags, mem->speed,
	       mem->sys_start, mem->sys_stop, mem->card_start);
#endif
    return 0;
} /* i365_get_mem_map */

/*====================================================================*/
  
static int i365_set_mem_map(u_short lsock, struct pcmcia_mem_map *mem)
{
    u_short psock = socket_table[lsock].psock;
    u_short base, i;
    u_char map;
    
#ifdef PCMCIA_DEBUG
    if (pc_debug > 1)
	printk("i82365: SetMemMap(%d, %d, %#2.2x, %d ns, %#5.5lx-%#5.5lx,"
	       " %#5.5lx)\n", lsock, mem->map, mem->flags, mem->speed,
	       mem->sys_start, mem->sys_stop, mem->card_start);
#endif
    map = mem->map;
    if ((map > 4) || (mem->card_start > 0x3ffffff) ||
	(mem->sys_start > 0xffffff) || (mem->sys_stop > 0xffffff) ||
	(mem->sys_start > mem->sys_stop) || (mem->speed > 1000))
	return -EINVAL;
    /* Turn off the window before changing anything */
    if (i365_get(psock, I365_ADDRWIN) & I365_ENA_MEM(map))
	i365_bclr(psock, I365_ADDRWIN, I365_ENA_MEM(map));
    base = I365_MEM(map);
    
    i = mem->sys_start >> 12;
    if (mem->flags & MAP_16BIT) i |= I365_MEM_16BIT;
    if (mem->flags & MAP_0WS) i |= I365_MEM_0WS;
    i365_set_pair(psock, base+I365_W_START, i);
    
    i = mem->sys_stop >> 12;
    switch (to_cycles(mem->speed)) {
    case 0:	break;
    case 1:	i |= I365_MEM_WS0; break;
    case 2:	i |= I365_MEM_WS1; break;
    default:	i |= I365_MEM_WS1 | I365_MEM_WS0; break;
    }
    i365_set_pair(psock, base+I365_W_STOP, i);
    
    i = ((mem->card_start - mem->sys_start) >> 12) & 0x3fff;
    if (mem->flags & MAP_WRPROT) i |= I365_MEM_WRPROT;
    if (mem->flags & MAP_ATTRIB) i |= I365_MEM_REG;
    i365_set_pair(psock, base+I365_W_OFF, i);
    
    /* Turn on the window if necessary */
    if (mem->flags & MAP_ACTIVE)
	i365_bset(psock, I365_ADDRWIN, I365_ENA_MEM(map));
    return 0;
} /* i365_set_mem_map */

/*====================================================================*/

typedef int (*subfn_t)(u_short, void *);

static subfn_t service_table[] = {
    (subfn_t)&i365_register_callback,
    (subfn_t)&i365_get_status,
    (subfn_t)&i365_inquire_socket,
    (subfn_t)&i365_get_socket,
    (subfn_t)&i365_set_socket,
    (subfn_t)&i365_get_io_map,
    (subfn_t)&i365_set_io_map,
    (subfn_t)&i365_get_mem_map,
    (subfn_t)&i365_set_mem_map,
};

#define NFUNC (sizeof(service_table)/sizeof(subfn_t))

static int i365_service(u_int lsock, u_int cmd, void *arg)
{
    int err;

#ifdef PCMCIA_DEBUG
    if (pc_debug > 1) 
	printk("i365_ioctl(%d, %d, 0x%p)\n", lsock, cmd, arg);
#endif

    if (cmd < NFUNC)
	err = service_table[cmd](lsock, arg);
    else
	err = -EINVAL;

    return err;
} /* i365_ioctl */

/*====================================================================*/

#ifdef MODULE
char kernel_version[] = UTS_RELEASE;

int init_module(void)
{
    servinfo_t serv;
    CardServices(GetCardServicesInfo, &serv);
    if (serv.Revision != CS_RELEASE_CODE) {
	printk("i82365: Card Services release does not match!\n");
	return -1;
    }
    return i365_init();
}

void cleanup_module(void)
{
    i365_finish();
}
#endif /* MODULE */
