/* spifi_rom_api.c: API for SPIFI in NXP MCUs
   copyright (c) 2010-2011 NXP Semiconductors
   written by CAM  start                                     4/16/10
                   first testing                             5/12/10
                   OK with first SST & Winbond devices       6/ 8/10
				   OK with Gigadevice, Numonyx, Atmel,
				                              some Macronyx  7/13/10
				   consensus with BK, performance optimized  8/24/10
				   fixed bug in spifi_program,
				     added SKIP_LEADING_FFs, VERIFY_ALL     12/ 1/10
				   removed SKIP_LEADING_FFs,
				     prevented page wrap in spifi_program   12/ 6/10
				   revised for V2 hardware                   1/ 7/11
				   minimal operand of init, OK with
				     SO16- and SO8N-packaged devices         1/27/11
				   global switch VERIFY_ALL replaced by
				     verify operand of program and erase,
					 works with SPIFI v2 only                3/16/11
				   fixed bug in spifi_erase re-protection    3/30/11
				   removed clock setting, I/O configuration,
				     consolidated options into 1 operand,
					 added S_DUAL, S_CALLER_ERASE options    4/ 8/11
				   verified dual and ability to re-init
				     for all devices incl. AMIC & Chingis	 4/29/11
				   added S_CALLER_PROT option to program
				     and erase, as suggested by PS           5/ 2/11
				   OK with Elite and Eon, 
				     merged obj.protMode bits into obj.opts,
				     eliminated timeout operand of init,
					 put program&erase operands in struct,
					 added ROM dispatch table	             5/17/11
				   Added cmd(), all functions <= 4 operands
				     per Reinhard Keil's advice              5/20/11
				   Added checkAd() to API                    5/24/11
				   Preserve VCR2:0 on Numonyx N25Qxx         5/31/11
				   Applied timeout in wait_busy() to avoid 
				     hangups with fast serial clock          8/30/11
				   Internal mods for use with Keil tools, 
				     no _intentional_ functional changes     9/21/11
*/

#include "lpc43xx_spifi.h"
#include "spifi_sys_config.h"
#include "lpc43xx.h"
#include "mfgers.h"
#include "error.h"

#include <stdio.h>

/* self memory function */
void *_memset(void *s, uint8_t c, uint32_t n)
{
	uint32_t w;
	uint8_t *buf = (uint8_t *)(s);

	for (w = 0; w < n; w++) {
		buf[w] = c;
	}

	return buf;
}

void *_memcpy(void *dest, const void *src, uint32_t n)
{
	uint32_t w;
	const uint8_t *src_buf = (const uint8_t *)(src);
	uint8_t *dest_buf = (uint8_t *)(dest);

	for (w = 0; w < n; w++) {
		dest_buf[w] = src_buf[w];
	}

	return dest_buf;
}

/* hardware-control routine used by spifi_rom_api.c */
 
void pullMISO(int high) {
  /* undocumented bit 7 included as 1, Aug 2 2011 */
	LPC_SCU->SFSP3_6 = (high == 0) ? 0xDB	               /* pull down */
	                               : (high == 1) ? 0xC3  /* pull up */
	                                             : 0xD3; /* neither */
}

/******************* basic functions and subroutines ******************/
unsigned read_flash(unsigned cmd) {
	unsigned ret;
	/* write the command register, thus initiating the command */
	SPIFI_COMMAND = cmd;
	switch (cmd & 0x3FFF) {
		case 0:  ret = 0; break;
		/* read 1 to 4 bytes */
		case 1:  ret = SPIFI_DATA_BYTE; break;
		case 2:  ret = SPIFI_DATA_HWORD; break;
		case 3:	 ret = SPIFI_DATA_HWORD;
                 ret|= SPIFI_DATA_BYTE<<16;
                 break;
		default: ret = SPIFI_DATA_WORD;
	}
	/* wait for completion */
	while (SPIFI_STATUS & CMD_MASK);
	return ret;
}
unsigned cmd(uc op, uc addrLen, uc intLen, unsigned short len) {
	return op<<OPCODE_SHIFT | (addrLen+1)<<FRAMEFORM_SHIFT
	     | intLen<<INTLEN_SHIFT | len;
}

unsigned all_quad(SPIFIobj *obj) {
	return obj->opts & OPT_ALL_QUAD ? 3<<PS_SHIFT : 0;
}
/* opcode-only command, or command that reads 1-4 bytes */
unsigned read04(SPIFIobj *obj, uc op, uc len) {
	return read_flash(cmd(op, 0, 0, len) | all_quad(obj));
}
/* read data from address, write optional intermediate data before calling */
unsigned readAd(SPIFIobj *obj, unsigned cmd, unsigned addr) {
	SPIFI_ADDRESS = addr;
//	if (obj->opts & OPT_4BAD && addrLen == 3) addrLen = 4;
	return read_flash(cmd | all_quad(obj));
}
/* command (other than program) with up to 4 MS-first bytes after opcode */
void send04 (SPIFIobj *obj, uc op, uc len, unsigned value) {
	SPIFI_ADDRESS = value;
	SPIFI_COMMAND = cmd(op, len, 0, 0) | all_quad(obj);
	/* wait for completion */
	while (SPIFI_STATUS & CMD_MASK);
}
/* send write-enable command */
void wren (SPIFIobj *obj) {
    /* Write Enable command */
	read04(obj, CMD_WREN, 0);
}
/* write enable then command with bytes after opcode */
void wren_send04 (SPIFIobj *obj, uc op, uc len, unsigned value) {
    wren(obj);
	send04(obj, op, len, value);
}
/* write enable then send address and 4 bytes or less data
   (initially for Numonyx Write Lock command) */
void wren_sendAd (SPIFIobj *obj, unsigned cmd, unsigned addr, unsigned value) {
    wren(obj);
	SPIFI_ADDRESS = addr;
	SPIFI_COMMAND = cmd | all_quad(obj) | DOUT;

	switch (cmd & 0x3FFF) {
	    case 3:  SPIFI_DATA_HWORD = value; value >>= 16;
		case 1:  SPIFI_DATA_BYTE  = value;
		case 0:  break;
		case 2:  SPIFI_DATA_HWORD = value; break;
		default: SPIFI_DATA_WORD  = value;
	}
	/* wait for completion */
	while (SPIFI_STATUS & CMD_MASK);
}
/* get out of memory mode */
void cancel_mem_mode(SPIFIobj *obj) {
	unsigned stat = SPIFI_STATUS;
 	if (stat & (CMD_MASK | MCMD_MASK)) {
		/* a Reset is needed to end memory mode or a command */
  		SPIFI_STATUS = RESET_MASK;
		while (SPIFI_STATUS & (CMD_MASK | MCMD_MASK | RESET_MASK));

		if (stat & MCMD_MASK
		 && (obj->mem_cmd & FRAMEFORM_MASK) >= 6<<FRAMEFORM_SHIFT) {
			/* sflash has been told that a no-opcode memory command is next:
				UNDO THAT! */
			SPIFI_IDATA_BYTE = OPCODE_FOLLOWS;
			SPIFI_COMMAND = obj->mem_cmd & 0xFFFF0000 | 1;
			SPIFI_DATA_BYTE;
			while (SPIFI_STATUS & CMD_MASK);
}	}	}

