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

    A simple MTD for Intel Series 2+ Flash devices

    Written by David Hinds, dhinds@allegro.stanford.edu

    For efficiency and simplicity, this driver is very block oriented.
    Reads and writes must not span erase block boundaries.  Erases
    are limited to one erase block per request.  This makes it much
    easier to manage multiple asynchronous erases efficiently.
    
======================================================================*/

#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	= 1000;	/* in ticks */
static int vpp_settle		= 10;	/* in ticks */
static int erase_timeout	= 100;	/* in ms */
static int erase_limit		= 1000;	/* in ticks */

static int write_limit		= 4;	/* max retries */

static u_long max_tries       	= 4096*256;

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

/* 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 *);

#define MAX_CELLS		32

/* A flash region is composed of one or more "cells", where we allow
   simultaneous erases if they are in different cells */
typedef struct flash_region_t {
    region_info_t	region;
    u_long		cell_size;
    struct flash_cell_t {
	u_long		state;
	u_long		erase_time;
	u_long		erase_addr;
    } cell[MAX_CELLS];
} flash_region_t;

typedef struct flash_dev_t {
    caddr_t		Base;
    window_handle_t	ESRwin;
    caddr_t		ESRbase;
    int			vpp_usage;
    u_long		vpp_start;
    struct timer_list	vpp_timeout;
    flash_region_t	*flash[2*CISTPL_MAX_DEVICES];
} flash_dev_t;

#define FLASH_PENDING		0x01
#define FLASH_ERASING		0x02
#define FLASH_ERASE_SUSPEND	0x04

static dev_info_t dev_info = "iflash2+_mtd";

static dev_link_t *dev_list = NULL;

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

    Low level routines for programming the flash card.
    
======================================================================*/

static void log_esr(char *s, volatile u_short *esr)
{
    u_short CSR, GSR, BSR;
    
    *esr = IF_READ_CSR;
    CSR = *esr;
    *esr = IF_READ_ESR;
    BSR = esr[2];
    GSR = esr[4];
    if (s)
	printk("%s: CSR = 0x%04x, BSR = 0x%04x, GSR = 0x%04x\n",
	       s, CSR, BSR, GSR);
    else
	printk("CSR = 0x%04x, BSR = 0x%04x, GSR = 0x%04x\n",
	       CSR, BSR, GSR);
}
    
static int check_write(volatile u_short *esr)
{
    u_short BSR = 0;
    u_long i;
    
    *esr = IF_READ_ESR;
    for (i = 0; i < max_tries; i++) {
	BSR = esr[2];
	if ((BSR & BSR_READY) == BSR_READY) break;
    }
    if (i == max_tries) {
	printk("check_write: timed out!\n");
	log_esr(NULL, esr);
	return CS_GENERAL_FAILURE;
    }
    if (BSR & BSR_FAILED) {
	log_esr("write error", esr);
	return CS_WRITE_FAILURE;
    }
    else
	return CS_SUCCESS;
}

static int page_setup(volatile u_short *esr, volatile u_short *address,
		      u_short count)
{
    u_short GSR, nw;
    u_long i;
    
    *esr = IF_READ_ESR;
    for (i = 0; i < max_tries; i++) {
	GSR = esr[4];
	if ((GSR & GSR_PAGE_AVAIL) == GSR_PAGE_AVAIL) break;
    }
    if (i == max_tries) {
	printk("page_setup timed out\n");
	log_esr(NULL, esr);
	return CS_GENERAL_FAILURE;
    }
    nw = count >> 1;
    if (nw == 0) {
	/* Special case of single byte write */
	*(u_char *)address = LOW(IF_SEQ_LOAD);
	*(u_char *)address = 1;
	*(u_char *)address = 0;
    }
    else {
	*address = IF_SEQ_LOAD;
	/* Tricky: split count into low bytes and high bytes */
	*address = (count-nw-1) | ((nw-1) << 8);
	*address = 0;
    }
    return CS_SUCCESS;
}

static int page_write(volatile u_short *esr, volatile u_short *address,
		      u_short count)
{
    u_short BSR;
    u_long i;

    count = (count >> 1) - 1;
    *esr = IF_READ_ESR;
    for (i = 0; i < max_tries; i++) {
	BSR = esr[2];
	if (!(BSR & BSR_QUEUE_FULL)) break;
    }
    if (i == max_tries) {
	printk("page_write timed out\n");
	log_esr(NULL, esr);
	return CS_GENERAL_FAILURE;
    }
    if (count == 1) {
	*(u_char *)address = LOW(IF_PAGE_WRITE);
	*(u_char *)address = 1;
	*(u_char *)address = 0;
    }
    else {
	*address = IF_PAGE_WRITE;
	*address = count | (count << 8);
	*address = 0;
    }
    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)) {
	log_esr("erase error", address);
	return CS_WRITE_FAILURE;
    }
    else
	return CS_SUCCESS;
}

