/*
 *   i28f160.c
 *
 *   This file is part of Emu48
 *
 *   Copyright (C) 2000 Christoph Gießelink
 *
 */
#include "pch.h"
#include "Emu48.h"
#include "i28f160.h"

#define ARRAYSIZEOF(a)	(sizeof(a) / sizeof(a[0]))

// Flash Command Set
#define READ_ARRAY			0xFF
#define READ_ID_CODES		0x90
#define READ_QUERY			0x98
#define READ_STATUS_REG		0x70
#define CLEAR_STATUS_REG	0x50
#define WRITE_BUFFER		0xE8
#define WORD_BYTE_PROG1		0x40
#define WORD_BYTE_PROG2		0x10
#define BLOCK_ERASE			0x20
#define BLOCK_ERASE_SUSPEND	0xB0
#define BLOCK_ERASE_RESUME	0xD0
#define STS_CONFIG			0xB8
#define SET_CLR_BLOCK_LOCK	0x60
#define FULL_CHIP_ERASE		0x30

#define CONFIRM				0xD0

// Status Register Definition
#define WSMS				0x80			// WRITE STATE MACHINE STATUS
#define ESS					0x40			// ERASE SUSPEND STATUS
#define ECLBS				0x20			// ERASE AND CLEAR LOCK-BIT STATUS
#define BWSLBS				0x10			// PROGRAM AND SET LOCK-BIT STATUS
#define VPPS				0x08			// Vpp STATUS
#define BWSS				0x04			// PROGRAM SUSPEND STATUS
#define DPS					0x02			// DEVICE PROTECT STATUS

// Extended Status Register Definition
#define WBS					0x80			// WRITE BUFFER STATUS

// write state defines
#define WRS_DATA			0				// idle state
#define WRS_WR_BUFFER_N		1				// write buffer no. of data
#define WRS_WR_BUFFER_D		2				// write buffer data
#define WRS_WR_BUFFER_C		3				// write buffer confirm
#define WRS_WR_BYTE			4				// write byte/word
#define WRS_BLOCK_ERASE		5				// block erase
#define WRS_CHIP_ERASE		6				// full chip erase
#define WRS_STS_PIN_CONFIG	7				// STS pin Configuration
#define WRS_LOCK_BITS		8				// Set/Clear Block Lock-Bits

// read state defines
#define RDS_DATA			0				// data read
#define RDS_ID				1				// read identifier codes
#define RDS_QUERY			2				// read query
#define RDS_SR				3				// read status register
#define RDS_XSR				4				// read extended status register

// global data
WSMSET WSMset;
BOOL   bWP = FALSE;							// WP# = low, locked blocks cannot be erased

// function prototypes
// write function WSM state
static VOID WrStateIdle(BYTE a, DWORD d);
static VOID WrStateE8(DWORD d);
static VOID WrStateE8N(BYTE a, DWORD d);
static VOID WrStateE8D(BYTE a, DWORD d);
static VOID WrStateE8C(BYTE a, DWORD d);
static VOID WrState40(DWORD d);
static VOID WrState40D(BYTE a, DWORD d);
static VOID WrState20(DWORD d);
static VOID WrState20C(BYTE a, DWORD d);
static VOID WrState30(DWORD d);
static VOID WrState30C(BYTE a, DWORD d);
static VOID WrStateB8(DWORD d);
static VOID WrStateB8D(BYTE a, DWORD d);
static VOID WrState60(DWORD d);
static VOID WrState60D(BYTE a, DWORD d);

static VOID (*CONST fnWrState[])(BYTE a, DWORD d) =
{
	WrStateIdle, 
	WrStateE8N, WrStateE8D, WrStateE8C,
	WrState40D,
	WrState20C,
	WrState30C,
	WrStateB8D,
	WrState60D
};

// read function WSM state
static BYTE RdStateData(DWORD d);
static BYTE RdStateId(DWORD d);
static BYTE RdStateQuery(DWORD d);
static BYTE RdStateSR(DWORD d);
static BYTE RdStateXSR(DWORD d);

static BYTE (*CONST fnRdState[])(DWORD d) =
{
	RdStateData, RdStateId, RdStateQuery, RdStateSR, RdStateXSR
};


