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

    A simple MTD for Intel Series 2 Flash devices

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

#include <linux/config.h>

#define PCMCIA_DEBUG 1

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

#include <linux/kernel.h>
#include <linux/sched.h>
#include <linux/ptrace.h>
#include <linux/malloc.h>
#include <linux/string.h>
#include <linux/timer.h>
#include <linux/major.h>
#include <linux/fs.h>
#include <asm/io.h>
#include <asm/system.h>
#include <asm/segment.h>
#include <stdarg.h>

#include "version.h"
#include "cs_types.h"
#include "cs.h"
#include "bulkmem.h"
#include "cistpl.h"
#include "ds.h"
#include "iflash.h"

#ifdef PCMCIA_DEBUG
static int pc_debug = PCMCIA_DEBUG;
static char *version =
"iflash2_mtd.c 1.5 1995/05/15 01:42:03 (David Hinds)\n";
#endif

static int vpp_timeout_period	= 500;	/* in ticks */
static int vpp_settle		= 10;	/* in ticks */
static int erase_timeout	= 100;	/* in ms */
static int erase_limit		= 100;	/* times erase_timeout */

static u_short max_tries	= 4096;

#define USE_MEMCPY

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

/* Size of the memory window: 8K */
#define WINDOW_SIZE	0x2000

static void flash_config(dev_link_t *link);
static void flash_release(u_long arg);
static int flash_event(event_t event, int priority,
		       event_callback_args_t *args);

static dev_link_t *flash_attach(void);
static void flash_detach(dev_link_t *);

typedef struct flash_dev_t {
    u_long		state;
    u_long		erase_start;
    u_long		erase_retries;
    caddr_t		Base;
    int			vpp_ok;
    struct timer_list	vpp_timeout;
    u_long		cur_erase;
    region_info_t	region[2*CISTPL_MAX_DEVICES];
} flash_dev_t;

#define FLASH_ERASING		0x01

static dev_info_t dev_info = "iflash2_mtd";

static dev_link_t *dev_list = NULL;

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

static inline int byte_write(volatile u_char *address, u_char data)
{
    register u_char CSR = 0;
    register u_short i;
    for (i = 0; i < max_tries; i++) {
	CSR = *address;
	if (CSR & LOW(CSR_WR_READY)) {
	    *address = LOW(IF_WRITE);
	    *address = data;
	    return CS_SUCCESS;
	}
    }
    printk("byte_write timed out at 0x%p, CSR = 0x%x\n", address, CSR);
    return CS_WRITE_FAILURE;
}

static inline int word_write(volatile u_short *address, u_short data)
{
#if 0
__asm__ __volatile__(
	"1:\tmovb (%1), %%al\n\t"
	"testb %%al,%%al\n\t"
	"jge 1b\n\t"
	"movw $0x4040, (%1)\n\t"
	"movw %0, (%1)\n\t"
	: /* no output */
	:"r" (data),"r" (address)
	:"ax","memory");
#else
    register u_short CSR = 0, i;

    for (i = 0; i < max_tries; i++) {
	CSR = *address;
	if ((CSR & CSR_WR_READY) == CSR_WR_READY) {
	    *address = IF_WRITE;
	    *address = data;
	    return CS_SUCCESS;
	}
    }
    printk("word_write timed out at 0x%p, CSR = 0x%x\n", address, CSR);
    return CS_WRITE_FAILURE;
#endif
}

static int check_write(volatile u_short *address)
{
    u_short CSR = 0, i;
    *address = IF_READ_CSR;
    for (i = 0; i < max_tries; i++) {
	CSR = *address;
	if ((CSR & CSR_WR_READY) == CSR_WR_READY) break;
    }
    if (i == max_tries) {
	printk("check_write: timed out!  CSR = 0x%x\n", CSR);
	return CS_WRITE_FAILURE;
    }
    if (CSR & (CSR_WR_ERR | CSR_VPP_LOW)) {
	printk("write error: CSR = 0x%x\n", CSR);
	return CS_WRITE_FAILURE;
    }
    else
	return CS_SUCCESS;
}

static void block_erase(volatile u_short *address)
{
    *address = IF_BLOCK_ERASE;
    *address = IF_CONFIRM;
}