static int suspend_erase(volatile u_short *esr)
{
    u_short GSR;
    u_long i;
    
    *esr = IF_ERASE_SUSPEND;
    *esr = IF_READ_ESR;
    for (i = 0; i < max_tries; i++) {
	GSR = esr[4];
	if ((GSR & GSR_WR_READY) == GSR_WR_READY) break;
    }
    if (i == max_tries) {
	printk("suspend_erase timed out\n");
	log_esr(NULL, esr);
	return CS_GENERAL_FAILURE;
    }
    *esr = IF_READ_ARRAY;
    return CS_SUCCESS;
}

static void resume_erase(volatile u_short *esr)
{
    u_short GSR;
    *esr = IF_READ_ESR;
    GSR = esr[4];
    /* Only give resume signal if the erase is really suspended */
    if (GSR & GSR_OP_SUSPEND)
	*esr = IF_CONFIRM;
}

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

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

static void set_global_lock(window_handle_t win,
			    volatile caddr_t base, int set)
{
    mtd_mod_win_t mod;
    mod.Attributes = WIN_MEMORY_TYPE_AM;
    mod.AccessSpeed = 250;
    mod.CardOffset = 0x4000;
    MTDHelperEntry(MTDModifyWindow, win, &mod);
    base[CISREG_WP] = (set) ? WP_BLKEN : 0;
}

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

    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.

    vpp_setup() assumes that it will be called at the top of a
    request handler, and that it can use the MTD_REQ_TIMEOUT flag
    to tell if it has already been called for this particular
    request, so that it can count Vpp users.

    A handler should call vpp_shutdown() once for each request that
    does a vpp_setup().
    
======================================================================*/

static int vpp_setup(dev_link_t *link, mtd_request_t *req)
{
    flash_dev_t *dev = (flash_dev_t *)link->priv;
    mtd_vpp_req_t vpp_req;

    /* First time for this request? */
    if (!(req->Function & MTD_REQ_TIMEOUT)) {
	dev->vpp_usage++;
	/* If no other users, kill shutdown timer and apply power */
	if (dev->vpp_usage == 1) {
#ifdef PCMCIA_DEBUG
	    if (pc_debug > 1)
		printk("Raising Vpp...\n");
#endif
	    del_timer(&dev->vpp_timeout);
	    dev->vpp_start = jiffies;
	    vpp_req.Vpp1 = vpp_req.Vpp2 = 120;
	    MTDHelperEntry(MTDSetVpp, link->handle, &vpp_req);
	}
    }
    /* Wait for Vpp to settle if it was just applied */
    if (jiffies < dev->vpp_start + vpp_settle) {
	req->Status = MTD_WAITTIMER;
	req->Timeout = vpp_settle * 10;
	return 1;
    }
    return 0;
}

static void vpp_off(u_long arg)
{
    dev_link_t *link = (dev_link_t *)arg;
    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);
}

static void vpp_shutdown(dev_link_t *link)
{
    flash_dev_t *dev;
    dev = (flash_dev_t *)link->priv;
    dev->vpp_usage--;
    if (dev->vpp_usage == 0)
	add_timer(&dev->vpp_timeout);
}

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

    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_off;
    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;
    }
    dev = link->priv;
    dev->Base = req.Base;

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

    link->state |= DEV_CONFIG;

    /* Grab info for all the memory regions we can access */
    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;
	    dev->flash[i] = kmalloc(sizeof(struct flash_region_t),
				    GFP_KERNEL);
	    reg.MediaID = (u_long)dev->flash[i];
	    ret = CardServices(RegisterMTD, link->handle, &reg);
	    if (ret != 0) {
		kfree_s(dev->flash[i], sizeof(struct flash_region_t));
		break;
	    }
	    printk("iflash2+_mtd: %s at 0x%lx, ",
		   attr ? "attr" : "common", region.CardOffset);
	    printk_size(region.RegionSize);
	    printk(", %ld ns\n", region.AccessSpeed);
	    memset(dev->flash[i], 0, sizeof(struct flash_region_t));
	    dev->flash[i]->region = region;
	    /* Distinguish between 4MB..20MB cards and 40MB cards */
	    if (region.RegionSize > 0x1400000)
		dev->flash[i]->cell_size = 0x800000; /* 8MB components */
	    else
		dev->flash[i]->cell_size = 0x400000; /* 4MB components */
	    i++;
	    ret = CardServices(GetNextRegion, link->handle, &region);
	}
    }
    dev->flash[i] = NULL;
    
} /* 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;
    int i;

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

    link->state &= ~DEV_CONFIG;
    if (link->win) {
	int ret = CardServices(ReleaseWindow, link->win);
	if (ret != CS_SUCCESS)
	    cs_error(ReleaseWindow, ret);
    }
    dev = link->priv;
    if (dev->ESRwin) {
	int ret = CardServices(ReleaseWindow, dev->ESRwin);
	if (ret != CS_SUCCESS)
	    cs_error(ReleaseWindow, ret);
    }
    if (dev->vpp_usage == 0)
	del_timer(&dev->vpp_timeout);
    vpp_off((u_long)link);
    for (i = 0; i < 2*CISTPL_MAX_DEVICES; i++)
	if (dev->flash[i])
	    kfree_s(dev->flash[i], sizeof(struct flash_region_t));
	else break;
    
    if (link->state & DEV_STALE_LINK)
	flash_detach(link);
    
} /* flash_release */

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

    The read request handler.  This handler supports suspending
    current erase requests.  Reading from a block that is currently
    erasing is undefined.
    
