/*
 * $Log:   P:/user/amir/lite/vcs/cfiscs.c_v  $
 *
 *    Rev 1.19	 06 Oct 1997  9:53:30	danig
 * VPP functions under #ifdef
 *
 *    Rev 1.18	 18 Sep 1997 10:05:40	danig
 * Warnings
 *
 *    Rev 1.17	 10 Sep 1997 16:31:16	danig
 * Got rid of generic names
 *
 *    Rev 1.16	 04 Sep 1997 18:19:34	danig
 * Debug messages
 *
 *    Rev 1.15	 31 Aug 1997 14:50:52	danig
 * Registration routine return status
 *
 *    Rev 1.14	 27 Jul 1997 15:00:38	danig
 * FAR -> FAR0
 *
 *    Rev 1.13	 21 Jul 1997 19:58:24	danig
 * No watchDogTimer
 *
 *    Rev 1.12	 15 Jul 1997 19:18:32	danig
 * Ver 2.0
 *
 *    Rev 1.11	 09 Jul 1997 10:58:52	danig
 * Fixed byte erase bug & changed identification routines
 *
 *    Rev 1.10	 20 May 1997 14:48:02	danig
 * Changed overwrite to mode in write routines
 *
 *    Rev 1.9	18 May 1997 13:54:58   danig
 * JEDEC ID independent
 *
 *    Rev 1.8	13 May 1997 16:43:10   danig
 * Added getMultiplier.
 *
 *    Rev 1.7	08 May 1997 19:56:12   danig
 * Added cfiscsByteSize
 *
 *    Rev 1.6	04 May 1997 14:01:16   danig
 * Changed cfiscsByteErase and added multiplier
 *
 *    Rev 1.4	15 Apr 1997 11:38:52   danig
 * Changed word identification and IDs.
 *
 *    Rev 1.3	15 Jan 1997 18:21:40   danig
 * Bigger ID string buffers and removed unused definitions.
 *
 *    Rev 1.2	08 Jan 1997 14:54:06   danig
 * Changes in specification
 *
 *    Rev 1.1	25 Dec 1996 18:21:44   danig
 * Initial revision
 */

/************************************************************************/
/*									*/
/*		FAT-FTL Lite Software Development Kit			*/
/*		Copyright (C) M-Systems Ltd. 1995-1997			*/
/*									*/
/************************************************************************/


/*----------------------------------------------------------------------*/
/* This MTD supports the SCS/CFI technology.				*/
/*----------------------------------------------------------------------*/

#include "flflash.h"
#ifdef FL_BACKGROUND
#include "backgrnd.h"
#endif

/* JEDEC-IDs */

#define VOYAGER_ID		0x8915
#define KING_COBRA_ID		0xb0d0

/* command set IDs */

#define INTEL_COMMAND_SET      0x0001
#define AMDFUJ_COMMAND_SET     0x0002
#define INTEL_ALT_COMMAND_SET  0x0001
#define AMDFUJ_ALT_COMMAND_SET 0x0004
#define ALT_NOT_SUPPORTED      0x0000


/* CFI identification strings */

#define ID_STR_LENGTH	   3
#define QUERY_ID_STR	   "QRY"
#define PRIMARY_ID_STR	   "PRI"
#define ALTERNATE_ID_STR   "ALT"


/* commands */

#define CONFIRM_SET_LOCK_BIT	0x01
#define SETUP_BLOCK_ERASE	0x20
#define SETUP_QUEUE_ERASE	0x28
#define SETUP_CHIP_ERASE	0x30
#define CLEAR_STATUS		0x50
#define SET_LOCK_BIT		0x60
#define CLEAR_LOCK_BIT		0x60
#define READ_STATUS		0x70
#define READ_ID 		0x90
#define QUERY			0x98
#define SUSPEND_WRITE		0xb0
#define SUSPEND_ERASE		0xb0
#define CONFIG			0xb8
#define CONFIRM_WRITE		0xd0
#define RESUME_WRITE		0xd0
#define CONFIRM_ERASE		0xd0
#define RESUME_ERASE		0xd0
#define CONFIRM_CLEAR_LOCK_BIT	0xd0
#define WRITE_TO_BUFFER 	0xe8
#define READ_ARRAY		0xff


/* status register bits */

#define WSM_ERROR		0x3a
#define SR_BLOCK_LOCK		0x02
#define SR_WRITE_SUSPEND	0x04
#define SR_VPP_ERROR		0x08
#define SR_WRITE_ERROR		0x10
#define SR_LOCK_SET_ERROR	0x10
#define SR_ERASE_ERROR		0x20
#define SR_LOCK_RESET_ERROR	0x20
#define SR_ERASE_SUSPEND	0x40
#define SR_READY		0x80


/* optional commands support */

#define CHIP_ERASE_SUPPORT	     0x0001
#define SUSPEND_ERASE_SUPPORT	     0x0002
#define SUSPEND_WRITE_SUPPORT	     0x0004
#define LOCK_SUPPORT		     0x0008
#define QUEUED_ERASE_SUPPORT	     0x0010


/* supported functions after suspend */

#define WRITE_AFTER_SUSPEND_SUPPORT  0x0001


/* a structure that hold important CFI data. */
typedef struct {

  ULONG 	commandSetId;		 /* id of a specific command set. */
  ULONG 	altCommandSetId;	    /* id of alternate command set.  */
  FLBoolean	   wordMode;		    /* TRUE - word mode.	     */
					    /* FALSE - byte mode.	     */
  LONG		   multiplier;		    /* the number of times each byte */
					    /* of data appears in READ_ID    */
					    /* and QUERY commands.	     */
  ULONG 	maxBytesWrite;		    /* maximum number of bytes	     */
					    /* in multi-byte write.	     */
  FLBoolean	   vpp; 		    /* if = TRUE, need vpp.	     */
  LONG		   optionalCommands;	    /* optional commands supported   */
					    /* (1 = yes, 0 = no):	     */
					    /* bit 0 - chip erase.	     */
					    /* bit 1 - suspend erase.	     */
					    /* bit 2 - suspend write	     */
					    /* bit 3 - lock/unlock.	     */
					    /* bit 4 - queued erase.	     */
  ULONG    afterSuspend;	    /* functions supported after     */
					    /* suspend (1 = yes, 0 = no):    */
					    /* bit 0 - write after erase     */
					    /*	       suspend. 	     */
} CFI;

CFI mtdVars_cfiscs[SOCKETS];

#define thisCFI   ((CFI *)vol.mtdVars)

