//JFLASH V0.3

//Changes:- Steve@steves-house.org.uk 2000/06/28
//	supports SA1100 not SA1110 (trivially reversible)
//	No support for CFI, LART flashes don't support this interface
//	Supports only 28F160F3 Flashes. Trivially changeable
//	Extra devices removed from jtag chain
//	Support for plaited data bus to flash
//	Erasing done in 4Kword blocks

//Issues:-
//	needs tidying, but works under Linux at least...
//	Erase will multiply erase some blocks (all blocks are treated as 4Kwords, but many of the blocks in the Flash are 32Kwords
//	This is safe but suboptimal, it's caused by the address bus plaiting. 

//Compiling:-
//	gcc -O2 -s -o Jflash-linux Jflash.cpp
//

//running:-
//	You need to be root
//	Nothing else must be trying to use the printer port, including kernel drivers. 

//cabling:-
//	A hacky but functional LED cable is described later in this file. 
//	Do not attempt to connect the SA1100 pins directly to the printer port. 

//See also:-
//	Nicolas Pitres readme (Readme.Linux)
//	http://www.lart.tudelft.nl/ for all things LARTy



#include <stdio.h>
#include <time.h>
//#define IODIRECT
#ifndef __linux__
#include <windows.h>
#include <conio.h>
#else
#include <unistd.h>
#include <ctype.h>
#include <sys/io.h>
#include <errno.h>
#define IODIRECT 
#endif
#include "sa1100jtag.h"
#ifndef IODIRECT 
#include "mmp.h"
#endif


#define LPT1 0x3bc	// hardware base address for parallel port
#define LPT2 0x378	// the search order is LPT1 then 2 then 3
#define LPT3 0x278	// first valid address found is used (re-order if needed for multiple ports)

#define SA1100ID   "X001 0001000010000100 00000110101 1"	// JTAG ID-codes for the SA-1100 - don't know which way the leading X will be

#define READ 0		// Flags used to modify the SA-1100 JTAG chain data depending on
#define WRITE 1		// the access mode of the Flash Memory
#define SETUP 2
#define HOLD 3
#define RS 4

#define IP 0		// Flag used when accessing the parallel port
#define RP 1		// RP = 'read port', IP = 'ignore port', using IP will speed access  //was 1

#define PLAIT 1		//use for command access to the Flash, when we care about the Flashes point of view
#define NOPLAIT 0	//use for data access to the flash, when we care about the SA1100s point of view

#define MAX_IN_LENGTH 100 // max length for user input strings
#define STATUS_UPDATE 2	// time between updates of program/verify status in seconds

int lpt_address;	// Global variable assigned to parallel port address
int wom;

#ifndef IODIRECT	//define IODIRECT for direct access to parallel port (DOS and Windows9x)

// define variables used to access the MMP driver (Needed for WindowsNT)
char ID[256];   
PHYSICAL_MEMORY_INFO pmi;
PVOID pPartyMem = NULL;
char *aInterfaceType[] =  {"Internal",
                           "Isa",
                           "Eisa",
                           "MicroChannel",
                           "TurboChannel" };

#endif

#ifdef __linux__

/* Some types */
typedef long DWORD;

/* in/out macros */
#include <asm/io.h>
#define _inp(a) inb(a)
#define _outp(a,d) outb(d,a)

#endif

int debug_jtag = 0;

FILE *in_file; 
int putp(int,int, int);  // writes the JTAG data on the parallel port
void id_command(void);	// issues the JTAG command to read the device ID for all 3 chips
void bypass_all(void);	// issues the JTAG command to put all 3 device in bypass mode
void extest(void);		// issues the JTAG command EXTEST to the SA-1110
DWORD access_rom(int, DWORD, DWORD, int, int);	// Passes read/write/setup data for the Flash memory
DWORD access_bus(int, DWORD, DWORD, int, int);	// Read/write access to the SA-1110 pins
int test_port(void);	// Looks for and finds a valid parallel port address
int check_id(char*);	// Compares the device IDs for the 3 JTAG chips to expected values
void error_out(char*);	// Prints error and exits program