/* wait for erase, program, or write status to complete

   NOTE that you cannot successfully single-step from
   the issuing of a erase or write status command, or
   from the last data write of a program sequence, to this
   routine: you will get a timeout waiting for busy */

int wait_busy (SPIFIobj *obj, inst_type inst_type) {
	unsigned ct_busy=0;
	uc /*stat0,*/ stat1=0, busyMask = 1 << obj->busy;

	/* wait for command + any data to be completed */
	while (SPIFI_STATUS & CMD_MASK)/* ct_send++ */;
	/* wait for device to get busy */
	while (++ct_busy < LOOP_CT_WAIT_BUSY) {
		stat1 = read04(obj, CMD_RDSTAT, 1);
		if (stat1 & busyMask) break;
	}
	obj->stat.byte[0] = stat1;
	if (!(stat1 & busyMask)) {
		//SPIFI busy lasted for %d counts\n", ct_send);
		return ERR_SPIFI_TIMEOUT;
	}
	/* Timeout for busy wait depends on opcode. 
	   This should only come into play when the serial clock is 
	   too fast, so it fouls up data capture from the device */
	switch (inst_type) {
		case prog_inst:   ct_busy =     230000; break; /*   8 mS */
		case stat_inst:   ct_busy =    4300000; break; /* 150 mS */
		case block_erase: ct_busy =  172000000; break; /*   6 S */
	    case chip_erase:  ct_busy = 0xFFFFFFFF;        /* long as possible */
	}
	/* send read_status command with poll option */
	SPIFI_COMMAND = CMD_RDSTAT<<OPCODE_SHIFT | 1<<FRAMEFORM_SHIFT
				  | all_quad(obj) | POLLRS | obj->busy;
	/* wait for command to end because device busy is 0 */
	while (SPIFI_STATUS & CMD_MASK && --ct_busy);
	if (!ct_busy) {
		/* waiting for not busy failed: serial clock probably too fast */
		cancel_mem_mode(obj);     /* cancel the command */
		return ERR_SPIFI_TIMEOUT;
	}
	obj->stat.byte[0] = SPIFI_DATA_BYTE;

	/* if the device provides program-or-erase-error bit(s), check it/them */
	if (inst_type != stat_inst && obj->errCheck) {
		uc op = obj->errCheck & 0xFF;
		uc st = op == 5 ?  obj->stat.byte[0]
		                : (obj->stat.byte[1] = read04(obj, op, 1));
		if (st & obj->errCheck >> 8 & 0xFF) {
			op = obj->errCheck >> 16;
			if (op) read04(obj, op, 0); /* clear the status if reqd */
			return ERR_SPIFI_STATUS_PROBLEM;
	}	}
	return 0;
}
/* write the status register */
int write_stat (SPIFIobj *obj, uc len, unsigned short value) {
	wren_sendAd(obj, cmd(CMD_WRSTAT, 0, 0, len), 0, value);
	return obj->opts & OPT_01_NO_BUSY ? 0
									  : wait_busy(obj, stat_inst);
}
/* set memory mode, using obj->mem_cmd */
void set_mem_mode(SPIFIobj *obj) {
	unsigned mcmd = obj->mem_cmd;
	register int ctr = DECR_LOOP_CT_1_8_US;

	if (obj->opts & OPT_4BAD
	 && (mcmd & 5<<FRAMEFORM_SHIFT) == 4<<FRAMEFORM_SHIFT) {
		/* frameform 4->5, 6->7 */
		obj->mem_cmd = (mcmd |= 1<<FRAMEFORM_SHIFT);
		obj->prog_cmd |= 1<<FRAMEFORM_SHIFT;
	}
	if (obj->opts & OPT_81) {
		/* set the Numonyx-style dummy clock cycles to twice (quad) or
		   4 times (dual) the number of intermediate bytes in the
		   memory read command, and enable XIP mode by clearing 0x08 */
		unsigned leftShift = stat_DUMMIES_SHIFT + (obj->opts & OPT_DUAL ? 2 : 1);
		wren_send04(obj, CMD_WR_VOL_CONF, 1,
			        (read04(obj, CMD_RD_VOL_CONF, 1) & 7) /* new 5/31/11 */
				  | (obj->mem_cmd & INTLEN_MASK) >> (INTLEN_SHIFT - leftShift));
	}
	if (obj->opts & OPT_SEND_A3) {
		/* High performance mode command for Winbond */
		send04(obj, CMD_SET_HIPERF, 3, 0);
		/* Winbond specifies min 1.8 uS delay before next command
		   WATCH THAT OPTIMIZER DOESN'T ELIMINATE THIS DELAY ****/
		while (--ctr);
	}
	if (((mcmd & FRAMEFORM_MASK) >> FRAMEFORM_SHIFT) >= 6) {
		/* the memory command includes no-opcode mode */
		SPIFI_IDATA_BYTE = NO_OPCODE_FOLLOWS;
		/* in a moment of weakness, CM let NK talk him into a remapping
		   of the frameForm field of the Memory Command register.
		   now 0 acts like 6 should, 1 acts like 7 should,
		   so convert 6-7 to 0-1 */
		mcmd &= ~(6<<FRAMEFORM_SHIFT);
	} else
		SPIFI_IDATA_BYTE = OPCODE_FOLLOWS;

	SPIFI_MCOMMAND = mcmd;
	/* MCMD status is not set until SW or DMA accesses memory */
}
void setWPSEL (SPIFIobj * obj) {
	if (obj->opts & OPT_WPSEL) {
		/* dynamic mode set based on WPSEL bit in Security register */
		obj->stat.byte[1] = read04(obj, CMD_RD_SECURITY, 1);
		obj->opts = obj->opts & ~OPT_PROT_MASK 
		          | (obj->stat.byte[1] & 0x80 ? OPT_PROT_CMD3 : OPT_PROT_STAT);
}	}
/* post protection descriptor */
void setProtEnts(SPIFIobj *obj, const protEnt *p, unsigned protTabLen) {
	obj->protEnts = (protEnt *)p;
	obj->sectors = 0;
	obj->protBytes = 7;  /* for rounding from bits to bytes */
	while (protTabLen >= sizeof(protEnt)) {
		obj->sectors += p->rept;
		obj->protBytes += p->rept * (p->flags & RWPROT ? 2 : 1);
		p++;
		protTabLen -= sizeof(protEnt);
	}
	obj->protBytes >>= 3;
} 
/* get the # sectors and # bytes in the protection register */
unsigned short getProtBytes (SPIFIobj *obj, unsigned short *sectors) {
	if (obj->protEnts) {
		if (sectors) *sectors = obj->sectors;
		return obj->protBytes;
	}
	if (sectors) *sectors = obj->devSize >> 16;
	return obj->devSize >> 19;
}
/* read the protection register into the object */
void readProt(SPIFIobj *obj) {
	char *p = obj->prot; /* prot is an array so that's its address */
	unsigned short i = getProtBytes(obj, NULL);
	if (i) {
 		obj->opts |= OPT_PROT_READ;
		SPIFI_COMMAND = CMD_RD_PROTREG<<OPCODE_SHIFT | 1<<FRAMEFORM_SHIFT
        	          | all_quad(obj) | i;
		while (i--) *p++ = SPIFI_DATA_BYTE;
		while (p < obj->prot + sizeof(obj->prot)) *p++ = 0;
		while (SPIFI_STATUS & CMD_MASK);
}	}