/*----------------------------------------------------------------------*/
/*			    c f i s c s B y t e S i z e 		*/
/*									*/
/* Identify the card size for byte mode.				*/
/* Sets the value of flash.noOfChips.					*/
/*									*/
/* Parameters:								*/
/*	vol		: Pointer identifying drive			*/
/*	amdCmdRoutine	: Routine to read-id AMD/Fujitsu style at	*/
/*			  a specific location. If null, Intel procedure */
/*			  is used.					*/
/*	idOffset	: Chip offset to use for identification 	*/
/*									*/
/* Returns:								*/
/*	FLStatus	: 0 = OK, otherwise failed (invalid Flash array)*/
/*----------------------------------------------------------------------*/

FLStatus cfiscsByteSize(FLFlash vol)
{
  CHAR queryIdStr[ID_STR_LENGTH + 1] = QUERY_ID_STR;

  FlashPTR flashPtr = (FlashPTR) flMap(vol.socket, 0);
  tffsWriteByteFlash(flashPtr + (0x55 * vol.interleaving), QUERY);
  /* We leave the first chip in QUERY mode, so that we can		*/
  /* discover an address wraparound.					*/

  for (vol.noOfChips = 0;	/* Scan the chips */
       vol.noOfChips < 2000;  /* Big enough ? */
       vol.noOfChips += vol.interleaving) {
    LONG i;

    flashPtr = (FlashPTR) flMap(vol.socket, vol.noOfChips * vol.chipSize);

    /* Check for address wraparound to the first chip */
    if (vol.noOfChips > 0 &&
	(queryIdStr[0] == tffsReadByteFlash(flashPtr +
			  0x10 * vol.interleaving * thisCFI->multiplier) &&
	 queryIdStr[1] == tffsReadByteFlash(flashPtr +
			  0x11 * vol.interleaving * thisCFI->multiplier) &&
	 queryIdStr[2] == tffsReadByteFlash(flashPtr +
			  0x12 * vol.interleaving * thisCFI->multiplier)))
      goto noMoreChips;    /* wraparound */

    /* Check if chip displays the "QRY" ID string */
    for (i = (vol.noOfChips ? 0 : 1); i < vol.interleaving; i++) {
       tffsWriteByteFlash(flashPtr + vol.interleaving * 0x55 + i, QUERY);
       if (queryIdStr[0] != tffsReadByteFlash(flashPtr +
			    0x10 * vol.interleaving * thisCFI->multiplier + i) ||
	   queryIdStr[1] != tffsReadByteFlash(flashPtr +
			    0x11 * vol.interleaving * thisCFI->multiplier + i) ||
	   queryIdStr[2] != tffsReadByteFlash(flashPtr +
			    0x12 * vol.interleaving * thisCFI->multiplier + i))
	goto noMoreChips;  /* This "chip" doesn't respond correctly, so we're done */

      tffsWriteByteFlash(flashPtr+i, READ_ARRAY);
    }
  }

noMoreChips:
  flashPtr = (FlashPTR) flMap(vol.socket, 0);
  tffsWriteByteFlash(flashPtr, READ_ARRAY);		/* reset the original chip */

  return (vol.noOfChips == 0) ? flUnknownMedia : flOK;
}


/*----------------------------------------------------------------------*/
/*			 c f i s c s B y t e I d e n t i f y		*/
/*									*/
/* Identify the Flash type for cards in byte mode.			*/
/* Sets the value of flash.type (JEDEC id) & flash.interleaving.	*/
/* Calculate the number of times each byte of data appears in READ_ID	*/
/* and QUERY commands.							*/
/*									*/
/* Parameters:								*/
/*	vol		: Pointer identifying drive			*/
/*									*/
/* Returns:								*/
/*	FLStatus	: 0 = OK, otherwise failed (invalid Flash array)*/
/*----------------------------------------------------------------------*/
FLStatus cfiscsByteIdentify(FLFlash vol)
{
  LONG inlv, mul;
  FlashPTR flashPtr = (FlashPTR) flMap(vol.socket, 0);

  for (inlv = 1; inlv <= 8; inlv++) /* let us assume that interleaving is 8 */
    tffsWriteByteFlash(flashPtr+inlv, READ_ARRAY);    /* and reset all the interleaved chips  */

  for (inlv = 1; inlv <= 8; inlv++) {
    for (mul = 1; mul <= 8; mul++) {   /* try all possibilities */
      LONG letter;

      tffsWriteByteFlash(flashPtr + 0x55 * inlv, QUERY);

      for (letter = 0; letter < ID_STR_LENGTH; letter++) {  /* look for "QRY" id string */
    CHAR idChar = '?';
	LONG offset, counter;

	switch (letter) {
	  case 0:
	    idChar = 'Q';
	    break;
	  case 1:
	    idChar = 'R';
	    break;
	  case 2:
	    idChar = 'Y';
	    break;
	}

	for (counter = 0, offset = (0x10 + letter) * inlv * mul;
	     counter < mul;
	     counter++, offset += inlv)  /*  each character should appear mul times */
	  if (tffsReadByteFlash(flashPtr+offset) != idChar)
	    break;

	if (counter < mul)  /* no match */
	  break;
      }

      tffsWriteByteFlash(flashPtr + 0x55 * inlv, READ_ARRAY);  /* reset the chip */
      if (letter >= ID_STR_LENGTH)
	goto checkInlv;
    }
  }

checkInlv:

  if (inlv > 8) 		  /* too much */
    return flUnknownMedia;

  if (inlv & (inlv - 1))
    return flUnknownMedia;	    /* not a power of 2, no way ! */

  vol.interleaving = (unsigned short)inlv;
  thisCFI->multiplier = mul;
  tffsWriteByteFlash(flashPtr + 0x55 * inlv, QUERY);
  vol.type = (FlashType) ((tffsReadByteFlash(flashPtr) << 8) |
			    tffsReadByteFlash(flashPtr + inlv * thisCFI->multiplier));
  tffsWriteByteFlash(flashPtr+inlv, READ_ARRAY);

  return flOK;

}