static int check_erase(volatile u_short *address)
{
    u_short CSR;
    *address = IF_READ_CSR;
    CSR = *address;
    if ((CSR & CSR_WR_READY) != CSR_WR_READY)
	return CS_BUSY;
    else if (CSR & (CSR_ERA_ERR | CSR_VPP_LOW | CSR_WR_ERR)) {
	printk("erase failed: CSR = 0x%x\n", CSR);
	return CS_WRITE_FAILURE;
    }
    else
	return CS_SUCCESS;
}

static void reset_block(volatile u_short *address)
{
    *address = IF_CLEAR_CSR;
    *address = IF_READ_ARRAY;
}

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

    Vpp management functions.  The vpp_setup() function checks to
    see if Vpp is available for the specified device.  If not, it
    turns on Vpp.  The vpp_shutdown() function is scheduled to turn
    Vpp off after an interval of inactivity.
    
======================================================================*/

static int vpp_setup(dev_link_t *link, mtd_request_t *req)
{
    flash_dev_t *dev = (flash_dev_t *)link->priv;
    if (dev->vpp_ok)
	del_timer(&dev->vpp_timeout);
    else {
	if (req->Function & MTD_REQ_TIMEOUT)
	    dev->vpp_ok = 1;
	else {
	    mtd_vpp_req_t vpp_req;
#ifdef PCMCIA_DEBUG
	    if (pc_debug > 1)
		printk("Raising Vpp...\n");
#endif
	    vpp_req.Vpp1 = vpp_req.Vpp2 = 120;
	    MTDHelperEntry(MTDSetVpp, link->handle, &vpp_req);
	    req->Status = MTD_WAITTIMER;
	    req->Timeout = vpp_settle;
	    return 1;
	}
    }
    return 0;
}

static void vpp_shutdown(u_long arg)
{
    dev_link_t *link = (dev_link_t *)arg;
    flash_dev_t *dev;
    mtd_vpp_req_t req;

#ifdef PCMCIA_DEBUG
    if (pc_debug > 1)
	printk("Lowering Vpp...\n");
#endif
    req.Vpp1 = req.Vpp2 = 0;
    MTDHelperEntry(MTDSetVpp, link->handle, &req);
    dev = (flash_dev_t *)link->priv;
    dev->vpp_ok = 0;
}

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

    These are used to keep track of when a device is busy and when
    it is ready to accept a new request.
    
======================================================================*/

static int get_lock(dev_link_t *link)
{
    cli();
    while (link->state & DEV_BUSY) {
	sti();
	interruptible_sleep_on(&link->pending);
	if (current->signal & ~current->blocked)
	    return -EINTR;
	cli();
    }
    link->state |= DEV_BUSY;
    sti();
    return 0;
}

static void free_lock(dev_link_t *link)
{
    link->state &= ~DEV_BUSY;
    wake_up_interruptible(&link->pending);
}

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

static void cs_error(int func, int ret)
{
    CardServices(ReportError, dev_info, (void *)func, (void *)ret);
}

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

    If we're linked into the kernel, this gets called from init.c.
    
======================================================================*/

#ifndef MODULE
u_long flash_init(u_long kmem_start, u_long kmem_end)
{
#ifdef PCMCIA_DEBUG
    if (pc_debug)
	printk(version);
#endif
    register_pcmcia_driver(&dev_info, &flash_attach, &flash_detach);
    return kmem_start;
}
#endif

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

    flash_attach() creates an "instance" of the driver, allocating
    local data structures for one device.  The device is registered
    with Card Services.

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