void main( int argc, char *argv[] )
{


#ifndef IODIRECT   //stuff to setup the MMP driver
	// OPEN up the driver
		if (MMPOpen())
			{
			printf("ERROR: can't open driver!\n");
			exit(1);
			}
	// GET DRIVER ID
		MMPGetID(ID);
	// MAP MEMORY
		pmi.InterfaceType       = Isa;                // 1 = Isa, 2 = Eisa, 3 = Microchannel, 4 = TurboChannel
		pmi.BusNumber           = 0;                  // bus number, i.e. 0 for standard x86 ISA systems
		pmi.BusAddress.QuadPart = (LONGLONG)0xD0000;  // physical address to map
		pmi.AddressSpace        = 0;                  // 0 = memory, 1 = I/O
		pmi.Length              = 10;                 // length of section to map

		if (MMPMapUserPhysicalMem(pmi, &pPartyMem))
			{
			error_out("Error: can't map memory!");
			}
		else
			{
			MMPUnmapUserPhysicalMem(pPartyMem);
			}
#endif

	lpt_address = test_port();	// find a valid parallel port address
	if(!lpt_address)error_out("Error, unable to find parallel port");
	printf("using printer port at %3x\n\r",lpt_address);


	putp(1,1,IP);	//
	putp(1,1,IP);
	putp(1,1,IP);
	putp(1,1,IP);
	putp(1,0,IP);	//run test/idle  
	putp(1,0,IP);	//run test/idle

	char filename[MAX_IN_LENGTH];
	if(argc >= 2)
		strcpy(filename,argv[1]);
	else
		{
		printf("enter binary file name: ");
		gets(filename);
		}

	id_command();
	bypass_all();


//	access_rom(SETUP, 0, 0x500050L, IP); // clear status register
//	access_rom(WRITE, 0, 0x500050L, IP);
//	access_rom(HOLD, 0, 0x500050L, IP);



	access_rom(SETUP, 0, 0x900090L, IP, PLAIT); // read ID codes
	access_rom(WRITE, 0, 0x900090L, IP, PLAIT);
	access_rom(HOLD , 0, 0x900090L, IP, PLAIT);


	// To read data from the Flash Memory you must first fill the SA-1110 JTAG chain 
	// with the Address, then pump the entire chain out.
	// however while pumping data out you can be pumping the next cycle's Address in
	// Therefor the JTAG chain looks like a pipeline, valid read data always coming
	// out one cycle late.

	// Note that both Flash Memory devices are accessed in parallel (i.e. 32 data bits) 
	
	
	access_rom(READ, 0x0, 0, IP, PLAIT);					//ask for Manufacturer code, get crap
	if(access_rom(READ, 0x1, 0, RP, PLAIT) != 0x00890089)	//get Manufacturer code, ask for device code
		error_out("error reading manufacturer code\n\r");
	if(access_rom(READ, 0x1, 0, RP, PLAIT) != 0x88F488F4)	//get Device  code, ask for device code (again, discard)
		error_out("error reading device code\n\r");

	printf("Seems to be a pair of 28F160F3, bottom boot. Good. \n\r");

	if( (in_file = fopen(filename, "rb" )) == NULL)
		error_out("error, can not open binary input file");

	DWORD max_erase_time = 5; 
	DWORD dsize = 1 * 1024 * 1024; //16Mbits per device
	int nblocks = 128; //sort of a lie. There are 8 off 4Kword blocks and 31 off 32Kword blocks. Some may get erased several times (safe)...
	DWORD max_write_buffer = 16;  //size of a write chunk. The flash has no write buffer...

	DWORD fsize = 0;
	DWORD last_non_zero = 0 ;
	DWORD last_non_ff = 0;
	DWORD li,li1;
	DWORD lj; 
	for(;;)
		{
		fread((DWORD *)&li, sizeof(DWORD) , 1, in_file);
		if(feof(in_file))break;
		if(li != 0)
			last_non_zero = fsize;
		if(li != 0xffffffff)
			last_non_ff = fsize;
		fsize++;
		}
	rewind(in_file);

	if(fsize * 2 > dsize)
		error_out("error, file size is bigger than device size");
	fsize = last_non_ff + 1;


	if(100 - (last_non_zero * 100)/last_non_ff > 20)
	{
		printf("The last %2ld percent of image file is all zeros\n",100 - (last_non_zero * 100)/last_non_ff);
		printf("Would you like to save time by not programming that area? [y/n]: ");
		if(toupper(getchar()) == 'Y')
			fsize = last_non_zero + 1;
	}
	printf("\nStarting erase for %6x bytes\n",fsize);
	int block_number = 0;
	time_t start, now;
	for(lj = 0; lj < fsize; lj = lj + dsize / 2 / nblocks)  // Erase only blocks to be programmed
	{
		//printf("erasing sector at %6x \n\r",lj);
		access_rom(SETUP, 0, 0xFF00FFL, IP, PLAIT); // return to array read
		access_rom(WRITE, 0, 0xFF00FFL, IP, PLAIT); 
		access_rom(HOLD, 0, 0xFF00FFL, IP, PLAIT);

		access_rom(SETUP, 0, 0x200020L, IP, PLAIT); // Erase block command
		access_rom(WRITE, 0, 0x200020L, IP, PLAIT); 
		access_rom(HOLD, 0, 0x200020L, IP, PLAIT);
	
		access_rom(SETUP, lj, 0xd000d0L, IP, PLAIT); // Erase confirm at the block address
		access_rom(WRITE, lj, 0xd000d0L, IP, PLAIT);
		access_rom(HOLD, lj, 0xd000d0L, IP, PLAIT);

		time(&start);
		printf("Erasing block %3d   \n\r",block_number++);
		while(access_rom(RS, 0, 0, RP, PLAIT) != 0x800080L)	// Loop until successful status return
		{
			access_rom(READ, 0, 0, RP, PLAIT);
			time(&now);
			if(difftime(now,start) > max_erase_time + 1)	// Check for erase timeout
				error_out("Error, Block erase timed out");
		}
	}
	printf("Erasing done                                           \nStarting programming\n");

	

	DWORD write_word_count = (max_write_buffer - 1) + ((max_write_buffer - 1) << 16);
	DWORD lk; 
	time(&start);

	for(lj = 0; lj < fsize; lj = lj + max_write_buffer)
		{
		access_rom(READ, lj, 0, IP, PLAIT); // preload address bus with start address

		time(&now);
		if(difftime(now,start) > STATUS_UPDATE)	// Update status every 2 seconds
		{
			printf("Writing flash at hex address %8lx, %5.2f%% done    \n\r",lj,(float)lj/(float)fsize*100.0);
			time(&start);
		}

		//printf("burning block %3d\n\r",lj);
		
		for(lk = 0; lk < max_write_buffer; lk++)
			{
			fread((DWORD *)&li, sizeof(DWORD) , 1, in_file);
			access_rom(SETUP, lj+lk, 0x400040L, IP, PLAIT); // Write command
			access_rom(WRITE, lj+lk, 0x400040L, IP, PLAIT);
			access_rom(HOLD , lj+lk, 0x400040L, IP, PLAIT);
			access_rom(SETUP, lj+lk, li, IP, NOPLAIT); // Write data
			access_rom(WRITE, lj+lk, li, IP, NOPLAIT);
			access_rom(HOLD , lj+lk, li, IP, NOPLAIT);
			//printf("writing %8x to %8x\n\r", li, lj + lk);
		}
	}

	printf("Programming done                                            \nStarting verify\n");
	
	access_rom(SETUP, 0, 0xff00ffL, IP, PLAIT); // put back into read mode
	access_rom(WRITE, 0, 0xff00ffL, IP, PLAIT);
	access_rom(HOLD, 0, 0xff00ffL, IP, PLAIT);
	
	rewind(in_file);

	access_rom(READ, 0x0L, 0x0L, IP, NOPLAIT); //extra read to start to get the pipeline going

	time(&start);

	for(lj = 1; lj <= fsize; lj++)
		{
		fread((DWORD *)&li, sizeof(DWORD) , 1, in_file);
		li1 = access_rom(READ, 0x0L + lj, 0x0L, RP, NOPLAIT);
		time(&now);
		if(difftime(now,start) > STATUS_UPDATE)	// Update status every 2 seconds
		{
			printf("Verifying flash at hex address %8lx, %5.2f%% done    \n\r",lj,(float)lj/(float)fsize*100.0);
			time(&start);
		}
		//printf("verifying address = %lx exp_dat = %lx act_dat = %lx\n",lj,li,li1);
		if(li != li1)
			{
			printf("verify error at address = %lx exp_dat = %lx act_dat = %lx\n",lj,li,li1);
#ifndef IODIRECT 
			MMPClose();   // Close WinNT parallel port driver
#endif
			exit(1);
			}
		}
	
	printf("Verification successful!                                                    \n");

	fclose(in_file);

	putp(1,1,IP);	//test logic reset
	putp(1,1,IP);
	putp(1,1,IP);
	putp(1,1,IP);
	putp(1,1,IP);
	putp(1,1,IP);
	putp(1,1,IP);

#ifndef IODIRECT 
		MMPClose();
#endif

}
int putp(int tdi, int tms, int rp)
{
// Cable used had 100 ohm resistors between the following pins (Cable shipped as part of SA-1110 Development Kit)
// Output pins (LPT driving)
// LPT D0 Pin 2 and TCK J10 Pin 4
// LPT D1 Pin 3 and TDI J10 Pin 11
// LPT D2 Pin 4 and TMS J10 Pin 9

// each of these needs a voltage limiter like this, to clamp the voltage to 3V, as the SA1100 is not 5V tolerant  :-

//
//LPTport --->---/\/\-----|-------|-->Jtag connector
//                1K      |       | 
//                       _|_     === 470pf
//     LED (Yellow)      \ /      |
//     (one per line)   __V__    _|_ JTAG & LPTPORT ground 
//                        |
//                        | <---tie all 3 of these points together
//                       _|_
//     1N4148            \ /
//     (one per widget) __V__
//                        |
//      __________________|____________________ JTAG & LPTPORT ground
// 

// Input pin (SA-1110 board drives)
// LPT Busy Pin 11 and TDO J10 Pin 13
// (steve) TDO does go straight to the port, although I suspect a 33R resistor
// in series would be a good thing. 

// (Steve) nTRST I tied on the PCB to nRESET (pin 2 of the MAX811). (I see no
// downside to doing this on future versions of the LART - it's what I've
// done on SA1100 designs before)
// 
	
	
int tdo = -1;
#ifndef IODIRECT 
//		MMPOutp(lpt_address, tms*4+tdi*2);		//TCK low
		MMPOutp(lpt_address, tms*4+tdi*2+1);	// TCK high
//		if(rp == RP)MMPOutp(lpt_address, tms*4+tdi*2);	// TCK low
		MMPOutp(lpt_address, tms*4+tdi*2);	// TCK low
		if(rp == RP)tdo = !((int)MMPInp(lpt_address + 1) >> 7);  // get TDO data
#else
		_outp(lpt_address, tms*4+tdi*2);	// TCK low
		_outp(lpt_address, tms*4+tdi*2+1);	// TCK high
		if(rp == RP)_outp(lpt_address, tms*4+tdi*2);	// TCK low
		if(rp == RP)tdo = !((int)_inp(lpt_address + 1) >> 7);	// get TDO data
#endif
	return tdo;

}
void id_command(void)
{

	putp(1,1,IP);	//get to TLR   
	putp(1,1,IP);	
	putp(1,1,IP);	
	putp(1,1,IP);	
	putp(1,1,IP);	
	putp(1,1,IP);	
	putp(1,1,IP);	
	putp(1,1,IP);	


	putp(1,0,IP);	//Run-Test/Idle
	putp(1,0,IP);	//Run-Test/Idle
	putp(1,0,IP);	//Run-Test/Idle
	putp(1,0,IP);	//Run-Test/Idle
	putp(1,1,IP);
	putp(1,1,IP);	//select IR scan
	putp(1,0,IP);	//capture IR
	putp(1,0,IP);	//shift IR

	putp(0,0,IP);	//SA1100 IDCODE
	putp(1,0,IP);	//
	putp(1,0,IP);	//
	putp(0,0,IP);	//
	putp(0,1,IP);	//Exit1-IR

	putp(1,1,IP);	//Update-IR		
	putp(1,0,IP);	//Run-Test/Idle
	putp(1,0,IP);	//Run-Test/Idle
	putp(1,0,IP);	//Run-Test/Idle
	
	putp(1,1,IP);	//sel_DR
	putp(1,0,IP);	

	if(check_id(SA1100ID))
		error_out("failed to read device ID for the SA-1100");

	putp(1,1,IP);	//Exit1-DR
	putp(1,1,IP);	//Update-DR		
	putp(1,0,IP);	//Run-Test/Idle
	putp(1,0,IP);	//Run-Test/Idle
	putp(1,0,IP);	//Run-Test/Idle

}
void bypass_all(void)
{


	putp(1,0,IP);	//Run-Test/Idle
	putp(1,0,IP);	//Run-Test/Idle
	putp(1,0,IP);	//Run-Test/Idle
	putp(1,0,IP);	//Run-Test/Idle
	putp(1,1,IP);
	putp(1,1,IP);	//select IR scan
	putp(1,0,IP);	//capture IR
	putp(1,0,IP);	//shift IR

	putp(1,0,IP);	//SA1100 BYPASS
	putp(1,0,IP);	//
	putp(1,0,IP);	//
	putp(1,0,IP);	//
	putp(1,1,IP);	//Exit1-IR

	putp(1,1,IP);	//Update-IR		
	putp(1,0,IP);	//Run-Test/Idle
	putp(1,0,IP);	//Run-Test/Idle
	putp(1,0,IP);	//Run-Test/Idle

}
void extest(void)
{


	putp(1,0,IP);	//Run-Test/Idle
	putp(1,0,IP);	//Run-Test/Idle
	putp(1,0,IP);	//Run-Test/Idle
	putp(1,0,IP);	//Run-Test/Idle
	putp(1,1,IP);
	putp(1,1,IP);	//select IR scan
	putp(1,0,IP);	//capture IR
	putp(1,0,IP);	//shift IR

	putp(0,0,IP);	//SA110 extest
	putp(0,0,IP);	//
	putp(0,0,IP);	//
	putp(0,0,IP);	//
	putp(0,1,IP);	//

	putp(1,1,IP);	//Update-IR		
	putp(1,0,IP);	//Run-Test/Idle
	putp(1,0,IP);	//Run-Test/Idle
	putp(1,0,IP);	//Run-Test/Idle

}
DWORD access_rom(int rw, DWORD address, DWORD data, int rp, int plait)
{
	// Shift Flash address making A2 the LSB
	return(access_bus(rw, address << 2, data, rp, plait));
}
DWORD access_bus(int rw, DWORD address, DWORD data, int rp, int plait)
{

	// Preset SA-1100 pins to default values (all others set in sa1100jtag.h)
	pin[nCS0_OUT] = 1;
	pin[nCS1_OUT] = 1;
	pin[nCS2_OUT] = 1;
	pin[nCS3_OUT] = 1;

	pin[nOE_OUT] = 0;
	pin[nWE_OUT] = 1;
	//pin[RD_nWR_OUT] = 0;

	pin[D31_0_EN] = 1;

	for(int i = 0; i < 26; i++)
		pin[i+28] = (int)((address >> i) & 1);	// set address 0 thru 25



	if(rw == READ)
	{
		//pin[RD_nWR_OUT] = 1;
		if (debug_jtag) printf ("READ from %8x ",address);
		switch(address >> 27)
		{
			case 0:{pin[nCS0_OUT] = 0; break;}
			case 1:{pin[nCS1_OUT] = 0; break;}
			case 2:{pin[nCS2_OUT] = 0; break;}
			case 3:{pin[nCS3_OUT] = 0; break;}
		}
	}

	if(rw == WRITE)
	{
		if (debug_jtag) printf ("WRITE %8x to %8x ", data, address);
		pin[nWE_OUT] = 0;
		pin[nOE_OUT] = 1;
		pin[D31_0_EN] = 0;  // switch data pins to drive
		switch(address >> 27)  // set CS pin for corresponding address
		{
			case 0:{pin[nCS0_OUT] = 0; break;}
			case 1:{pin[nCS1_OUT] = 0; break;}
			case 2:{pin[nCS2_OUT] = 0; break;}
			case 3:{pin[nCS3_OUT] = 0; break;}
		}

		for(DWORD li = 0L; li < 32L; li++)
			if (plait)
			{
				pin[command_dat_order[li]] = (int)((data >> li) & 1L);	// set data pins
			}
			else
			{
				pin[data_dat_order[li]] = (int)((data >> li) & 1L);	// set data pins
			}

	}

	if(rw == SETUP || rw == HOLD)	// just like a write except WE, WE needs setup time
	{
		if (debug_jtag) printf ("SET/HOLD at %8x ", address);
		pin[nOE_OUT] = 1;
		pin[D31_0_EN] = 0;
		for(DWORD li = 0L; li < 32L; li++)
			if (plait)
			{
				pin[command_dat_order[li]] = (int)((data >> li) & 1L);	// set data pins
			}
			else
			{
				pin[data_dat_order[li]] = (int)((data >> li) & 1L);	// set data pins
			}
	}

	if(rw == RS)	// setup prior to RD_nWR_OUT
	{
		if (debug_jtag) printf ("RS ");
		pin[nOE_OUT] = 1;
	}


	putp(1,0,IP);	//Run-Test/Idle
	putp(1,0,IP);	//Run-Test/Idle
	putp(1,0,IP);	//Run-Test/Idle
	putp(1,0,IP);	//Run-Test/Idle
	putp(1,1,IP);	//select DR scan
	putp(1,0,IP);	//capture DR
	putp(1,0,IP);	//shift DR

	int out_dat[300];
	int i; 
	for(i = 0; i < (SA1100_CHAIN_LENGTH - 1); i++)	// shift write data in to JTAG port and read data out
		out_dat[i] = putp(pin[i],0,rp);

	putp(0,1,IP);	//Exit1-DR
	putp(1,1,IP);	//Update-DR		
	putp(1,0,IP);	//Run-Test/Idle
	putp(1,0,IP);	//Run-Test/Idle
	putp(1,0,IP);	//Run-Test/Idle

	DWORD busdat = 0;
	
	for(i = 0; i < 32; i++)	// convert serial data to single DWORD
	{
			if (plait)
			{
				busdat = busdat | (DWORD)(out_dat[command_dat_order[i] - 2] << i);
			}
			else
			{
				busdat = busdat | (DWORD)(out_dat[data_dat_order[i] - 2] << i);
			}
	}
	extest();
	if (debug_jtag) if (rp == RP) printf("prev.busdat = %8x\n\r",busdat); else printf("\n\r");
	return(busdat);
}