/****************** routines for initial setup *******************/

/* store device size in object */
void setSize (SPIFIobj *obj, int value) {
//	SPIFI_CONTROL = SPIFI_CONTROL & ~AMSB_MASK | value;
	obj->devSize = 1<<(value+1);
	obj->memSize = MEM_AREA_SIZE < obj->devSize ? MEM_AREA_SIZE : obj->devSize;
}
/* called by manufacturer-specific routine or user
	to store options and commands */
int setDev (SPIFIobj *obj, unsigned opts, unsigned mem_cmd, unsigned prog_cmd) {
	int rc;
	/* if spifi_init was called for minimal setup, don't set multibit mode */
	if (obj->mem_cmd != BASE_READ_CMD) {
		obj->opts = opts;
		setWPSEL(obj);

		/* for devices >= 32MByte (256 Mbit), do an EN4B command
   			as other 256 Mb = 32 MB devices are released,
   			check if they have tne same EN4B command */
		if (obj->devSize > 0x1000000) {
			/* command the big memory to use 4 address bytes */
			read04(obj, CMD_4BYTE_ADDR, 0);
			obj->opts |= OPT_4BAD;
		}
		/* set the dual bit in the Control reg if indicated */
		if (opts & OPT_DUAL) SPIFI_CONTROL |= DUAL_MASK;

		if (opts & OPT_35_OR02_01) {
			/* read Winbond-style status reg 2 */
			obj->stat.byte[1] = read04(obj, CMD_RDSTAT2, 1);
			if (!(obj->stat.byte[1] & stat_QE2)) {
				/* can't write status reg 2 if SRP1 is set */
				if (obj->stat.byte[1] & stat_SRP1)
					return ERR_SPIFI_STATUS_PROBLEM;
				/* write QE=1 in status register 2 */
				rc = write_stat(obj, 2, read04(obj, CMD_RDSTAT, 1)
			    	                  | (obj->stat.byte[1] | stat_QE2) << 8);
				if (rc) return rc;
				/* check status reg 2 */
				obj->stat.byte[1] = read04(obj, CMD_RDSTAT2, 1);
				if (!(obj->stat.byte[1] & stat_QE2)) return ERR_SPIFI_STATUS_PROBLEM;
		}	}
		else if (opts & OPT_05_OR40_01) {
			/* similar to OPT_35_OR02_01 but seemingly unique to Chingis */
			obj->stat.byte[0] = read04(obj, CMD_RDSTAT, 1);
			if (!(obj->stat.byte[0] & stat_QE_chi))	{
				rc = write_stat(obj, 1, obj->stat.byte[0] | stat_QE_chi);
				if (rc) return rc;
				/* check status reg 2 */
				obj->stat.byte[0] = read04(obj, CMD_RDSTAT, 1);
				if (!(obj->stat.byte[0] & stat_QE_chi))
					return ERR_SPIFI_STATUS_PROBLEM;
		}	}
		else if (opts & OPT_3F_OR80_3E) {
			/* read Atmel-style Configuration Register */
			obj->stat.byte[1] = read04(obj, CMD_RDCONF, 1);
			if (!(obj->stat.byte[1] & stat_QE80)) {
				/* set QE=1 in config register */
				wren_send04(obj, CMD_WRCONF, 1, obj->stat.byte[1] | stat_QE80);
				/* check config register */
				obj->stat.byte[1] = read04(obj, CMD_RDCONF, 1);
				if (!(obj->stat.byte[1] & stat_QE80)) return ERR_SPIFI_STATUS_PROBLEM;
		}	}
		if (opts & OPT_SEND_38) {
			/* send SST/Eon/Winbond-style Enable Quad I/O command
			SST devices support only FF, 03, 0B, and 9F ops until 38 command */
			read04 (obj, CMD_EN_QUADIO, 0);
		}
		else if (opts & OPT_65_CLR_C0_61) {
			/* set all-quad or all-dual mode on Numonyx */
			uc mask = opts & OPT_DUAL ? stat_DIS_DUAL : stat_DIS_QUAD;
			/* read Numonyx-style Volatile Enhanced Configuration Register */
			obj->stat.byte[1] = read04(obj, CMD_RD_VOL_ENH_CONF, 1);
			if (obj->stat.byte[1] & mask) {
				/* write VECR with one disable bit 0 */
				wren_send04(obj, CMD_WR_VOL_ENH_CONF, 1,
				            (obj->stat.byte[1] | stat_DIS_DUAL | stat_DIS_QUAD)
						  & ~mask);
		}	}
		/* save all-quad bit for use in other commands */
		obj->opts = (mem_cmd & PS_MASK) == 3<<PS_SHIFT 
		          ? obj->opts |  OPT_ALL_QUAD
		          : obj->opts & ~OPT_ALL_QUAD;

		if (opts & OPT_C0) {
			/* set # of dummy clocks for Winbond reads (must follow 38) */
			uc intlen = (mem_cmd & INTLEN_MASK) >> INTLEN_SHIFT;
			send04 (obj, CMD_SET_READ_PARAMS, 1, intlen-1);
		}
		/* set the memory mode and program commands */
		obj->mem_cmd = mem_cmd;
		obj->prog_cmd = prog_cmd;
	}
	return 0;  /* success */
}
#if 0
/************** table of flash devices for Init routine **************/
typedef struct {
	uc mfg;
	int (*mfgRtn)(SPIFIobj *obj, unsigned options, unsigned mhz);
} mfgEnt;

/* const */ mfgEnt mfgTab[] = {
	{0x01, &span}, /* Spansion */
	{0x1C, &eon},  /* Eon */
	{0x1F, &atm},  /* Atmel */
	{0x20, &numo}, /* Numonyx (was ST, now Micron) */
	{0x37, &amic}, /* AMIC */
	{0x7F, &chi},  /* Chingis */
	{0x8C, &esmt}, /* Elite/ESMT */
	{0xBF, &sst},  /* SST */
	{0xC2, &mxic}, /* Macronix */
	{0xC8, &giga}, /* Gigadevice */
	{0xEF, &wbd}   /* Winbond */
};
#endif
/************** this routine initializes the hardware, ****************
                performs a Read JEDEC ID command,
				stores the result, and if it recognizes
				  the response, sets up the device and
				  returns information about it in the SPIFIobj */