static dev_link_t *flash_attach(void)
{
    client_reg_t client_reg;
    dev_link_t *link;
    flash_dev_t *dev;
    int ret;
    
#ifdef PCMCIA_DEBUG
    if (pc_debug > 1)
	printk("flash_attach()\n");
#endif

    /* Create new memory card device */
    link = kmalloc(sizeof(struct dev_link_t), GFP_KERNEL);
    memset(link, 0, sizeof(*link));
    link->release.function = &flash_release;
    link->release.data = (u_long)link;
    dev = kmalloc(sizeof(struct flash_dev_t), GFP_KERNEL);
    memset(dev, 0, sizeof(*dev));
    dev->vpp_timeout.function = vpp_shutdown;
    dev->vpp_timeout.data = (u_long)link;
    link->priv = dev;

    /* Register with Card Services */
    link->next = dev_list;
    dev_list = link;
    client_reg.dev_info = &dev_info;
    client_reg.Attributes = INFO_MTD_CLIENT | INFO_CARD_SHARE;
    client_reg.EventMask =
	CS_EVENT_RESET_PHYSICAL | CS_EVENT_CARD_RESET |
	CS_EVENT_CARD_INSERTION | CS_EVENT_CARD_REMOVAL |
	CS_EVENT_PM_SUSPEND | CS_EVENT_PM_RESUME;
    client_reg.event_handler = &flash_event;
    client_reg.Version = 0x0210;
    client_reg.event_callback_args.client_data = link;
    ret = CardServices(RegisterClient, &link->handle, &client_reg);
    if (ret != 0) {
	cs_error(RegisterClient, ret);
	flash_detach(link);
	return NULL;
    }
    
    return link;
} /* flash_attach */

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

    This deletes a driver "instance".  The device is de-registered
    with Card Services.  If it has been released, all local data
    structures are freed.  Otherwise, the structures will be freed
    when the device is released.

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

static void flash_detach(dev_link_t *link)
{
    dev_link_t **linkp;
    int ret;
    long flags;

#ifdef PCMCIA_DEBUG
    if (pc_debug > 1)
	printk("flash_detach(0x%p)\n", link);
#endif
    
    /* Locate device structure */
    for (linkp = &dev_list; *linkp; linkp = &(*linkp)->next)
	if (*linkp == link) break;
    if (*linkp == NULL)
	return;

    save_flags(flags);
    cli();
    if (link->state & DEV_RELEASE_PENDING) {
	del_timer(&link->release);
	link->state &= ~DEV_RELEASE_PENDING;
    }
    restore_flags(flags);
    
    if (link->state & DEV_CONFIG)
	flash_release((u_long)link);

    if (link->handle) {
	ret = CardServices(DeregisterClient, link->handle);
	if (ret != CS_SUCCESS)
	    cs_error(DeregisterClient, ret);
    }
    
    /* Unlink device structure, free bits */
    *linkp = link->next;
    kfree_s(link->priv, sizeof(struct flash_dev_t));
    kfree_s(link, sizeof(struct dev_link_t));
    
} /* flash_detach */

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

    flash_config() is scheduled to run after a CARD_INSERTION event
    is received, to bind the MTD to appropriate memory regions.
    
======================================================================*/

static void printk_size(u_long sz)
{
    if (sz & 0xfffff)
	printk("%ld kb", sz >> 10);
    else
	printk("%ld mb", sz >> 20);
}

static void flash_config(dev_link_t *link)
{
    flash_dev_t *dev;
    win_req_t req;
    mtd_reg_t reg;
    region_info_t region;
    int i, attr, ret;

#ifdef PCMCIA_DEBUG
    if (pc_debug > 1)
	printk("flash_config(0x%p)\n", link);
#endif

    /* Allocate a 4K memory window */
    req.Attributes = WIN_DATA_WIDTH_16;
    req.Base = NULL; req.Size = WINDOW_SIZE;
    req.AccessSpeed = 0;
    link->win = (window_handle_t)link->handle;
    ret = CardServices(RequestWindow, &link->win, &req);
    if (ret != 0) {
	cs_error(RequestWindow, ret);
	link->state &= ~DEV_CONFIG_PENDING;
	flash_release((u_long)link);
	return;
    }

    link->state |= DEV_CONFIG;

    /* Grab info for all the memory regions we can access */
    dev = link->priv;
    dev->Base = req.Base;
    i = 0;
    for (attr = 0; attr < 2; attr++) {
	region.Attributes = attr ? REGION_TYPE_AM : REGION_TYPE_CM;
	ret = CardServices(GetFirstRegion, link->handle, &region);
	while (ret == CS_SUCCESS) {
	    reg.Attributes = region.Attributes;
	    reg.Offset = region.CardOffset;
	    reg.MediaID = (u_long)&dev->region[i];
	    ret = CardServices(RegisterMTD, link->handle, &reg);
	    if (ret != 0) break;		
	    printk("iflash2_mtd: %s at 0x%lx, ",
		   attr ? "attr" : "common", region.CardOffset);
	    printk_size(region.RegionSize);
	    printk(", %ld ns\n", region.AccessSpeed);
	    dev->region[i] = region; i++;
	    ret = CardServices(GetNextRegion, link->handle, &region);
	}
    }
    
} /* flash_config */

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

    After a card is removed, flash_release() will release the memory
    window allocated for this socket.
    
