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

    PCMCIA Card Services driver

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

#include <linux/autoconf.h>

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

#include <linux/kernel.h>
#include <linux/string.h>
#include <linux/major.h>
#include <linux/errno.h>
#include <linux/malloc.h>
#include <linux/mm.h>
#include <linux/sched.h>
#include <linux/timer.h>
#include <linux/ioport.h>
#include <linux/delay.h>
#include <asm/system.h>

#include "version.h"
#include "cs_types.h"
#include "rsrc_mgr.h"
#include "ss.h"

#define IN_CARD_SERVICES
#include "cs.h"
#include "bulkmem.h"
#include "cs_internal.h"
#include "cistpl.h"
#include "cisreg.h"

#ifdef CONFIG_APM
#include <linux/apm_bios.h>
static int handle_apm_event(apm_event_t event);
#endif

#ifdef PCMCIA_DEBUG
int pc_debug = PCMCIA_DEBUG;
static const char *version =
    "cs.c 1.98 1995/05/03 16:19:15 (David Hinds)\n";
#endif

static const char *release =
    "Linux PCMCIA Card Services " CS_RELEASE;

#undef CONFIG_MODVERSIONS
static struct symbol_table cs_symtab = {
#include <linux/symtab_begin.h>
    X(register_ss_entry),
    X(unregister_ss_entry),
    X(CardServices),
    X(MTDHelperEntry),
#include <linux/symtab_end.h>
};

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

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

static int setup_delay		= 5;	/* ticks */
static int shutdown_delay	= 5;	/* ticks */
static int vcc_settle		= 30;	/* ticks */
static int reset_time		= 10;	/* usecs */
static int unreset_check	= 10;	/* ticks */
static int unreset_limit	= 30;	/* times unreset_check */

/* Access speed for attribute memory windows */
static int cis_speed		= 300;	/* ns */

/* Access speed for IO windows */
static int io_speed		= 0;	/* ns */

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

typedef struct irq_info_t {
    u_long	Attributes;
    int		time_share, dyn_share;
} irq_info_t;

/* Possible irq uses */
#define CSC_IRQ	1
#define IO_IRQ	2

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

static socket_state_t dead_socket = {
    0, SS_DETECT, 0, 0, 0
};

/* Table of sockets */
socket_t sockets = 0;
socket_info_t *socket_table[MAX_SOCK];

/* Table of IRQ assignments */
static irq_info_t irq_table[16] = { { 0, 0, 0 }, /* etc */ };

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

/* String tables for error messages */

typedef struct lookup_t {
    int key;
    char *msg;
} lookup_t;

static const lookup_t error_table[] = {
    { CS_SUCCESS,		"Operation succeeded" },
    { CS_BAD_ADAPTER,		"Bad adapter" },
    { CS_BAD_ATTRIBUTE, 	"Bad attribute", },
    { CS_BAD_BASE,		"Bad base address" },
    { CS_BAD_EDC,		"Bad EDC" },
    { CS_BAD_IRQ,		"Bad IRQ" },
    { CS_BAD_OFFSET,		"Bad offset" },
    { CS_BAD_PAGE,		"Bad page number" },
    { CS_READ_FAILURE,		"Read failure" },
    { CS_BAD_SIZE,		"Bad size" },
    { CS_BAD_SOCKET,		"Bad socket" },
    { CS_BAD_TYPE,		"Bad type" },
    { CS_BAD_VCC,		"Bad Vcc" },
    { CS_BAD_VPP,		"Bad Vpp" },
    { CS_BAD_WINDOW,		"Bad window" },
    { CS_WRITE_FAILURE,		"Write failure" },
    { CS_NO_CARD,		"No card present" },
    { CS_UNSUPPORTED_FUNCTION,	"Usupported function" },
    { CS_UNSUPPORTED_MODE,	"Unsupported mode" },
    { CS_BAD_SPEED,		"Bad speed" },
    { CS_BUSY,			"Resource busy" },
    { CS_GENERAL_FAILURE,	"General failure" },
    { CS_WRITE_PROTECTED,	"Write protected" },
    { CS_BAD_ARG_LENGTH,	"Bad argument length" },
    { CS_BAD_ARGS,		"Bad arguments" },
    { CS_CONFIGURATION_LOCKED,	"Configuration locked" },
    { CS_IN_USE,		"Resource in use" },
    { CS_NO_MORE_ITEMS,		"No more items" },
    { CS_OUT_OF_RESOURCE,	"Out of resource" },
    { CS_BAD_HANDLE,		"Bad handle" },
    { CS_BAD_TUPLE,		"Bad CIS tuple" }
};
#define ERROR_COUNT (sizeof(error_table)/sizeof(lookup_t))

static const lookup_t service_table[] = {
    { AccessConfigurationRegister,	"AccessConfigurationRegister" },
    { AdjustResourceInfo,		"AdjustResourceInfo" },
    { CheckEraseQueue,			"CheckEraseQueue" },
    { CloseMemory,			"CloseMemory" },
    { DeregisterClient,			"DeregisterClient" },
    { GetCardServicesInfo,		"GetCardServicesInfo" },
    { GetClientInfo,			"GetClientInfo" },
    { GetConfigurationInfo,		"GetConfigurationInfo" },
    { GetEventMask,			"GetEventMask" },
    { GetFirstClient,			"GetFirstClient" },
    { GetFirstRegion,			"GetFirstRegion" },
    { GetFirstTuple,			"GetFirstTuple" },
    { GetNextClient,			"GetNextClient" },
    { GetNextRegion,			"GetNextRegion" },
    { GetNextTuple,			"GetNextTuple" },
    { GetStatus,			"GetStatus" },
    { GetTupleData,			"GetTupleData" },
    { MapMemPage,			"MapMemPage" },
    { ModifyConfiguration,		"ModifyConfiguration" },
    { ModifyWindow,			"ModifyWindow" },
    { OpenMemory,			"OpenMemory" },
    { ParseTuple,			"ParseTuple" },
    { ReadMemory,			"ReadMemory" },
    { RegisterClient,			"RegisterClient" },
    { RegisterMTD,			"RegisterMTD" },
    { ReleaseConfiguration,		"ReleaseConfiguration" },
    { ReleaseIO,			"ReleaseIO" },
    { ReleaseIRQ,			"ReleaseIRQ" },
    { ReleaseWindow,			"ReleaseWindow" },
    { RequestConfiguration,		"RequestConfiguration" },
    { RequestIO,			"RequestIO" },
    { RequestIRQ,			"RequestIRQ" },
    { RequestSocketMask,		"RequestSocketMask" },
    { RequestWindow,			"RequestWindow" },
    { ResetCard,			"ResetCard" },
    { SetEventMask,			"SetEventMask" },
    { ValidateCIS,			"ValidateCIS" },
    { WriteMemory,			"WriteMemory" },
    { BindDevice,			"BindDevice" },
    { BindMTD,				"BindMTD" },
    { ReportError,			"ReportError" },
    { SuspendCard,			"SuspendCard" },
    { ResumeCard,			"ResumeCard" }
};
#define SERVICE_COUNT (sizeof(service_table)/sizeof(lookup_t))

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

    Very basic initialization stuff -- if we're linked into the base
    kernel, this will be invoked from init.c; otherwise, it gets
    called at module initialization time.
    
======================================================================*/

#ifdef MODULE
static
#endif

ulong cs_init(u_long kmem_start, u_long kmem_end)
{
#ifdef CONFIG_I82365
    i365_init();
#endif
#ifdef CONFIG_TCIC
    tcic_init();
#endif
    printk("%s\n", release);
#ifdef PCMCIA_DEBUG
    if (pc_debug)
	printk(version);
#endif
#ifdef CONFIG_APM
    apm_register_callback(&handle_apm_event);
#endif
    register_symtab(&cs_symtab);
    return kmem_start;
} /* cs_init */

#ifdef MODULE
static void cs_finish(void)
{
    resource_entry_t *p, *q;
    
    printk("unloading PCMCIA Card Services\n");
#ifdef CONFIG_APM
    apm_unregister_callback(&handle_apm_event);
#endif
    
    for (p = mem_db.next; p != &mem_db; p = q) {
	q = p->next;
	kfree_s(p, sizeof(resource_entry_t));
    }
    for (p = io_db.next; p != &io_db; p = q) {
	q = p->next;
	kfree_s(p, sizeof(resource_entry_t));
    }
} /* cs_finish */
#endif
/*======================================================================

    Setting a card's COR seems to need a short delay.  We have to
    busy loop because it might happen in an interrupt routine.  So
    here's a generic busy loop.
    
======================================================================*/