// read query table
// device address A16-A1, A0 unused
static CONST BYTE byQueryTab[] =
{
	// access with "Read Identifier Codes" command
	// Identifier codes
	0xB0,	// 00, Manufacturer Code
	0xD0,	// 01, Device Code (16 Mbit)
	0x00,	// 02, Block Lock Configuration
	0x02,	// 03, ??

	0x00,	// 04, Reserved for vendor-specific information
	0x00,	// 05,					"
	0x00,	// 06,					"
	0x00,	// 07,					"
	0x00,	// 08,					"
	0x00,	// 09,					"
	0x00,	// 0A,					"
	0x00,	// 0B,					"
	0x00,	// 0C,					"
	0x00,	// 0D,					"
	0x00,	// 0E,					"
	0x00,	// 0F,					"

	// access with "Read Query" command
	// CFI query identification string
	0x51,	// 10, Query-Unique ASCII string "Q"
	0x52,	// 11, Query-Unique ASCII string "R"
	0x59,	// 12, Query-Unique ASCII string "Y"
	0x01,	// 13, Primary Vendor Command Set and Control Interface ID CODE
	0x00,	// 14,					"
	0x31,	// 15, Address for Primary Algorithm Extended Query Table
	0x00,	// 16,					"
	0x00,	// 17, Alternate Vendor Command Set and Control Interface ID Code
	0x00,	// 18,					"
	0x00,	// 19, Address for Secondary Algorithm Extended Query Table
	0x00,	// 1A,					"

	// System interface information
	0x30,	// 1B, Vcc Logic Supply Minimum Program/Erase Voltage (0x27 intel doc, 0x30 real chip)
	0x55,	// 1C, Vcc Logic Supply Maximum Program/Erase Voltage
	0x30,	// 1D, Vpp [Programming] Supply Minimum Program/Erase Voltage  (0x27 intel doc, 0x30 real chip)
	0x55,	// 1E, Vpp [Programming] Supply Maximum Program/Erase Voltage
	0x03,	// 1F, Typical Time-Out per Single Byte/Word Program
	0x06,	// 20, Typical Time-Out for Max. Buffer Write
	0x0A,	// 21, Typical Time-Out per Individual Block Erase
	0x0F,	// 22, Typical Time-Out for Full Chip Erase
	0x04,	// 23, Maximum Time-Out for Byte/Word Program
	0x04,	// 24, Maximum Time-Out for Buffer Write
	0x04,	// 25, Maximum Time-Out per Individual Block Erase
	0x04,	// 26, Maximum Time-Out for Full Chip Erase
	0x15,	// 27, Device Size
	0x02,	// 28, Flash Device Interface Description
	0x00,	// 29,					"
	0x05,	// 2A, Maximum Number of Bytes in Write Buffer
	0x00,	// 2B,					"
	0x01,	// 2C, Number of Erase Block Regions within Device
	0x1F,	// 2D, Erase Block Region Information
	0x00,	// 2E,					"
	0x00,	// 2F,					"
	0x01,	// 30,					"

	// Intel-specific extended query table
	0x50,	// 31, Primary Extended Query Table, Unique ASCII string "P"
	0x52,	// 32, Primary Extended Query Table, Unique ASCII string "R"
	0x49,	// 33, Primary Extended Query Table, Unique ASCII string "I"
	0x31,	// 34, Major Version Number, ASCII
	0x30,	// 35, Minor Version Number, ASCII
	0x0F,	// 36, Optional Feature & Command Support
	0x00,	// 37,					"
	0x00,	// 38,					"
	0x00,	// 39,					"
	0x01,	// 3A, Supported Functions after Suspend
	0x03,	// 3B, Block Status Register Mask
	0x00,	// 3C,					"
	0x50,	// 3D, Vcc Logic Supply Optimum Program/Erase voltage
	0x50,	// 3E, Vpp [Programming] Supply Optimum Program/Erase voltage
	0x00	// 3F, ??
};


//
// ROM buffer access functions
//

static __inline void WrDirtyPage(DWORD d)
{
	if (pbyRomDirtyPage)					// using dirty ROM page table
	{
		DWORD dwPage = d / ROMPAGESIZE;		// this is the page

		_ASSERT(dwPage < dwRomDirtyPageSize);
		pbyRomDirtyPage[dwPage] = TRUE;		// page is dirty
	}
	return;
}

static __inline void EraseBlock(DWORD d,DWORD dwNibSize)
{
	LPBYTE pbyAddr = pbyRom + d;

	while (dwNibSize--)
	{
		WrDirtyPage(d++);					// make page dirty
		*pbyAddr++ = 0x0F;					// clear address
	}
	return;
}