======================================================================*/

static void flash_release(u_long arg)
{
    dev_link_t *link = (dev_link_t *)arg;
    flash_dev_t *dev;

#ifdef PCMCIA_DEBUG
    if (pc_debug > 1)
	printk("flash_release(0x%p)\n", link);
#endif

    if (link->win) {
	int ret = CardServices(ReleaseWindow, link->win);
	if (ret != CS_SUCCESS)
	    cs_error(ReleaseWindow, ret);
    }
    dev = link->priv;
    if (dev->vpp_ok) {
	del_timer(&dev->vpp_timeout);
	vpp_shutdown((u_long)link);
    }
    link->state &= ~DEV_CONFIG;
    
    if (link->state & DEV_STALE_LINK)
	flash_detach(link);
    
} /* flash_release */

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

static int flash_read(dev_link_t *link, char *buf, mtd_request_t *req)
{
    flash_dev_t *dev = (flash_dev_t *)link->priv;
    region_info_t *region;
    mtd_mod_win_t mod;
    u_long from, length, nb;
    int ret;
    
#ifdef PCMCIA_DEBUG
    if (pc_debug > 1)
	printk("flash_read(0x%p, 0x%ld, 0x%p, 0x%lx, 0x%lx)\n",
	       link, req->MediaID, buf, req->SrcCardOffset,
	       req->TransferLength);
#endif

    region = (region_info_t *)(req->MediaID);
    if (region->Attributes & REGION_TYPE_AM)
	mod.Attributes = WIN_MEMORY_TYPE_AM;
    else
	mod.Attributes = WIN_MEMORY_TYPE_CM;
    mod.AccessSpeed = region->AccessSpeed;

    mod.CardOffset = req->SrcCardOffset & ~(WINDOW_SIZE-1);
    from = req->SrcCardOffset & (WINDOW_SIZE-1);
    for (length = req->TransferLength; length > 0; length -= nb) {
	ret = MTDHelperEntry(MTDModifyWindow, link->win, &mod);
	if (ret != CS_SUCCESS) {
	    cs_error(MapMemPage, ret);
	    return ret;
	}
	nb = (from+length > WINDOW_SIZE) ? WINDOW_SIZE-from : length;
	
#ifdef USE_MEMCPY
	if (req->Function & MTD_REQ_KERNEL)
	    memcpy(buf, &dev->Base[from], nb);
	else
	    memcpy_tofs(buf, &dev->Base[from], nb);
	buf += nb;
#else
	if (req->Function & MTD_REQ_KERNEL)
	    for (i = 0; i < nb; i++, buf++)
		*buf = dev->Base[from+i];
	else
	    for (i = 0; i < nb; i++, buf++)
		put_fs_byte(dev->Base[from+i], buf);
#endif
	
	from = 0;
	mod.CardOffset += WINDOW_SIZE;
    }
    return CS_SUCCESS;
} /* flash_read */

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