/*----------------------------------------------------------------------*/
/*		      c f i s c s W o r d S i z e			*/
/*									*/
/* Identify the card size for a word-mode Flash array.			*/
/* Sets the value of flash.noOfChips.					*/
/*									*/
/* Parameters:								*/
/*	vol		: Pointer identifying drive			*/
/*									*/
/* Returns:								*/
/*	FLStatus	: 0 = OK, otherwise failed (invalid Flash array)*/
/*----------------------------------------------------------------------*/
FLStatus cfiscsWordSize(FLFlash vol)
{
  CHAR queryIdStr[ID_STR_LENGTH + 1] = QUERY_ID_STR;

  FlashWPTR flashPtr = (FlashWPTR) flMap(vol.socket, 0);
  tffsWriteWordFlash(flashPtr, CLEAR_STATUS);
  tffsWriteWordFlash(flashPtr+0x55, QUERY);
  /* We leave the first chip in QUERY mode, so that we can		*/
  /* discover an address wraparound.					*/

  for (vol.noOfChips = 1;	/* Scan the chips */
       vol.noOfChips < 2000;  /* Big enough ? */
       vol.noOfChips++) {
    flashPtr = (FlashWPTR) flMap(vol.socket, vol.noOfChips * vol.chipSize);

    if ((tffsReadWordFlash(flashPtr+0x10) == (USHORT)queryIdStr[0]) &&
	(tffsReadWordFlash(flashPtr+0x11) == (USHORT)queryIdStr[1]) &&
	(tffsReadWordFlash(flashPtr+0x12) == (USHORT)queryIdStr[2]))
      break;	  /* We've wrapped around to the first chip ! */

    tffsWriteWordFlash(flashPtr+0x55, QUERY);
    if ((tffsReadWordFlash(flashPtr+0x10) != (USHORT)queryIdStr[0]) ||
	(tffsReadWordFlash(flashPtr+0x11) != (USHORT)queryIdStr[1]) ||
	(tffsReadWordFlash(flashPtr+0x12) != (USHORT)queryIdStr[2]))
      break;

    tffsWriteWordFlash(flashPtr, CLEAR_STATUS);
    tffsWriteWordFlash(flashPtr, READ_ARRAY);
  }

  flashPtr = (FlashWPTR) flMap(vol.socket, 0);
  tffsWriteWordFlash(flashPtr, READ_ARRAY);

  return flOK;
}

/*----------------------------------------------------------------------*/
/*		      g e t B y t e C F I				*/
/*									*/
/* Load important CFI data to the CFI structure in a byte-mode. 	*/
/*									*/
/* Parameters:								*/
/*	vol		: Pointer identifying drive			*/
/*									*/
/* Returns:								*/
/*	FLStatus	: 0 = OK, otherwise failed.			*/
/*----------------------------------------------------------------------*/

FLStatus getByteCFI(FLFlash vol)
{
  ULONG primaryTable, secondaryTable;
  CHAR queryIdStr[ID_STR_LENGTH + 1] = QUERY_ID_STR;
  CHAR priIdStr[ID_STR_LENGTH + 1] = PRIMARY_ID_STR;
  FlashPTR flashPtr;

  DEBUG_PRINT(("Debug: reading CFI for byte mode.\n"));

  flashPtr = (FlashPTR)flMap(vol.socket, 0);
  tffsWriteByteFlash(flashPtr + 0x55 * vol.interleaving, QUERY);

  vol.interleaving *= (unsigned short)thisCFI->multiplier; /* jump over the copies of the
						same byte */

  /* look for the query identification string "QRY" */
  if (queryIdStr[0] != tffsReadByteFlash(flashPtr + 0x10 * vol.interleaving) ||
      queryIdStr[1] != tffsReadByteFlash(flashPtr + 0x11 * vol.interleaving) ||
      queryIdStr[2] != tffsReadByteFlash(flashPtr + 0x12 * vol.interleaving)) {
    DEBUG_PRINT(("Debug: did not recognize CFI.\n"));
    return flUnknownMedia;
  }

  /* check the command set ID */
  thisCFI->commandSetId = tffsReadByteFlash(flashPtr +0x13 * vol.interleaving) |
			  ((ULONG)tffsReadByteFlash(flashPtr + 0x14 * vol.interleaving) << 8);
  if (thisCFI->commandSetId != INTEL_COMMAND_SET &&
      thisCFI->commandSetId != AMDFUJ_COMMAND_SET) {
    DEBUG_PRINT(("Debug: did not recognize command set.\n"));
    return flUnknownMedia;
  }
  /* get address for primary algorithm extended table. */
  primaryTable = tffsReadByteFlash(flashPtr + 0x15 * vol.interleaving) |
		 ((ULONG)tffsReadByteFlash(flashPtr + 0x16 * vol.interleaving) << 8);

  /* check alternate command set ID. */
  thisCFI->altCommandSetId = tffsReadByteFlash(flashPtr + 0x17 * vol.interleaving) |
			     ((ULONG)tffsReadByteFlash(flashPtr + 0x18 * vol.interleaving) << 8);
  if (thisCFI->altCommandSetId != INTEL_ALT_COMMAND_SET &&
      thisCFI->altCommandSetId != AMDFUJ_ALT_COMMAND_SET &&
      thisCFI->altCommandSetId != ALT_NOT_SUPPORTED)
    return flUnknownMedia;

  /* get address for secondary algorithm extended table. */
  secondaryTable = tffsReadByteFlash(flashPtr + 0x19 * vol.interleaving) |
		   ((ULONG)tffsReadByteFlash(flashPtr + 0x1a * vol.interleaving) << 8);

  thisCFI->vpp = tffsReadByteFlash(flashPtr + 0x1d * vol.interleaving);

  vol.chipSize = 1L << tffsReadByteFlash(flashPtr + 0x27 * vol.interleaving);

  thisCFI->maxBytesWrite = 1L << (tffsReadByteFlash(flashPtr + 0x2a * vol.interleaving) |
			   ((ULONG)tffsReadByteFlash(flashPtr + 0x2b * vol.interleaving) << 8));


  /* divide by multiplier because interleaving is multiplied by multiplier */
  vol.erasableBlockSize = (tffsReadByteFlash(flashPtr + 0x2f * vol.interleaving) |
			    ((ULONG)tffsReadByteFlash(flashPtr + 0x30 * vol.interleaving)) << 8) *
			    0x100L * vol.interleaving / thisCFI->multiplier;

  /* In this part we access the primary extended table implemented by Intel.
     If the device uses a different extended table, it should be accessed
     according to the vendor specifications. */
  if ((primaryTable) && (thisCFI->commandSetId == INTEL_COMMAND_SET)) {
    /* look for the primary table identification string "PRI" */
    if (priIdStr[0] != tffsReadByteFlash(flashPtr + primaryTable * vol.interleaving) ||
	priIdStr[1] != tffsReadByteFlash(flashPtr + (primaryTable + 1) * vol.interleaving) ||
	priIdStr[2] != tffsReadByteFlash(flashPtr + (primaryTable + 2) * vol.interleaving))
      return flUnknownMedia;

    thisCFI->optionalCommands = tffsReadByteFlash(flashPtr + (primaryTable + 5) * vol.interleaving) |
				((LONG)tffsReadByteFlash(flashPtr + (primaryTable + 6) *
						  vol.interleaving) << 8) |
				((LONG)tffsReadByteFlash(flashPtr + (primaryTable + 7) *
						  vol.interleaving) << 16) |
				((LONG)tffsReadByteFlash(flashPtr + (primaryTable + 8) *
						  vol.interleaving) << 24);

    thisCFI->afterSuspend = tffsReadByteFlash(flashPtr + (primaryTable + 9) * vol.interleaving);
  }
  else {
    thisCFI->optionalCommands = 0;
    thisCFI->afterSuspend = 0;
  }

  tffsWriteByteFlash(flashPtr, READ_ARRAY);

  vol.interleaving /= (unsigned short)thisCFI->multiplier; /* return to the real interleaving*/

  return flOK;
}