static __inline void WriteByte(DWORD d,BYTE byData)
{
	WrDirtyPage(d);							// make page dirty

	_ASSERT(d+1 < dwRomSize);				// address valid?
	*(pbyRom+d)   &= (byData & 0x0F);		// write LSB
	*(pbyRom+d+1) &= (byData >> 4);			// write MSB
	return;
}

static __inline BYTE ReadByte(DWORD d)
{
	_ASSERT(d+1 < dwRomSize);				// address valid?
	return *(pbyRom+d)|(*(pbyRom+d+1)<<4);	// get byte
}


//
// write state functions
//

static VOID WrStateIdle(BYTE a, DWORD d)
{
	WSMset.bRomArray = FALSE;				// register access

	switch(a)
	{
	case READ_ARRAY: // read array mode, normal operation
		WSMset.bRomArray = TRUE;			// data array access
		WSMset.uWrState = WRS_DATA;
		WSMset.uRdState = RDS_DATA;
		break;
	case READ_ID_CODES: // read identifier codes register
		WSMset.uRdState = RDS_ID;
		break;
	case READ_QUERY: // read query register	
		WSMset.uRdState = RDS_QUERY;
		break;
	case READ_STATUS_REG: // read status register
		WSMset.uRdState = RDS_SR;
		break;
	case CLEAR_STATUS_REG: // clear status register
		WSMset.byStatusReg = 0;
		break;
	case WRITE_BUFFER: // write to buffer
		WrStateE8(d);
		break;
	case WORD_BYTE_PROG1:
	case WORD_BYTE_PROG2: // byte/word program
		WrState40(d);
		break;
	case BLOCK_ERASE: // block erase
		WrState20(d);
		break;
	case BLOCK_ERASE_SUSPEND: // block erase, word/byte program suspend
		WSMset.byStatusReg |= WSMS;			// operation suspended
		WSMset.byStatusReg &= ~ESS;			// block erase completed (because no timing emulation)
		WSMset.byStatusReg &= ~BWSS;		// program completed (because no timing emulation)
		WSMset.uRdState = RDS_SR;
		break;
	case BLOCK_ERASE_RESUME: // block erase, word/byte program resume
		WSMset.byStatusReg &= ~WSMS;		// operation in progress
		WSMset.byStatusReg &= ~ESS;			// block erase in progress
		WSMset.byStatusReg &= ~BWSS;		// program in progress
		WSMset.byStatusReg |= WSMS;			// operation completed (because no timing emulation)
		WSMset.uRdState = RDS_SR;
		break;
	case STS_CONFIG:
		WSMset.bRomArray = bFlashRomArray;	// old access mode
		WrStateB8(d);
		break;
	case SET_CLR_BLOCK_LOCK: // set/clear block lock-bits
		WrState60(d);
		break;
	case FULL_CHIP_ERASE: // full chip erase
		WrState30(d);
		break;
	default: // wrong command
		WSMset.bRomArray = bFlashRomArray;	// old access mode
		break;
	}

	if (bFlashRomArray != WSMset.bRomArray)	// new access mode
	{
		bFlashRomArray = WSMset.bRomArray;	// change register access
		Map(0x00,0xFF);						// update memory mapping
		UpdatePatches(bFlashRomArray);		// patch/unpatch ROM again
	}
	return;
}

// write to buffer initial command
static VOID WrStateE8(DWORD d)
{
	// @todo add 2nd write buffer implementation
	// @todo add program timing implementation

	WSMset.byExStatusReg = 0;				// no write buffer
	if (WSMset.byWrite1No == 0)				// buffer1 available
	{
		WSMset.byWrite1No = 1;				// buffer1 in use
		WSMset.dwWrite1Addr = d;			// byte block address of buffer1
		WSMset.byExStatusReg = WBS;			// write buffer available
		// fill write buffer
		FillMemory(WSMset.pbyWrite1,ARRAYSIZEOF(WSMset.pbyWrite1),0xFF);
		WSMset.uWrState = WRS_WR_BUFFER_N;	// set state machine
		WSMset.uRdState = RDS_XSR;
	}
	return;
}