static int flash_write(dev_link_t *link, char *buf, mtd_request_t *req)
{
    flash_dev_t *dev = (flash_dev_t *)link->priv;
    mtd_mod_win_t mod;
    region_info_t *region;
    u_long from, length, nb, i, time;
    u_char *dest;
    status_t status;
    int ret;

#ifdef PCMCIA_DEBUG
    if (pc_debug > 1)
	printk("flash_write(0x%p, 0x%lx, 0x%p, 0x%lx, 0x%lx)\n",
	       link, req->MediaID, buf, req->DestCardOffset,
	       req->TransferLength);
#endif

    /* Check card write protect status */
    ret = CardServices(GetStatus, link->handle, &status);
    if (ret != 0) {
	cs_error(GetStatus, ret);
	return CS_GENERAL_FAILURE;
    }
    if (status.CardState & CS_EVENT_WRITE_PROTECT)
	return CS_WRITE_PROTECTED;

    if (vpp_setup(link, req) != 0)
	return CS_BUSY;

    region = (region_info_t *)(req->MediaID);
    if (region->Attributes & REGION_TYPE_AM)
	mod.Attributes = WIN_MEMORY_TYPE_AM;
    else
	mod.Attributes = WIN_MEMORY_TYPE_CM;
    mod.AccessSpeed = region->AccessSpeed;

    time = jiffies;
    mod.CardOffset = req->DestCardOffset & ~(WINDOW_SIZE-1);
    from = req->DestCardOffset & (WINDOW_SIZE-1);
    for (length = req->TransferLength ; length > 0; length -= nb) {
	ret = MTDHelperEntry(MTDModifyWindow, link->win, &mod);
	if (ret != CS_SUCCESS) {
	    cs_error(MapMemPage, ret);
	    goto done;
	}
	nb = (from+length > WINDOW_SIZE) ? WINDOW_SIZE-from : length;
	
	dest = dev->Base+from;
	*(u_short *)dest = IF_READ_CSR;
	if (req->Function & MTD_REQ_KERNEL) {
	    i = nb;
	    if (i & 1) {
		ret = byte_write(dest, *buf);
		if (ret != CS_SUCCESS) goto done;
		dest++; buf++; i--;
	    }
	    for (; i != 0; dest += 2, buf += 2, i -= 2) {
		ret = word_write((u_short *)dest, *(u_short *)buf);
		if (ret != CS_SUCCESS) goto done;
	    }
	}
	else {
	    i = nb;
	    if (i & 1) {
		ret = byte_write(dest, get_fs_byte(buf));
		if (ret != CS_SUCCESS) goto done;
		dest++; buf++; i--;
	    }
	    for (; i != 0; dest += 2, buf += 2, i -= 2) {
		ret = word_write((u_short *)dest, get_fs_word((u_short *)buf));
		if (ret != CS_SUCCESS) goto done;
	    }
	}
	ret = check_write((u_short *)dev->Base);
	if (ret != CS_SUCCESS) goto done;
	
	from = 0;
	mod.CardOffset += WINDOW_SIZE;
    }

done:
#ifdef PCMCIA_DEBUG
    time = jiffies - time;
    if (pc_debug)
	printk("write complete, time = %ld, avg = %ld us\n",
	       time, time*10000/65536);
#endif
    reset_block((u_short *)dev->Base);
    /* Fire up the Vpp timer */
    dev->vpp_timeout.expires = vpp_timeout_period;
    add_timer(&dev->vpp_timeout);
    return ret;
} /* flash_write */

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

static int flash_erase(dev_link_t *link, char *buf, mtd_request_t *req)
{
    flash_dev_t *dev = (flash_dev_t *)link->priv;
    status_t status;
    region_info_t *region;
    mtd_mod_win_t mod;
    volatile u_short *addr;
    int ret;

#ifdef PCMCIA_DEBUG
    if (pc_debug > 1)
	printk("flash_erase(0x%p, 0x%lx, 0x%lx, 0x%lx)\n",
	       link, req->MediaID, req->DestCardOffset,
	       req->TransferLength);
#endif

    /* First see if another erase is in progress */
    if ((dev->state & FLASH_ERASING) &&
	!(req->Function & MTD_REQ_TIMEOUT)) {
	req->Status = MTD_WAITREQ;
	return CS_BUSY;
    }
    
    /* Check card write protect status */
    ret = CardServices(GetStatus, link->handle, &status);
    if (ret != 0) {
	cs_error(GetStatus, ret);
	return CS_GENERAL_FAILURE;
    }
    if (status.CardState & CS_EVENT_WRITE_PROTECT)
	return CS_WRITE_PROTECTED;
    
    if (vpp_setup(link, req) != 0)
	return CS_BUSY;

    region = (region_info_t *)(req->MediaID);
    if (region->Attributes & REGION_TYPE_AM)
	mod.Attributes = WIN_MEMORY_TYPE_AM;
    else
	mod.Attributes = WIN_MEMORY_TYPE_CM;
    mod.AccessSpeed = region->AccessSpeed;

    addr = (u_short *)dev->Base;
    if (dev->state & FLASH_ERASING) {
	mod.CardOffset = dev->cur_erase;
	ret = MTDHelperEntry(MTDModifyWindow, link->win, &mod);
	if (ret != CS_SUCCESS) {
	    cs_error(MapMemPage, ret);
	    goto done;
	}
	ret = check_erase(addr);
	if (ret == CS_BUSY)
	    goto retry;
	else if (ret != CS_SUCCESS)
	    goto done;
	dev->cur_erase += region->BlockSize;
	if (dev->cur_erase >= req->DestCardOffset+req->TransferLength)
	    goto done;
    }
    else {
	dev->cur_erase = req->DestCardOffset;
	dev->state |= FLASH_ERASING;
    }

    /* Start a new block erase */
    mod.CardOffset = dev->cur_erase;
    ret = MTDHelperEntry(MTDModifyWindow, link->win, &mod);
    if (ret != CS_SUCCESS) {
	cs_error(MapMemPage, ret);
	goto done;
    }

    dev->erase_start = jiffies;
    dev->erase_retries = 0;
    block_erase(addr);

retry:
    if (++dev->erase_retries > erase_limit) {
	printk("erase timed out!\n");
	ret = CS_GENERAL_FAILURE;
	goto done;
    }
    req->Status = MTD_WAITTIMER;
    req->Timeout = erase_timeout;
    return CS_BUSY;
    
done:
#ifdef PCMCIA_DEBUG
    if (pc_debug)
	printk("erase complete, time = %ld\n", jiffies - dev->erase_start);
#endif
    dev->state &= ~FLASH_ERASING;
    reset_block(addr);
    /* Fire up the Vpp timer */
    dev->vpp_timeout.expires = vpp_timeout_period;
    add_timer(&dev->vpp_timeout);
    return ret;
} /* flash_erase */
    