static void busy_loop(u_long len)
{
    u_long timeout = jiffies + len;
    u_long flags;
    save_flags(flags);
    sti();
    while (timeout >= jiffies)
	;
    restore_flags(flags);
} /* busy_loop */
  
static int try_irq(irq_req_t *req, int irq, int specific)
{
    irq_info_t *info = &irq_table[irq];
    if (info->Attributes & RES_ALLOCATED) {
	switch (req->Attributes & IRQ_TYPE) {
	case IRQ_TYPE_EXCLUSIVE:
	    return CS_IN_USE;
	case IRQ_TYPE_TIME:
	    if ((info->Attributes & RES_IRQ_TYPE)
		!= RES_IRQ_TYPE_TIME)
		return CS_IN_USE;
	    if (req->Attributes & IRQ_FIRST_SHARED)
		return CS_BAD_ATTRIBUTE;
	    break;
	case IRQ_TYPE_DYNAMIC_SHARING:
	    if ((info->Attributes & RES_IRQ_TYPE)
		!= RES_IRQ_TYPE_DYNAMIC)
		return CS_IN_USE;
	    if (req->Attributes & IRQ_FIRST_SHARED)
		return CS_BAD_ATTRIBUTE;
	    break;
	}
    }
    else {
	if (((req->Attributes & IRQ_TYPE) != IRQ_TYPE_EXCLUSIVE)
	    && !(req->Attributes & IRQ_FIRST_SHARED))
	    return CS_BAD_ATTRIBUTE;
	if ((info->Attributes & RES_RESERVED) && !specific)
	    return CS_IN_USE;
	if (request_irq(irq, NULL, 0, "bogus") == -EBUSY)
	    return CS_IN_USE;
    }
    return 0;
} /* try_irq */

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

    Reset a socket to the default state
    
======================================================================*/

static void init_socket(socket_info_t *s)
{
    int i;
    pcmcia_io_map io;
    pcmcia_mem_map mem;

    s->socket = dead_socket;
    s->ss_entry(s->sock, SS_SetSocket, &s->socket);
    for (i = 0; i < 2; i++) {
	io.map = i;
	s->ss_entry(s->sock, SS_GetIOMap, &io);
	if (io.flags & MAP_ACTIVE) {
	    io.flags &= ~MAP_ACTIVE;
	    s->ss_entry(s->sock, SS_SetIOMap, &io);
	}
    }
    for (i = 0; i < 4; i++) {
	mem.map = i;
	s->ss_entry(s->sock, SS_GetMemMap, &mem);
	if (mem.flags & MAP_ACTIVE) {
	    mem.flags &= ~MAP_ACTIVE;
	    s->ss_entry(s->sock, SS_SetMemMap, &mem);
	}
    }
}

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

    Low-level PCMCIA interface drivers need to register with Card
    Services using these calls.
    
======================================================================*/

static void setup_socket(u_long i);
static void shutdown_socket(u_long i);
static void reset_socket(u_long i);
static void unreset_socket(u_long i);
static void parse_events(void *info, u_long events);

int register_ss_entry(int nsock, ss_entry_t ss_entry)
{
    int i, ns;
    socket_info_t *s;

#ifdef PCMCIA_DEBUG
    if (pc_debug)
	printk ("cs: register_ss_entry(%d, 0x%p)\n", nsock, ss_entry);
#endif

#ifdef MODULE
    MOD_INC_USE_COUNT;
#endif

    for (ns = 0; ns < nsock; ns++) {
	s = kmalloc(sizeof(struct socket_info_t), GFP_KERNEL);
	memset(s, 0, sizeof(socket_info_t));
    
	s->ss_entry = ss_entry;
	s->sock = ns;
	s->setup.data = sockets;
	s->setup.function = &setup_socket;
	s->shutdown.data = sockets;
	s->shutdown.function = &shutdown_socket;
	s->unreset.data = sockets;
	s->unreset.function = &unreset_socket;
	/* base address = 0, map = 0 */
	s->cis_mem.flags = MAP_ACTIVE;
	s->cis_mem.speed = cis_speed;
	s->erase_busy.next = s->erase_busy.prev = &s->erase_busy;
	
	for (i = 0; i < sockets; i++)
	    if (socket_table[i] == NULL) break;
	socket_table[i] = s;
	if (i == sockets) sockets++;

	init_socket(s);
	ss_entry(ns, SS_InquireSocket, &s->cap);
    }
    
    return 0;
} /* register_ss_entry */

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

void unregister_ss_entry(ss_entry_t ss_entry)
{
    int i, j;
    socket_info_t *s;
    
    for (i = 0; i < sockets; i++) {
	s = socket_table[i];
	if (s->ss_entry == ss_entry) {
	    s->ss_entry = NULL;
	    release_mem_region(s->cis_mem.sys_start, 0x1000);
	    kfree_s(s, sizeof(struct socket_info_t));
	    socket_table[i] = NULL;
	    for (j = i; j < sockets-1; j++)
		socket_table[j] = socket_table[j+1];
	    sockets--;
	}
    }
    
#ifdef MODULE
    MOD_DEC_USE_COUNT;
#endif
} /* unregister_ss_entry */

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

    Shutdown_Socket() and setup_socket() are scheduled using add_timer
    calls by the main event handler when card insertion and removal
    events are received.  Shutdown_Socket() unconfigures a socket and
    turns off socket power.  Setup_socket() turns on socket power
    and resets the socket, in two stages.

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

static void free_regions(memory_handle_t *list)
{
    memory_handle_t tmp;
    while (*list != NULL) {
	tmp = *list;
	*list = tmp->info.next;
	tmp->region_magic = 0;
	kfree_s(tmp, sizeof(*tmp));
    }
}

static int send_event(socket_info_t *s, event_t event, int priority);

static void shutdown_socket(u_long i)
{
    socket_info_t *s = socket_table[i];
    
    sti();
#ifdef PCMCIA_DEBUG
    if (pc_debug)
	printk("cs: shutdown_socket(%ld)\n", i);
#endif

    /* Blank out the socket state */
    s->state &= SOCKET_PRESENT|SOCKET_RESET_PENDING|SOCKET_SETUP_PENDING;
    init_socket(s);
    s->irq.AssignedIRQ = 0;
    s->io.NumPorts1 = s->io.NumPorts2 = 0;
    free_regions(&s->a_region);
    free_regions(&s->c_region);
} /* shutdown_socket */

static void setup_socket(u_long i)
{
    int val;
    socket_info_t *s = socket_table[i];

    sti();
    s->ss_entry(s->sock, SS_GetStatus, &val);
    if (val & SS_DETECT) {
#ifdef PCMCIA_DEBUG
	if (pc_debug)
	    printk("cs: setup_socket(%ld): applying power\n", i);
#endif
	s->state |= SOCKET_PRESENT;
	s->socket.flags = 0;
	s->socket.Vcc = 50;
	s->ss_entry(s->sock, SS_SetSocket, &s->socket);
	s->setup.function = &reset_socket;
	s->setup.expires = vcc_settle;
	add_timer(&s->setup);
    }
#ifdef PCMCIA_DEBUG
    else
	if (pc_debug)
	    printk("cs: setup_socket(%ld): no card!\n", i);
#endif
} /* setup_socket */

static void reset_socket(u_long i)
{
    socket_info_t *s = socket_table[i];

#ifdef PCMCIA_DEBUG
	if (pc_debug)
	    printk("cs: resetting socket %ld\n", i);
#endif
    s->socket.flags |= SS_OUTPUT_ENA | SS_RESET;
    s->ss_entry(s->sock, SS_SetSocket, &s->socket);
    udelay((long)reset_time);
    s->socket.flags &= ~SS_RESET;
    s->ss_entry(s->sock, SS_SetSocket, &s->socket);
    s->unreset_timeout = 0;
    s->unreset.expires = unreset_check;
    add_timer(&s->unreset);
}

