/*
 * Copyright(C) Paul und Scherer (mct.de/mct.net)
 *
 * This example demonstrates how to...
 *
 *  ... read and write the serial Flash.
 */

#include <stdio.h>
#include <target.h>

/*
 * Pins
 */
#define PIN_CSADC	0x00010000		// P0.16
#define PIN_MS3		0x00400000		// P0.22
#define PIN_CSFLASH	0x00800000		// P0.23
#define PIN_PORT	0xff000000		// P0.24.. 31

/*
 * Choose sector and page.
 *
 * The Flash  has 256 sectors a 4096 Bytes.
 * One sector has  16 pages   a  256 Bytes.
 */
#define SECT	7				// used sector
#define PAGE	3				//      page
#define SSIZE	4096				// size of sector
#define PSIZE	 256				//         page
#define SADDR	(SECT*SSIZE)			// addr of sector
#define PADDR	(SADDR+PAGE*PSIZE)		//         page

/*
 * Flash command codes
 */
#define	WREN	0x06				// write enable
#define	RDSR	0x05				// read status register
#define	READ	0x03				//      data
#define	SE	0x20				// sector erase
#define PP	0x02				// page program

/*
 * Initialize hardware.
 */
static void
init(void)
{
	/*
	 * Define I/Os, set all outputs hi.
	 */
	Intern_ioset  = PIN_CSADC|PIN_MS3|PIN_CSFLASH|PIN_PORT;
	Intern_iodir |= PIN_CSADC|PIN_MS3|PIN_CSFLASH|PIN_PORT;

	/*
	 * De-select SPImS
	 */
	Intern_ioclr = PIN_MS3;
	Intern_ioset = PIN_MS3;

	Intern_spcr	= 0x20;			// MSTR
	Intern_spccr	= 8;			// SPI SCK =pclk/8
	Intern_pinsel0 |= 0x5500;		// enable SPI pins
}

/*
 * SPI transfer (no error check!)
 *
 * Return received data.
 */
static int
spi_xfer(int c)
{
	Intern_spdr = c;			// start transfer

	/*
	 * Wait for transfer complete or error
	 * (mask the reserved bits 0, 1 and 2).
	 */
	while (!(Intern_spsr&0xf8)) ;

	return Intern_spdr;
}

/*
 * Blank check or verify
 *
 * Starting from dst, n bytes are compared to
 *
 *  0xff (blank check)  if src is zero, or to
 *  memory (verify)     if src is non-zero.
 *  
 */
static int
check(long dst, int n, char *src)
{
	Intern_ioclr = PIN_CSFLASH;		// CS lo
	spi_xfer(READ);				// READ cmd
	spi_xfer(dst>>16);			// dst
	spi_xfer(dst>> 8);			//  address
	spi_xfer(dst	);			//  MSB first

	while (n--) if (spi_xfer(0) != (src? *src++: 0xff)) break;

	Intern_ioset = PIN_CSFLASH;		// CS hi
	return n < 0;
}

/*
 * Wait while WIP (write in progress).
 */
static void
wait(void)
{
	Intern_ioclr = PIN_CSFLASH;		// CS lo
	spi_xfer(RDSR);				// read status register cmd
	while (spi_xfer(0)&1) ;			// busy...
	Intern_ioset = PIN_CSFLASH;		// CS hi
}

/*
 * Write page.
 */
static void
p_write(void)
{
	static char data[256];			// data buffer

	char *p = data;				// ptr to data
	int n = PSIZE;

	/*
	 * If not blank, dump page data.
	 */
	if (!check(PADDR, PSIZE, 0)) {
		int d;

		puts("Page not blank!\n"
		     "Page data:\n"
		);
		Intern_ioclr = PIN_CSFLASH;	// CS lo
		spi_xfer(READ);			// READ cmd
		spi_xfer(PADDR>>16);		// page
		spi_xfer(PADDR>> 8);		//  address
		spi_xfer(PADDR	  );		//  MSB first
		while ((d = spi_xfer(0))) {
			if (d > 0x1f && d < 0x7f) putchar(d);
			else printf("\\x%02x", d);
		}
		Intern_ioset = PIN_CSFLASH;	// CS hi
		putchar('\n');
		return;
	}

	/*
	 * If no data entered, skip write.
	 */
	puts("Enter data (single RETURN to abort):");
	if (*fgets(data, sizeof(data), stdin) == '\n') {
		puts("Aborted.");
		return;
	}

	Intern_ioclr = PIN_CSFLASH;		// CS lo
	spi_xfer(WREN);				// write enable cmd
	Intern_ioset = PIN_CSFLASH;		// CS hi
	Intern_ioclr = PIN_CSFLASH;		// CS lo
	spi_xfer(PP);				// page program cmd
	spi_xfer(PADDR>>16);			// page
	spi_xfer(PADDR>> 8);			//  address
	spi_xfer(PADDR	  );			//  MSB first
	while (n--) spi_xfer(*p++);		// write data
	Intern_ioset = PIN_CSFLASH;		// CS hi
	wait();
	if (!check(PADDR, PSIZE, data)) puts("--ERROR: Writing page failed\7\n\n");
	else puts("Data successfully written.");
}

/*
 * Erase sector.
 */
static void
s_erase(void)
{
	/*
	 * If blank, skip erase.
	 */
	if (check(SADDR, SSIZE, 0)) {
		puts("Sector already blank!");
		return;
	}

	Intern_ioclr = PIN_CSFLASH;		// CS lo
	spi_xfer(WREN);				// write enable cmd
	Intern_ioset = PIN_CSFLASH;		// CS hi
	Intern_ioclr = PIN_CSFLASH;		// CS lo
	spi_xfer(SE);				// sector erase cmd
	spi_xfer(SADDR>>16);			// sector
	spi_xfer(SADDR>> 8);			//  address
	spi_xfer(SADDR	  );			//  MSB first
	Intern_ioset = PIN_CSFLASH;		// CS hi
	wait();
	if (!check(SADDR, PSIZE, 0)) puts("--ERROR: Erasing sector failed\7\n\n");
	else puts("Sector successfully erased.");
}

/*
 * Flash functions menu
 *
 * Write page only writes to a blank page.
 * Erase sector only erases non-blank sectors.
 * A verify is done for each write/erase call.
 */
int
main(void)
{
	init();

	while (1) {
		int c;

		printf("\n"
		     "     SECTOR #%d, PAGE #%d\n\n"
		     " (1) Write page\n"
		     " (2) Erase sector\n\n"
		     "Select... ", SECT, PAGE
		);
		c = getchar();
		puts("\n");
		switch (c) {
		case '1': p_write(); break;
		case '2': s_erase(); break;
		}
	}
}