int32_t spifi_init (SPIFIobj *obj, uint32_t csHigh, uint32_t options, uint32_t mhz)
{
	int i, rc;
	unsigned u;
	int (*mfgRtn)(SPIFIobj *obj, unsigned options, unsigned mhz) = NULL;

	/* for re-init, reconstruct memory command from its SPIFI register */
	u = SPIFI_MCOMMAND;
	obj->mem_cmd = !(u & 6<<FRAMEFORM_SHIFT) ? u | 6<<FRAMEFORM_SHIFT
										     : u;
    obj->opts = (u & PS_MASK) == 3<<PS_SHIFT ? OPT_ALL_QUAD	: 0;

	/* This call will not do anything if the SPIFI hasn't been used.
	   If it has been used on a flash that provides no-opcode mode, 
	     it allows re-initing by cancelling no-opcode mode. */
	cancel_mem_mode(obj);

	/* clear Numonyx all-quad/dual mode if we can see it in read */
	u = read04(obj, CMD_RD_VOL_ENH_CONF, 1);
	if (u >= 0x40 && u < 0xC0 && !(u & 0x20))
		wren_send04(obj, CMD_WR_VOL_ENH_CONF, 1, u | 0xC0);

	/* opcode FF resets SST/Neo/Winbond all-quad mode, but we do it
		for all devices 'cause it may clear some other states */
 	read04(obj, CMD_CLR_STUFF, 0);

	/* clear the object to all zeroes */
	_memset (obj, 0, sizeof(SPIFIobj));

	/* Set the control register from fields in the call.
	   The following 3 bits are arranged in the options operand
	     in the same way as in the Control register */
	u = options & S_MODE3 + S_RCVCLK + S_FULLCLK;
	if (u == S_MODE3 + S_RCVCLK + S_FULLCLK) return ERR_SPIFI_OPERAND_ERROR;
	SPIFI_CONTROL = u << MODE3_SHIFT
				  | csHigh << CSHI_SHIFT & CSHI_MASK
				  | 0xFFFF;

	/* read and store the JEDEC ID
	    NOTE: SST quads support only FF, 03, 0B, and 9F ops until 38 command */
	*(unsigned *)(&obj->mfger) = u = read04(obj, CMD_RD_JEDEC_ID, 3);

#ifdef TESTING
	/****** for testing ******/
	printf("mfg=%02X, type=%02X, id=%02X: ",
		   obj->mfger, obj->devType, obj->devID);
#endif
	/* post addresses for this device, in case the user doesn't know them */
	obj->base    = SPIFI_MEM_BASE;
	obj->regbase = SPIFI_REG_BASE;

	/* there's no device if read JEDEC ID yielded all zeroes or all ones */
	if (!u || u == 0xFFFFFF || u == 0xCCCCCC) return ERR_SPIFI_NO_DEVICE;

	/* init fields of the object to the most common values */
	obj->opts = OPT_PROT_STAT;
	obj->write_prot = 0x1C;	/* 3 prot bits in status */
	obj->set_prot   = 0x9C;	/* plus an SWRD bit */
	obj->mem_cmd  = options & S_MINIMAL ? BASE_READ_CMD : FAST_READ_CMD;
    obj->prog_cmd = BASE_PROG_CMD;
	/* obj->busy = 0     all except SST quads */

	/* the most common erase operations are op 20 = 4KB, op D8 = 64 KB */
	obj->erase_ops[0] = CMD_ERASE_4K;
	obj->erase_shifts[0] = 12;
	obj->erase_ops[2] = CMD_ERASE_64K;
	obj->erase_shifts[2] = 16;

	/* see if the manufacturer code is one we know */
	rc = ERR_SPIFI_UNKNOWN_MFG;
	switch (obj->mfger) {
		case 0x01: mfgRtn = &span; break;
 		case 0x1C: mfgRtn = &eon;  break;
// 		case 0x1F: mfgRtn = &atm;  break;
// 		case 0x20: mfgRtn = &numo; break;
// 		case 0x37: mfgRtn = &amic; break;
 		case 0x7F: mfgRtn = &chi;  break;
// 		case 0x8C: mfgRtn = &esmt; break;
// 		case 0xBF: mfgRtn = &sst;  break;
// 		case 0xC2: mfgRtn = &mxic; break;
// 		case 0xC8: mfgRtn = &giga; break;
// 		case 0xEF: mfgRtn = &wbd;
	}
	/* if so, call the init routine for this manufacturer */
	if (mfgRtn) rc = (mfgRtn)(obj, options, mhz);

	/* if rc is still SPIFI_UNKNOWN_MFG from above,
		or another nonzero status from the handler routine,
		tell our caller but set the most basic memory mode command */
	if (rc) {
		obj->mem_cmd = BASE_READ_CMD;
		/* detect 3- vs. 4-byte address in basic read command */
		for (i=0; i<2; i++) {
			pullMISO(i);
			if (readAd(obj, cmd(CMD_RD, 3, 0, 1), 0) != (i ? 0xFF : 0))
				break;
		}
		if (i==2) obj->opts |= OPT_4BAD;
		pullMISO(2); /* no pull resistor */
	} else {
		/* read in the protection if it's known to be reg protection
	   		(wait if WPSEL device) */
		if (obj->opts & OPT_PROT_REG) readProt(obj);
	}
	/* put device in memory mode */
	set_mem_mode(obj);
	return rc;
}
/********************* program and erase routines *******************/