static void unreset_socket(u_long i)
{
    socket_info_t *s = socket_table[i];
    int val;

    sti();
    s->ss_entry(s->sock, SS_GetStatus, &val);
    if (val & SS_READY) {
#ifdef PCMCIA_DEBUG
	if (pc_debug)
	    printk("cs: reset done on socket %ld\n", i);
#endif
	if (s->state & SOCKET_SETUP_PENDING) {
	    send_event(s, CS_EVENT_CARD_INSERTION, CS_EVENT_PRI_LOW);
	    s->state &= ~SOCKET_SETUP_PENDING;
	}
	else if (s->state & SOCKET_SUSPEND) {
	    send_event(s, CS_EVENT_PM_RESUME, CS_EVENT_PRI_LOW);
	    s->state &= ~SOCKET_SUSPEND;
	}
	else {
	    send_event(s, CS_EVENT_CARD_RESET, CS_EVENT_PRI_LOW);
	    s->reset_handle->event_callback_args.info = NULL;
	    EVENT(s->reset_handle, CS_EVENT_RESET_COMPLETE,
		  CS_EVENT_PRI_LOW);
	    s->state &= ~SOCKET_RESET_PENDING;
	}
    }
    else {
#ifdef PCMCIA_DEBUG
	if (pc_debug > 1)
	    printk("cs: socket %ld not ready yet\n", i);
#endif
	if (s->unreset_timeout > unreset_limit) {
	    printk("cs: socket %ld timed out during reset\n", i);
	    s->state &= ~SOCKET_RESET_PENDING;
	}
	else {
	    s->unreset_timeout++;
	    s->unreset.expires = unreset_check;
	    add_timer(&s->unreset);
	}
    }
}

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

    The central event handler.  Send_event() sends an event to all
    valid clients.  Parse_events() interprets the event bits from
    a card status change report.
    
======================================================================*/

static int send_event(socket_info_t *s, event_t event, int priority)
{
    client_t *client = s->clients;
    int ret;
#ifdef PCMCIA_DEBUG
    if (pc_debug > 1)
	printk("cs: send_event(sock %d, event %d, pri %d)\n",
	       s->sock, event, priority);
#endif
    ret = 0;
    for (; client; client = client->next) { 
	if (client->state & (CLIENT_UNBOUND|CLIENT_STALE))
	    continue;
	if (client->EventMask & event) {
	    ret = EVENT(client, event, priority);
	    if (ret != 0)
		return ret;
	}
    }
    return ret;
} /* send_event */

static void do_shutdown(socket_info_t *s)
{
    client_t *client;
    s->state |= SOCKET_SHUTDOWN_PENDING;
    send_event(s, CS_EVENT_CARD_REMOVAL, CS_EVENT_PRI_HIGH);
    for (client = s->clients; client; client = client->next)
	if (!(client->Attributes & INFO_DRIVER_SERVICES))
	    client->state |= CLIENT_STALE;
    if (s->state & SOCKET_SETUP_PENDING) {
	printk("cs: flushing pending setup\n");
	del_timer(&s->setup);
	s->state &= ~SOCKET_SETUP_PENDING;
    }
    s->shutdown.expires = shutdown_delay;
    add_timer(&s->shutdown);
    s->state &= ~SOCKET_PRESENT;
}

static void parse_events(void *info, u_long events)
{
    socket_info_t *s = info;
    if (events & SS_DETECT) {
	int status;
	cli();
	if ((s->state & SOCKET_PRESENT) &&
	    !(s->state & SOCKET_SHUTDOWN_PENDING))
	    do_shutdown(s);
	s->ss_entry(s->sock, SS_GetStatus, &status);
	if (status & SS_DETECT) {
	    if (!(s->state & SOCKET_SETUP_PENDING))
		s->state |= SOCKET_SETUP_PENDING;
	    else {
#ifdef PCMCIA_DEBUG
		if (pc_debug)
		    printk("cs: delaying pending setup\n");
#endif
		del_timer(&s->setup);
	    }
	    s->setup.function = &setup_socket;
	    s->setup.expires = setup_delay;
	    add_timer(&s->setup);
	}
	sti();
    }
    if (events & SS_BATDEAD)
	send_event(s, CS_EVENT_BATTERY_DEAD, CS_EVENT_PRI_LOW);
    if (events & SS_BATWARN)
	send_event(s, CS_EVENT_BATTERY_LOW, CS_EVENT_PRI_LOW);
    if (events & SS_READY) {
	if (!(s->state & SOCKET_RESET_PENDING))
	    send_event(s, CS_EVENT_READY_CHANGE, CS_EVENT_PRI_LOW);
#ifdef PCMCIA_DEBUG
	else if (pc_debug)
	    printk("cs: ready change during reset\n");
#endif
    }
} /* parse_events */

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

    Another event handler, for power management events.
    
======================================================================*/

#ifdef CONFIG_APM
static int handle_apm_event(apm_event_t event)
{
    int i, stat;
    socket_info_t *s;
    
    switch (event) {
    case APM_SYS_SUSPEND:
    case APM_USER_SUSPEND:
#ifdef PCMCIA_DEBUG
	if (pc_debug)
	    printk("cs: received suspend notification\n");
#endif
	for (i = 0; i < sockets; i++) {
	    s = socket_table[i];
	    if (s->state & SOCKET_PRESENT) {
		send_event(s, CS_EVENT_PM_SUSPEND, CS_EVENT_PRI_LOW);
		s->ss_entry(s->sock, SS_SetSocket, &dead_socket);
		s->state |= SOCKET_SUSPEND;
	    }
	}
	break;
    case APM_NORMAL_RESUME:
    case APM_CRITICAL_RESUME:
#ifdef PCMCIA_DEBUG
	if (pc_debug)
	    printk("cs: received resume notification\n");
#endif
	for (i = 0; i < sockets; i++) {
	    s = socket_table[i];
	    /* Do this just to reinitialize the socket */
	    s->ss_entry(s->sock, SS_SetSocket, &dead_socket);
	    s->ss_entry(s->sock, SS_GetStatus, &stat);
	    /* Did we miss an event? */
	    if (s->state & SOCKET_PRESENT) {
		if (stat & SS_DETECT)
		    /* Normal resume -- assume card is the same */
		    setup_socket(i);
		else
		    /* We missed a removal event */
		    parse_events(s, SS_DETECT);
	    }
	    else {
		if (stat & SS_DETECT)
		    /* we missed an insertion event */
		    parse_events(s, SS_DETECT);
		/* Otherwise, nothing to do */
	    }
	}
	break;
    }
    return 0;
} /* handle_apm_event */
#endif

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

    Access_configuration_register() reads and writes configuration
    registers in attribute memory.  Memory window 0 is reserved for
    this and the tuple reading services.
    
======================================================================*/

static int access_configuration_register(client_handle_t handle,
					 conf_reg_t *reg)
{
    socket_info_t *s;
    long addr;
    u_char val;
    
    if (CHECK_HANDLE(handle))
	return CS_BAD_HANDLE;
    s = SOCKET(handle);
    if (!(s->state & SOCKET_CONFIG_LOCKED))
	return CS_CONFIGURATION_LOCKED;

    if (setup_cis_mem(s) != 0)
	return CS_OUT_OF_RESOURCE;
    addr = (long)s->ConfigBase + reg->Offset;
    
    switch (reg->Action) {
    case CS_READ:
	read_cis_mem(s, 1, addr, 1, &val);
	reg->Value = val;
	break;
    case CS_WRITE:
	val = reg->Value;
	write_cis_mem(s, 1, addr, 1, &val);
	break;
    default:
	return CS_BAD_ARGS;
	break;
    }
    return CS_SUCCESS;
} /* access_configuration_register */

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

    Bind_device() associates a device driver with a particular socket.
    It is normally called by Driver Services after it has identified
    a newly inserted card.  An instance of that driver will then be
    eligible to register as a client of this socket.
    
======================================================================*/

static int bind_device(bind_req_t *req)
{
    client_t *client;
    socket_info_t *s;
    ss_callback_t call;
    int status;

    if (CHECK_SOCKET(req->Socket))
	return CS_BAD_SOCKET;
    s = SOCKET(req);

#ifdef MODULE
    MOD_INC_USE_COUNT;
#endif
    if (s->clients == NULL) {
	call.handler = &parse_events;
	call.info = s;
	s->ss_entry(req->Socket, SS_RegisterCallback, &call);
	s->ss_entry(req->Socket, SS_GetStatus, &status);
	if (status & SS_DETECT) {
	    s->state |= SOCKET_SETUP_PENDING;
	    setup_socket(req->Socket);
	}
    }
    
    client = (client_t *)kmalloc(sizeof(client_t), GFP_KERNEL);
    memset(client, '\0', sizeof(client_t));
    client->client_magic = CLIENT_MAGIC;
    client->dev_info = req->dev_info;
    client->Socket = req->Socket;
    client->state = CLIENT_UNBOUND;
    client->erase_busy.next = &client->erase_busy;
    client->erase_busy.prev = &client->erase_busy;
    client->next = s->clients;
    s->clients = client;
#ifdef PCMCIA_DEBUG
    if (pc_debug)
	printk("cs: bind_device(): client 0x%p, sock %d, dev %s\n",
	       client, client->Socket, (char *)client->dev_info);
#endif
    return CS_SUCCESS;
} /* bind_device */

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

    Bind_mtd() associates a device driver with a particular memory
    region.  It is normally called by Driver Services after it has
    identified a memory device type.  An instance of the corresponding
    driver will then be able to register to control this region.
    