/*----------------------------------------------------------------------*/
/*		      g e t W o r d C F I				*/
/*									*/
/* Load important CFI data to the CFI structure in a word-mode. 	*/
/*									*/
/* Parameters:								*/
/*	vol		: Pointer identifying drive			*/
/*									*/
/* Returns:								*/
/*	FLStatus	: 0 = OK, otherwise failed.			*/
/*----------------------------------------------------------------------*/

FLStatus getWordCFI(FLFlash vol)
{
  ULONG primaryTable, secondaryTable;
  CHAR queryIdStr[ID_STR_LENGTH + 1] = QUERY_ID_STR;
  CHAR priIdStr[ID_STR_LENGTH + 1] = PRIMARY_ID_STR;
  FlashWPTR flashPtr;

  DEBUG_PRINT(("Debug: reading CFI for word mode.\n"));

  flashPtr = (FlashWPTR)flMap(vol.socket, 0);
  tffsWriteWordFlash(flashPtr+0x55, QUERY);

  /* look for the query identification string "QRY" */
  if (queryIdStr[0] != (CHAR)tffsReadWordFlash(flashPtr+0x10) ||
      queryIdStr[1] != (CHAR)tffsReadWordFlash(flashPtr+0x11) ||
      queryIdStr[2] != (CHAR)tffsReadWordFlash(flashPtr+0x12)) {
    DEBUG_PRINT(("Debug: did not recognize CFI.\n"));
    return flUnknownMedia;
  }

  /* check the command set ID */
  thisCFI->commandSetId = tffsReadWordFlash(flashPtr+0x13) |
			 (tffsReadWordFlash(flashPtr+0x14) << 8);
  if (thisCFI->commandSetId != INTEL_COMMAND_SET &&
      thisCFI->commandSetId != AMDFUJ_COMMAND_SET) {
    DEBUG_PRINT(("Debug: did not recognize command set.\n"));
    return flUnknownMedia;
  }

  /* get address for primary algorithm extended table. */
  primaryTable = tffsReadWordFlash(flashPtr+0x15) |
		(tffsReadWordFlash(flashPtr+0x16) << 8);

  /* check alternate command set ID. */
  thisCFI->altCommandSetId = tffsReadWordFlash(flashPtr+0x17) |
			    (tffsReadWordFlash(flashPtr+0x18) << 8);
  if (thisCFI->altCommandSetId != INTEL_ALT_COMMAND_SET &&
      thisCFI->altCommandSetId != AMDFUJ_ALT_COMMAND_SET &&
      thisCFI->altCommandSetId != ALT_NOT_SUPPORTED)
    return flUnknownMedia;

  /* get address for secondary algorithm extended table. */
  secondaryTable = tffsReadWordFlash(flashPtr+0x19) |
		  (tffsReadWordFlash(flashPtr+0x1a) << 8);

  thisCFI->vpp = tffsReadWordFlash(flashPtr+0x1d);

  vol.chipSize = 1L << tffsReadWordFlash(flashPtr+0x27);

  thisCFI->maxBytesWrite = 1L << (tffsReadWordFlash(flashPtr+0x2a) |
				 (tffsReadWordFlash(flashPtr+0x2b) << 8));

  vol.erasableBlockSize = (tffsReadWordFlash(flashPtr+0x2f) |
			  (tffsReadWordFlash(flashPtr+0x30) << 8)) * 0x100L;

  /* In this part we access the primary extended table implemented by Intel.
     If the device uses a different extended table, it should be accessed
     according to the vendor specifications. */
  if ((primaryTable) && (thisCFI->commandSetId == INTEL_COMMAND_SET)) {
    /* look for the primary table identification string "PRI" */
    if (priIdStr[0] != (CHAR)tffsReadWordFlash(flashPtr+primaryTable) ||
	priIdStr[1] != (CHAR)tffsReadWordFlash(flashPtr+primaryTable + 1) ||
	priIdStr[2] != (CHAR)tffsReadWordFlash(flashPtr+primaryTable + 2))
      return flUnknownMedia;

    thisCFI->optionalCommands = tffsReadWordFlash(flashPtr+primaryTable + 5) |
				(tffsReadWordFlash(flashPtr+primaryTable + 6) << 8) |
				((LONG)tffsReadWordFlash(flashPtr+primaryTable + 7) << 16) |
				((LONG)tffsReadWordFlash(flashPtr+primaryTable + 8) << 24);

    thisCFI->afterSuspend = tffsReadWordFlash(flashPtr+primaryTable + 9);
  }
  else {
    thisCFI->optionalCommands = 0;
    thisCFI->afterSuspend = 0;
  }

  tffsWriteWordFlash(flashPtr, READ_ARRAY);

  return flOK;
}