#ifdef __linux__

int io_access_on( unsigned long port )
{
	if (ioperm (port, 3, 1)) {
		perror ("ioperm()");
		return 0;
	}
	return 1;
}

void io_access_off( unsigned long port )
{
	ioperm (port, 3, 0);
}

#else
#define io_access(x) (1)
#define io_access(x)
#endif


int test_port(void)
{
	// search for valid parallel port
#ifndef IODIRECT 
		MMPOutp(LPT1, 0x55);
		if((int)MMPInp(LPT1) == 0x55)return LPT1;
		MMPOutp(LPT2, 0x55);
		if((int)MMPInp(LPT2) == 0x55)return LPT2;
		MMPOutp(LPT1, 0x55);
		if((int)MMPInp(LPT3) == 0x55)return LPT3;
#else
	if( io_access_on(LPT1) ){
		_outp(LPT1, 0x55);
		if((int)_inp(LPT1) == 0x55)return LPT1;
		io_access_off(LPT1);
	}
	if( io_access_on(LPT2) ){
		_outp(LPT2, 0x55);
		if((int)_inp(LPT2) == 0x55)return LPT2;
		io_access_off(LPT2);
	}
	if( io_access_on(LPT3) ){
		_outp(LPT1, 0x55);
		if((int)_inp(LPT3) == 0x55)return LPT3;
		io_access_off(LPT3);
	}
#endif
	return(0);	// return zero if none found
}

int check_id(char *device_id)
{
	// compare passed device ID to the one returned from the ID command
	char in_id[40];

	for(int i = 34; i >= 0; i--)
	{
		if(i == 4 || i == 21 || i == 33)
		{
			in_id[i] = ' ';
			i--;
		}
		if(putp(1,0,RP) == 0)
			in_id[i] = '0';
		else
			in_id[i] = '1';

	}
	in_id[35] = 0;
	if(strcmp(device_id+4,in_id+4))	// add 4 to compare all but the device version
	{
		printf("error, failed to read device ID\n");
		printf("ACT: %s\n",in_id);
		printf("EXP: %s\n\n",device_id);
		return -1;
	}
	return 0;
}
void error_out(char *error_string)
{
#ifndef IODIRECT 
	MMPClose();
#endif
	printf("%s\n",error_string);
	exit(0);
}