======================================================================*/

static int bind_mtd(mtd_bind_t *req)
{
    socket_info_t *s;
    memory_handle_t region;
    
    if (CHECK_SOCKET(req->Socket))
	return CS_BAD_SOCKET;
    s = SOCKET(req);

    if (req->Attributes & REGION_TYPE_AM)
	region = s->a_region;
    else
	region = s->c_region;

    while (region) {
	if (region->info.CardOffset == req->CardOffset) break;
	region = region->info.next;
    }
    if (!region || (region->mtd != NULL))
	return CS_BAD_OFFSET;
    region->dev_info = req->dev_info;
    
#ifdef PCMCIA_DEBUG
    if (pc_debug)
	printk("cs: bind_mtd(): attr 0x%lx, offset 0x%lx, dev %s\n",
	       req->Attributes, req->CardOffset, (char *)req->dev_info);
#endif
    return CS_SUCCESS;
} /* bind_mtd */

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

    We use standard Linux resource management for ports, address
    windows, and interrupts.  But we keep an internal list of
    things to avoid, for bogus drivers that don't allocate the
    stuff they use.
    
======================================================================*/

static int adjust_memory(adjust_t *adj)
{
    u_long base, num;

    base = (u_long)adj->resource.memory.Base;
    num = adj->resource.memory.Size;
    if ((num == 0) || (base+num-1 < base))
	return CS_BAD_SIZE;

    switch (adj->Action) {
    case ADD_MANAGED_RESOURCE:
	if (add_interval(&mem_db, base, num) != 0)
	    return CS_IN_USE;
	break;
    case REMOVE_MANAGED_RESOURCE:
	sub_interval(&mem_db, base, num);
	break;
    default:
	return CS_UNSUPPORTED_FUNCTION;
	break;
    }
    
    return CS_SUCCESS;
} /* adjust_mem */

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

static int adjust_io(adjust_t *adj)
{
    int base, num, ret;
    
    base = adj->resource.io.BasePort;
    num = adj->resource.io.NumPorts;
    if ((base < 0) || (base > 0xffff))
	return CS_BAD_BASE;
    if ((num <= 0) || (base+num > 0x10000) || (base+num <= base))
	return CS_BAD_SIZE;

    switch (adj->Action) {
    case ADD_MANAGED_RESOURCE:
	ret = add_interval(&io_db, base, num);
	break;
    case REMOVE_MANAGED_RESOURCE:
	ret = sub_interval(&io_db, base, num);
	break;
    default:
	ret = CS_UNSUPPORTED_FUNCTION;
	break;
    }

    return ret;
} /* adjust_io */

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

static int adjust_irq(adjust_t *adj)
{
    int irq;
    irq_info_t *info;
    
    irq = adj->resource.irq.IRQ;
    if ((irq < 0) || (irq > 15))
	return CS_BAD_IRQ;
    info = &irq_table[irq];
    
    switch (adj->Action) {
    case ADD_MANAGED_RESOURCE:
	if (info->Attributes & RES_REMOVED)
	    info->Attributes &= ~(RES_REMOVED|RES_ALLOCATED);
	else
	    if (adj->Attributes & RES_ALLOCATED)
		return CS_IN_USE;
	if (adj->Attributes & RES_RESERVED)
	    info->Attributes |= RES_RESERVED;
	else
	    info->Attributes &= ~RES_RESERVED;
	break;
    case REMOVE_MANAGED_RESOURCE:
	if (info->Attributes & RES_REMOVED)
	    return 0;
	if (info->Attributes & RES_ALLOCATED)
	    return CS_IN_USE;
	info->Attributes |= RES_ALLOCATED|RES_REMOVED;
	info->Attributes &= ~RES_RESERVED;
	break;
    default:
	return CS_UNSUPPORTED_FUNCTION;
	break;
    }
    
    return 0;
} /* adjust_irq */

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

static int adjust_resource_info(client_handle_t handle, adjust_t *adj)
{
    if (CHECK_HANDLE(handle))
	return CS_BAD_HANDLE;
    
    switch (adj->Resource) {
    case RES_MEMORY_RANGE:
	return adjust_memory(adj);
	break;
    case RES_IO_RANGE:
	return adjust_io(adj);
	break;
    case RES_IRQ:
	return adjust_irq(adj);
	break;
    }
    return CS_UNSUPPORTED_FUNCTION;
} /* adjust_resource_info */

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

static int deregister_client(client_handle_t handle)
{
    client_t **client;
    socket_info_t *s;
    memory_handle_t region;
    u_long flags;
    int i, sn;
    
#ifdef PCMCIA_DEBUG
    if (pc_debug)
	printk("cs: deregister_client(%p)\n", handle);
#endif
    if (CHECK_HANDLE(handle))
	return CS_BAD_HANDLE;
    if (handle->state &
	(CLIENT_IRQ_REQ|CLIENT_IO_REQ|CLIENT_CONFIG_LOCKED))
	return CS_IN_USE;
    for (i = 0; i < MAX_WIN; i++)
	if (handle->state & CLIENT_WIN_REQ(i))
	    return CS_IN_USE;

    /* Disconnect all MTD links */
    s = SOCKET(handle);
    if (handle->mtd_count) {
	for (region = s->a_region; region; region = region->info.next)
	    if (region->mtd == handle) region->mtd = NULL;
	for (region = s->c_region; region; region = region->info.next)
	    if (region->mtd == handle) region->mtd = NULL;
    }
    
    save_flags(flags);
    cli();
    sn = handle->Socket; s = socket_table[sn];
    client = &s->clients;
    while ((*client) && ((*client) != handle))
	client = &(*client)->next;
    if (*client == NULL)
	return CS_BAD_HANDLE;
    *client = handle->next;
    handle->client_magic = 0;
    kfree_s(handle, sizeof(struct client_t));

#ifdef MODULE
    MOD_DEC_USE_COUNT;
#endif
    if (s->clients == NULL) {
	s->ss_entry(sn, SS_RegisterCallback, NULL);
    }
    
    restore_flags(flags);
    return CS_SUCCESS;
} /* deregister_client */

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

static int get_configuration_info(client_handle_t handle,
				  config_t *config)
{
    int i;
    socket_info_t *s;
    
    if (CHECK_HANDLE(handle))
	return CS_BAD_HANDLE;
    i = handle->Socket; s = socket_table[i];
    if (!(s->state & SOCKET_PRESENT))
	return CS_NO_CARD;
    if (!(s->state & SOCKET_CONFIG_LOCKED))
	return CS_CONFIGURATION_LOCKED;

    /* !!! This is a hack !!! */
    memcpy(&config->Attributes, &s->Attributes, 56);
    config->IRQAttributes = s->irq.Attributes;
    config->AssignedIRQ = s->irq.AssignedIRQ;
    config->BasePort1 = s->io.BasePort1;
    config->NumPorts1 = s->io.NumPorts1;
    config->Attributes1 = s->io.Attributes1;
    config->BasePort2 = s->io.BasePort2;
    config->NumPorts2 = s->io.NumPorts2;
    config->Attributes2 = s->io.Attributes2;
    config->IOAddrLines = s->io.IOAddrLines;
    
    return CS_SUCCESS;
} /* get_configuration_info */

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

    The Revision and CSLevel fields are sort-of just made up.
    
======================================================================*/

static int get_card_services_info(servinfo_t *info)
{
    info->Signature[0] = 'C';
    info->Signature[1] = 'S';
    info->Count = sockets;
    info->Revision = CS_RELEASE_CODE;
    info->CSLevel = 0x0210;
    info->VendorString = (char *)release;
    return CS_SUCCESS;
} /* get_card_services_info */

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

    Note that get_first_client() *does* recognize the Socket field
    in the request structure.
    
======================================================================*/