/*----------------------------------------------------------------------*/
/*			c f i s c s B y t e W r i t e			*/
/*									*/
/* Write a block of bytes to Flash in a byte-mode.			*/
/*									*/
/* This routine will be registered as the MTD flash.write routine	*/
/*									*/
/* Parameters:								*/
/*	vol		: Pointer identifying drive			*/
/*	address 	: Card address to write to			*/
/*	buffer		: Address of data to write			*/
/*	length		: Number of bytes to write			*/
/*	mode		: write mode (overwrite yes/no) 		*/
/*									*/
/* Returns:								*/
/*	FLStatus	: 0 on success, failed otherwise		*/
/*----------------------------------------------------------------------*/
FLStatus cfiscsByteWrite(FLFlash vol,
				CardAddress address,
				const VOID FAR1 *buffer,
				dword length,
				word mode)
{
  FLStatus status = flOK;
  FlashPTR flashPtr;
  ULONG i, from, eachWrite;
  const CHAR FAR1 *temp = (const CHAR FAR1 *)buffer;
  /* Set timeout to 5 seconds from now */
  ULONG writeTimeout = flMsecCounter + 5000;

  if (flWriteProtected(vol.socket))
    return flWriteProtect;

#ifdef SOCKET_12_VOLTS
  if (thisCFI->vpp)
    checkStatus(flNeedVpp(vol.socket));
#endif

  if (thisCFI->maxBytesWrite > 1) /* multi-byte write supported */
    eachWrite = thisCFI->maxBytesWrite * vol.interleaving;
  else
    eachWrite = vol.interleaving;

  for (from = 0; from < (ULONG) length && status == flOK; from += eachWrite) {
    LONG thisLength = length - from;
    FlashPTR currPtr;
    ULONG tailBytes, lengthByte;
    CHAR FAR1 *fromPtr;
    UCHAR byteToWrite;

    if ((ULONG)thisLength > eachWrite)
      thisLength = eachWrite;

    lengthByte = thisLength / vol.interleaving;
    tailBytes = thisLength % vol.interleaving;

    flashPtr = (FlashPTR) flMap(vol.socket, address + from);

    for (i = 0, currPtr = flashPtr;
	 i < (ULONG) vol.interleaving && i < (ULONG) thisLength;
	 i++, currPtr++) {
      do {
	tffsWriteByteFlash(currPtr, WRITE_TO_BUFFER);
      } while (!(tffsReadByteFlash(currPtr) & SR_READY) && (flMsecCounter < writeTimeout));
      if (!(tffsReadByteFlash(currPtr) & SR_READY)) {
	DEBUG_PRINT(("Debug: timeout error in CFISCS write.\n"));
	status = flWriteFault;
      }
      byteToWrite = i < tailBytes ? (UCHAR) lengthByte : (UCHAR) (lengthByte - 1);
      tffsWriteByteFlash(currPtr, byteToWrite);
    }

    for(i = 0, currPtr = flashPtr,fromPtr = (CHAR *)temp + from;
	i < (ULONG) thisLength;
	i++, flashPtr++, fromPtr++)
      tffsWriteByteFlash(currPtr, *fromPtr);


    for (i = 0, currPtr = flashPtr;
	 i < (ULONG) vol.interleaving && i < (ULONG) thisLength;
	 i++, currPtr++)
      tffsWriteByteFlash(currPtr, CONFIRM_WRITE);

    for (i = 0, currPtr = flashPtr;
	 i < (ULONG) vol.interleaving && i < (ULONG) thisLength;
	 i++, currPtr++) {
      while (!(tffsReadByteFlash(currPtr) & SR_READY) && (flMsecCounter < writeTimeout))
	;
      if (!(tffsReadByteFlash(currPtr) & SR_READY)) {
	DEBUG_PRINT(("Debug: timeout error in CFISCS write.\n"));
	status = flWriteFault;
      }
      if (tffsReadByteFlash(currPtr) & WSM_ERROR) {
	DEBUG_PRINT(("Debug: error in CFISCS write.\n"));
	status = (tffsReadByteFlash(currPtr) & SR_VPP_ERROR) ? flVppFailure : flWriteFault;
	tffsWriteByteFlash(currPtr, CLEAR_STATUS);
      }
      tffsWriteByteFlash(currPtr, READ_ARRAY);
    }
  }

#ifdef SOCKET_12_VOLTS
  if (thisCFI->vpp)
    flDontNeedVpp(vol.socket);
#endif

  flashPtr = (FlashPTR) flMap(vol.socket, address);
  /* verify the data */
  if (status == flOK) {
    for(i = 0; i < (ULONG) length - 4; i += 4) {
      if (tffsReadDwordFlash((PUCHAR)(flashPtr+i)) != *(ULONG *)(temp+i)) {
	DEBUG_PRINT(("Debug: CFISCS write failed in verification.\n"));
	status = flWriteFault;
      }
    }
    for(; i < (ULONG) length; i++) {
      if (tffsReadByteFlash(flashPtr+i) != *(UCHAR *)(temp+i)) {
	DEBUG_PRINT(("Debug: CFISCS write failed in verification.\n"));
	status = flWriteFault;
      }
    }
  }

  return status;

}