/* set or remove protection (called by spifi_program and spifi_erase) */
int32_t setProt(SPIFIobj *obj, SPIFIopers *opers, char *change, char *saveProt) {
	uc stat16;
	unsigned short hw;
    int i, rc=0, prot = opers->protect;
	unsigned opts;

	cancel_mem_mode(obj);
	setWPSEL(obj);
	opts = obj->opts;

	if (opts & OPT_PROT_STAT) {
		/* status register protection */
		stat16 = (obj->set_prot & 0xFF00
		       || opts & OPT_35_OR02_01 + OPT_05_OR40_01) ? 1 : 0;
		obj->stat.byte[0] = read04(obj, CMD_RDSTAT, 1);
		obj->stat.byte[1] = stat16 ? read04(obj, CMD_RDSTAT2, 1)
								   : 0;
		hw = obj->stat.hw;
		if (prot == -1) {
			/* put protection back like it was at start of operation */
			if (*change) rc = write_stat(obj, 1+stat16,
			                             hw
									   & ~obj->write_prot
									   | *(unsigned short *)saveProt
									   &  obj->write_prot);
		} else if (prot) {
			/* set a specific protection at the end of an operation */
			rc = write_stat(obj, 1+stat16, hw   &~obj->set_prot
					                     | prot & obj->set_prot);
		} else {
			/* prot==0: this may be first call or ending call & leave wren */
			if (hw & obj->write_prot) {
				/* this is first call and we are going to change protection */
				*change = 1;
				*(unsigned short *)saveProt = hw;
				/* some devices include status reg bits that protect other bits,
				   so try to clear them up to 3 times */
				for (i = 0; i < 3; i++) {
					rc = write_stat(obj, 1+stat16,
					                hw & ~obj->write_prot);
					if (!rc) break;
				}
				if (!rc) {
					/**** Numonyx M25P16 needed a delay here ****/
					i = DECR_LOOP_CT_1_8_US;
					while (--i);
					obj->stat.byte[0] = read04(obj, CMD_RDSTAT, 1);
					obj->stat.byte[1] = stat16 ? read04(obj, CMD_RDSTAT2, 1)
					                           : 0;
					/* tell the caller if the write-enable didn't take */
					if (obj->stat.hw & obj->write_prot)
						rc = ERR_SPIFI_STATUS_PROBLEM;
		}	}	}
		if (rc) {
			set_mem_mode(obj);
			return rc;
	}	}
	if (opts & (OPT_PROT_REG | OPT_PROT_CMD3 | OPT_PROT_CMDE)) {

		/* register-based and command-based protection */
		protEnt *secPtr = obj->protEnts;
		unsigned short sectors, rept=0;
		uc mask=0, shift=1, allprot = prot ? 0xFF : 0;
		unsigned sectAd = 0, sectLen = 1<<16;
		unsigned endAd = (unsigned)(opers->dest + opers->length);
		char *p = obj->prot;
		uc adL = 3 + (obj->mem_cmd>>FRAMEFORM_SHIFT & 1);

		/* process protection descriptor if any */
		unsigned short protBytes = getProtBytes(obj, &sectors);
		if (!secPtr) rept = sectors;

		if (opts & OPT_PROT_REG
		 && !(opts & OPT_PROT_READ)) readProt(obj);

		if (prot == -1) {
			if (*change) {
				if (opts & OPT_PROT_REG) {
					/* restore the value of the protection register */
					_memcpy (obj->prot, saveProt, protBytes);
					sectors = 0;  /* don't bother going through the bits */
			}	} else {
				set_mem_mode(obj); /* nothing to do if "restore" and no change */
				return 0;
		}	} else if (!prot) {
			*change = 0;
			if (opts & OPT_PROT_REG)
				/* save the value of the protection register */
				_memcpy (saveProt, obj->prot, protBytes);
		}
		while (sectors--) {
			/* are we starting, or has the previous descriptor expired? */
			if (!rept) {
				/* yes, get info from new descriptor */
				rept    = secPtr->rept;
				sectAd  = secPtr->base;
				sectLen = secPtr->log2 < 0 ? 1 << -secPtr->log2
				                           : 1 <<  secPtr->log2;
				if (secPtr->flags & RWPROT) {
					allprot = prot<<6 | prot<<4 | prot<<2 | prot;
					shift = 2;
					if (mask == 0x40 || mask == 0x10 || mask == 4 || mask == 1) {
						set_mem_mode(obj);
						return ERR_SPIFI_INTERNAL_ERROR;
					}
					if (mask == 0x20 || mask == 8 || mask == 2) mask |= mask>>1;
				} else {
					allprot = prot ? 0xFF : 0;
					shift = 1;
					if (mask == 0x30 || mask == 0xC || mask == 3)
						mask &= mask<<1;
			}	}
			if (!mask) mask	= secPtr && secPtr->flags & RWPROT ? 0xC0 : 0x80;

			/* does the current sector overlap the programmed/erased area? */
			if (opers->dest < (char *)sectAd + sectLen
			 && endAd > sectAd) {
			 	/* yes */
			 	if (opts & (OPT_PROT_CMD3 | OPT_PROT_CMDE)) {
					/* command-based protection */
					if (prot == -1 && *p & mask
					 || prot > 0) {
					 	/* protect the sector */
						if (opts & OPT_PROT_CMD3)
							wren_send04(obj, CMD_PROT_SECT, adL, sectAd);
						else wren_sendAd(obj, cmd(CMD_WR_LOCK_REG, adL, 0, 1), 
										 sectAd, 1);
					} else if (!prot) {
					    /* read the current protection */
						uc protC = readAd(obj, 
										  cmd(opts & OPT_PROT_CMD3 ? CMD_RD_PROT_SECT
						                                           : CMD_RD_LOCK_REG,
						                      adL, 0, 1), 
										  sectAd);
						if (opts & OPT_PROT_CMDE
						 && (protC & 3) == 3) {
							/* write protected and locked */
							set_mem_mode(obj);
							return ERR_SPIFI_STATUS_PROBLEM;
						}
						if (protC & 1) {
							/* write-enable the sector */
							if (opts & OPT_PROT_CMD3)
								 wren_send04(obj, CMD_UNPROT_SECT, adL, sectAd);
							else wren_sendAd(obj, 
							                 cmd(CMD_WR_LOCK_REG, adL, 0, 1),
											 sectAd, 0);
							*change = 1;
							*p |= mask;
						} else *p &= ~mask;
					}
					if (rc) {
						set_mem_mode(obj);
						return rc;
					}
				} else {
			 		/* register-based: does the protection differ from desired? */
					uc new = *p & ~mask | allprot & mask;
					if (new != *p) {
						/* yes, change the protection, remember that we did */
						*p = new;
						*change = 1;
			}	}	}
			/* advance mask (also p if byte boundary) */
			if (!(mask >>= shift)) p++;
			/* change address to next sector,
			   decrement descriptor repeat count,
			   advance descriptor pointer if repeat is expired */
			sectAd += secPtr && secPtr->log2 < 0 ? -sectLen : sectLen;
			if (!--rept && secPtr) secPtr++;
		}
		/* are we changing or restoring register-based protection? */
		if (*change && opts & OPT_PROT_REG) {
			/* yes, write it back to the device */
			wren(obj);
			p = obj->prot;
			SPIFI_COMMAND = CMD_WR_PROTREG<<OPCODE_SHIFT | 1<<FRAMEFORM_SHIFT
		    	          | all_quad(obj) | DOUT | protBytes;
			while (protBytes--) SPIFI_DATA_BYTE = *p++;
			while (SPIFI_STATUS & CMD_MASK);
	}	}
	set_mem_mode(obj);
	return rc;
}