static int get_first_client(client_handle_t *handle, client_req_t *req)
{
    socket_t s;
    if (req->Attributes & CLIENT_THIS_SOCKET)
	s = req->Socket;
    else
	s = 0;
    if (CHECK_SOCKET(req->Socket))
	return CS_BAD_SOCKET;
    if (socket_table[s]->clients == NULL)
	return CS_NO_MORE_ITEMS;
    *handle = socket_table[s]->clients;
    return CS_SUCCESS;
} /* get_first_client */

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

static int get_next_client(client_handle_t *handle, client_req_t *req)
{
    socket_info_t *s;
    if ((handle == NULL) || CHECK_HANDLE(*handle))
	return CS_BAD_HANDLE;
    if ((*handle)->next == NULL) {
	if (req->Attributes & CLIENT_THIS_SOCKET)
	    return CS_NO_MORE_ITEMS;
	s = SOCKET(*handle);
	if (s->clients == NULL)
	    return CS_NO_MORE_ITEMS;
	*handle = s->clients;
    }
    else
	*handle = (*handle)->next;
    return CS_SUCCESS;
} /* get_next_client */

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

static int get_status(client_handle_t handle, status_t *status)
{
    socket_info_t *s;
    int val;

    if (CHECK_HANDLE(handle))
	return CS_BAD_HANDLE;
    s = SOCKET(handle);
    s->ss_entry(s->sock, SS_GetStatus, &val);
    status->CardState = status->SocketState = 0;
    status->CardState |= (val & SS_WRPROT) ? CS_EVENT_WRITE_PROTECT : 0;
    status->CardState |= (val & SS_BATDEAD) ? CS_EVENT_BATTERY_DEAD : 0;
    status->CardState |= (val & SS_BATWARN) ? CS_EVENT_BATTERY_LOW : 0;
    status->CardState |= (val & SS_READY) ? CS_EVENT_READY_CHANGE : 0;
    status->CardState |= (val & SS_DETECT) ? CS_EVENT_CARD_DETECT : 0;
    if (s->state & SOCKET_SUSPEND)
	status->CardState |= CS_EVENT_PM_SUSPEND;
    return CS_SUCCESS;
} /* get_status */

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

static int map_mem_page(window_handle_t win, memreq_t *req)
{
    if ((win == NULL) || (win->magic != WINDOW_MAGIC))
	return CS_BAD_HANDLE;
    if (req->Page != 0)
	return CS_BAD_PAGE;

    win->ctl.card_start = req->CardOffset;
    win->sock->ss_entry(win->sock->sock, SS_SetMemMap, &win->ctl);
    
    return CS_SUCCESS;
} /* map_mem_page */

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

    Modify a locked socket configuration
    
======================================================================*/

static int modify_configuration(client_handle_t handle,
				modconf_t *mod)
{
    socket_info_t *s;
    
    if (CHECK_HANDLE(handle))
	return CS_BAD_HANDLE;
    s = SOCKET(handle);
    if (!(s->state & SOCKET_PRESENT))
	return CS_NO_CARD;
    if (!(s->state & SOCKET_CONFIG_LOCKED))
	return CS_CONFIGURATION_LOCKED;
    
    if (mod->Attributes & CONF_IRQ_CHANGE_VALID) {
	if (mod->Attributes & CONF_ENABLE_IRQ) {
	    s->Attributes |= CONF_ENABLE_IRQ;
	    s->socket.io_irq = s->irq.AssignedIRQ;
	}
	else {
	    s->Attributes &= ~CONF_ENABLE_IRQ;
	    s->socket.io_irq = 0;
	}
	s->ss_entry(s->sock, SS_SetSocket, &s->socket);
    }

    if (mod->Attributes & CONF_VCC_CHANGE_VALID) {
	s->socket.Vcc = mod->Vcc;
	if (s->ss_entry(s->sock, SS_SetSocket, &s->socket))
	    return CS_BAD_VCC;
    }

    /* We only allow changing Vpp1 and Vpp2 to the same value */
    if ((mod->Attributes & CONF_VPP1_CHANGE_VALID) &&
	(mod->Attributes & CONF_VPP2_CHANGE_VALID)) {
	if (mod->Vpp1 != mod->Vpp2)
	    return CS_BAD_VPP;
	s->socket.Vpp = mod->Vpp1;
	if (s->ss_entry(s->sock, SS_SetSocket, &s->socket))
	    return CS_BAD_VPP;
    }
    else if ((mod->Attributes & CONF_VPP1_CHANGE_VALID) ||
	     (mod->Attributes & CONF_VPP2_CHANGE_VALID))
	return CS_BAD_VPP;

    return CS_SUCCESS;
} /* modify_configuration */

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

    Modify the attributes of a window returned by RequestWindow.

    I'm not sure if it is strictly cool to set the bus width here,
    since it isn't mentioned in the Solaris spec.
    
======================================================================*/

static int modify_window(window_handle_t win, modwin_t *req)
{
    if ((win == NULL) || (win->magic != WINDOW_MAGIC))
	return CS_BAD_HANDLE;

    win->ctl.flags &= ~(MAP_ATTRIB|MAP_ACTIVE);
    if (req->Attributes & WIN_MEMORY_TYPE)
	win->ctl.flags |= MAP_ATTRIB;
    if (req->Attributes & WIN_ENABLE)
	win->ctl.flags |= MAP_ACTIVE;
    if (req->Attributes & WIN_DATA_WIDTH)
	win->ctl.flags |= MAP_16BIT;
    if (req->Attributes & WIN_USE_WAIT)
	win->ctl.flags |= MAP_USE_WAIT;
    win->ctl.speed = req->AccessSpeed;
    win->sock->ss_entry(win->sock->sock, SS_SetMemMap, &win->ctl);
    
    return CS_SUCCESS;
} /* modify_window */

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

    Register_client() uses the dev_info_t handle to match the
    caller with a socket.  The driver must have already been bound
    to a socket with bind_device() -- in fact, bind_device()
    allocates the client structure that will be used.
    
======================================================================*/

static int register_client(client_handle_t *handle, client_reg_t *req)
{
    client_t *client;
    socket_t s;
    
    /* Look for unbound client with matching dev_info */
    client = NULL;
    for (s = 0; s < sockets; s++) {
	client = socket_table[s]->clients;
	while (client != NULL) {
	    if ((client->dev_info == req->dev_info) &&
		(client->state & CLIENT_UNBOUND)) break;
	    client = client->next;
	}
	if (client != NULL) break;
    }
    
    if (client == NULL)
	return CS_OUT_OF_RESOURCE;
    *handle = client;
    client->state &= ~CLIENT_UNBOUND;
    client->Socket = s;
    client->Attributes = req->Attributes;
    client->EventMask = req->EventMask;
    client->event_handler = req->event_handler;
    client->event_callback_args = req->event_callback_args;
    client->event_callback_args.client_handle = client;

#ifdef PCMCIA_DEBUG
    if (pc_debug)
	printk("cs: register_client(): client 0x%p, sock %d, dev %s\n",
	       client, client->Socket, (char *)client->dev_info);
#endif
    if (client->EventMask & CS_EVENT_REGISTRATION_COMPLETE)
	EVENT(client, CS_EVENT_REGISTRATION_COMPLETE, CS_EVENT_PRI_LOW);
    if ((socket_table[s]->state & SOCKET_PRESENT) &&
	!(socket_table[s]->state & SOCKET_SETUP_PENDING)) {
	if (client->EventMask & CS_EVENT_CARD_INSERTION)
	    EVENT(client, CS_EVENT_CARD_INSERTION, CS_EVENT_PRI_LOW);
	else
	    client->PendingEvents |= CS_EVENT_CARD_INSERTION;
    }
    return CS_SUCCESS;
} /* register_client */

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

static int release_configuration(client_handle_t handle,
				 socket_t *Socket)
{
    pcmcia_io_map io;
    int i;
    
    if (CHECK_HANDLE(handle) ||
	!(handle->state & CLIENT_CONFIG_LOCKED))
	return CS_BAD_HANDLE;
    handle->state &= ~CLIENT_CONFIG_LOCKED;
    
    if (!(handle->state & CLIENT_STALE)) {
	socket_info_t *s = SOCKET(handle);
	s->socket.flags = SS_OUTPUT_ENA;
	s->socket.Vcc = 50; s->socket.Vpp = 0;
	s->socket.io_irq = 0;
	s->ss_entry(s->sock, SS_SetSocket, &s->socket);
	for (i = 0; i < 2; i++) {
	    io.map = i;
	    s->ss_entry(s->sock, SS_GetIOMap, &io);
	    io.flags &= ~MAP_ACTIVE;
	    s->ss_entry(s->sock, SS_SetIOMap, &io);
	}
	s->state &= ~SOCKET_CONFIG_LOCKED;
    }
    
    return CS_SUCCESS;
} /* release_configuration */

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

    Release_io() releases the I/O ranges allocated by a client.  This
    may be invoked some time after a card ejection has already dumped
    the actual socket configuration, so if the client is "stale", we
    don't bother checking the port ranges against the current socket
    values.
    