// write to buffer number of byte
static VOID WrStateE8N(BYTE a, DWORD d)
{
	if (a < (1 << byQueryTab[0x2A]))		// byte is length information
	{
		WSMset.byWrite1No += a;				// save no. of byte to program
		WSMset.byWrite1Size = a;			// save size to check write buffer boundaries
		WSMset.dwWrite1Addr = d;			// byte block address of buffer1
		WSMset.byStatusReg &= ~WSMS;		// state machine busy
		WSMset.uWrState = WRS_WR_BUFFER_D;
	}
	else
	{
		WSMset.byWrite1No = 0;				// free write buffer
		// improper command sequence
		WSMset.byStatusReg |= (ECLBS | BWSLBS);
		WSMset.byStatusReg |= WSMS;			// data written
		WSMset.uWrState = WRS_DATA;
	}
	WSMset.uRdState = RDS_SR;
	return;
}

// write to buffer data
static VOID WrStateE8D(BYTE a, DWORD d)
{
	// first data byte
	if (WSMset.byWrite1No == WSMset.byWrite1Size + 1)
	{
		DWORD dwBlockMask = ~(((byQueryTab[0x30] << 8) | byQueryTab[0x2F]) * 256 - 1);

		// same block
		if ((WSMset.dwWrite1Addr & dwBlockMask) == (d & dwBlockMask))
		{
			WSMset.dwWrite1Addr = d;		// byte block address of buffer1
			WSMset.pbyWrite1[0] = a;		// save byte
		}
		else
		{
			WSMset.byWrite1No = 0;			// free write buffer
			// improper command sequence
			WSMset.byStatusReg |= (ECLBS | BWSLBS);
			WSMset.byStatusReg |= WSMS;		// data written
			WSMset.uWrState = WRS_DATA;
			return;
		}
	}
	else
	{
		// write address within buffer
		if (d >= WSMset.dwWrite1Addr && d <= WSMset.dwWrite1Addr + WSMset.byWrite1Size)
		{
			// save byte in buffer
			WSMset.pbyWrite1[d-WSMset.dwWrite1Addr] = a;
		}
		else
		{
			WSMset.byWrite1No = 0;			// free write buffer
			// improper command sequence
			WSMset.byStatusReg |= (ECLBS | BWSLBS);
			WSMset.byStatusReg |= WSMS;		// data written
			WSMset.uWrState = WRS_DATA;
			return;
		}
	}

	if (--WSMset.byWrite1No == 0)			// last byte written
		WSMset.uWrState = WRS_WR_BUFFER_C;	// goto confirm state
	return;
}

// write to buffer confirm
static VOID WrStateE8C(BYTE a, DWORD d)
{
	if (CONFIRM == a)						// write buffer confirm?
	{
		BYTE  byPos;

		d = WSMset.dwWrite1Addr << 1;		// nibble start address

		for (byPos = 0; byPos <= WSMset.byWrite1Size; ++byPos)
		{
			a = WSMset.pbyWrite1[byPos];	// get char from buffer

			_ASSERT(d+1 < dwRomSize);		// address valid?
			// no error set in BWSLBS, because I could alway program a "0"
			WriteByte(d,a);					// write byte
			d += 2;							// next address
		}
	}
	else
	{
		WSMset.byWrite1No = 0;				// free write buffer
		// improper command sequence
		WSMset.byStatusReg |= (ECLBS | BWSLBS);
	}
	WSMset.byStatusReg |= WSMS;				// data written
	WSMset.uWrState = WRS_DATA;
	return;
}

// byte/word program initial command
static VOID WrState40(DWORD d)
{
	WSMset.byStatusReg &= ~WSMS;			// state machine busy
	WSMset.uWrState = WRS_WR_BYTE;
	WSMset.uRdState = RDS_SR;
	return;
	UNREFERENCED_PARAMETER(d);
}

// byte/word program data
static VOID WrState40D(BYTE a, DWORD d)
{
	// no error set in BWSLBS, because I could alway program a "0"
	WriteByte(d << 1,a);					// write byte
	WSMset.byStatusReg |= WSMS;				// data written
	WSMset.uWrState = WRS_DATA;
	return;
}

// block erase initial command
static VOID WrState20(DWORD d)
{
	WSMset.byStatusReg &= ~WSMS;			// state machine busy
	WSMset.uWrState = WRS_BLOCK_ERASE;
	WSMset.uRdState = RDS_SR;
	return;
	UNREFERENCED_PARAMETER(d);
}