/* called by program and erase to check their destination address operand */
int32_t checkAd (SPIFIobj *obj, SPIFIopers *opers) {
	unsigned addr = (unsigned)opers->dest;
	/* if the device isn't known, can't do program or erase */
	if (!obj->devSize) return ERR_SPIFI_UNKNOWN_ID;

	/* address in SPIFI memory area is OK */
	if (addr                 >= obj->base
	 && addr + opers->length <= obj->base + obj->memSize)
		addr -= obj->base;

	/* address within device is OK */
	if (addr                 <  obj->devSize
	 && addr + opers->length <= obj->devSize
	 && (!(opers->options & (S_VERIFY_PROG | S_VERIFY_ERASE))
	 	 /* if verify, entire transfer must be within the SPIFI memory area */
	  || addr + opers->length <= obj->memSize)) {
		opers->dest = (char *)addr; 
		return 0;
	}
	return ERR_SPIFI_OPERAND_ERROR;
}
/* send an erase command -- used by spifi_program, spifi_erase */
int send_erase_cmd (SPIFIobj *obj, unsigned char op, unsigned addr) {
	cancel_mem_mode(obj);
	wren(obj);
	SPIFI_ADDRESS = addr;
	SPIFI_COMMAND = op<<OPCODE_SHIFT | obj->prog_cmd & FRAMEFORM_MASK
				  | all_quad(obj);
	/* wait for erase to complete */
	return wait_busy(obj, op==CMD_ERASE_CHIP || op==CMD_ERASE_CHIP2 ? chip_erase
											                        : block_erase);
}
/* send a program command -- used by spifi_program >1 place */
void send_prog_cmd (SPIFIobj *obj, unsigned addr, unsigned length) {
	wren(obj);
	SPIFI_ADDRESS = addr;
	SPIFI_COMMAND = obj->prog_cmd | length;
	/* return to caller, which will provide the write data */
}
/* prog_block programs (length) bytes at dest from source */
int prog_block (SPIFIobj *obj, char *source, SPIFIopers *opers,	
                unsigned *left_in_page) {
	int rc;
	unsigned in_page = *left_in_page, save_in_page, length = opers->length;

	while (length) {
		while (length && !in_page) {
            /* don't let programming wrap around a page boundary */
			in_page = PROG_SIZE - ((unsigned)opers->dest & (PROG_SIZE-1));
			if (opers->options
			 && length < in_page) in_page = length;

			/* Scan off leading all-ones bytes.
			   This can eliminate unnecessary page programming,
			   and also eliminates a problem in which some intelligent
			   devices don't go busy if asked to program all ones.
			   Advance over all-ones bytes to word alignment */
			save_in_page = in_page;
			while ((unsigned)source & sizeof(unsigned)-1
			    && *source == 0xFF && length && in_page) {
				source++;
				length--;
				in_page--;
			}
			if (!((unsigned)source & sizeof(unsigned)-1)) {
				/* Advance over all-ones words */
				while (*(unsigned *)source == (unsigned)-1
					&& length  >= sizeof(unsigned)
				    && in_page >= sizeof(unsigned)) {
					source  += sizeof(unsigned);
					length  -= sizeof(unsigned);
					in_page -= sizeof(unsigned);
			}	}
            /* advance over all-ones bytes */
            while (length
                && in_page
                && in_page < sizeof(unsigned)
                && *source == 0xFF) {
				source++;
				length--;
				in_page--;
			}
			/* advance dest address over leading all-one bytes */
		    opers->dest += save_in_page - in_page;

			/* unless we scanned to the end of data to program, start programming */
			if (length && in_page) {
				/* send program command to device */
				//printf ("just before prog, dest=%X, in_page=%X, stat=%X\n",
				//        *dest, in_page, SPIFI_STATUS);
				// while (SPIFI_STATUS & (CMD_MASK | MCMD_MASK | RESET_MASK));
				send_prog_cmd (obj, (unsigned)opers->dest, in_page);
				opers->dest += in_page;
		}	}
		if (length && in_page) {
			/* program to word boundary	if necessary */
			while ((unsigned)source & sizeof(unsigned)-1
		    	&& length && in_page) {
				SPIFI_DATA_BYTE = *source++;
				length--;
				in_page--;
			}
			/* program words */
			while (length  >= sizeof(unsigned)
		    	&& in_page >= sizeof(unsigned)) {
				SPIFI_DATA_WORD = *(unsigned *)source;
				source  += sizeof(unsigned);
				length  -= sizeof(unsigned);
				in_page -= sizeof(unsigned);
			}
			/* program trailing bytes */
			while (length && in_page) {
				SPIFI_DATA_BYTE = *source++;
				length--;
				in_page--;
			}
			if (!in_page) {
				/* last byte of page sent, wait for programming to complete */
				rc = wait_busy(obj, prog_inst);
				if (rc) {
#ifdef TESTING
					printf ("error %X, flash status = %02X %02X\n", rc, 
							read04(obj, CMD_RDSTAT, 1), read04(obj, CMD_RDSTAT2, 1));
#endif
					return rc;
	}	}	}	}
	*left_in_page = in_page; /* update the caller's variable */
	return 0;
}
/* routine to verify erase */
unsigned ck_erase (SPIFIobj *obj, unsigned *addr, unsigned length) {
	addr = (unsigned *)(obj->base + (unsigned)addr);
	while (length) {
		if (*addr != 0xFFFFFFFF) return (unsigned)addr;
		addr++;
		length -= sizeof(unsigned);
	}
	return 0;
}
/* setup ck_prog and check_block */
int ck_setup (SPIFIobj *obj, char *source, char **dest) {
	/* make the caller's address (back?) into an address in the SPIFI memory area */
	*dest += obj->base;
	/* return nonzero if words can be read and compared */
	return !(((unsigned)source ^ (unsigned)*dest) & sizeof(unsigned)-1);
}
#define udest ((unsigned)dest)
/* routine to verify programming */
unsigned ck_prog (SPIFIobj *obj, char *source, char *dest, unsigned length) {
	int sync = ck_setup(obj, source, &dest);
	/* unsigned destW; */

	while (length) {
		if (sync) {
			/* advance to word boundary	if necessary */
			while ((unsigned)source & sizeof(unsigned)-1
			    && length) {
				/* check a byte */
				if (*source++ != *dest)
					return udest;
				dest++;
				length--;
			}
			/* check words */
			while (length >= sizeof(unsigned)) {
				if (*(unsigned *)source != (/* destW = */*(unsigned *)dest)) {
#if 0
					printf ("ck_prog failure at %X, data %X, cmd=%X, stat=%X\n",
					        dest, destW, SPIFI_COMMAND, SPIFI_STATUS);
#endif
					return udest;
				}
				source += sizeof(unsigned);
				dest   += sizeof(unsigned);
				length -= sizeof(unsigned);
		}	}
		/* trailing bytes, or whole block if not sync */
		while (length) {
			/* check a byte */
			if (*source++ != *dest)
				return udest;
			dest++;
			length--;
	}	}
	return 0;
}
/* check_block tests if erasure or programming needs to be done */
int check_block (SPIFIobj *obj, char *source, SPIFIopers *opers, 
                 unsigned check_program) {
	char *dest = opers->dest;
	unsigned length = opers->length, check_erase = ~check_program;
	int sync = ck_setup(obj, source, &dest);
    /* unsigned sw, dw;	*/

	while (length) {
		if (sync) {
			/* advance to word boundary	if necessary */
			while ((unsigned)source & sizeof(unsigned)-1
			    && length) {
				/* check a byte */
				if (((uc)check_program^*source++) & ((uc)check_erase^*dest++))
					return 1;
				length--;
			}
			/* check words */
			while (length >= sizeof(unsigned)) {
				if ((check_program^(/*sw = */*(unsigned *)source))
				  & (check_erase  ^(/*dw = */*(unsigned *)dest))) {
#if 0
				    if (check_erase
					 && udest >= obj->base + 0x1000   // for 64K sectors
					 && udest <  obj->base + 0xF000) {
						printf ("unexpected erase needed @%X: source %X, dest %X\n",
						        dest, sw, dw);
					}
#endif
				    return 1;
				}
				source += sizeof(unsigned);
				dest   += sizeof(unsigned);
				length -= sizeof(unsigned);
		}	}
		/* trailing bytes, or whole block if not sync */
		while (length) {
			/* check a byte */
			if (((uc)check_program^*source++) & ((uc)check_erase^*dest++))
				return 1;
			length--;
	}	}
	return 0;
}
/************** this routine programs a block of SPIFI flash **************/
#define uldest (unsigned)lopers.dest