======================================================================*/

static int release_io(client_handle_t handle, io_req_t *req)
{
    socket_info_t *s;
    
    if (CHECK_HANDLE(handle) || !(handle->state & CLIENT_IO_REQ))
	return CS_BAD_HANDLE;
    handle->state &= ~CLIENT_IO_REQ;
    s = SOCKET(handle);
    
    if (!(handle->state & CLIENT_STALE)) {
	if (s->state & SOCKET_CONFIG_LOCKED)
	    return CS_CONFIGURATION_LOCKED;
	if ((s->io.BasePort1 != req->BasePort1) ||
	    (s->io.NumPorts1 != req->NumPorts1) ||
	    (s->io.BasePort2 != req->BasePort2) ||
	    (s->io.NumPorts2 != req->NumPorts2))
	    return CS_BAD_ARGS;
	s->state &= ~SOCKET_IO_REQ;
    }

    release_region(req->BasePort1, req->NumPorts1);
    if (req->NumPorts2)
	release_region(req->BasePort2, req->NumPorts2);
    
    return CS_SUCCESS;
} /* release_io */

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

static int release_irq(client_handle_t handle, irq_req_t *req)
{
    irq_info_t *info;
    
    if (CHECK_HANDLE(handle) || !(handle->state & CLIENT_IRQ_REQ))
	return CS_BAD_HANDLE;
    handle->state &= ~CLIENT_IRQ_REQ;
    
    if (!(handle->state & CLIENT_STALE)) {
	socket_info_t *s = SOCKET(handle);
	if (s->state & SOCKET_CONFIG_LOCKED)
	    return CS_CONFIGURATION_LOCKED;
	if (s->irq.Attributes != req->Attributes)
	    return CS_BAD_ATTRIBUTE;
	if (s->irq.AssignedIRQ != req->AssignedIRQ)
	    return CS_BAD_IRQ;
	s->state &= ~SOCKET_IRQ_REQ;
    }
    
    info = &irq_table[req->AssignedIRQ];
    switch (req->Attributes & IRQ_TYPE) {
    case IRQ_TYPE_EXCLUSIVE:
	info->Attributes &= RES_RESERVED;
	break;
    case IRQ_TYPE_TIME:
	info->time_share--;
	if (info->time_share == 0)
	    info->Attributes &= RES_RESERVED;
	break;
    case IRQ_TYPE_DYNAMIC_SHARING:
	info->dyn_share--;
	if (info->dyn_share == 0)
	    info->Attributes &= RES_RESERVED;
	break;
    }
    
    return CS_SUCCESS;
} /* release_irq */

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

static int release_window(window_handle_t win)
{
    socket_info_t *s;
    
    if ((win == NULL) || (win->magic != WINDOW_MAGIC))
	return CS_BAD_HANDLE;
    s = win->sock;
    if (!(win->handle->state & CLIENT_WIN_REQ(win->index)))
	return CS_BAD_HANDLE;

    /* Shut down memory window */
    win->ctl.flags &= ~MAP_ACTIVE;
    s->ss_entry(s->sock, SS_SetMemMap, &win->ctl);
    s->state &= ~SOCKET_WIN_REQ(win->index);

    /* Release system memory */
    release_mem_region(win->base, win->size);
    win->handle->state &= ~CLIENT_WIN_REQ(win->index);

    win->magic = 0;
    
    return CS_SUCCESS;
} /* release_window */

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

static int request_configuration(client_handle_t handle,
				 config_req_t *req)
{
    int i;
    socket_info_t *s;
    pcmcia_io_map iomap;
    
    if (CHECK_HANDLE(handle))
	return CS_BAD_HANDLE;
    i = handle->Socket; s = socket_table[i];
    if (!(s->state & SOCKET_PRESENT))
	return CS_NO_CARD;
    if (s->state & SOCKET_CONFIG_LOCKED)
	return CS_CONFIGURATION_LOCKED;
    if (setup_cis_mem(s) != 0)
	return CS_OUT_OF_RESOURCE;

    /* Do power control */
    s->socket.Vcc = req->Vcc;
    if (s->ss_entry(s->sock, SS_SetSocket, &s->socket))
	return CS_BAD_VCC;
    
    if (req->Vpp1 != req->Vpp2)
	return CS_BAD_VPP;
    s->socket.Vpp = req->Vpp1;
    if (s->ss_entry(s->sock, SS_SetSocket, &s->socket))
	return CS_BAD_VPP;
    
    s->Vcc = req->Vcc; s->Vpp1 = s->Vpp2 = req->Vpp1;
    
    /* Pick memory or I/O card, DMA mode, interrupt */
    s->IntType = req->IntType;
    s->Attributes = req->Attributes;
    if (req->IntType & INT_MEMORY_AND_IO)
	s->socket.flags |= SS_IOCARD;
    if (req->Attributes & CONF_ENABLE_DMA)
	s->socket.flags |= SS_DMA_MODE;
    if (req->Attributes & CONF_ENABLE_SPKR)
	s->socket.flags |= SS_SPKR_ENA;
    if (req->Attributes & CONF_ENABLE_IRQ)
	s->socket.io_irq = s->irq.AssignedIRQ;
    else
	s->socket.io_irq = 0;
    s->ss_entry(s->sock, SS_SetSocket, &s->socket);
    
    /* Set up CIS configuration registers */
    s->ConfigBase = req->ConfigBase;
    s->Present = s->CardValues = req->Present;
    if (req->Present & PRESENT_OPTION) {
	s->Option = req->ConfigIndex & COR_CONFIG_MASK;
	if (s->state & SOCKET_IRQ_REQ)
	    if (!(s->irq.Attributes & IRQ_FORCED_PULSE))
		s->Option |= COR_LEVEL_REQ;
	write_cis_mem(s, 1, (long)req->ConfigBase+CISREG_COR,
		       1, &s->Option);
	busy_loop(4);
    }
    if (req->Present & PRESENT_STATUS) {
	s->Status = req->Status;
	write_cis_mem(s, 1, (long)req->ConfigBase+CISREG_CCSR,
		       1, &s->Status);
    }
    if (req->Present & PRESENT_PIN_REPLACE) {
	s->Pin = req->Pin;
	write_cis_mem(s, 1, (long)req->ConfigBase+CISREG_PRR,
		       1, &s->Pin);
    }
    if (req->Present & PRESENT_COPY) {
	s->Copy = req->Copy;
	write_cis_mem(s, 1, (long)req->ConfigBase+CISREG_SCR,
		       1, &s->Copy);
    }
    
    /* Configure I/O windows */
    if (s->state & SOCKET_IO_REQ) {
	if (s->io.NumPorts1) {
	    iomap.map = 0; iomap.speed = io_speed;
	    iomap.flags = MAP_ACTIVE;
	    switch (s->io.Attributes1 & IO_DATA_PATH_WIDTH) {
	    case IO_DATA_PATH_WIDTH_16:
		iomap.flags |= MAP_16BIT; break;
	    case IO_DATA_PATH_WIDTH_AUTO:
		iomap.flags |= MAP_AUTOSZ; break;
	    default:
		break;
	    }
	    iomap.start = s->io.BasePort1;
	    iomap.stop = iomap.start + s->io.NumPorts1 - 1;
	    s->ss_entry(s->sock, SS_SetIOMap, &iomap);
	}
	if (s->io.NumPorts2) {
	    iomap.map = 1; iomap.speed = io_speed;
	    iomap.flags = MAP_ACTIVE;
	    switch (s->io.Attributes2 & IO_DATA_PATH_WIDTH) {
	    case IO_DATA_PATH_WIDTH_16:
		iomap.flags |= MAP_16BIT; break;
	    case IO_DATA_PATH_WIDTH_AUTO:
		iomap.flags |= MAP_AUTOSZ; break;
	    default:
		break;
	    }
	    iomap.start = s->io.BasePort2;
	    iomap.stop = iomap.start + s->io.NumPorts2 - 1;
	    s->ss_entry(s->sock, SS_SetIOMap, &iomap);
	}
    }
    
    s->state |= SOCKET_CONFIG_LOCKED;
    handle->state |= CLIENT_CONFIG_LOCKED;
    return CS_SUCCESS;
} /* request_configuration */