======================================================================*/

static int flash_read(dev_link_t *link, char *buf, mtd_request_t *req)
{
    flash_dev_t *dev = (flash_dev_t *)link->priv;
    flash_region_t *flash;
    region_info_t *region;
    mtd_mod_win_t mod;
    u_long from, length, nb, time, cell;
    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

    flash = (flash_region_t *)(req->MediaID);
    region = &flash->region;
    if ((req->SrcCardOffset / region->BlockSize) !=
	((req->SrcCardOffset+req->TransferLength-1) / region->BlockSize))
	return CS_BAD_SIZE;
    if (region->Attributes & REGION_TYPE_AM)
	mod.Attributes = WIN_MEMORY_TYPE_AM;
    else
	mod.Attributes = WIN_MEMORY_TYPE_CM;
    mod.AccessSpeed = region->AccessSpeed;

    cell = (mod.CardOffset - region->CardOffset) / flash->cell_size;
    ret = CS_SUCCESS;
    /* Suspend an in-progress block erase */
    if (flash->cell[cell].state & FLASH_ERASING) {
	mod.CardOffset = flash->cell[cell].erase_addr;
	ret = MTDHelperEntry(MTDModifyWindow, dev->ESRwin, &mod);
	if (ret != CS_SUCCESS) goto done;
	ret = suspend_erase((u_short *)dev->ESRbase);
	if (ret != CS_SUCCESS) goto done;
	flash->cell[cell].state |= FLASH_ERASE_SUSPEND;
    }

    mod.CardOffset = req->SrcCardOffset & ~(WINDOW_SIZE-1);
    from = req->SrcCardOffset & (WINDOW_SIZE-1);
    
    time = jiffies;
    for (length = req->TransferLength; length > 0; length -= nb) {
	
	ret = MTDHelperEntry(MTDModifyWindow, link->win, &mod);
	if (ret != CS_SUCCESS) goto done;
	nb = (from+length > WINDOW_SIZE) ? WINDOW_SIZE-from : length;

	if (req->Function & MTD_REQ_KERNEL)
	    memcpy(buf, &dev->Base[from], nb);
	else
	    memcpy_tofs(buf, &dev->Base[from], nb);
	
	buf += nb;
	from = 0;
	mod.CardOffset += WINDOW_SIZE;
    }
    
#ifdef PCMCIA_DEBUG
    time = jiffies - time;
    if (pc_debug)
	printk("read complete, time = %ld, avg = %ld ns\n",
	       time, time*10000000/65536);
#endif
    
done:
    if (flash->cell[cell].state & FLASH_ERASE_SUSPEND) {
	resume_erase((u_short *)dev->ESRbase);
	flash->cell[cell].state &= ~FLASH_ERASE_SUSPEND;
    }
    return ret;
} /* flash_read */

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

    basic_write() handles a write that fits completely within a
    memory window that has already been set up.  It does a series
    of pipelined page buffer writes.
    
======================================================================*/

static int basic_write(char *esr, char *dest, char *buf,
		       u_long nb, u_long is_krnl)
{
    u_short npb;
    int ret;

    for (; nb > 0; nb -= npb) {
	    
	/* npb = # of bytes to write to page buffer */
	npb = (nb > 512) ? 512 : nb;
	ret = page_setup((u_short *)esr, (u_short *)dest, npb);
	if (ret != CS_SUCCESS) return ret;
	
	if (is_krnl)
	    memcpy(dest, buf, npb);
	else
	    memcpy_fromfs(dest, buf, npb);
	
	ret = page_write((u_short *)esr, (u_short *)dest, npb);
	if (ret != CS_SUCCESS) return ret;
	
	dest += npb;
	buf += npb;
    }
    return check_write((u_short *)esr);
}

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

    The write request handler.  The Series 2+ cards support automatic
    erase suspend for writes.
    