int32_t spifi_program (SPIFIobj *obj, char *source, SPIFIopers *opers) {

#if 0
	SPIFIopers lopers = *opers;
	char *prog_start, erase, *svD;
	char change=0, saveProt[LONGEST_PROT];
	unsigned length = opers->length, scan_l, erase_start, erase_end, 
			 save_at_start, save_at_end, prog_end, in_page;

	/* check destination address operand,
	   if SPIFI-memory-area address convert it to zero-based */
	int rc = checkAd(obj, &lopers);
#else
	SPIFIopers lopers;
	char *prog_start, erase, *svD;
	char change=0, saveProt[LONGEST_PROT];
	unsigned length = opers->length, scan_l, erase_start, erase_end, 
			 save_at_start, save_at_end, prog_end, in_page;
	int rc;

	_memcpy(&lopers, opers, sizeof(lopers));

	/* check destination address operand,
	   if SPIFI-memory-area address convert it to zero-based */
	rc = checkAd(obj, &lopers);
#endif
	if (rc) return rc;
	
	svD = lopers.dest;
	prog_end = uldest + length;

	/* if desired, enable writing dest thru dest+length */
	lopers.protect = 0;
	if (!(opers->options & S_CALLER_PROT))
		rc = setProt(obj, &lopers, &change, saveProt);
		  /* array name = address of array--^ */
	if (rc) return rc;
	
	/* this loop is executed for each of the smallest erasable sectors */
	while (length) {
	    /* compute boundaries of the smallest available erasable sector */
		erase_end = 1 << obj->erase_shifts[0];
		erase_start = uldest & ~(erase_end-1);
		erase_end += erase_start;
		scan_l = erase_end - uldest;
		if (length < scan_l) scan_l = length;

		lopers.length = scan_l;
		erase = opers->options & S_FORCE_ERASE
				/* check if erase is needed */
		     || check_block (obj, source, &lopers, 0);

		prog_start = lopers.dest;
		if (erase) {
			if (opers->options & S_ERASE_NOT_REQD) return ERR_SPIFI_ERASE_NEEDED;
			/* erase is required for the current erasable sector */
			if (opers->scratch) {
				save_at_start = uldest - erase_start;
				save_at_end = prog_end < erase_end ? erase_end - prog_end : 0;
				if (save_at_start) {
					/* save data in the sector preceding the programmed area */
					_memcpy (opers->scratch, (char *)erase_start, save_at_start);
					lopers.dest = (char *)erase_start;
				}
				if (save_at_end)
					/* save data in the sector following the programmed area */
					_memcpy (opers->scratch + save_at_start, 
							(void *)prog_end, save_at_end);
			} else {
				save_at_start = save_at_end = 0;
			}
			/* cancel memory mode, then erase the block of flash */
#if 0
	  		printf ("erase being done at dest=%X\n", dest);
#endif
			rc = send_erase_cmd (obj, obj->erase_ops[0], erase_start);
			if (rc) {
				set_mem_mode(obj);
				return rc;
			}
			if (opers->options & S_VERIFY_ERASE) {
				/* verify that the erase made all ones */
				set_mem_mode(obj);
				rc = ck_erase(obj, (unsigned *)erase_start,
							  1 << obj->erase_shifts[0]);
				if (rc) return rc;
				cancel_mem_mode(obj);
			}
			/* program the block that we have erased */
			in_page = 0;
			/* restore the saved data before the programmed data (if any) */
			lopers.length = save_at_start;
			lopers.options = 0;  /* not final */
			rc = prog_block(obj, opers->scratch, &lopers, &in_page);
			/* program the requested data */
			lopers.length = scan_l;
			lopers.options = !save_at_end;
			if (!rc) rc = prog_block(obj, source, &lopers, &in_page);
			/* restore the saved data after the programmed data (if any) */
			lopers.length = save_at_end;
			lopers.options = 1;
			if (!rc) rc = prog_block(obj, opers->scratch + save_at_start, &lopers, 
			                         &in_page);
			if (!rc && in_page) rc = ERR_SPIFI_INTERNAL_ERROR;

			/* put device back in memory mode */
			set_mem_mode(obj);
			if (!rc && opers->options & S_VERIFY_PROG) {
				/* verify the requested data */
				rc = ck_prog(obj, source, prog_start, scan_l);
			}
			/* quit if error in programming	*/
			if (rc) return rc;
			/* update source address and length for this sector */
			source += scan_l;
			length -= scan_l;
			/* dest was advanced by prog_block in the calls above */

		} else /* this sector doesn't need erasing
		          the following while block is executed for each page */

		while (length && uldest < erase_end) {
			scan_l = (uldest | PROG_SIZE-1) - uldest + 1;
			if (length < scan_l) scan_l = length;
			lopers.length = scan_l;
			if (check_block(obj, source, &lopers, (unsigned)-1)) {
				/* program the block of flash */
				cancel_mem_mode(obj);
				in_page = 0;
				/* We only come here if the initial check_block says that no
				   existing zeroes are being change to ones.  So prog_block's
				   skipping leading all-ones bytes is OK when called from here */
				lopers.options = 1;		/* "final" */
				rc = prog_block(obj, source, &lopers, &in_page);
				/* put SPIFI back in memory mode */
				set_mem_mode(obj);

				if (!rc && opers->options & S_VERIFY_PROG) {
					/* check programming */
					rc = ck_prog(obj, source, prog_start, scan_l);
				}
				if (rc) return rc;
				/* dest was advanced by prog_block in prog_block above */
			} else lopers.dest += scan_l;  /* page doesn't need to be programmed */
			source += scan_l;
			length -= scan_l;
	}	}
	/* if desired, set protection on dest thru dest+length */
	if (opers->protect) {
		lopers.protect = opers->protect;
		lopers.dest = svD;
		lopers.length = opers->length;
		rc = setProt(obj, &lopers, &change, saveProt);
	}
	return rc;
}