/*======================================================================
  
    Request_io() reserves ranges of port addresses for a socket.
    I have not implemented range sharing or alias addressing.
    
======================================================================*/

static int request_io(client_handle_t handle, io_req_t *req)
{
    socket_info_t *s;
    
    if (CHECK_HANDLE(handle))
	return CS_BAD_HANDLE;
    s = SOCKET(handle);
    if (!(s->state & SOCKET_PRESENT))
	return CS_NO_CARD;
    if (s->state & SOCKET_IO_REQ)
	return CS_IN_USE;
    if (s->state & SOCKET_CONFIG_LOCKED)
	return CS_CONFIGURATION_LOCKED;
    if (req->Attributes1 & (IO_SHARED | IO_FORCE_ALIAS_ACCESS))
	return CS_BAD_ATTRIBUTE;
    if ((req->NumPorts2 > 0) &&
	(req->Attributes2 & (IO_SHARED | IO_FORCE_ALIAS_ACCESS)))
	return CS_BAD_ATTRIBUTE;

    if (find_io_region(&req->BasePort1, req->NumPorts1,
		       (char *)handle->dev_info))
	return CS_IN_USE;

    if (req->NumPorts2) {
	if (find_io_region(&req->BasePort2, req->NumPorts2,
			   (char *)handle->dev_info)) {
	    release_region(req->BasePort1, req->NumPorts1);
	    return CS_IN_USE;
	}
    }

    s->io = *req;
    s->state |= SOCKET_IO_REQ;
    handle->state |= CLIENT_IO_REQ;
    return CS_SUCCESS;
} /* request_io */

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

    Request_irq() reserves an irq for this client.  I've tried to
    implement some support for shared interrupts, but for dynamic
    sharing, the handlers still need to chain themselves together.
    Also, since Linux only reserves irq's when they are actually
    hooked, we don't guarantee that an irq will still be available
    when the configuration is locked.  Now that I think about it,
    there might be a way to fix this using a dummy handler.
    
======================================================================*/

static int cs_request_irq(client_handle_t handle, irq_req_t *req)
{
    socket_info_t *s;
    int ret, irq;
    irq_info_t *info;
    u_long mask;
    
    if (CHECK_HANDLE(handle))
	return CS_BAD_HANDLE;
    s = SOCKET(handle);
    if (!(s->state & SOCKET_PRESENT))
	return CS_NO_CARD;
    if (s->state & SOCKET_IRQ_REQ)
	return CS_IN_USE;
    if (s->state & SOCKET_CONFIG_LOCKED)
	return CS_CONFIGURATION_LOCKED;
    
    ret = CS_BAD_ARGS;
    if (req->IRQInfo1 & IRQ_INFO2_VALID) {
	mask = req->IRQInfo2 & s->cap.irq_mask;
	for (irq = 0; irq < 16; irq++)
	    if ((mask >> irq) & 1) {
		ret = try_irq(req, irq, 0);
		if (ret == 0) break;
	    }
	if (irq == 16)
	    for (irq = 0; irq < 16; irq++)
		if ((mask >> irq) & 1) {
		    ret = try_irq(req, irq, 1);
		    if (ret == 0) break;
		}
    }
    else {
	irq = req->IRQInfo1 & IRQ_MASK;
	ret = try_irq(req, irq, 1);
    }
    if (ret != 0) return ret;

    info = &irq_table[irq];
    info->Attributes |= RES_ALLOCATED;
    switch (req->Attributes & IRQ_TYPE) {
    case IRQ_TYPE_TIME:
	info->Attributes |= RES_IRQ_TYPE_TIME;
	info->time_share++;
	break;
    case IRQ_TYPE_DYNAMIC_SHARING:
	info->Attributes |= RES_IRQ_TYPE_DYNAMIC;
	info->dyn_share++;
	break;
    }
    
    req->AssignedIRQ = irq;
    s->irq = *req;
    s->state |= SOCKET_IRQ_REQ;
    handle->state |= CLIENT_IRQ_REQ;
    return CS_SUCCESS;
} /* cs_request_irq */

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

    Request_window() establishes a mapping between card memory space
    and system memory space.

    Hmmm, maybe we should check the return value from the ioctl.
    
======================================================================*/

static int request_window(client_handle_t *handle, win_req_t *req)
{
    socket_info_t *s;
    window_t *win;
    int w;
    
    if (CHECK_HANDLE(*handle))
	return CS_BAD_HANDLE;
    s = SOCKET(*handle);
    if (!(s->state & SOCKET_PRESENT))
	return CS_NO_CARD;
    if (req->Attributes & (WIN_PAGED | WIN_SHARED))
	return CS_BAD_ATTRIBUTE;

    for (w = 0; w < MAX_WIN; w++)
	if (!(s->state & SOCKET_WIN_REQ(w))) break;
    if (w == MAX_WIN)
	return CS_OUT_OF_RESOURCE;

    /* Allocate system memory window */
    win = &s->win[w];
    win->magic = WINDOW_MAGIC;
    win->index = w;
    win->handle = *handle;
    win->sock = s;
    win->base = (u_long)req->Base;
    win->size = req->Size;
    if (find_mem_region(&win->base, win->size,
			(char *)(*handle)->dev_info))
	return CS_IN_USE;
    req->Base = (caddr_t)win->base;
    (*handle)->state |= CLIENT_WIN_REQ(w);

    /* Configure the PCMCIA controller */
    win->ctl.map = w+1;
    win->ctl.flags = 0;
    win->ctl.speed = req->AccessSpeed;
    if (req->Attributes & WIN_MEMORY_TYPE)
	win->ctl.flags |= MAP_ATTRIB;
    if (req->Attributes & WIN_ENABLE)
	win->ctl.flags |= MAP_ACTIVE;
    if (req->Attributes & WIN_DATA_WIDTH)
	win->ctl.flags |= MAP_16BIT;
    if (req->Attributes & WIN_USE_WAIT)
	win->ctl.flags |= MAP_USE_WAIT;
    win->ctl.sys_start = (u_long)req->Base;
    win->ctl.sys_stop = (u_long)req->Base + req->Size-1;
    win->ctl.card_start = 0;
    s->ss_entry(s->sock, SS_SetMemMap, &win->ctl);
    s->state |= SOCKET_WIN_REQ(w);

    /* Return window handle */
    *handle = (client_handle_t)win;
    
    return CS_SUCCESS;
} /* request_window */

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

    I'm not sure which "reset" function this is supposed to use,
    but for now, it uses the low-level interface's reset, not the
    CIS register.
    
======================================================================*/

static int reset_card(client_handle_t handle, client_req_t *req)
{
    int i, ret;
    socket_info_t *s;
    
    if (CHECK_HANDLE(handle))
	return CS_BAD_HANDLE;
    i = handle->Socket; s = socket_table[i];
    if (!(s->state & SOCKET_PRESENT))
	return CS_NO_CARD;
    if (s->state & SOCKET_RESET_PENDING)
	return CS_IN_USE;
    s->state |= SOCKET_RESET_PENDING;

    ret = send_event(s, CS_EVENT_RESET_REQUEST, CS_EVENT_PRI_LOW);
    if (ret != 0) {
	s->state &= ~SOCKET_RESET_PENDING;
	handle->event_callback_args.info = (void *)ret;
	EVENT(handle, CS_EVENT_RESET_COMPLETE, CS_EVENT_PRI_LOW);
    }
    else {
#ifdef PCMCIA_DEBUG
	if (pc_debug)
	    printk("cs: resetting socket %d\n", i);
#endif
	send_event(s, CS_EVENT_RESET_PHYSICAL, CS_EVENT_PRI_LOW);
	s->reset_handle = handle;
	reset_socket(i);
    }
    return CS_SUCCESS;
} /* reset_card */

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

    These shut down or wake up a socket.  They are sort of user
    initiated versions of the APM suspend and resume actions.
    
======================================================================*/

static int suspend_card(client_handle_t handle, client_req_t *req)
{
    int i;
    socket_info_t *s;
    
    if (CHECK_HANDLE(handle))
	return CS_BAD_HANDLE;
    i = handle->Socket; s = socket_table[i];
    if (!(s->state & SOCKET_PRESENT))
	return CS_NO_CARD;
    if (s->state & SOCKET_SUSPEND)
	return CS_IN_USE;

#ifdef PCMCIA_DEBUG
    if (pc_debug)
	printk("cs: suspending socket %d\n", i);
#endif
    send_event(s, CS_EVENT_PM_SUSPEND, CS_EVENT_PRI_LOW);
    s->ss_entry(s->sock, SS_SetSocket, &dead_socket);
    s->state |= SOCKET_SUSPEND;

    return CS_SUCCESS;
} /* suspend_card */