======================================================================*/

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;
    flash_region_t *flash;
    region_info_t *region;
    u_long from, length, nb, time, retry;
    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 */
    if (CardServices(GetStatus, link->handle, &status) != CS_SUCCESS)
	return CS_GENERAL_FAILURE;
    if (status.CardState & CS_EVENT_WRITE_PROTECT)
	return CS_WRITE_PROTECTED;

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

    flash = (flash_region_t *)(req->MediaID);
    region = &flash->region;
    if ((req->DestCardOffset / region->BlockSize) !=
	((req->DestCardOffset+req->TransferLength-1) / region->BlockSize))
	return CS_BAD_SIZE;
    if (region->Attributes & REGION_TYPE_AM)
	mod.Attributes = WIN_MEMORY_TYPE_AM;
    else
	mod.Attributes = WIN_MEMORY_TYPE_CM;
    mod.AccessSpeed = region->AccessSpeed;

    /* Set up window for ESR accesses */
    mod.CardOffset = req->DestCardOffset & ~(region->BlockSize-1);
    ret = MTDHelperEntry(MTDModifyWindow, dev->ESRwin, &mod);
    if (ret != CS_SUCCESS) goto done;
    
    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) goto done;

	nb = (from+length > WINDOW_SIZE) ? WINDOW_SIZE-from : length;

	for (retry = 0; retry < write_limit; retry++) {
	    ret = basic_write(dev->ESRbase, dev->Base+from, buf, nb,
			      (req->Function & MTD_REQ_KERNEL));
	    if (ret == CS_SUCCESS)
		break;
	    if (ret == CS_GENERAL_FAILURE)
		goto done;
	    reset_block((u_short *)dev->Base);
	}
	if (retry == write_limit) {
	    printk("write failed: too many retries!\n");
	    goto done;
	}
	
	buf += nb;
	from = 0;
	mod.CardOffset += WINDOW_SIZE;
    }

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

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

    The erase request handler.  This handler supports simultaneous
    erases in different device components.
    
======================================================================*/

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;
    flash_region_t *flash;
    region_info_t *region;
    mtd_mod_win_t mod;
    int i, 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

    flash = (flash_region_t *)(req->MediaID);
    region = &flash->region;
    if (region->BlockSize != req->TransferLength)
	return CS_BAD_SIZE;
    
    i = (req->DestCardOffset-region->CardOffset)/flash->cell_size;
    
    if (!(req->Function & MTD_REQ_TIMEOUT)) {
	if (flash->cell[i].state & (FLASH_ERASING|FLASH_PENDING)) {
	    req->Status = MTD_WAITREQ;
	    return CS_BUSY;
	}
	/* Check card write protect status */
	if (CardServices(GetStatus, link->handle, &status) != CS_SUCCESS)
	    return CS_GENERAL_FAILURE;
	if (status.CardState & CS_EVENT_WRITE_PROTECT)
	    return CS_WRITE_PROTECTED;
	flash->cell[i].state |= FLASH_PENDING;
	/* Activate Vpp if necessary */
	if (vpp_setup(link, req) != 0)
	    return CS_BUSY;
    }

    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->DestCardOffset;
    ret = MTDHelperEntry(MTDModifyWindow, link->win, &mod);
    if (ret != CS_SUCCESS)
	goto done;
    
    if (flash->cell[i].state & FLASH_PENDING) {
	/* Start a new block erase */
	flash->cell[i].state &= ~FLASH_PENDING;
	flash->cell[i].state |= FLASH_ERASING;
	flash->cell[i].erase_addr = mod.CardOffset;
	flash->cell[i].erase_time = jiffies;
	set_global_lock(dev->ESRwin, dev->ESRbase, 0);
	block_erase((u_short *)dev->Base);
    }
    else {
	/* Check on an already started erase */
	ret = check_erase((u_short *)dev->Base);
	if (ret != CS_BUSY) {
	    reset_block((u_short *)dev->Base);
	    goto done;
	}
    }

    /* If the request is not complete, has it taken too long? */
    if (jiffies > flash->cell[i].erase_time + erase_limit) {
	printk("erase timed out!\n");
	log_esr(NULL, (u_short *)dev->ESRbase);
	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 - flash->cell[i].erase_time);
#endif
    flash->cell[i].state &= ~(FLASH_ERASING|FLASH_PENDING);
    set_global_lock(dev->ESRwin, dev->ESRbase, 1);
    vpp_shutdown(link);
    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 */