/*====================================================================*/

static int flash_request(dev_link_t *link, void *buf, mtd_request_t *req)
{
    int ret = 0;
    if (!(link->state & DEV_PRESENT))
	return CS_NO_CARD;
    switch (req->Function & MTD_REQ_ACTION) {
    case MTD_REQ_READ:
	ret = flash_read(link, buf, req);
	break;
    case MTD_REQ_WRITE:
	ret = flash_write(link, buf, req);
	break;
    case MTD_REQ_ERASE:
	ret = flash_erase(link, buf, req);
	break;
    case MTD_REQ_COPY:
	ret = CS_UNSUPPORTED_FUNCTION;
	break;
    }
    if (!(link->state & DEV_PRESENT))
	return CS_GENERAL_FAILURE;
    return ret;
} /* flash_request */

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

    The card status event handler.  Mostly, this schedules other
    stuff to run after an event is received.  A CARD_REMOVAL event
    also sets some flags to discourage the driver from trying to
    talk to the card any more.
    
======================================================================*/

static int flash_event(event_t event, int priority,
		      event_callback_args_t *args)
{
    dev_link_t *link = args->client_data;

#ifdef PCMCIA_DEBUG
    if (pc_debug > 1)
	printk("flash_event(%x)\n", event);
#endif
    
    switch (event) {
	
    case CS_EVENT_CARD_REMOVAL:
	link->state &= ~DEV_PRESENT;
	if (link->state & DEV_CONFIG) {
	    link->release.expires = 5;
	    add_timer(&link->release);
	}
	break;
	
    case CS_EVENT_CARD_INSERTION:
	link->state |= DEV_PRESENT | DEV_CONFIG_PENDING;
	flash_config(link);
	break;
	
    case CS_EVENT_PM_SUSPEND:
	link->state |= DEV_SUSPEND;
	/* Fall through... */
    case CS_EVENT_RESET_PHYSICAL:
	get_lock(link);
	break;
	
    case CS_EVENT_PM_RESUME:
	link->state &= ~DEV_SUSPEND;
	/* Fall through... */
    case CS_EVENT_CARD_RESET:
	free_lock(link);
	break;
	
    case CS_EVENT_MTD_REQUEST:
	return flash_request(link, args->buffer, args->mtdrequest);
	break;
	
    }
    return CS_SUCCESS;
} /* flash_event */

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

#ifdef MODULE
char kernel_version[] = UTS_RELEASE;

int init_module(void)
{
    servinfo_t serv;
    
#ifdef PCMCIA_DEBUG
    if (pc_debug)
	printk(version);
#endif
    
    CardServices(GetCardServicesInfo, &serv);
    if (serv.Revision != CS_RELEASE_CODE) {
	printk("iflash2_mtd: Card Services release does not match!\n");
	return -1;
    }
    
    register_pcmcia_driver(&dev_info, &flash_attach, &flash_detach);

    return 0;
}

void cleanup_module(void)
{
    printk("iflash2_mtd: unloading\n");
    unregister_pcmcia_driver(&dev_info);
    while (dev_list != NULL)
	flash_detach(dev_list);
}
#endif /* MODULE */