/************ this routine erases a block of SPIFI flash *************
   this is not necessary because the program routine handles erasing,
   but this may be faster because it can use larger erase commands */
int32_t spifi_erase (SPIFIobj *obj, SPIFIopers *opers) {
#if 0
	SPIFIopers lopers = *opers;
	char change=0, saveProt[LONGEST_PROT], *svD;
	unsigned length = opers->length, block_size, erase_start, erase_end, 
			 save_at_start, save_at_end;
	/* size of the smallest erasable sector */
    unsigned last_size = 1 << obj->erase_shifts[0];
	unsigned call_end, in_page=0;


	/* check address operand, if SPIFI-memory-area address make it zero-based */
	int i, rc = checkAd(obj, &lopers);
#else
	SPIFIopers lopers;
	char change=0, saveProt[LONGEST_PROT], *svD;
	unsigned length = opers->length, block_size, erase_start, erase_end, 
			 save_at_start, save_at_end;
	/* size of the smallest erasable sector */
    unsigned last_size = 1 << obj->erase_shifts[0];
	unsigned call_end, in_page=0;
	int i, rc;

	_memcpy(&lopers, opers, sizeof(lopers));

	/* check address operand, if SPIFI-memory-area address make it zero-based */
	rc = checkAd(obj, &lopers);
#endif
	if (rc) return rc;

	/* if desired, remove protection on the specified sector(s) */
	lopers.protect = 0;
	if (!(opers->options & S_CALLER_PROT)) 
		rc = setProt(obj, &lopers, &change, saveProt);
	       /* array name = address of array-^ */
	if (rc) return rc;
	call_end = uldest + length;
	svD = lopers.dest;

	if (!uldest
	 && length >= obj->devSize
	 && !(obj->opts & OPT_NO_DEV_ERASE)) {
	 	/* full chip erase */
		cancel_mem_mode(obj);
		wren_send04 (obj, CMD_ERASE_CHIP, 0, 0);
		rc = wait_busy(obj, chip_erase);
		set_mem_mode(obj);

	} else while (length) {
		for (i = sizeof(obj->erase_shifts)-1; i >= 0; i--) {
			if (obj->erase_shifts[i]) {
				/* see if this is the minimum erase for the current start ad */
				block_size = 1 << obj->erase_shifts[i];
				erase_start = uldest & ~(block_size - 1);
				save_at_start = uldest - erase_start;
				erase_end = erase_start + block_size;
				save_at_end = call_end < erase_end ? erase_end - call_end
												   : 0;
				/* if i is 0, the following 'if' will fall through */
				if (save_at_start >= last_size
				 || save_at_end   >= last_size) continue;

				/* we should erase using the current size
				   first, if scratch is provided, save any bytes that
				   we want to preserve */
				if (opers->scratch) {
					if (save_at_start) _memcpy (opers->scratch, (char *)erase_start,
											   save_at_start);
					if (save_at_end) _memcpy (opers->scratch + save_at_start,
											 (char *)call_end, save_at_end);
				}
				/* do the erase command */
				rc = send_erase_cmd (obj, obj->erase_ops[i], erase_start);
 				set_mem_mode(obj);
				if (rc) return rc;

				/* check all ones if the caller wants */
				if (opers->options & S_VERIFY_ERASE
				 && (rc = ck_erase(obj, (unsigned *)erase_start,
				     1<<obj->erase_shifts[i])) != 0)
					return rc;

				/* reprogram any saved bytes */
				if (opers->scratch) {
					lopers.options = 1;  /* final */
					if (save_at_start) {
						cancel_mem_mode(obj);
						lopers.dest = (char *)erase_start;
						lopers.length = save_at_start;
						rc = prog_block(obj, opers->scratch, &lopers, &in_page);
						set_mem_mode(obj);
						if (!rc && in_page) rc = ERR_SPIFI_INTERNAL_ERROR;
						else if (opers->options & S_VERIFY_PROG)
							rc = ck_prog(obj, opers->scratch, 
										 (char *)erase_start,
										 save_at_start);
						if (rc) return rc;
					}
					if (save_at_end) {
						cancel_mem_mode(obj);
						lopers.dest = (char *)call_end;
						lopers.length = save_at_end;
						rc = prog_block(obj, opers->scratch + save_at_start, 
										&lopers, &in_page);
						set_mem_mode(obj);
						if (!rc) {
							if (in_page) rc = ERR_SPIFI_INTERNAL_ERROR;
							else if (!opers->options & S_VERIFY_PROG)
								rc = ck_prog(obj, opers->scratch + save_at_start,
											 (char *)call_end, 
											 save_at_end);
						}
						if (rc) return rc;
				}	}
				/* update the dest and length values */
				lopers.dest = (char *)erase_end;
				if (length <= block_size) length = 0;
				else length -= block_size;
				/* one erase per block: escape from for loop */
				break;
	}	}	}
	if (!rc && opers->protect) {
		lopers.dest = svD;
		lopers.length = opers->length;
		lopers.protect = opers->protect;
		rc = setProt(obj, &lopers, &change, saveProt);
	}
	return rc;
}

void spifi_reset(SPIFIobj *obj)
{
	unsigned u;

	/* clear Numonyx all-quad/dual mode if we can see it in read */
	u = read04(obj, CMD_RD_VOL_ENH_CONF, 1);
	if (u >= 0x40 && u < 0xC0 && !(u & 0x20)) {
		wren_send04(obj, CMD_WR_VOL_ENH_CONF, 1, u | 0xC0);
	}

	/* opcode FF resets SST/Neo/Winbond all-quad mode, but we do it
	   for all devices 'cause it may clear some other states */
	read04(obj, CMD_CLR_STUFF, 0);
}

#ifndef OMIT_ROM_TABLE
/* ROM dispatch table */
/*
const SPIFI_RTNS spifi_table = {
	&spifi_init,
	&spifi_program,
	&spifi_erase,
	&cancel_mem_mode,
	&set_mem_mode, 
	&checkAd,
	&setProt,
	&check_block, 
	&send_erase_cmd,
	&ck_erase,
	&prog_block,
	&ck_prog, 
	&setSize,
	&setDev,
	&cmd,
	&readAd,
	&send04,
	&wren_sendAd,
	&write_stat,
	&wait_busy,
};
*/
#endif