static int resume_card(client_handle_t handle, client_req_t *req)
{
    int i;
    socket_info_t *s;
    
    if (CHECK_HANDLE(handle))
	return CS_BAD_HANDLE;
    i = handle->Socket; s = socket_table[i];
    if (!(s->state & SOCKET_PRESENT))
	return CS_NO_CARD;
    if (!(s->state & SOCKET_SUSPEND))
	return CS_IN_USE;

#ifdef PCMCIA_DEBUG
    if (pc_debug)
	printk("cs: waking up socket %d\n", i);
#endif
    setup_socket(i);

    return CS_SUCCESS;
} /* resume_card */

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

    These handle user requests to eject or insert a card.
    
======================================================================*/

static int eject_card(client_handle_t handle, client_req_t *req)
{
    int i, ret;
    socket_info_t *s;
    u_long flags;
    
    if (CHECK_HANDLE(handle))
	return CS_BAD_HANDLE;
    i = handle->Socket; s = socket_table[i];
    if (!(s->state & SOCKET_PRESENT))
	return CS_NO_CARD;

#ifdef PCMCIA_DEBUG
    if (pc_debug)
	printk("cs: user eject request on socket %d\n", i);
#endif

    ret = send_event(s, CS_EVENT_EJECTION_REQUEST, CS_EVENT_PRI_LOW);
    if (ret != 0)
	return ret;

    save_flags(flags);
    cli();
    if (!(s->state & SOCKET_SHUTDOWN_PENDING))
	do_shutdown(s);
    restore_flags(flags);
    return CS_SUCCESS;
    
} /* eject_card */

static int insert_card(client_handle_t handle, client_req_t *req)
{
    int i, status;
    socket_info_t *s;
    u_long flags;
    
    if (CHECK_HANDLE(handle))
	return CS_BAD_HANDLE;
    i = handle->Socket; s = socket_table[i];
    if (s->state & SOCKET_PRESENT)
	return CS_IN_USE;

#ifdef PCMCIA_DEBUG
    if (pc_debug)
	printk("cs: user insert request on socket %d\n", i);
#endif

    save_flags(flags);
    cli();
    if (!(s->state & SOCKET_SETUP_PENDING)) {
	s->state |= SOCKET_SETUP_PENDING;
	restore_flags(flags);
	s->ss_entry(i, SS_GetStatus, &status);
	if (status & SS_DETECT)
	    setup_socket(i);
	else {
	    s->state &= ~SOCKET_SETUP_PENDING;
	    return CS_NO_CARD;
	}
    }
    else
	restore_flags(flags);

    return CS_SUCCESS;
} /* insert_card */

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

    Maybe this should send a CS_EVENT_CARD_INSERTION event if we
    haven't sent one to this client yet?
    
======================================================================*/

static int set_event_mask(client_handle_t handle, eventmask_t *mask)
{
    u_long events, bit;
    if (CHECK_HANDLE(handle))
	return CS_BAD_HANDLE;
    if (handle->Attributes & CONF_EVENT_MASK_VALID)
	return CS_BAD_SOCKET;
    handle->EventMask = mask->EventMask;
    events = handle->PendingEvents & handle->EventMask;
    handle->PendingEvents -= events;
    while (events != 0) {
	bit = ((events ^ (events-1)) + 1) >> 1;
	EVENT(handle, bit, CS_EVENT_PRI_LOW);
	events -= bit;
    }
    return CS_SUCCESS;
} /* set_event_mask */

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

static int report_error(char *prefix, void *a1, void *a2)
{
    int i, func = (int)(a1), ret = (int)(a2);
    char *serv, *err;

    for (i = 0; i < ERROR_COUNT; i++)
	if (error_table[i].key == ret) break;
    if (i < ERROR_COUNT)
	err = error_table[i].msg;
    else
	err = "Unknown error code";

    for (i = 0; i < SERVICE_COUNT; i++)
	if (service_table[i].key == func) break;
    if (i < SERVICE_COUNT)
	serv = service_table[i].msg;
    else
	serv = "Unknown service number";

    if (prefix)
	printk("%s: %s: %s\n", prefix, serv, err);
    else
	printk("%s: %s\n", serv, err);
    return CS_SUCCESS;
} /* report_error */

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

int CardServices(int func, void *a1, void *a2, void *a3)
{

#ifdef PCMCIA_DEBUG
    if (pc_debug > 1) {
	int i;
	for (i = 0; i < SERVICE_COUNT; i++)
	    if (service_table[i].key == func) break;
	if (i < SERVICE_COUNT)
	    printk("cs: CardServices(%s, 0x%p, 0x%p)\n",
		   service_table[i].msg, a1, a2);
	else
	    printk("cs: CardServices(Unknown func %d, 0x%p, 0x%p)\n",
		   func, a1, a2);
    }
#endif
    switch (func) {
    case AccessConfigurationRegister:
	return access_configuration_register(a1, a2); break;
    case AdjustResourceInfo:
	return adjust_resource_info(a1, a2); break;
    case CheckEraseQueue:
	return check_erase_queue(a1); break;
    case CloseMemory:
	return close_memory(a1); break;
    case CopyMemory:
	return copy_memory(a1, a2); break;
    case DeregisterClient:
	return deregister_client(a1); break;
    case DeregisterEraseQueue:
	return deregister_erase_queue(a1); break;
    case GetFirstClient:
	return get_first_client(a1, a2); break;
    case GetCardServicesInfo:
	return get_card_services_info(a1); break;
    case GetConfigurationInfo:
	return get_configuration_info(a1, a2); break;
    case GetNextClient:
	return get_next_client(a1, a2); break;
    case GetFirstRegion:
	return get_first_region(a1, a2); break;
    case GetFirstTuple:
	return get_first_tuple(a1, a2); break;
    case GetNextRegion:
	return get_next_region(a1, a2); break;
    case GetNextTuple:
	return get_next_tuple(a1, a2); break;
    case GetStatus:
	return get_status(a1, a2); break;
    case GetTupleData:
	return get_tuple_data(a1, a2); break;
    case MapMemPage:
	return map_mem_page(a1, a2); break;
    case ModifyConfiguration:
	return modify_configuration(a1, a2); break;
    case ModifyWindow:
	return modify_window(a1, a2); break;
    case OpenMemory:
	return open_memory(a1, a2);
    case ParseTuple:
	return parse_tuple(a1, a2, a3); break;
    case ReadMemory:
	return read_memory(a1, a2, a3); break;
    case RegisterClient:
	return register_client(a1, a2); break;
    case RegisterEraseQueue:
	return register_erase_queue(a1, a2); break;
    case RegisterMTD:
	return register_mtd(a1, a2); break;
    case ReleaseConfiguration:
	return release_configuration(a1, a2); break;
    case ReleaseIO:
	return release_io(a1, a2); break;
    case ReleaseIRQ:
	return release_irq(a1, a2); break;
    case ReleaseWindow:
	return release_window(a1); break;
    case RequestConfiguration:
	return request_configuration(a1, a2); break;
    case RequestIO:
	return request_io(a1, a2); break;
    case RequestIRQ:
	return cs_request_irq(a1, a2); break;
    case RequestWindow:
	return request_window(a1, a2); break;
    case ResetCard:
	return reset_card(a1, a2); break;
    case SetEventMask:
	return set_event_mask(a1, a2); break;
    case ValidateCIS:
	return validate_cis(a1, a2); break;
    case WriteMemory:
	return write_memory(a1, a2, a3); break;
    case BindDevice:
	return bind_device(a1); break;
    case BindMTD:
	return bind_mtd(a1); break;
    case ReportError:
	return report_error(a1, a2, a3); break;
    case SuspendCard:
	return suspend_card(a1, a2); break;
    case ResumeCard:
	return resume_card(a1, a2); break;
    case EjectCard:
	return eject_card(a1, a2); break;
    case InsertCard:
	return insert_card(a1, a2); break;
    default:
	return CS_UNSUPPORTED_FUNCTION; break;
    }
    
} /* CardServices */

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

#ifdef MODULE
char kernel_version[] = UTS_RELEASE;

int init_module(void)
{
    cs_init(0,0);
    return 0;
}

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