/*----------------------------------------------------------------------*/
/*			c f i s c s W o r d W r i t e			*/
/*									*/
/* Write a block of bytes to Flash in a word-mode.			*/
/*									*/
/* This routine will be registered as the MTD flash.write routine	*/
/*									*/
/* Parameters:								*/
/*	vol		: Pointer identifying drive			*/
/*	address 	: Card address to write to			*/
/*	buffer		: Address of data to write			*/
/*	length		: Number of bytes to write			*/
/*	mode		: write mode (overwrite yes/no) 		*/
/*									*/
/* Returns:								*/
/*	FLStatus	: 0 on success, failed otherwise		*/
/*----------------------------------------------------------------------*/
FLStatus cfiscsWordWrite(FLFlash vol,
				CardAddress address,
				const VOID FAR1 *buffer,
				dword length,
				word mode)
{
  FLStatus status = flOK;
  FlashPTR byteFlashPtr;
  FlashWPTR flashPtr;
  ULONG from;
	ULONG i, eachWrite;
  const CHAR FAR1 *temp = (const CHAR FAR1 *)buffer;
  /* Set timeout to 5 seconds from now */
  ULONG writeTimeout = flMsecCounter + 5000;

  if (flWriteProtected(vol.socket))
    return flWriteProtect;

  if ((length & 1) || (address & 1))	/* Only write words on word-boundary */
    return flBadParameter;

#ifdef SOCKET_12_VOLTS
  if (thisCFI->vpp)
    checkStatus(flNeedVpp(vol.socket));
#endif

  if (thisCFI->maxBytesWrite > 1) /* multi-byte write supported */
    eachWrite = thisCFI->maxBytesWrite / 2;   /* we are counting words */
  else
    eachWrite = 1;

  /* we assume that the interleaving is 1. */
  for (from = 0; (from < length / 2) && (status == flOK); from += eachWrite) {
    USHORT *fromPtr;
    ULONG thisLength = (length / 2) - from;

    if (thisLength > eachWrite)
      thisLength = eachWrite;

    flashPtr = (FlashWPTR)flMap(vol.socket, address + from * 2);

    do {
      tffsWriteWordFlash(flashPtr, WRITE_TO_BUFFER);
    } while (!(tffsReadByteFlash(flashPtr) & SR_READY) && (flMsecCounter < writeTimeout));
    if (!(tffsReadByteFlash(flashPtr) & SR_READY)) {
      DEBUG_PRINT(("Debug: timeout error in CFISCS write.\n"));
      status = flWriteFault;
    }
    tffsWriteWordFlash(flashPtr, (USHORT) (thisLength - 1));

    for(i = 0, fromPtr = (USHORT *)(temp + from * 2);
	i < thisLength;
	i++, fromPtr++)
      tffsWriteWordFlash(flashPtr + i, *fromPtr);


    tffsWriteWordFlash(flashPtr, CONFIRM_WRITE);

    while (!(tffsReadByteFlash(flashPtr) & SR_READY) && (flMsecCounter < writeTimeout))
      ;
    if (!(tffsReadByteFlash(flashPtr) & SR_READY)) {
      DEBUG_PRINT(("Debug: timeout error in CFISCS write.\n"));
      status = flWriteFault;
    }
    if (tffsReadByteFlash(flashPtr) & WSM_ERROR) {
      DEBUG_PRINT(("Debug: CFISCS write error.\n"));
      status = (tffsReadByteFlash(flashPtr) & SR_VPP_ERROR) ? flVppFailure : flWriteFault;
      tffsWriteWordFlash(flashPtr, CLEAR_STATUS);
    }
    tffsWriteWordFlash(flashPtr, READ_ARRAY);
  }

#ifdef SOCKET_12_VOLTS
  if (thisCFI->vpp)
    flDontNeedVpp(vol.socket);
#endif

  byteFlashPtr = (FlashPTR) flMap(vol.socket, address);
  /* verify the data */
  if (status == flOK) {
    for(i = 0; i < length - 4; i += 4) {
      if (tffsReadDwordFlash((PUCHAR)(byteFlashPtr+i)) != *(ULONG *)(temp+i)) {
	DEBUG_PRINT(("Debug: CFISCS write failed in verification.\n"));
	status = flWriteFault;
      }
    }
    for(; i < length; i++) {
      if (tffsReadByteFlash(byteFlashPtr+i) != *(UCHAR *)(temp+i)) {
	DEBUG_PRINT(("Debug: CFISCS write failed in verification.\n"));
	status = flWriteFault;
      }
    }
  }

  return status;
}


/************************************************************************/
/*		  Auxiliary routines for cfiscsByteErase		*/
/************************************************************************/

/*----------------------------------------------------------------------*/
/*			m a k e C o m m a n d				*/
/*									*/
/* Create a command to write to the flash. This routine is used for	*/
/* byte mode, write command to the relevant chip and 0xff to the other	*/
/* chip if interleaving is greater than 1, or write the command if	*/
/* interleaving is 1.							*/
/*									*/
/* Parameters:								*/
/*	vol		: Pointer identifying drive			*/
/*	command 	: Command to be written to the media		*/
/*	chip		: first chip (0) or second chip (1)		*/
/*									*/
/* Returns:								*/
/*	The command that should be written to the media 		*/
/*----------------------------------------------------------------------*/

USHORT makeCommand(FLFlash vol, USHORT command, LONG chip)
{
  if ((vol.interleaving == 1) || (chip == 0))
    return command | 0xff00;
  else
    return (command << 8) | 0xff;
}

/*----------------------------------------------------------------------*/
/*			g e t D a t a					*/
/*									*/
/* Read the lower byte or the upper byte from a given word.		*/
/*									*/
/* Parameters:								*/
/*	vol		: Pointer identifying drive			*/
/*	wordData	: the given word				*/
/*	chip		: if chip = 0 read lower byte			*/
/*			  if chip = 1 read upper byte			*/
/*									*/
/* Returns:								*/
/*	The byte that was read. 					*/
/*----------------------------------------------------------------------*/

UCHAR getData(FLFlash vol, USHORT wordData, LONG chip)
{
  if ((vol.interleaving == 1) || (chip == 0))
    return (UCHAR)wordData;	     /* lower byte */
  else
    return (UCHAR)(wordData >> 8);   /* upper byte */
}

/*----------------------------------------------------------------------*/
/*			c f i s c s B y t e E r a s e			*/
/*									*/
/* Erase one or more contiguous Flash erasable blocks in a byte-mode.	*/
/*									*/
/* This routine will be registered as the MTD flash.erase routine	*/
/*									*/
/* Parameters:								*/
/*	vol		   : Pointer identifying drive			*/
/*	firstErasableBlock : Number of first block to erase		*/
/*	numOfErasableBlocks: Number of blocks to erase			*/
/*									*/
/* Returns:								*/
/*	FLStatus	   : 0 on success, failed otherwise		*/
/*----------------------------------------------------------------------*/