// block erase data & confirm
static VOID WrState20C(BYTE a, DWORD d)
{
	if (CONFIRM == a)						// block erase confirm?
	{
		// lock bit of block is set
		if ((WSMset.dwLockCnfg & (1<<(d>>16))) != 0)	
		{
			WSMset.byStatusReg |= ECLBS;	// error in block erasure
			WSMset.byStatusReg |= DPS;		// lock bit detected
		}
		else
		{
			DWORD dwBlockSize = ((byQueryTab[0x30] << 8) | byQueryTab[0x2F]) * 256;

			d &= ~(dwBlockSize-1);			// start of block
			dwBlockSize *= 2;				// block size in nibbles
			_ASSERT(d+dwBlockSize <= dwRomSize); // address valid?
			EraseBlock(d << 1,dwBlockSize);	// erase 128K nibble
		}
	}
	else
	{
		// improper command sequence
		WSMset.byStatusReg |= (ECLBS | BWSLBS);
	}
	WSMset.byStatusReg |= WSMS;				// block erased
	WSMset.uWrState = WRS_DATA;
	return;
}

// full chip erase initial command
static VOID WrState30(DWORD d)
{
	WSMset.byStatusReg &= ~WSMS;			// state machine busy
	WSMset.uWrState = WRS_CHIP_ERASE;
	WSMset.uRdState = RDS_SR;
	return;
	UNREFERENCED_PARAMETER(d);
}

// full chip erase confirm
static VOID WrState30C(BYTE a, DWORD d)
{
	if (CONFIRM == a)						// chip erase confirm?
	{
		UINT  i;

		WORD  wNoOfBlocks = (byQueryTab[0x2E] << 8) | byQueryTab[0x2D];
		DWORD dwBlockSize = ((byQueryTab[0x30] << 8) | byQueryTab[0x2F]) * 256;

		DWORD dwBlockAddr = 0;

		dwBlockSize *= 2;					// block size in nibbles

		for (i = 0; i <= wNoOfBlocks; ++i)	// check all blocks
		{
			_ASSERT((i+1)*dwBlockSize <= dwRomSize);

			// lock bit of block is set & WP# = low, locked blocks cannot be erased
			if ((WSMset.dwLockCnfg & (1<<i)) == 0 || bWP != FALSE)
			{
				// clear block lock bit
				WSMset.dwLockCnfg &= ~(1<<i);

				// erase 128K nibble
				EraseBlock(dwBlockAddr,dwBlockSize);
			}

			dwBlockAddr += dwBlockSize;		// next block
		}
	}
	else
	{
		// improper command sequence
		WSMset.byStatusReg |= (ECLBS | BWSLBS);
	}
	WSMset.byStatusReg |= WSMS;				// chip erased
	WSMset.uWrState = WRS_DATA;
	return;
	UNREFERENCED_PARAMETER(d);
}

// STS pin Configuration initial command
static VOID WrStateB8(DWORD d)
{
	WSMset.uWrState = WRS_STS_PIN_CONFIG;
	return;
	UNREFERENCED_PARAMETER(d);
}

// STS pin Configuration data
static VOID WrStateB8D(BYTE a, DWORD d)
{
	// no emulation of STS pin Configuration
	WSMset.uWrState = WRS_DATA;
	return;
	UNREFERENCED_PARAMETER(a);
	UNREFERENCED_PARAMETER(d);
}

// Set/Clear block Lock-Bits initial command
static VOID WrState60(DWORD d)
{
	WSMset.byStatusReg &= ~WSMS;			// state machine busy
	WSMset.uWrState = WRS_LOCK_BITS;
	WSMset.uRdState = RDS_SR;
	return;
	UNREFERENCED_PARAMETER(d);
}

// Set/Clear block Lock-Bits confirm
static VOID WrState60D(BYTE a, DWORD d)
{
	UINT i;

	switch(a)
	{
	case 0x01: // set block lock bit
		if (bWP)							// WP# = high, can change block lock status
			WSMset.dwLockCnfg |= (1<<(d>>16));	// set block lock bit
		else
			WSMset.byStatusReg |= (BWSLBS | DPS); // device protect detect, WP# = low
		break;
	case CONFIRM: // clear block lock bits
		if (bWP)							// WP# = high, can change block lock status
		{
			WORD wNoOfBlocks = (byQueryTab[0x2E] << 8) | byQueryTab[0x2D];

			for (i = 0; i <= wNoOfBlocks; ++i) // clear all lock bits
			{
				WSMset.dwLockCnfg &= ~(1 << i); // clear block lock bit
			}
		}
		else
		{
			WSMset.byStatusReg |= (ECLBS | DPS); // device protect detect, WP# = low
		}
		break;
	default: // improper command sequence
		WSMset.byStatusReg |= (ECLBS | BWSLBS);
	}
	WSMset.byStatusReg |= WSMS;				// block lock-bit changed
	WSMset.uWrState = WRS_DATA;
	return;
}