FLStatus cfiscsByteErase(FLFlash vol,
				word firstErasableBlock,
				word numOfErasableBlocks)
{
  LONG iBlock;
  /* Set timeout to 5 seconds from now */
  ULONG writeTimeout = flMsecCounter + 5000;

  FLStatus status = flOK;	/* unless proven otherwise */

  if (flWriteProtected(vol.socket))
    return flWriteProtect;

#ifdef SOCKET_12_VOLTS
  if (thisCFI->vpp)
    checkStatus(flNeedVpp(vol.socket));
#endif

  for (iBlock = 0; iBlock < numOfErasableBlocks && status == flOK; iBlock++) {
    LONG j;
    FLBoolean finished;

    FlashWPTR flashPtr = (FlashWPTR)
			 flMap(vol.socket, (firstErasableBlock + iBlock) * vol.erasableBlockSize);

    for (j = 0; j * 2 < vol.interleaving; j++) {  /* access chips in pairs */
      LONG i;

      for (i = 0; i < (vol.interleaving == 1 ? 1 : 2); i++) { /* write to each chip seperately */
	if (thisCFI->optionalCommands & QUEUED_ERASE_SUPPORT) {
	  do {
	    tffsWriteWordFlash(flashPtr+j, makeCommand(&vol, SETUP_QUEUE_ERASE, i));
	  } while (!(getData(&vol, tffsReadWordFlash(flashPtr+j), i) & SR_READY) &&
		   (flMsecCounter < writeTimeout));
	  if (!(getData(&vol, tffsReadWordFlash(flashPtr+j), i) & SR_READY)) {
	    DEBUG_PRINT(("Debug: timeout error in CFISCS erase.\n"));
	    status = flWriteFault;
	  }
	  else
	    tffsWriteWordFlash(flashPtr+j, makeCommand(&vol, CONFIRM_ERASE, i));
	}
	else {
	  tffsWriteWordFlash(flashPtr+j, makeCommand(&vol, SETUP_BLOCK_ERASE, i));
	  tffsWriteWordFlash(flashPtr+j, makeCommand(&vol, CONFIRM_ERASE, i));
	}
      }
    }

    do {
#ifdef FL_BACKGROUND
      if (thisCFI->optionalCommands & SUSPEND_ERASE_SUPPORT) {
	while (flForeground(1) == BG_SUSPEND) { 	 /* suspend */
	  for (j = 0; j < vol.interleaving; j += 2, flashPtr++) {
	    LONG i;

	    for (i = 0; i < (vol.interleaving == 1 ? 1 : 2); i++) {
	      tffsWriteWordFlash(flashPtr+j, makeCommand(&vol, READ_STATUS, i));
	      if (!(getData(&vol, tffsReadWordFlash(flashPtr+j), i) & SR_READY)) {
		tffsWriteWordFlash(flashPtr+j, makeCommand(&vol, SUSPEND_ERASE, i));
		tffsWriteWordFlash(flashPtr+j, makeCommand(&vol, READ_STATUS, i));
		while (!(getData(&vol, tffsReadWordFlash(flashPtr+j), i) & SR_READY))
		;
	      }
	      tffsWriteWordFlash(flashPtr+j, makeCommand(&vol, READ_ARRAY, i));
	    }
	  }
	}
      }
#endif
      finished = TRUE;
      for (j = 0; j * 2 < vol.interleaving; j++) {
	LONG i;

	for (i = 0; i < (vol.interleaving == 1 ? 1 : 2); i++) {
	  tffsWriteWordFlash(flashPtr+j, makeCommand(&vol, READ_STATUS, i));
	  if (!(getData(&vol, tffsReadWordFlash(flashPtr+j), i) & SR_READY))
	    finished = FALSE;
	  else if (getData(&vol, tffsReadWordFlash(flashPtr+j), i) & SR_ERASE_SUSPEND) {
	    tffsWriteWordFlash(flashPtr+j, makeCommand(&vol, RESUME_ERASE, i));
	    finished = FALSE;
	  }
	  else {
	    if (getData(&vol, tffsReadWordFlash(flashPtr+j), i) & WSM_ERROR) {
	      DEBUG_PRINT(("Debug: CFISCS erase error.\n"));
	      status = (getData(&vol, tffsReadWordFlash(flashPtr+j), i) & SR_VPP_ERROR) ?
			flVppFailure : flWriteFault;
	      tffsWriteWordFlash(flashPtr+j, makeCommand(&vol, CLEAR_STATUS, i));
	    }
	    tffsWriteWordFlash(flashPtr+j, makeCommand(&vol, READ_ARRAY, i));
	  }
	}
      }
      flDelayMsecs(1);
    } while (!finished);
  }

#ifdef SOCKET_12_VOLTS
  if (thisCFI->vpp)
    flDontNeedVpp(vol.socket);
#endif

  return status;
}

/*----------------------------------------------------------------------*/
/*			c f i s c s W o r d E r a s e			*/
/*									*/
/* Erase one or more contiguous Flash erasable blocks in a word-mode	*/
/*									*/
/* This routine will be registered as the MTD flash.erase routine	*/
/*									*/
/* Parameters:								*/
/*	vol		   : Pointer identifying drive			*/
/*	firstErasableBlock : Number of first block to erase		*/
/*	numOfErasableBlocks: Number of blocks to erase			*/
/*									*/
/* Returns:								*/
/*	FLStatus	   : 0 on success, failed otherwise		*/
/*----------------------------------------------------------------------*/
FLStatus cfiscsWordErase(FLFlash vol,
				word firstErasableBlock,
				word numOfErasableBlocks)
{
  FLStatus status = flOK;	/* unless proven otherwise */
  LONG iBlock;
  /* Set timeout to 5 seconds from now */
  ULONG writeTimeout = flMsecCounter + 5000;

  if (flWriteProtected(vol.socket))
    return flWriteProtect;

#ifdef SOCKET_12_VOLTS
  if (thisCFI->vpp)
    checkStatus(flNeedVpp(vol.socket));
#endif

  for (iBlock = 0; iBlock < numOfErasableBlocks && status == flOK; iBlock++) {
    FLBoolean finished;

    FlashWPTR flashPtr = (FlashWPTR)
			 flMap(vol.socket,(firstErasableBlock + iBlock) * vol.erasableBlockSize);

    if (thisCFI->optionalCommands & QUEUED_ERASE_SUPPORT) {
      do {
	tffsWriteWordFlash(flashPtr, SETUP_QUEUE_ERASE);
      } while (!(tffsReadByteFlash(flashPtr) & SR_READY) && (flMsecCounter < writeTimeout));
      if (!(tffsReadByteFlash(flashPtr) & SR_READY)) {
	DEBUG_PRINT(("Debug: timeout error in CFISCS erase.\n"));
	status = flWriteFault;
      }
      else
	tffsWriteWordFlash(flashPtr, CONFIRM_ERASE);
    }
    else {
      tffsWriteWordFlash(flashPtr, SETUP_BLOCK_ERASE);
      tffsWriteWordFlash(flashPtr, CONFIRM_ERASE);
    }

    do {
#ifdef FL_BACKGROUND
      if (thisCFI->optionalCommands & SUSPEND_ERASE_SUPPORT) {
	while (flForeground(1) == BG_SUSPEND) { 	/* suspend */
	  if (!(tffsReadByteFlash(flashPtr) & SR_READY)) {
	    tffsWriteWordFlash(flashPtr, SUSPEND_ERASE);
	    tffsWriteWordFlash(flashPtr, READ_STATUS);
	    while (!(tffsReadByteFlash(flashPtr) & SR_READY))
	      ;
	  }
	  tffsWriteWordFlash(flashPtr, READ_ARRAY);
	}
      }
#endif

      finished = TRUE;

      if (!(tffsReadByteFlash(flashPtr) & SR_READY))
	finished = FALSE;
      else if (tffsReadByteFlash(flashPtr) & SR_ERASE_SUSPEND) {
	tffsWriteWordFlash(flashPtr, RESUME_ERASE);
	finished = FALSE;
      }
      else {
	if (tffsReadByteFlash(flashPtr) & WSM_ERROR) {
	  DEBUG_PRINT(("Debug: CFISCS erase error.\n"));
	  status = (tffsReadByteFlash(flashPtr) & SR_VPP_ERROR) ? flVppFailure : flWriteFault;
	  tffsWriteWordFlash(flashPtr, CLEAR_STATUS);
	}
	tffsWriteWordFlash(flashPtr, READ_ARRAY);
      }
      flDelayMsecs(1);
    } while (!finished);
  }

#ifdef SOCKET_12_VOLTS
  if (thisCFI->vpp)
    flDontNeedVpp(vol.socket);
#endif

  return status;
}


/*----------------------------------------------------------------------*/
/*			  c f i s c s M a p				*/
/*									*/
/* Map through buffer. This routine will be registered as the map	*/
/* routine for this MTD.						*/
/*									*/
/* Parameters:								*/
/*	vol	: Pointer identifying drive				*/
/*	address : Flash address to be mapped.				*/
/*	length	: number of bytes to map.				*/
/*									*/
/* Returns:								*/
/*	Pointer to the buffer data was mapped to.			*/
/*									*/
/*----------------------------------------------------------------------*/

VOID FAR0 *cfiscsMap (FLFlash vol, CardAddress address, int length)
{
  vol.socket->remapped = TRUE;
  return mapThroughBuffer(&vol,address,length);
}


/*----------------------------------------------------------------------*/
/*			  c f i s c s R e a d				*/
/*									*/
/* Read some data from the flash. This routine will be registered as	*/
/* the read routine for this MTD.					*/
/*									*/
/* Parameters:								*/
/*	vol	: Pointer identifying drive				*/
/*	address : Address to read from. 				*/
/*	buffer	: buffer to read to.					*/
/*	length	: number of bytes to read (up to sector size).		*/
/*	modes	: EDC flag etc. 					*/
/*									*/
/* Returns:								*/
/*	FLStatus	: 0 on success, otherwise failed.		*/
/*									*/
/*----------------------------------------------------------------------*/

FLStatus cfiscsRead(FLFlash vol,
			 CardAddress address,
			 VOID FAR1 *buffer,
			 dword length,
			 word modes)
{
  ULONG i;
  UCHAR * byteBuffer;
  FlashPTR byteFlashPtr;
  ULONG * doubleWordBuffer = (ULONG *)buffer;
  FlashDPTR doubleWordFlashPtr = (FlashDPTR)flMap(vol.socket, address);

  for (i = 0; i < length - 4; i += 4, doubleWordBuffer++, doubleWordFlashPtr++) {
    *doubleWordBuffer = tffsReadDwordFlash(doubleWordFlashPtr);
  }
  byteBuffer = (UCHAR *)doubleWordBuffer;
  byteFlashPtr = (FlashPTR)doubleWordFlashPtr;
  for(; i < length; i++, byteBuffer++, byteFlashPtr++) {
    *byteBuffer = tffsReadByteFlash(byteFlashPtr);
  }
  return flOK ;
}


/*----------------------------------------------------------------------*/
/*		       c f i s c s I d e n t i f y			*/
/*									*/
/* Identifies media based on SCS/CFI and registers as an MTD for	*/
/* such.								*/
/*									*/
/* This routine will be placed on the MTD list in custom.h. It must be	*/
/* an extern routine.							*/
/*									*/
/* On successful identification, the Flash structure is filled out and	*/
/* the write and erase routines registered.				*/
/*									*/
/* Parameters:								*/
/*	vol		: Pointer identifying drive			*/
/*									*/
/* Returns:								*/
/*	FLStatus	: 0 on positive identificaion, failed otherwise */
/*----------------------------------------------------------------------*/
FLStatus cfiscsIdentify(FLFlash vol)
{
  FlashWPTR flashPtr;
  CHAR queryIdStr[ID_STR_LENGTH + 1] = QUERY_ID_STR;

  DEBUG_PRINT(("Debug: entering CFISCS identification routine.\n"));

  flSetWindowBusWidth(vol.socket, 16);/* use 16-bits */
  flSetWindowSpeed(vol.socket, 150);  /* 120 nsec. */
  flSetWindowSize(vol.socket, 2);	/* 8 KBytes */

  vol.mtdVars = &mtdVars_cfiscs[flSocketNoOf(vol.socket)];

  /* try word mode first */
  flashPtr = (FlashWPTR)flMap(vol.socket, 0);
  tffsWriteWordFlash(flashPtr+0x55, QUERY);
  if ((tffsReadWordFlash(flashPtr+0x10) == (USHORT)queryIdStr[0]) &&
      (tffsReadWordFlash(flashPtr+0x11) == (USHORT)queryIdStr[1]) &&
      (tffsReadWordFlash(flashPtr+0x12) == (USHORT)queryIdStr[2])) {
    vol.type = (tffsReadWordFlash(flashPtr) << 8) |
		tffsReadWordFlash(flashPtr+1);
    vol.interleaving = 1;
    thisCFI->wordMode = TRUE;
    vol.write = cfiscsWordWrite;
    vol.erase = cfiscsWordErase;
    checkStatus(getWordCFI(&vol));
    DEBUG_PRINT(("Debug: identified 16-bit CFISCS.\n"));
  }
  else {      /* Use standard identification routine to detect byte-mode */
    checkStatus(cfiscsByteIdentify(&vol));
    thisCFI->wordMode = FALSE;
    vol.write = cfiscsByteWrite;
    vol.erase = cfiscsByteErase;
    checkStatus(getByteCFI(&vol));
    DEBUG_PRINT(("Debug: identified 8-bit CFISCS.\n"));
  }

  checkStatus(thisCFI->wordMode ? cfiscsWordSize(&vol) : cfiscsByteSize(&vol));

  vol.map = cfiscsMap;
  vol.read = cfiscsRead;

  return flOK;
}


/*----------------------------------------------------------------------*/
/*		     f l R e g i s t e r C F I S C S			*/
/*									*/
/* Registers this MTD for use						*/
/*									*/
/* Parameters:								*/
/*	None								*/
/*									*/
/* Returns:								*/
/*	FLStatus	: 0 on success, otherwise failure		*/
/*----------------------------------------------------------------------*/

FLStatus flRegisterCFISCS(VOID)
{
  if (noOfMTDs >= MTDS)
    return flTooManyComponents;

  mtdTable[noOfMTDs++] = cfiscsIdentify;

  return flOK;
}