//
// read state functions
//

// read array
static BYTE RdStateData(DWORD d)
{
	return ReadByte(d << 1);				// get byte
}

// read identifier codes
static BYTE RdStateId(DWORD d)
{
	BYTE byData;

	d >>= 1;								// A0 is not connected, ignore it
	if ((d & 0x03) != 0x02)					// id code request
	{
		d &= 0x03;							// data repetition
		byData = byQueryTab[d];				// get data from first 4 bytes id/query table
	}
	else									// block lock table
	{
		// get data from block lock table
		byData = (BYTE) ((WSMset.dwLockCnfg >> (d >> 15)) & 1);

		d &= 0x1F;							// data repetition
		if (d >= 4)	byData |= 0x02;			// set bit 1 on wrong ID adress
	}
	return byData;
}

// read query
static BYTE RdStateQuery(DWORD d)
{
	BYTE byData;

	d >>= 1;								// A0 is not connected, ignore it
	if ((d & 0x7F) != 0x02)					// query request
	{
		d &= 0x7F;							// data repetition

		// get data from id/query table
		byData = (d >= 0x40 && d < 0x50) ? 0 : byQueryTab[d&0x3F];
	}
	else									// block lock table
	{
		// get data from block lock table
		byData = (BYTE) ((WSMset.dwLockCnfg >> (d >> 15)) & 1);
	}
	return byData;
}

// read status register
static BYTE RdStateSR(DWORD d)
{
	return WSMset.byStatusReg;
	UNREFERENCED_PARAMETER(d);
}

// read extended status register
static BYTE RdStateXSR(DWORD d)
{
	return WSMset.byExStatusReg;
	UNREFERENCED_PARAMETER(d);
}


//
// public functions
//

VOID FlashInit(VOID)
{
	// check if locking bit table has more or equal than 32 bit
	_ASSERT(sizeof(WSMset.dwLockCnfg) * 8 >= 32);

	ZeroMemory(&WSMset,sizeof(WSMset));
	strcpy((LPSTR) WSMset.byType,"WSM");	// Write State Machine header
	WSMset.uSize = sizeof(WSMset);			// size of this structure
	WSMset.byVersion = WSMVER;				// version of flash implementation structure

	// factory setting of locking bits
	WSMset.dwLockCnfg = (1 << 0);			// first 64KB block is locked

	WSMset.uWrState = WRS_DATA;
	WSMset.uRdState = RDS_DATA;

	// data mode of ROM
	WSMset.bRomArray = bFlashRomArray = TRUE;
	return;
}

VOID FlashRead(BYTE *a, DWORD d, UINT s)
{
	BYTE v;

	while (s)								// each nibble
	{
		// output muliplexer
		_ASSERT(WSMset.uRdState < ARRAYSIZEOF(fnRdState));
		v = fnRdState[WSMset.uRdState](d>>1);

		if ((d & 1) == 0)					// even address
		{
			*a++ = v & 0xf; ++d; --s;
		}
		if (s && (d & 1))					// odd address
		{
			*a++ = v >> 4; ++d; --s;
		}
	}
	return;
}

VOID FlashWrite(BYTE *a, DWORD d, UINT s)
{
	BYTE  v;
	DWORD p;

	while (s)								// each nibble
	{
		p = d >> 1;							// byte address
		if (s > 1 && (d & 1) == 0)			// more than one byte on even address
		{
			v =  *a++;						// LSB
			v |= *a++ << 4;					// MSB
			d += 2; s -= 2;
		}
		else
		{
			// get byte from output muliplexer
			_ASSERT(WSMset.uRdState < ARRAYSIZEOF(fnRdState));
			v = fnRdState[WSMset.uRdState](p);

			if (d & 1)						// odd address
				v = (v & 0x0F) | (*a << 4);	// replace MSB
			else							// even address
				v = (v & 0xF0) | *a;		// replace LSB
			++a; ++d; --s;
		}

		_ASSERT(WSMset.uWrState < ARRAYSIZEOF(fnWrState));
		fnWrState[WSMset.uWrState](v,p);	// WSM
	}
	return;
}
