/* 
    Floppy Emu, copyright 2013 Steve Chamberlin, "Big Mess o' Wires". All rights reserved.
	
    Floppy Emu is licensed under a Creative Commons Attribution-NonCommercial 3.0 Unported 
	license. (CC BY-NC 3.0) The terms of the license may be viewed at 	
	http://creativecommons.org/licenses/by-nc/3.0/
	
	Based on a work at http://www.bigmessowires.com/macintosh-floppy-emu/
	
    Permissions beyond the scope of this license may be available at www.bigmessowires.com
	or from mailto:steve@bigmessowires.com.
*/

#include <math.h>
#include <avr/eeprom.h>
#include <avr/io.h>
#include <avr/pgmspace.h>
#include <avr/interrupt.h>
#include <util/atomic.h>
#include <util/delay.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>

#include "portmacros.h"
#include "noklcd.h"
#include "millitimer.h"
#include "SdFat.h"
#include "SdBaseFile.h"
#include "micro.h"
#include "ports.h"
#include "diskmenu.h"

#ifdef PROGMEM_WORKAROUND
// work-around for compiler bug
#undef PROGMEM 
#define PROGMEM __attribute__(( section(".progmem.data") )) 
#undef PSTR 
#define PSTR(s) (__extension__({static prog_char __c[] PROGMEM = (s); &__c[0];})) 
#endif

// I/O pin assignments
#define CPLD_RESET_PORT B
#define CPLD_RESET_PIN 0

#define CPLD_STEP_DIR_MOTOR_ON_PORT C
#define CPLD_STEP_DIR_MOTOR_ON_PIN 7

#define CPLD_STEP_REQ_PORT D
#define CPLD_STEP_REQ_PIN 0 // PCINT24
#define CPLD_STEP_REQ_INT_MSK PCMSK3
#define CPLD_STEP_REQ_INT_PIN PCINT24
#define CPLD_STEP_REQ_INT_ENABLE PCIE3

#define CPLD_CURRENT_SIDE_PORT C
#define CPLD_CURRENT_SIDE_PIN 1 // PCINT17
#define CPLD_CURRENT_SIDE_INT_MSK PCMSK2
#define CPLD_CURRENT_SIDE_INT_PIN PCINT17
#define CPLD_CURRENT_SIDE_INT_ENABLE PCIE2

#define CPLD_EJECT_REQ_PORT D
#define CPLD_EJECT_REQ_PIN 3

#define CPLD_STEP_ACK_DISK_IN_PORT C
#define CPLD_STEP_ACK_DISK_IN_PIN 2

#define CPLD_WR_REQ_PORT C
#define CPLD_WR_REQ_PIN 0 // PCINT16
#define CPLD_WR_REQ_INT_MSK PCMSK2
#define CPLD_WR_REQ_INT_PIN PCINT16
#define CPLD_WR_REQ_INT_ENABLE PCIE2

#define CPLD_RD_READY_TK0_PORT C
#define CPLD_RD_READY_TK0_PIN 5

#define CPLD_RD_ACK_WR_TICK_PORT A
#define CPLD_RD_ACK_WR_TICK_PIN 7 // PCINT7
#define CPLD_RD_ACK_WR_TICK_INT_MSK PCMSK0
#define CPLD_RD_ACK_WR_TICK_INT_PIN PCINT7
#define CPLD_RD_ACK_WR_TICK_INT_ENABLE PCIE0

#define CPLD_DATA_PORT A

#define CPLD_DATA_HIZ_PORT C
#define CPLD_DATA_HIZ_PIN 6

#define CPLD_TACH_PORT D
#define CPLD_TACH_PIN 5

#define CPLD_TMS_PORT C
#define CPLD_TMS_PIN 3

#define SELECT_BUTTON_PORT D
#define SELECT_BUTTON_PIN 4

#define PREV_BUTTON_PORT D
#define PREV_BUTTON_PIN 1

#define NEXT_BUTTON_PORT D
#define NEXT_BUTTON_PIN 2

#define STATUS_LED_PORT B
#define STATUS_LED_PIN 3

#define CARD_WPROT_PORT D
#define CARD_WPROT_PIN 7

#define SECTOR_DATA_SIZE 512
#define INTER_SECTOR_GAP_SIZE 55
#define ADDRESS_DATA_GAP_SIZE 10
#define SECTOR_DATA_HEADER_SIZE 3
#define SECTOR_DATA_SECTORNUM_START SECTOR_DATA_HEADER_SIZE
#define SECTOR_DATA_SECTORNUM_SIZE 1
#define SECTOR_DATA_ENCODED_TAGS_START (SECTOR_DATA_HEADER_SIZE+SECTOR_DATA_SECTORNUM_SIZE)
#define SECTOR_DATA_ENCODED_TAGS_SIZE 16
#define SECTOR_DATA_ENCODED_DATA_START (SECTOR_DATA_HEADER_SIZE+SECTOR_DATA_SECTORNUM_SIZE+SECTOR_DATA_ENCODED_TAGS_SIZE)
#define SECTOR_DATA_ENCODED_DATA_SIZE 683
#define SECTOR_DATA_CHECKSUM_START (SECTOR_DATA_ENCODED_DATA_START+SECTOR_DATA_ENCODED_DATA_SIZE)

// 8 byte marker placed at the end of the program binary, used by the bootloader.
// Configure the .bootldrinfo address to be 8 bytes below the bootloader start address for the type of Atmega being used.
#define DEVICEID_HIGH 0xDDDD  
#define DEVICEID_LOW 0xDDDD     
#define VERSIONID 0x0100
const uint16_t bootloader_info[] __attribute__(( section(".bootldrinfo") )) = { DEVICEID_HIGH, DEVICEID_LOW, VERSIONID, 0x0000 };
	
const char versionStr[] PROGMEM = "App Version 1.0 L";

volatile uint8_t currentTrack;
volatile uint8_t prevTrack;
volatile uint8_t currentSide;
volatile uint8_t prevSide;
volatile uint8_t writeMode;
volatile bool restartDisk;
volatile bool writeError;

bool diskInserted;
bool readOnly;
bool mfmMode;
uint16_t crc;
uint8_t numberOfDiskSides;
uint8_t currentSector;
uint16_t driveTachHalfPeriod;
uint8_t tachFlutter;

uint8_t writeDisplayTimer;
uint8_t cpldFirmwareVersion;

#define TEXTBUF_SIZE 22
char textBuf[TEXTBUF_SIZE];

#define NUM_BUFFERS 24
uint8_t sectorBuf[NUM_BUFFERS][SECTOR_DATA_SIZE];
uint8_t extraBuf[SECTOR_DATA_SIZE];

bool selectedFileIsDiskCopyFormat;

extern const uint16_t sony_track_start[] PROGMEM;
const uint16_t sony_track_start[80] = {
	0,    12,  24,  36,  48,  60 , 72,  84,
	96,  108, 120, 132, 144, 156, 168, 180,

	192, 203, 214, 225, 236, 247, 258, 269,
	280, 291, 302, 313, 324, 335, 346, 357,

	368, 378, 388, 398, 408, 418, 428, 438,
	448, 458, 468, 478, 488, 498, 508, 518,

	528, 537, 546, 555, 564, 573, 582, 591,
	600, 609, 618, 627, 636, 645, 654, 663,

	672, 680, 688, 696, 704, 712, 720, 728,
	736, 744, 752, 760, 768, 776, 784, 792
};

extern const uint8_t sony_track_len[] PROGMEM;
const uint8_t sony_track_len[80] = {
	12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12,
	11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11,
	10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,
	9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,
	8,  8,  8,  8,  8,  8,  8,  8,  8,  8,  8,  8,  8,  8,  8,  8
};
	
extern const uint8_t sony_to_disk_byte[] PROGMEM;
const uint8_t sony_to_disk_byte[] = {
	0x96, 0x97, 0x9A, 0x9B,  0x9D, 0x9E, 0x9F, 0xA6, /* 0x00 */
	0xA7, 0xAB, 0xAC, 0xAD,  0xAE, 0xAF, 0xB2, 0xB3,
	0xB4, 0xB5, 0xB6, 0xB7,  0xB9, 0xBA, 0xBB, 0xBC, /* 0x10 */
	0xBD, 0xBE, 0xBF, 0xCB,  0xCD, 0xCE, 0xCF, 0xD3,
	0xD6, 0xD7, 0xD9, 0xDA,  0xDB, 0xDC, 0xDD, 0xDE, /* 0x20 */
	0xDF, 0xE5, 0xE6, 0xE7,  0xE9, 0xEA, 0xEB, 0xEC,
	0xED, 0xEE, 0xEF, 0xF2,  0xF3, 0xF4, 0xF5, 0xF6, /* 0x30 */
	0xF7, 0xF9, 0xFA, 0xFB,  0xFC, 0xFD, 0xFE, 0xFF
};

extern const uint8_t disk_byte_to_sony[] PROGMEM;
const uint8_t disk_byte_to_sony[] = {
	/* table begins at disk byte 0x96, value of 0xFF is an invalid disk byte */
	/* 0x96 */ 0x00, 0x01, 0xFF, 0xFF, 0x02, 0x03, 0xFF, 0x04,
	/* 0x9E */ 0x05, 0x06, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
	/* 0xA6 */ 0x07, 0x08, 0xFF, 0xFF, 0xFF, 0x09, 0x0A, 0x0B,
	/* 0xAE */ 0x0C, 0x0D, 0xFF, 0xFF, 0x0E, 0x0F, 0x10, 0x11,
	/* 0xB6 */ 0x12, 0x13, 0xFF, 0x14, 0x15, 0x16, 0x17, 0x18,
	/* 0xBE */ 0x19, 0x1A, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
	/* 0xC6 */ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x1B, 0xFF, 0x1C,
	/* 0xCE */ 0x1D, 0x1E, 0xFF, 0xFF, 0xFF, 0x1F, 0xFF, 0xFF,
	/* 0xD6 */ 0x20, 0x21, 0xFF, 0x22, 0x23, 0x24, 0x25, 0x26,
	/* 0xDE */ 0x27, 0x28, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x29,
	/* 0xE6 */ 0x2A, 0x2B, 0xFF, 0x2C, 0x2D, 0x2E, 0x2F, 0x30,
	/* 0xEE */ 0x31, 0x32, 0xFF, 0xFF, 0x33, 0x34, 0x35, 0x36,
	/* 0xF6 */ 0x37, 0x38, 0xFF, 0x39, 0x3A, 0x3B, 0x3C, 0x3D,
	/* 0xFE */ 0x3E, 0x3F
};	
	
uint8_t sectorDataHeaderGCR[] = { 0xD5, 0xAA, 0xAD };

extern const uint16_t crc_ccitt[] PROGMEM;
const uint16_t crc_ccitt[] = {
	0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50A5, 0x60C6, 0x70E7,
	0x8108, 0x9129, 0xA14A, 0xB16B, 0xC18C, 0xD1AD, 0xE1CE, 0xF1EF,
	0x1231, 0x0210, 0x3273, 0x2252, 0x52B5, 0x4294, 0x72F7, 0x62D6,
	0x9339, 0x8318, 0xB37B, 0xA35A, 0xD3BD, 0xC39C, 0xF3FF, 0xE3DE,
	0x2462, 0x3443, 0x0420, 0x1401, 0x64E6, 0x74C7, 0x44A4, 0x5485,
	0xA56A, 0xB54B, 0x8528, 0x9509, 0xE5EE, 0xF5CF, 0xC5AC, 0xD58D,
	0x3653, 0x2672, 0x1611, 0x0630, 0x76D7, 0x66F6, 0x5695, 0x46B4,
	0xB75B, 0xA77A, 0x9719, 0x8738, 0xF7DF, 0xE7FE, 0xD79D, 0xC7BC,
	0x48C4, 0x58E5, 0x6886, 0x78A7, 0x0840, 0x1861, 0x2802, 0x3823,
	0xC9CC, 0xD9ED, 0xE98E, 0xF9AF, 0x8948, 0x9969, 0xA90A, 0xB92B,
	0x5AF5, 0x4AD4, 0x7AB7, 0x6A96, 0x1A71, 0x0A50, 0x3A33, 0x2A12,
	0xDBFD, 0xCBDC, 0xFBBF, 0xEB9E, 0x9B79, 0x8B58, 0xBB3B, 0xAB1A,
	0x6CA6, 0x7C87, 0x4CE4, 0x5CC5, 0x2C22, 0x3C03, 0x0C60, 0x1C41,
	0xEDAE, 0xFD8F, 0xCDEC, 0xDDCD, 0xAD2A, 0xBD0B, 0x8D68, 0x9D49,
	0x7E97, 0x6EB6, 0x5ED5, 0x4EF4, 0x3E13, 0x2E32, 0x1E51, 0x0E70,
	0xFF9F, 0xEFBE, 0xDFDD, 0xCFFC, 0xBF1B, 0xAF3A, 0x9F59, 0x8F78,
	0x9188, 0x81A9, 0xB1CA, 0xA1EB, 0xD10C, 0xC12D, 0xF14E, 0xE16F,
	0x1080, 0x00A1, 0x30C2, 0x20E3, 0x5004, 0x4025, 0x7046, 0x6067,
	0x83B9, 0x9398, 0xA3FB, 0xB3DA, 0xC33D, 0xD31C, 0xE37F, 0xF35E,
	0x02B1, 0x1290, 0x22F3, 0x32D2, 0x4235, 0x5214, 0x6277, 0x7256,
	0xB5EA, 0xA5CB, 0x95A8, 0x8589, 0xF56E, 0xE54F, 0xD52C, 0xC50D,
	0x34E2, 0x24C3, 0x14A0, 0x0481, 0x7466, 0x6447, 0x5424, 0x4405,
	0xA7DB, 0xB7FA, 0x8799, 0x97B8, 0xE75F, 0xF77E, 0xC71D, 0xD73C,
	0x26D3, 0x36F2, 0x0691, 0x16B0, 0x6657, 0x7676, 0x4615, 0x5634,
	0xD94C, 0xC96D, 0xF90E, 0xE92F, 0x99C8, 0x89E9, 0xB98A, 0xA9AB,
	0x5844, 0x4865, 0x7806, 0x6827, 0x18C0, 0x08E1, 0x3882, 0x28A3,
	0xCB7D, 0xDB5C, 0xEB3F, 0xFB1E, 0x8BF9, 0x9BD8, 0xABBB, 0xBB9A,
	0x4A75, 0x5A54, 0x6A37, 0x7A16, 0x0AF1, 0x1AD0, 0x2AB3, 0x3A92,
	0xFD2E, 0xED0F, 0xDD6C, 0xCD4D, 0xBDAA, 0xAD8B, 0x9DE8, 0x8DC9,
	0x7C26, 0x6C07, 0x5C64, 0x4C45, 0x3CA2, 0x2C83, 0x1CE0, 0x0CC1,
	0xEF1F, 0xFF3E, 0xCF5D, 0xDF7C, 0xAF9B, 0xBFBA, 0x8FD9, 0x9FF8,
	0x6E17, 0x7E36, 0x4E55, 0x5E74, 0x2E93, 0x3EB2, 0x0ED1, 0x1EF0
};

void ResetDiskState();

uint16_t writeErrorNumber;

void error(const char* msg)
{
	bool wasWriteError = writeError;
	uint16_t wasWriteErrorNumber = writeErrorNumber;
	
	ResetDiskState(); // clears writeError and writeErrorNumber

	LcdClear();
	LcdGoto(0,0);
	if (wasWriteError)
		LcdTinyStringP(PSTR("WRITE ERROR          "), TEXT_INVERSE);		
	else
		LcdTinyStringP(PSTR("FATAL ERROR          "), TEXT_INVERSE);
	LcdGoto(0,1);
	LcdTinyString(msg, TEXT_NORMAL);
	
	if (wasWriteError)
	{		
		snprintf(textBuf, TEXTBUF_SIZE, "%u", wasWriteErrorNumber);
		LcdGoto(0,5);
		LcdTinyString(textBuf, TEXT_NORMAL);
	}
					
	while (1);	
}	
		
void InitPorts()
{
	// set all data lines as outputs, MSB (RD_ACK/WR_TICK) as input
	DDR(CPLD_DATA_PORT) = 0x7F;
	
	// initialize the other output lines
	DDR(CPLD_TACH_PORT) |= (1<<CPLD_TACH_PIN);
	DDR(CPLD_RESET_PORT) |= (1<<CPLD_RESET_PIN);
	DDR(CPLD_STEP_ACK_DISK_IN_PORT) |= (1<<CPLD_STEP_ACK_DISK_IN_PIN);
	DDR(CPLD_RD_READY_TK0_PORT) |= (1<<CPLD_RD_READY_TK0_PIN);
	DDR(CPLD_DATA_HIZ_PORT) |= (1<<CPLD_DATA_HIZ_PIN);
	DDR(CPLD_TMS_PORT) |= (1<<CPLD_TMS_PIN);
	DDR(STATUS_LED_PORT) |= (1<<STATUS_LED_PIN);
	
	// enable the pull-up resistor for the user buttons and SD card write protect
	PORT(SELECT_BUTTON_PORT) |= (1<<SELECT_BUTTON_PIN);
	PORT(PREV_BUTTON_PORT) |= (1<<PREV_BUTTON_PIN);
	PORT(NEXT_BUTTON_PORT) |= (1<<NEXT_BUTTON_PIN);
	PORT(CARD_WPROT_PORT) |= (1<<CARD_WPROT_PIN);
	
	// enable the pull-up resistors for the CPLD, for when we're midway through a build and it's not there yet
	PORT(CPLD_EJECT_REQ_PORT) |= (1<<CPLD_EJECT_REQ_PIN);
	PORT(CPLD_RD_ACK_WR_TICK_PORT) |= (1<<CPLD_RD_ACK_WR_TICK_PIN);
	PORT(CPLD_STEP_REQ_PORT) |= (1<<CPLD_STEP_REQ_PIN);
	PORT(CPLD_WR_REQ_PORT) |= (1<<CPLD_WR_REQ_PIN);
	PORT(CPLD_CURRENT_SIDE_PORT) |= (1<<CPLD_CURRENT_SIDE_PIN);
							
	// set initial output values
	PORT(CPLD_RESET_PORT) |= (1<<CPLD_RESET_PIN);
	PORT(CPLD_STEP_ACK_DISK_IN_PORT) |= (1<<CPLD_STEP_ACK_DISK_IN_PIN);
	PORT(CPLD_RD_READY_TK0_PORT) &= ~(1<<CPLD_RD_READY_TK0_PIN);
	PORT(CPLD_DATA_HIZ_PORT) &= ~(1<<CPLD_DATA_HIZ_PIN);
	PORT(CPLD_TMS_PORT) |= (1<<CPLD_TMS_PIN); // TMS=1, stay in JTAG reset
	PORT(STATUS_LED_PORT) |= (1<<STATUS_LED_PIN);
	
	// set the pin change interrupt masks
	CPLD_STEP_REQ_INT_MSK |= (1<<CPLD_STEP_REQ_INT_PIN);
	CPLD_CURRENT_SIDE_INT_MSK |= (1<<CPLD_CURRENT_SIDE_INT_PIN);
	CPLD_WR_REQ_INT_MSK |= (1<<CPLD_WR_REQ_INT_PIN);
	//CPLD_RD_ACK_WR_TICK_INT_MSK |= (1<<CPLD_RD_ACK_WR_TICK_INT_PIN);
}

void SetTach()
{	
	/*
	Produces 60 pulses for each rotation of the drive motor
	Data from Apple/Sony docs:
	   Tracks	RPM   Acceptable Speed Range in ROM
	   00-15:   394   1135-11E9
	   16-31:   429   12C6-138A
	   32-47:   472   14A7-157F
	   48-63:   525   16F2-17E2
	   64-79:   590   19D0-1ADE
			
	Experimentally determined TACH toggle rates for Plus Too, running at 8.125 MHz:
		TACH Half Period Clocks		Resulting Timing Value Computed By Mac
					9996				    $117B (4475)           
					9122  				    $1328 (4904)
					8292  				    $1513 (5395)
					7463  				    $176A (5994)
					6634				    $1A56 (6742)
		
	A real Mac runs slightly slower than Plus Too (7.8336 MHz vs 8.125 MHz), so those
	timing values should be scaled by multiplying by 7.8336/8.125 = 0.96414
	
	Scaling these values and converting to real time units, we get:
	   Tracks	TACH Half Period    Implied RPM   
	   00-15:      1276.04 us         391.84
	   16-31:      1164.47 us         429.38
	   32-47:      1058.52 us         472.36
	   48-63:       952.69 us         524.83
	   64-79:       846.86 us         590.41

	*/
	
	const uint16_t zoneRPM[] = { 394, 429, 472, 525, 590 };	
	uint8_t speedZone = currentTrack >> 4;	
	if (speedZone > 4)
		speedZone = 4;
				
	ATOMIC_BLOCK(ATOMIC_RESTORESTATE)
	{
		driveTachHalfPeriod = F_CPU / (2 * zoneRPM[speedZone]);		
		// OCR1A update will be performed during the next sector 0 read			
	}	
	
	TIFR1 = (1 << OCF1A); // Clear the timer 1 compare A match flag. Not sure this is actually necessary.
}

uint16_t trackStart(uint8_t trackNumber)
{
	return mfmMode ? trackNumber * 18 : pgm_read_word(&sony_track_start[trackNumber]);
}

uint8_t trackLength(uint8_t trackNumber)
{
	return mfmMode ? 18 : pgm_read_byte(&sony_track_len[trackNumber]);
}
	
#define BUFFER_DIRTY 1
#define BUFFER_DATA_VALID 2
#define BUFFER_LOCKED 4

volatile uint8_t bufferState[NUM_BUFFERS];
volatile uint8_t wrTrack;
volatile uint8_t wrSide;
volatile uint8_t wrSector;

// these variables are used only within the interrupt routine, and do not need to be declared volatile
uint8_t wrTick;
uint16_t writeCount;
uint8_t writeTemp;
uint8_t currentWriteBufferNumber;
uint8_t ck5, ck6, ck7;
uint8_t XBit;
uint8_t* pSectorBuf;
	
void WriteError()
{
	writeMode = false;
	restartDisk = true;
	writeError = true;
	writeCount = 0;
}

// pin state change interrupt: STEP
ISR(PCINT3_vect) 
{ 		
	// step to a new track?
	if (bit_is_set(PIN(CPLD_STEP_REQ_PORT), CPLD_STEP_REQ_PIN))
	{	
		// determine the range of dirty sector buffers
		uint8_t trackLen = trackLength(currentTrack);
		uint8_t firstDirtyBuffer = NUM_BUFFERS, lastDirtyBuffer=0;
	
		for (uint8_t i=0; i<trackLen*2 && i<NUM_BUFFERS; i++)
		{
			if (bufferState[i] & BUFFER_DIRTY)
			{
				if (firstDirtyBuffer == NUM_BUFFERS)
					firstDirtyBuffer = i;
				
				lastDirtyBuffer = i;
			}
		}
	
		// Lock all the buffers in the dirty range, preventing writes to them. This prevents a problem:
		// A write for the new track could fill a buffer before the old track's data was completely flushed back to SD, 
		// causing some of track N+1's data to get written to track N.
		// Also mark all buffers in the dirty range as dirty if they have valid data, even if they weren't actually 
		// dirty. The dirty range will have to be written out as a contiguous block anyway. The flush method
		// can then detect when buffers in the range don't have valid data, because they won't have the dirty flag.
		for (uint8_t i=firstDirtyBuffer; i<=lastDirtyBuffer; i++)					
		{
			if (bufferState[i] & BUFFER_DATA_VALID)
				bufferState[i] |= BUFFER_DIRTY;
				
			bufferState[i] |= BUFFER_LOCKED;
		}
				
		// step to the next track
		if (bit_is_set(PIN(CPLD_STEP_DIR_MOTOR_ON_PORT), CPLD_STEP_DIR_MOTOR_ON_PIN))
		{
			// bounds check at 0
			if (currentTrack != 0)
				currentTrack--;			
		}
		else
		{
			currentTrack++;
		}
		
		// assert ack and send new TK0 indicator
		if (currentTrack == 0)
			PORT(CPLD_RD_READY_TK0_PORT) &= ~(1<<CPLD_RD_READY_TK0_PIN);
		else
			PORT(CPLD_RD_READY_TK0_PORT) |= (1<<CPLD_RD_READY_TK0_PIN);		
		PORT(CPLD_STEP_ACK_DISK_IN_PORT) |= (1<<CPLD_STEP_ACK_DISK_IN_PIN);
				
		// wait for de-assertion of step request
		while (bit_is_set(PIN(CPLD_STEP_REQ_PORT), CPLD_STEP_REQ_PIN))
		{
		}
			
		// de-assert ack and RD_READY
		PORT(CPLD_STEP_ACK_DISK_IN_PORT) &= ~(1<<CPLD_STEP_ACK_DISK_IN_PIN);
		PORT(CPLD_RD_READY_TK0_PORT) |= (1<<CPLD_RD_READY_TK0_PIN);
		
		SetTach();
		
		// premature end of a write?
		if (writeCount >= SECTOR_DATA_SECTORNUM_START)
		{
			strncpy(textBuf, "incomplete write", TEXTBUF_SIZE);
			writeErrorNumber = 1000 + writeCount;
			WriteError();		
		}
										
		restartDisk = true;
	}
}
		
// pin state change interrupt: SIDE, WR_REQ
ISR(PCINT2_vect) 
{ 				
	// did the current side change?
	uint8_t newSide = ((PIN(CPLD_CURRENT_SIDE_PORT) >> CPLD_CURRENT_SIDE_PIN) & 0x01);
	if (newSide != currentSide)
	{
		if (numberOfDiskSides == 2)
			currentSide = newSide;
		else
			currentSide = 0;
		restartDisk = true;
		
		// premature end of a write?
		if (writeCount >= SECTOR_DATA_SECTORNUM_START)
		{
			strncpy(textBuf, "incomplete write", TEXTBUF_SIZE);
			writeErrorNumber = 2000 + writeCount;
			WriteError();		
		}		
	}		

	// did the read/write mode change? RD_RW: 0 = write, 1 = read
	uint8_t wreqBit = bit_is_clear(PIN(CPLD_WR_REQ_PORT), CPLD_WR_REQ_PIN);
	if (wreqBit != writeMode)
	{
		writeMode = wreqBit;	
		
		if (writeMode)
		{	
			// switch the DATA pins to inputs
			DDR(CPLD_DATA_PORT) = 0;
		
			// indicate that the data bus has been released
			PORT(CPLD_DATA_HIZ_PORT) |= (1<<CPLD_DATA_HIZ_PIN);
		
			// enable the WR_TICK interrupt
			CPLD_RD_ACK_WR_TICK_INT_MSK |= (1<<CPLD_RD_ACK_WR_TICK_INT_PIN);	
						
			wrTick = bit_is_set(PIN(CPLD_RD_ACK_WR_TICK_PORT), CPLD_RD_ACK_WR_TICK_PIN);
			
			writeCount = 0;
		}
		else
		{
			// switch the DATA pins to outputs
			DDR(CPLD_DATA_PORT) = 0x7F;
		
			// indicate that the data bus has been reacquired
			PORT(CPLD_DATA_HIZ_PORT) &= ~(1<<CPLD_DATA_HIZ_PIN);
		
			// disable the WR_TICK interrupt
			CPLD_RD_ACK_WR_TICK_INT_MSK &= ~(1<<CPLD_RD_ACK_WR_TICK_INT_PIN);
			
			// premature end of a write?
			if (writeCount >= SECTOR_DATA_SECTORNUM_START)
			{
				strncpy(textBuf, "incomplete write", TEXTBUF_SIZE);
				writeErrorNumber = 3000 + writeCount;
				WriteError();		
			}
		}		
		
		restartDisk = true;	
	}	
}

void HandleGCRWrite()
{			
	uint8_t diskByte = 0x80 | PIN(CPLD_DATA_PORT);
			
	if (writeCount < SECTOR_DATA_ENCODED_TAGS_START)
	{
		// look for the sector header
		// the final header byte is the sector number
		if (writeCount == SECTOR_DATA_SECTORNUM_START)
		{
			uint8_t sector = pgm_read_byte(&disk_byte_to_sony[diskByte - 0x96]);
		
			if (sector >= trackLength(currentTrack))
			{
				snprintf(textBuf, TEXTBUF_SIZE, "bad sector %d for t%d", sector, currentTrack);
				writeErrorNumber = 60;
				WriteError();	
			}		
		
			uint8_t trackLen = trackLength(currentTrack);
			currentWriteBufferNumber = trackLen * currentSide + sector;	
					
			if (bufferState[currentWriteBufferNumber] & BUFFER_LOCKED)
			{
				snprintf(textBuf, TEXTBUF_SIZE, "buf locked %d/%d:%d", currentTrack, currentSide, sector);
				writeErrorNumber = 61;
				WriteError();			
			}
										
			pSectorBuf = sectorBuf[currentWriteBufferNumber];
			bufferState[currentWriteBufferNumber] |= BUFFER_LOCKED;
			bufferState[currentWriteBufferNumber] &= ~BUFFER_DATA_VALID;
			wrTrack = currentTrack;
			wrSide = currentSide;
			wrSector = sector; 
					
			// turn on the LED when receiving a sector write
			PORT(STATUS_LED_PORT) &= ~(1<<STATUS_LED_PIN);
			
			ck5 = ck6 = ck7 = 0;	
		}
		else if (diskByte != sectorDataHeaderGCR[writeCount] || readOnly)
		{
			// header doesn't match: start over
			writeCount = 0;
			return;
		}			
	}
	else
	{
		uint8_t dataIn = pgm_read_byte(&disk_byte_to_sony[diskByte - 0x96]);
		uint8_t b;
				
		// read the sector data
		if (writeCount < SECTOR_DATA_CHECKSUM_START)
		{		
			uint16_t addResult;
					
			switch (writeCount & 0x03)
			{
				case 0:
					writeTemp = dataIn << 2; // get 0 0 A7 A6 B7 B6 C7 C6, store it as A7 A6 B7 B6 C7 C6 0 0
							
					//ROL(ck7); 
					XBit = ck7 >> 7;
					ck7 = (ck7 << 1) | XBit;
					break;
							
				case 1:			
					b = (writeTemp & 0xC0) | dataIn; // A7 A6 0 0 0 0 0 0 | 0 0 A5 A4 A3 A2 A1 A0
					b ^= ck7;

					if (writeCount >= SECTOR_DATA_ENCODED_DATA_START)
						*pSectorBuf++ = b;
							
					//ADDX(ck5, b);	
					addResult = (uint16_t)ck5 + b + XBit; 
					ck5 = addResult & 0xFF;
					XBit = addResult >> 8;
					writeTemp <<= 2; // B7 B6 C7 C6 0 0 0 0 
					break;
							
				case 2:
					b = (writeTemp & 0xC0) | dataIn; // B7 B6 0 0 0 0 0 0 | 0 0 B5 B4 B3 B2 B1 B0
					b ^= ck5;
							
					if (writeCount >= SECTOR_DATA_ENCODED_DATA_START)
						*pSectorBuf++ = b; 
							
					//ADDX(ck6, b);	
					addResult = (uint16_t)ck6 + b + XBit; 
					ck6 = addResult & 0xFF;
					XBit = addResult >> 8;
					writeTemp <<= 2; // C7 C6 0 0 0 0 0 0
					break;
							
				case 3:
					b = writeTemp | dataIn; // C7 C6 0 0 0 0 0 0 | 0 0 C5 C4 C3 C2 C1 C0
					b ^= ck6;
							
					if (writeCount >= SECTOR_DATA_ENCODED_DATA_START)
						*pSectorBuf++ = b;
								
					//ADDX(ck7, b);
					addResult = (uint16_t)ck7 + b + XBit; 
					ck7 = addResult & 0xFF;
					XBit = addResult >> 8;
					break;
			}
		}
		else
		{						
			// verify the checksum
			if (writeCount == SECTOR_DATA_CHECKSUM_START)
			{
				writeTemp = dataIn; 
				writeTemp <<= 2;
			}
			else if (writeCount == SECTOR_DATA_CHECKSUM_START+1)
			{
				b = (writeTemp & 0xC0) | dataIn;
				writeTemp <<= 2;
				if (b != ck5)
				{
					strncpy(textBuf, "checksum failure 0", TEXTBUF_SIZE);
					writeErrorNumber = 62;
					WriteError();				
				}
			}
			else if (writeCount == SECTOR_DATA_CHECKSUM_START+2)
			{
				b = (writeTemp & 0xC0) | dataIn;
				writeTemp <<= 2;
				if (b != ck6)
				{
					strncpy(textBuf, "checksum failure 1", TEXTBUF_SIZE);
					writeErrorNumber = 63;
					WriteError();
				}
			}
			else if (writeCount == SECTOR_DATA_CHECKSUM_START+3)
			{
				b = writeTemp | dataIn;
				writeTemp <<= 2;
				if (b != ck7)
				{
					strncpy(textBuf, "checksum failure 2", TEXTBUF_SIZE);
					writeErrorNumber = 64;
					WriteError();
				}
						
				// success! 
				bufferState[currentWriteBufferNumber] |= BUFFER_DATA_VALID;
				bufferState[currentWriteBufferNumber] |= BUFFER_DIRTY;
				bufferState[currentWriteBufferNumber] &= ~BUFFER_LOCKED;
						
				// turn off the LED at the end of a sector write
				PORT(STATUS_LED_PORT) |= (1<<STATUS_LED_PIN);

				// prepare for the next sector write
				writeCount = 0;
				return;
			}	
			else
			{
				// how'd we get here?
				writeCount = 0;
				return;
			}									
		}
	}	
			
	writeCount++;				
}

void CheckMFMCRC(uint8_t bufferNumber)
{
	uint16_t receivedCRC = crc;
	
	crc = 0xFFFF;
	crc = (crc << 8) ^ pgm_read_word(&crc_ccitt[(uint8_t)(crc >> 8) ^ 0xA1]);		
	crc = (crc << 8) ^ pgm_read_word(&crc_ccitt[(uint8_t)(crc >> 8) ^ 0xA1]);		
	crc = (crc << 8) ^ pgm_read_word(&crc_ccitt[(uint8_t)(crc >> 8) ^ 0xA1]);		
	crc = (crc << 8) ^ pgm_read_word(&crc_ccitt[(uint8_t)(crc >> 8) ^ 0xFB]);	
	
	for (uint16_t i=0; i<SECTOR_DATA_SIZE; i++)
	{
		crc = (crc << 8) ^ pgm_read_word(&crc_ccitt[(uint8_t)(crc >> 8) ^ sectorBuf[bufferNumber][i]]);	
	}			
	
	if (crc != receivedCRC)
	{
		strncpy(textBuf, "checksum fail", TEXTBUF_SIZE);
		writeErrorNumber = 70;
		WriteError();
	}	
}

// pin state change interrupt: WR_TICK
ISR(PCINT0_vect) 
{ 
	uint8_t wrTickBit = bit_is_set(PIN(CPLD_RD_ACK_WR_TICK_PORT), CPLD_RD_ACK_WR_TICK_PIN);
	
	// was a new byte written?
	if (writeMode && wrTickBit != wrTick)
	{
		wrTick = wrTickBit;
		
		if (!mfmMode)
		{
			HandleGCRWrite();
		}	
		else
		{
			if (wrTickBit == 0)
			{
				// high nibble arrives first
				writeTemp = (PIN(CPLD_DATA_PORT) << 4) & 0xF0;
				return;
			}					
			else
			{
				writeTemp |= (PIN(CPLD_DATA_PORT) & 0x0F);
				
				if (writeCount == 2)
				{
					if (writeTemp == 0xFB)
					{
						writeCount++;
						
						// header received OK!
						currentWriteBufferNumber = currentSector; // assume the buffer to write was the last one read	
					
						if (bufferState[currentWriteBufferNumber] & BUFFER_LOCKED)
						{
							snprintf(textBuf, TEXTBUF_SIZE, "buf locked %d/%d:%d", currentTrack, currentSide, currentSector);
							writeErrorNumber = 71;
							WriteError();
							return;		
						}
										
						pSectorBuf = sectorBuf[currentWriteBufferNumber];
						bufferState[currentWriteBufferNumber] |= BUFFER_LOCKED;
						bufferState[currentWriteBufferNumber] &= ~BUFFER_DATA_VALID;
						wrTrack = currentTrack;
						wrSide = currentSide;
						wrSector = currentSector; // assume the buffer to write was the last one read		
						
						// turn on the LED when receiving a sector write
						PORT(STATUS_LED_PORT) &= ~(1<<STATUS_LED_PIN);
					}						
					else if (writeTemp != 0xA1)
					{
						writeCount = 0;
					}						
				}
				else if (writeCount == 0)
				{
					if (writeTemp == 0xA1)
					writeCount++;
				}
				else if (writeCount == 1)
				{
					if (writeTemp == 0xA1)
						writeCount++;
					else
						writeCount = 0;
				}						
				else if (writeCount < SECTOR_DATA_SIZE+3)
				{
					sectorBuf[currentWriteBufferNumber][writeCount-3] = writeTemp;
					writeCount++;
				} 
				else if (writeCount == SECTOR_DATA_SIZE+3)
				{
					crc = writeTemp << 8;
					
					writeCount++; 
				} 
				else if (writeCount == SECTOR_DATA_SIZE+4)
				{
					crc |= writeTemp;
					
					CheckMFMCRC(currentWriteBufferNumber);
					
					// success! 
					bufferState[currentWriteBufferNumber] |= BUFFER_DATA_VALID;
					bufferState[currentWriteBufferNumber] |= BUFFER_DIRTY;
					bufferState[currentWriteBufferNumber] &= ~BUFFER_LOCKED;
						
					// turn off the LED at the end of a sector write
					PORT(STATUS_LED_PORT) |= (1<<STATUS_LED_PIN);
					
					// prepare for the next sector write
					writeCount = 0;
				} 
			}												
		}		
	}
}
	
void SendByte(uint8_t b)
{								
	cli();
	
	if (!writeMode)
	{		
		PORT(CPLD_DATA_PORT) = b & 0x7F;	
		PORT(CPLD_RD_READY_TK0_PORT) |= (1<<CPLD_RD_READY_TK0_PIN);		
		
		// wait for ack to go high, then low
		// TODO: find a way to avoid two busy waits here-- can it be done with just one?
		while (bit_is_clear(PIN(CPLD_RD_ACK_WR_TICK_PORT), CPLD_RD_ACK_WR_TICK_PIN)) 
		{}
	
		// is this really needed? Probably - when sending literal data like the GCR address header
		// the AVR is probably fast enough to call SendByte twice within the space of a single bit (320 clock cycles)
		// which would then do the wrong thing without this second busy wait.
		while (bit_is_set(PIN(CPLD_RD_ACK_WR_TICK_PORT), CPLD_RD_ACK_WR_TICK_PIN)) 
		{}
						
		// clear the read byte ready flag	
		PORT(CPLD_RD_READY_TK0_PORT) &= ~(1<<CPLD_RD_READY_TK0_PIN);	
	}
		
	sei();	
}

// make a 6-bit result from the top 2 bits of three input bytes
#define nib4(__c0, __c1, __c2) \
	(((__c0 & 0xC0) >> 2) | ((__c1 & 0xC0) >> 4) | ((__c2 & 0xC0) >> 6))

// rotate left
#define rot_ck0(__ck0)						\
	do {									\
		__ck0 &= 0xFF;						\
		__ck0 = (__ck0 << 1) | (__ck0 >> 7);\
	} while(0)

// ADC __ckr, __in; __out = __in ^ __ckl
#define enc_byte(__in, __out, __ckl, __ckr)	\
	do {									\
		uint8_t __d = __in;					\
		__ckr += __d;						\
		__ckr += (__ckl & 0x100) >> 8;		\
		__ckl &= 0xFF;						\
		__out = __d ^ __ckl;				\
	} while(0)

#define SendByteAndCheckRestart(b)			\
	do {									\
		if (restartDisk)					\
			goto restart;					\
		SendByte(b);						\
	} while(0)
		
void SendMFMSync()
{
	// send A1 sync
	
	// SendByte
	// TODO: what if an interrupt has switched the data port to an input? This will turn on pull-ups
	PORT(CPLD_DATA_PORT) = 0x0A; // data in bits 3-0, sync flag in bit 4	
	PORT(CPLD_RD_READY_TK0_PORT) |= (1<<CPLD_RD_READY_TK0_PIN);		
	// wait for ack to go high, then low
	while (bit_is_clear(PIN(CPLD_RD_ACK_WR_TICK_PORT), CPLD_RD_ACK_WR_TICK_PIN));
	while (bit_is_set(PIN(CPLD_RD_ACK_WR_TICK_PORT), CPLD_RD_ACK_WR_TICK_PIN));
	PORT(CPLD_RD_READY_TK0_PORT) &= ~(1<<CPLD_RD_READY_TK0_PIN);

	crc = (crc << 8) ^ pgm_read_word(&crc_ccitt[(uint8_t)(crc >> 8) ^ 0xA1]);
	
	// SendByte
	// TODO: what if an interrupt has switched the data port to an input? This will turn on pull-ups
	PORT(CPLD_DATA_PORT) = 0x11; // data in bits 3-0, sync flag in bit 4	
	PORT(CPLD_RD_READY_TK0_PORT) |= (1<<CPLD_RD_READY_TK0_PIN);		
	// wait for ack to go high, then low
	while (bit_is_clear(PIN(CPLD_RD_ACK_WR_TICK_PORT), CPLD_RD_ACK_WR_TICK_PIN));
	while (bit_is_set(PIN(CPLD_RD_ACK_WR_TICK_PORT), CPLD_RD_ACK_WR_TICK_PIN));
	PORT(CPLD_RD_READY_TK0_PORT) &= ~(1<<CPLD_RD_READY_TK0_PIN);		
}	
	
void SendMFMByte(uint8_t data)
{	
	// send X 0 0 0 D7 D6 D5 D4
	uint8_t out = (data >> 4) & 0x0F;
	
	//if (restartDisk)
	//	return;
		 
	// SendByte
	// TODO: what if an interrupt has switched the data port to an input? This will turn on pull-ups
	PORT(CPLD_DATA_PORT) = out;	
	PORT(CPLD_RD_READY_TK0_PORT) |= (1<<CPLD_RD_READY_TK0_PIN);		
	// wait for ack to go high, then low
	while (bit_is_clear(PIN(CPLD_RD_ACK_WR_TICK_PORT), CPLD_RD_ACK_WR_TICK_PIN));
	while (bit_is_set(PIN(CPLD_RD_ACK_WR_TICK_PORT), CPLD_RD_ACK_WR_TICK_PIN));
	PORT(CPLD_RD_READY_TK0_PORT) &= ~(1<<CPLD_RD_READY_TK0_PIN);

	// send X 0 0 0 D3 D2 D1 d0
	out = (data & 0x0F);
		
	crc = (crc << 8) ^ pgm_read_word(&crc_ccitt[(uint8_t)(crc >> 8) ^ data]);		
	
	//if (restartDisk)
	//	return;
			
	// SendByte
	// TODO: what if an interrupt has switched the data port to an input? This will turn on pull-ups
	PORT(CPLD_DATA_PORT) = out;	
	PORT(CPLD_RD_READY_TK0_PORT) |= (1<<CPLD_RD_READY_TK0_PIN);		
	// wait for ack to go high, then low
	while (bit_is_clear(PIN(CPLD_RD_ACK_WR_TICK_PORT), CPLD_RD_ACK_WR_TICK_PIN));
	while (bit_is_set(PIN(CPLD_RD_ACK_WR_TICK_PORT), CPLD_RD_ACK_WR_TICK_PIN));
	PORT(CPLD_RD_READY_TK0_PORT) &= ~(1<<CPLD_RD_READY_TK0_PIN);
}
	
#define SendMFMAndCheckRestart(d)			\
	do {									\
		if (restartDisk)					\
			goto restart;					\
		SendMFMByte(d);						\
	} while(0)
			
void SendGCRSectorData(const uint8_t* data)
{
	uint16_t ck0, ck1, ck2;
	uint8_t b0, b1, b2;
	uint8_t i;
	static const uint8_t tags[] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
	const uint8_t* p = tags;

	ck0 = ck1 = ck2 = 0;

	// Do 12 bytes of tags plus 510 bytes of data
	for (i = 0; i < 174; i++) 
	{
		if (i == 4)
			p = data;
	
		rot_ck0(ck0); // ROL byte by 1 bit
		enc_byte(*(p++), b0, ck0, ck2);
		enc_byte(*(p++), b1, ck2, ck1);
		enc_byte(*(p++), b2, ck1, ck0);
		SendByte(pgm_read_byte(&sony_to_disk_byte[nib4(b0, b1, b2)]));
		SendByte(pgm_read_byte(&sony_to_disk_byte[b0 & 0x3F]));
		
		// was there a disk restart?
		if (restartDisk)
			return;
			
		SendByte(pgm_read_byte(&sony_to_disk_byte[b1 & 0x3F]));	
		SendByte(pgm_read_byte(&sony_to_disk_byte[b2 & 0x3F]));	
	}

	// Then do remaining 2 bytes of data
	rot_ck0(ck0);
	enc_byte(*(p++), b0, ck0, ck2);
	enc_byte(*(p++), b1, ck2, ck1);

	SendByte(pgm_read_byte(&sony_to_disk_byte[nib4(b0, b1, 0)]));
	SendByte(pgm_read_byte(&sony_to_disk_byte[b0 & 0x3F]));
	SendByte(pgm_read_byte(&sony_to_disk_byte[b1 & 0x3F]));

	// And write out checksum
	SendByte(pgm_read_byte(&sony_to_disk_byte[nib4(ck2, ck1, ck0)]));
	SendByte(pgm_read_byte(&sony_to_disk_byte[ck2 & 0x3F]));		
	SendByte(pgm_read_byte(&sony_to_disk_byte[ck1 & 0x3F]));		
	SendByte(pgm_read_byte(&sony_to_disk_byte[ck0 & 0x3F]));
}

SdBaseFile f;
unsigned int sectorOffset;
unsigned char GetNextFirmwareByte()
{			
	if (sectorOffset == SECTOR_DATA_SIZE)
	{
		// read the next sector of the XSVF file
		if (f.read(&sectorBuf[0], SECTOR_DATA_SIZE) < 0)
		{
			error("SD read error R");
		}
		sectorOffset = 0;
		
		PORT(STATUS_LED_PORT) ^= (1<<STATUS_LED_PIN);
	}

	unsigned char result = sectorBuf[0][sectorOffset];
	
	sectorOffset++;
		
	return result;
}

void UpdateFirmware()
{
	LcdClear();
	LcdGoto(0,0);
	LcdTinyStringP(PSTR("Updating firmware..."), TEXT_NORMAL);	
	
	// wait for the NEXT button to be released
	while (bit_is_clear(PIN(NEXT_BUTTON_PORT), NEXT_BUTTON_PIN))
	{}
	
	_delay_ms(400);
			
	// open the XSVF file on the SD card			
	if (!f.open("firmware.xvf", O_RDONLY)) 
	{
		LcdGoto(0,2);
		LcdTinyStringP(PSTR("Could not open"), TEXT_NORMAL);
		LcdGoto(0,3);
		LcdTinyStringP(PSTR("firmware.xvf"), TEXT_NORMAL);
	}	
	else
	{	
		// read the first sector of the XSVF file
		if (f.read(&sectorBuf[0], SECTOR_DATA_SIZE) < 0)
		{
			error("SD read error R");
		}
		sectorOffset = 0;
		
		setReadCallback(GetNextFirmwareByte);
		
		// disable interrupts and execute the XSVF
		int result;
		ATOMIC_BLOCK(ATOMIC_RESTORESTATE)
		{
			result = xsvfExecute();
		}
		
		f.close();
		
		LcdGoto(0,2);
		
		if (result == 0)
			strncpy(textBuf, "Result: success", TEXTBUF_SIZE);
		else
			snprintf(textBuf, TEXTBUF_SIZE, "Result: error %d", result);
			
		LcdTinyString(textBuf, TEXT_NORMAL);	
	}
		
	// display the firmware update result until a button is pressed
	LcdGoto(0,5);
	LcdTinyStringP(PSTR("Press any button"), TEXT_NORMAL);
	
	while (bit_is_set(PIN(PREV_BUTTON_PORT), PREV_BUTTON_PIN) && 
		   bit_is_set(PIN(NEXT_BUTTON_PORT), NEXT_BUTTON_PIN) && 
		   bit_is_set(PIN(SELECT_BUTTON_PORT),SELECT_BUTTON_PIN))
	{}
		
	LcdClear();
}

void ShowVersion()
{	
	LcdGoto(0,0);
	LcdTinyStringP(PSTR("     FLOPPY EMU      "), TEXT_INVERSE);
	LcdGoto(2*(21-strlen_P(versionStr)),2);
	LcdTinyStringP(versionStr, TEXT_NORMAL);
	snprintf(textBuf, TEXTBUF_SIZE, "CPLD Firmware %d", cpldFirmwareVersion);
	LcdGoto(2*(21-strlen(textBuf)),3);
	LcdTinyString(textBuf, TEXT_NORMAL);
	
	// LED off
	PORT(STATUS_LED_PORT) &= ~(1<<STATUS_LED_PIN);
	
	millitimerOn();
							
	for (uint8_t i=0; i<5; i++)
	{
		_delay_ms(800);
		// toggle LED
		PORT(STATUS_LED_PORT) ^= (1<<STATUS_LED_PIN);
	}
	
	millitimerOff();
}

void AdjustContrast(void)
{
	LcdGoto(0,0);
	LcdClear();
	LcdGoto(0,0);
	LcdTinyStringP(PSTR("CONTRAST ADJUSTMENT"), TEXT_NORMAL);
	LcdGoto(0,2);
	LcdTinyStringP(PSTR("Release buttons to"), TEXT_NORMAL);
	LcdGoto(0,3);
	LcdTinyStringP(PSTR("begin"), TEXT_NORMAL);
		
	// wait for the buttons to be released
	while (bit_is_clear(PIN(PREV_BUTTON_PORT), PREV_BUTTON_PIN) || 
			bit_is_clear(PIN(NEXT_BUTTON_PORT), NEXT_BUTTON_PIN) ||
			bit_is_clear(PIN(SELECT_BUTTON_PORT), SELECT_BUTTON_PIN))
	{}
	
	LcdGoto(0,0);
	LcdClear();
		
	LcdGoto(0,2);
	LcdTinyStringP(PSTR("PREV: Lighter"), TEXT_NORMAL);
	LcdGoto(0,3);
	LcdTinyStringP(PSTR("NEXT: Darker"), TEXT_NORMAL);
	LcdGoto(0,4);
	LcdTinyStringP(PSTR("SELECT: Save"), TEXT_NORMAL);
		
	while (true)
	{
		LcdGoto(0,0);
		LcdTinyStringP(PSTR("Contrast: "), TEXT_NORMAL);
		char contrastStr[4];
		snprintf(contrastStr, 4, "%d", lcd_vop);
		LcdTinyString(contrastStr, TEXT_NORMAL);
			
		_delay_ms(400);
	
		// wait for a button press
		while (bit_is_set(PIN(PREV_BUTTON_PORT), PREV_BUTTON_PIN) && 
				bit_is_set(PIN(NEXT_BUTTON_PORT), NEXT_BUTTON_PIN) && 
				bit_is_set(PIN(SELECT_BUTTON_PORT),SELECT_BUTTON_PIN))
		{}
			
		if (bit_is_clear(PIN(NEXT_BUTTON_PORT), NEXT_BUTTON_PIN))
		{				
			if (lcd_vop < 255)
				lcd_vop++;
		}
		else if (bit_is_clear(PIN(PREV_BUTTON_PORT), PREV_BUTTON_PIN))
		{				
			if (lcd_vop > 128)
				lcd_vop--;
		}
		else if (bit_is_clear(PIN(SELECT_BUTTON_PORT), SELECT_BUTTON_PIN))
		{				
			eeprom_update_byte((uint8_t*)1, lcd_vop);
			break;
		}
		
		LcdWrite(LCD_CMD, 0x21); // LCD Extended Commands.
		LcdWrite(LCD_CMD, lcd_vop); // Set LCD Vop (Contrast). 
		LcdWrite(LCD_CMD, 0x20);
	}	
}

void PromptForFirmwareUpdate()
{
	LcdGoto(0,0);
	LcdClear();
	LcdGoto(0,0);
	LcdTinyStringP(PSTR("CPLD FIRMWARE UPDATE"), TEXT_NORMAL);
	LcdGoto(0,2);
	LcdTinyStringP(PSTR("Release buttons to"), TEXT_NORMAL);
	LcdGoto(0,3);
	LcdTinyStringP(PSTR("begin"), TEXT_NORMAL);
		
	// wait for the buttons to be released
	while (bit_is_clear(PIN(PREV_BUTTON_PORT), PREV_BUTTON_PIN) || 
			bit_is_clear(PIN(NEXT_BUTTON_PORT), NEXT_BUTTON_PIN) ||
			bit_is_clear(PIN(SELECT_BUTTON_PORT), SELECT_BUTTON_PIN))
	{}
		
	LcdGoto(0,2);
	LcdTinyStringP(PSTR("NEXT: Load firmware"), TEXT_NORMAL);
	LcdGoto(0,3);
	LcdTinyStringP(PSTR("PREV: Cancel"), TEXT_NORMAL);
	_delay_ms(400);
		
	// wait for a button press
	while (bit_is_set(PIN(PREV_BUTTON_PORT), PREV_BUTTON_PIN) && 
			bit_is_set(PIN(NEXT_BUTTON_PORT), NEXT_BUTTON_PIN) && 
			bit_is_set(PIN(SELECT_BUTTON_PORT),SELECT_BUTTON_PIN))
	{}
			
	if (bit_is_clear(PIN(NEXT_BUTTON_PORT), NEXT_BUTTON_PIN))
	{				
		UpdateFirmware();
	}
	else
	{
		LcdClear();
	}
}

uint32_t imageFirstBlock, imageLastBlock;
	
bool OpenImageFile()
{	
	LcdClear();
	LcdGoto(0,0);
	LcdTinyString(selectedLongFile, TEXT_NORMAL);
	
	LcdGoto(0,1);
	
	// open the disk image file
	// to-do: check if the file is read-only on the card	
	bool openOK = true;
	if (!f.open(selectedFile, O_RDWR)) 
	{
		if (f.open(selectedFile, O_RDONLY))
		{
			// TODO: How do we tell the CPLD the disk is read-only?
			readOnly = true;
		}
		else			
		{	
			LcdTinyStringP(PSTR("error opening image"), TEXT_NORMAL);
			openOK = false;
		}			
	}	
	else
	{	
		if (selectedFileType == DISK_IMAGE_400K || selectedFileType == DISK_IMAGE_DISKCOPY_400K)	
		{
			numberOfDiskSides = 1;
		}
		else 	
		{
			numberOfDiskSides = 2;
		}	
	}	
		
	// get address of file on SD
	if (openOK && !f.contiguousRange(&imageFirstBlock, &imageLastBlock)) 
	{
		LcdTinyStringP(PSTR("image not contiguous"), TEXT_NORMAL);
		openOK = false;
	}
	
	if (!openOK)
	{
		_delay_ms(4000); // wait 4 seconds
	}
	else
	{
		LcdGoto(0,1);
		// show disk image type
		switch (selectedFileType)
		{
			case DISK_IMAGE_400K:
				LcdTinyStringP(PSTR("400K raw image"), TEXT_NORMAL);
				break;
				
			case DISK_IMAGE_800K:
				LcdTinyStringP(PSTR("800K raw image"), TEXT_NORMAL);
				break;	

			case DISK_IMAGE_1440K:
				LcdTinyStringP(PSTR("1440K raw image"), TEXT_NORMAL);
				mfmMode = true;
				break;
					
			case DISK_IMAGE_DISKCOPY_400K:
				LcdTinyStringP(PSTR("400K DiskCopy image"), TEXT_NORMAL);
				break;
				
			case DISK_IMAGE_DISKCOPY_800K:
				LcdTinyStringP(PSTR("800K DiskCopy image"), TEXT_NORMAL);
				break;	

			case DISK_IMAGE_DISKCOPY_1440K:
				LcdTinyStringP(PSTR("1440K DiskCopy image"), TEXT_NORMAL);
				mfmMode = true;
				break;	
											
			default:
				break;	
		}
		
		selectedFileIsDiskCopyFormat = (selectedFileType >= DISK_IMAGE_DISKCOPY_400K);
	
		if (bit_is_set(PIN(CARD_WPROT_PORT), CARD_WPROT_PIN))
			readOnly = true;

		// mount DiskCopy images read-only
		if (selectedFileIsDiskCopyFormat)
			readOnly = true;
											
		uint16_t volumeNameOffset = selectedFileIsDiskCopyFormat ? 0x424 + 0x54 : 0x424;
		f.seekSet(volumeNameOffset); // offset of the Macintosh disk name in the image file
		f.read(&sectorBuf[0][0], SECTOR_DATA_SIZE);
		int nameLen = sectorBuf[0][0];
		uint8_t* name = &sectorBuf[0][1];
		name[nameLen] = 0;
		name[21] = 0; // in case nameLen was bogus, terminate the string after 21 chars, which is the longest displayable name on the LCD
		LcdGoto(0,2);
		LcdTinyString((char*)name, TEXT_NORMAL);
		LcdGoto(0,4);
		LcdTinyStringP(PSTR("Track    Side"), TEXT_NORMAL);
		
		// show a lock icon if the disk image is mounted as read-only
		if (readOnly)
		{
			LcdGoto(77,0);
			LcdWrite(LCD_DATA, 0x00);
			LcdWrite(LCD_DATA, 0x78);
			LcdWrite(LCD_DATA, 0x7E);
			LcdWrite(LCD_DATA, 0x79);
			LcdWrite(LCD_DATA, 0x79);
			LcdWrite(LCD_DATA, 0x7E);
			LcdWrite(LCD_DATA, 0x78);
		}
	}
	
	f.close();
	return openOK;
}

void ResetDiskState()
{
	ATOMIC_BLOCK(ATOMIC_RESTORESTATE)
	{
		// useless code to prevent the "unused" bootloader_info array from being optimized away when optimizations are turned on
		// there's probably a nicer way to accomplish this.
		currentTrack = bootloader_info[currentTrack];
	
		InitPorts();
	
		currentTrack = 0;
		prevTrack = 0;
		restartDisk = false;
		currentSide = 0;
		prevSide = 0;
		writeMode = 0;
		diskInserted = false;	
		numberOfDiskSides = 2; 
		currentSector = 0; 
		readOnly = false;
		mfmMode = false;
		writeError = false;
		writeDisplayTimer = 0;
		tachFlutter = 0;
		writeErrorNumber = 0;
		writeCount = 0;
		
		for (uint8_t i=0; i<NUM_BUFFERS; i++)
			bufferState[i] = 0;
		
		// Reset the CPLD, and get its 7-bit firmware version number.
		// switch the DATA pins to inputs
		DDR(CPLD_DATA_PORT) = 0;	
		// indicate that the data bus has been released
		PORT(CPLD_DATA_HIZ_PORT) |= (1<<CPLD_DATA_HIZ_PIN);
		// put the CPLD into reset
		PORT(CPLD_RESET_PORT) &= ~(1<<CPLD_RESET_PIN);
		_delay_ms(20);
		cpldFirmwareVersion = PIN(CPLD_DATA_PORT) & 0x7F;
		// indicate that the data bus has been reacquired
		PORT(CPLD_DATA_HIZ_PORT) &= ~(1<<CPLD_DATA_HIZ_PIN);	
		// switch the DATA pins to outputs
		DDR(CPLD_DATA_PORT) = 0x7F;		
		// exit the CPLD from reset
		PORT(CPLD_RESET_PORT) |= (1<<CPLD_RESET_PIN);
	
		// enable power to 16-bit Timer/Counter 1
		PRR0 &= ~(1<<PRTIM1); 
		// toggle OC1A when a counter compare match with OCR1A occurs. 
		// use Fast-PWM OCRnA mode - this provides double-buffering of OCR1A.
		// no clock prescale: increment counter every on clock cycle.		
		TCCR1A = (1<<COM1A0) | (1<<WGM11) | (1<<WGM10); 
		TCCR1B = (1<<WGM13) | (1<<WGM12) | (1<<CS10); 
		SetTach();
	
		// initialize the display
		LcdReset();
		LcdClear();	
	}
}

uint8_t NextInterleavedSector(uint8_t trackNumber, uint8_t prevSectorNumber)
{
	uint8_t trackLen = trackLength(trackNumber);
			
	if (mfmMode)
	{
		// 1:1 interleave
		if (prevSectorNumber >= trackLen - 1)
			return 0;
		else
			return prevSectorNumber + 1;
	}
	else
	{
		uint8_t halfTrackLen = (trackLen + 1) >> 1;
					
		// process sectors in interleaved order: 
		// 12 sector tracks: 0 6 1 7 2 8 3 9 4 10 5 11 
		// 11 sector tracks: 0 6 1 7 2 8 3 9 4 10 5
		// 10 sector tracks: 0 5 1 6 2 7 3 8 4 9
		//  9 sector tracks: 0 5 1 6 2 7 3 8 4
		//  8 sector tracks: 0 4 1 5 2 6 3 7
		//       This is how real floppies are formatted, and should improve read performance if the Mac
		//       can't completely process sector N before sector N+1 begins. It should also improve sector-by-sector
		//       write performance, because the Mac alternately reads (address header) and writes (data section) in
		//       this mode, and proper interleaving means it will read the desired address header sooner if the Mac
		//       isn't fast enough to process the sectors linearly (which it likely isn't).
		if (prevSectorNumber > trackLen - 1 ||
			((trackLen & 1) == 0 && prevSectorNumber == trackLen - 1) || 
			((trackLen & 1) == 1 && prevSectorNumber == halfTrackLen - 1))
			return 0;
		else if (prevSectorNumber < halfTrackLen)
			return prevSectorNumber + halfTrackLen;
		else
			return prevSectorNumber + 1 - halfTrackLen;	
	}			
}

void ReadDiskCopy42Block(SdFat& sd, uint32_t blockToRead, uint8_t bufferNumber)
{
	// for a DiskCopy 4.2 image, read two blocks into a temp buffer, then copy the unaligned data into the sector buffer.
	uint16_t i;
								
	if (!sd.card()->readStart(blockToRead))
		error("SD read start error");
								
	// read part 1
	if (!sd.card()->readData(extraBuf))
		error("SD read error D");
	for (i=0; i<512-0x54; i++)
		sectorBuf[bufferNumber][i] = extraBuf[0x54 + i];
									
	// read part 2	
	if (!sd.card()->readData(extraBuf))
		error("SD read error D");
	for (i=512-0x54; i<512; i++)
		sectorBuf[bufferNumber][i] = extraBuf[0x54 + i - 512];
		
	sd.card()->readStop();
}

void FlushDirtySectors(SdFat& sd, uint8_t trackNumber)
{					
	uint8_t trackLen = trackLength(trackNumber);
	uint8_t firstDirtyBuffer = NUM_BUFFERS, lastDirtyBuffer=0;
	
	// determine the dirty range
	for (uint8_t i=0; i<trackLen*2 && i<NUM_BUFFERS; i++)
	{
		if (bufferState[i] & BUFFER_DIRTY)
		{
			if (wrTrack != trackNumber)
			{
				snprintf(textBuf, TEXTBUF_SIZE, "wr wrong track %d/%d", trackNumber, wrTrack);
				error(textBuf);
			}					
			
			if (firstDirtyBuffer == NUM_BUFFERS)
				firstDirtyBuffer = i;
				
			lastDirtyBuffer = i;
		}
	}
											
	if (firstDirtyBuffer != NUM_BUFFERS) 
	{						
		if (readOnly)
		{
			for (uint8_t i=firstDirtyBuffer; i<=lastDirtyBuffer; i++)
			{					
				bufferState[i] &= ~BUFFER_DIRTY;
				bufferState[i] &= ~BUFFER_LOCKED;
			}
			
			snprintf(textBuf, TEXTBUF_SIZE, "Reverted trk %02d   ", trackNumber);
			LcdGoto(0,5);
			LcdTinyString(textBuf, TEXT_NORMAL);
		}
		else
		{
			millitimerOn();
			uint32_t t0 = millis();	
				
			// Are there any buffers in the dirty range that aren't actually dirty?
			// If so, they must be read back from the card, so that the whole range can be written back
			// as a single block.
			// (Alternatively, the range can be written out as several non-contiguous blocks, skipping 
			// the non-dirty buffers, but I think that would be slower.)
			for (uint8_t i=firstDirtyBuffer; i<=lastDirtyBuffer; i++)					
			{	
				if (!(bufferState[i] & BUFFER_DIRTY))
				{	
					uint32_t blockToRead = imageFirstBlock + ((uint32_t)trackStart(trackNumber) * numberOfDiskSides + i);
					
					if (mfmMode)
						blockToRead += trackLen * wrSide;
				
					if (!sd.card()->readBlock(blockToRead, sectorBuf[i]))
						error("SD read error W");
				}									
			}
									
			uint32_t firstBlockToWrite = imageFirstBlock + ((uint32_t)trackStart(trackNumber) * numberOfDiskSides + firstDirtyBuffer);
			
			if (mfmMode)
				firstBlockToWrite += trackLen * wrSide;
				
			uint32_t numBuffersToWrite = lastDirtyBuffer + 1 - firstDirtyBuffer;					
						
			if (!sd.card()->writeStart(firstBlockToWrite, numBuffersToWrite))
				error("SD writeStart fail");
			
			for (uint8_t i=firstDirtyBuffer; i<=lastDirtyBuffer; i++)
			{				
				if (!sd.card()->writeData(sectorBuf[i]))
					error("SD write error");
					
				bufferState[i] &= ~BUFFER_DIRTY;
				bufferState[i] &= ~BUFFER_LOCKED;
			}
													
			if (!sd.card()->writeStop())
				error("SD writeStop fail");
						
			writeDisplayTimer = 25;	
			millitimerOff();
						
			uint32_t writeTime = millis() - t0;
					
			snprintf(textBuf, TEXTBUF_SIZE, "Saved trk %02d in %lu  ", trackNumber, writeTime);							
			LcdGoto(0,5);
			LcdTinyString(textBuf, TEXT_NORMAL);
		}		
	}	
}

int main(void)
{	
	millitimerInit();
	ResetDiskState();
	
	ShowVersion();
	
	LcdClear();
						  
	sei();
	_delay_ms(100); // wait for pending interrupts??
	
	millitimerOn();
	_delay_ms(100); // wait for pending interrupts??
	
	// if select and next are both held down, enter contrast adjust mode
	if (bit_is_set(PIN(PREV_BUTTON_PORT), PREV_BUTTON_PIN) && 
		bit_is_clear(PIN(NEXT_BUTTON_PORT), NEXT_BUTTON_PIN) && 
		bit_is_clear(PIN(SELECT_BUTTON_PORT),SELECT_BUTTON_PIN))
	{				
		AdjustContrast();
	}
	
	SdFat sd;
	if (!sd.init(SPI_FULL_SPEED))
	{
		snprintf(textBuf, TEXTBUF_SIZE, "SD card error %d:%d", sd.card()->errorCode(), sd.card()->errorData());
		error(textBuf);
	}
		
	millitimerOff();
	
	// if prev and next are both held down, enter firmware update mode
	if (bit_is_clear(PIN(PREV_BUTTON_PORT), PREV_BUTTON_PIN) && 
		bit_is_clear(PIN(NEXT_BUTTON_PORT), NEXT_BUTTON_PIN) && 
		bit_is_set(PIN(SELECT_BUTTON_PORT),SELECT_BUTTON_PIN))
	{				
		PromptForFirmwareUpdate();
	}					
	
	InitDiskMenu(sd);
	DrawDiskMenu(sd);
	
	// main loop				
	while (true)
	{	
		if (writeError)
		{
			// report error encounted in the interrupt routine
			error(textBuf);
		}	
		
		cli();
		uint8_t trackNumber = currentTrack;	// save track in a local var, since currentTrack is volatile
		uint8_t sideNumber = currentSide; // save side in a local var, since currentSide is volatile
		restartDisk = false;
		sei();
								
		if (diskInserted)
		{											
			// show the current track and side
			snprintf(textBuf, TEXTBUF_SIZE, "%02d", trackNumber);
			LcdGoto(24,4);
			LcdTinyString(textBuf, TEXT_NORMAL);
			snprintf(textBuf, TEXTBUF_SIZE, "%d ", sideNumber);
			LcdGoto(56,4);
			LcdTinyString(textBuf, TEXT_NORMAL);
									 
			// sync RAM buffer with SD card when switching tracks, or also when switching sides for mfmMode
			if (prevTrack != trackNumber || (mfmMode && (prevSide != sideNumber)))
			{		
				// write any dirty sectors from the previous track/side back to the SD card	
				FlushDirtySectors(sd, prevTrack);	
				prevTrack = trackNumber;
				prevSide = sideNumber;
										
				// Also mark all the buffers on this track as invalid, since they don't contain valid data for the new track.
				for (uint8_t i=0; i<NUM_BUFFERS; i++)
					bufferState[i] &= ~BUFFER_DATA_VALID;
			}
			// continuously replay sectors from this track/side until interrupted 		
			while (!restartDisk)
			{																															
				if (writeMode)
				{
					// do nothing: incoming data is processed by the interrupt handler
					
					// show write state
					LcdGoto(64,4);
					LcdTinyStringP(PSTR("Write"), TEXT_NORMAL);
					
					while (!restartDisk)
					{}					
				}
				else if (trackNumber <= 79)
				{								
					uint8_t trackLen = trackLength(trackNumber);
					
					// when stepping from a track with more sectors to one with fewer, current sector number could potentially
					// end up out of range
					if (currentSector >= trackLen)
						currentSector = 0;
						
					bool prevMotorOn = !bit_is_clear(PIN(CPLD_STEP_DIR_MOTOR_ON_PORT), CPLD_STEP_DIR_MOTOR_ON_PIN);
					
					while (true)
					{									
						// check for disk eject
						if (bit_is_set(PIN(CPLD_EJECT_REQ_PORT), CPLD_EJECT_REQ_PIN))
						{
							PORT(CPLD_RD_READY_TK0_PORT) &= ~(1<<CPLD_RD_READY_TK0_PIN);
							PORT(CPLD_STEP_ACK_DISK_IN_PORT) |= (1<<CPLD_STEP_ACK_DISK_IN_PIN);
							diskInserted = false;
							
							// write any dirty sectors from the current track
							FlushDirtySectors(sd, trackNumber);
					
							_delay_ms(100);
							ResetDiskState();
							InitDiskMenu(sd);
							DrawDiskMenu(sd);
					
							goto restart;
						}		
	
						// show read/idle state
						bool motorOn = bit_is_clear(PIN(CPLD_STEP_DIR_MOTOR_ON_PORT), CPLD_STEP_DIR_MOTOR_ON_PIN);
						
						if (!motorOn)
						{
							// write any dirty sectors from the current track, when idle
							FlushDirtySectors(sd, trackNumber);
						}
						
						if (prevMotorOn != motorOn)
						{
							prevMotorOn = motorOn;
							LcdGoto(64,4);
							if (motorOn)
								LcdTinyStringP(PSTR(" Read"), TEXT_NORMAL);
							else
							{
								LcdTinyStringP(PSTR(" Idle"), TEXT_NORMAL);
								// turn LED off when idle
								PORT(STATUS_LED_PORT) |= (1<<STATUS_LED_PIN);
								
								writeDisplayTimer = 1; // clear old write alerts when going idle
							}								
						}
							
						// remove old write alerts when writeDisplayTimer reaches 1
						if (writeDisplayTimer == 1)
						{
							writeDisplayTimer = 0;
							LcdGoto(0,5);
							LcdTinyStringP(PSTR("                     "), TEXT_NORMAL);							
						}			
													
						uint8_t bufferNumber = mfmMode ? currentSector : (sideNumber * trackLen + currentSector);
						
						bool shouldReadSector = false;			
						
						// atomic check and acquire of buffer lock
						cli();	
						if ((bufferState[bufferNumber] & BUFFER_DATA_VALID) == 0 &&
							(bufferState[bufferNumber] & BUFFER_LOCKED) == 0)
						{
							// lock the buffer, so the Mac won't write to it while we're reading it from SD
							bufferState[bufferNumber] |= BUFFER_LOCKED;	
							shouldReadSector = true;
						}			
						sei();
						
						// read the sector from the SD card, if necessary
						if (shouldReadSector)
						{		
							uint32_t blockToRead = imageFirstBlock + ((uint32_t)trackStart(trackNumber) * numberOfDiskSides + sideNumber * trackLen + currentSector);
								
							millitimerOn();
											
							if (selectedFileIsDiskCopyFormat)
							{
								ReadDiskCopy42Block(sd, blockToRead, bufferNumber);
							}	
							else
							{
								if (!sd.card()->readBlock(blockToRead, sectorBuf[bufferNumber]))
									error("SD read error R");
							}
							
							millitimerOff();
															
							bufferState[bufferNumber] |= BUFFER_DATA_VALID;
							bufferState[bufferNumber] &= ~BUFFER_LOCKED;	
						}				
						
						if (currentSector == 0)
						{
							if (motorOn)
							{
								// toggle LED during drive activity
								PORT(STATUS_LED_PORT) ^= (1<<STATUS_LED_PIN);	
							
								if (writeDisplayTimer > 1)
									writeDisplayTimer--;
							}
															
							// "Flutter" the drive's TACH speed slightly, every time we pass sector 0 (about every 100-150ms). This avoids a bug
							// in P_Sony_MakeSpdTbl in the 64K ROM (used in the Mac 128K and Mac 512K) where 
							// the Mac will crash if two successive TACH measurements see the exact same speed.
							tachFlutter += 25;
							if (tachFlutter >= 125)
								tachFlutter = 0;		
														
							// Set the timeout. OC1A will toggle after this many counts. New timeout threshold won't take effect until the next timeout.	
							OCR1A = driveTachHalfPeriod - tachFlutter; 
						}	
																			
						if (mfmMode)
						{																							
							// insert sector-to-sector gap bytes
							for (uint8_t i=0; i<50; i++)
							{
								SendMFMAndCheckRestart(0x4E);
							}
							
							// insert sync bytes
							for (uint8_t i=0; i<12; i++)
							{
								SendMFMAndCheckRestart(0x00);
							}
							
							// send the address block
							crc = 0xFFFF; // reset CRC							
							SendMFMSync();
							SendMFMSync();
							SendMFMSync();
							SendMFMAndCheckRestart(0xFE);									
							SendMFMAndCheckRestart(trackNumber);
							SendMFMAndCheckRestart(sideNumber);
							SendMFMAndCheckRestart(currentSector+1); // MFM sector numbers are 1-based
							SendMFMAndCheckRestart(2); // size = 128 * 2^N bytes, so 2 means 512
							uint8_t crc0 = (crc >> 8) & 0xFF;
							uint8_t crc1 = crc & 0xFF;
							SendMFMAndCheckRestart(crc0);
							SendMFMAndCheckRestart(crc1);
							
							// insert Address to Data gap bytes
							for (uint8_t i=0; i<22; i++)
							{
								SendMFMAndCheckRestart(0x4E);
							}	
							
							// insert sync bytes
							for (uint8_t i=0; i<12; i++)
							{
								SendMFMAndCheckRestart(0x00);
							}
							
							// send the data block
							crc = 0xFFFF; // reset CRC
							SendMFMSync();
							SendMFMSync();
							SendMFMSync();
							SendMFMAndCheckRestart(0xFB);
							
							for (uint16_t i=0; i<SECTOR_DATA_SIZE; i++)
							{
								uint8_t d = sectorBuf[bufferNumber][i];
								SendMFMAndCheckRestart(d);
							}
							
							crc0 = (crc >> 8) & 0xFF;
							crc1 = crc & 0xFF;
							SendMFMAndCheckRestart(crc0);
							SendMFMAndCheckRestart(crc1);
						}	
						else
						{		
							// ensure a short gap between sectors - otherwise once they're all cached, one sector will appear
							// to immediately follow another on disk, which may cause problems for the Mac.
							// Bad voodoo here:
							// 1. In the Finder StuffIt copy test that sometimes dies after the first 18 tracks, the length of delay here
							// seems to affect what track it will freeze on.
							// 2. With a longer delay here, the first ~10 sectors of copying seem to have fewer or no "long writes".
							// 3. Depending on the delay here, the Transcend 2GB SD card sometimes gets "writeStop fail" when saving tracks.
							for (uint16_t i=0; i<INTER_SECTOR_GAP_SIZE; i++)
							{
								SendByteAndCheckRestart(0xFF);
							}
																								
							// send the address block
							uint8_t format = (numberOfDiskSides == 2) ? 0x22 : 0x02; // 0x22 = MacOS double-sided, 0x02 = single sided
							uint8_t trackLow = (uint8_t)(trackNumber & 0x3F);
							uint8_t trackHigh = (uint8_t)((sideNumber << 5) | (trackNumber >> 6));
							uint8_t checksum = (uint8_t)((trackLow ^ currentSector ^ trackHigh ^ format) & 0x3F);                  

							SendByteAndCheckRestart(0xD5);	
							SendByteAndCheckRestart(0xAA);
							SendByteAndCheckRestart(0x96);
							SendByteAndCheckRestart(pgm_read_byte(&sony_to_disk_byte[trackLow]));
							SendByteAndCheckRestart(pgm_read_byte(&sony_to_disk_byte[currentSector]));
							SendByteAndCheckRestart(pgm_read_byte(&sony_to_disk_byte[trackHigh]));
							SendByteAndCheckRestart(pgm_read_byte(&sony_to_disk_byte[format]));
							SendByteAndCheckRestart(pgm_read_byte(&sony_to_disk_byte[checksum]));
							SendByteAndCheckRestart(0xDE);
							SendByteAndCheckRestart(0xAA);
						
							// insert sync bytes between the address and data blocks
							for (uint8_t i=0; i<ADDRESS_DATA_GAP_SIZE; i++)
							{
								SendByteAndCheckRestart(0xFF);
							}	
									  
							// send the data block
							SendByteAndCheckRestart(0xD5);			
							SendByteAndCheckRestart(0xAA);
							SendByteAndCheckRestart(0xAD);
							SendByteAndCheckRestart(pgm_read_byte(&sony_to_disk_byte[currentSector]));
					
							SendGCRSectorData((const uint8_t*)sectorBuf[bufferNumber]);
							
							SendByteAndCheckRestart(0xDE);
							SendByteAndCheckRestart(0xAA);
							SendByteAndCheckRestart(0xFF);			
						}		
										
						currentSector = NextInterleavedSector(trackNumber, currentSector);
					}
				}											
			}		
restart: ;					
		}
		else
		{					
			while (!restartDisk)
			{
				// check for disk eject. This shouldn't normally happen, but if the CPLD and AVR get out of sync
				// and the CPLD thinks there's a disk while the AVR doesn't, it could.
				// But don't do this is ALL the CPLD inputs are asserted - that means you're probably midway 
				// through building the hardware, and the CPLD isn't there yet.
				if (bit_is_set(PIN(CPLD_EJECT_REQ_PORT), CPLD_EJECT_REQ_PIN) &&
				    !(bit_is_set(PIN(CPLD_RD_ACK_WR_TICK_PORT), CPLD_RD_ACK_WR_TICK_PIN) &&
					  bit_is_set(PIN(CPLD_STEP_REQ_PORT), CPLD_STEP_REQ_PIN) &&
					  bit_is_set(PIN(CPLD_WR_REQ_PORT), CPLD_WR_REQ_PIN) &&
					  bit_is_set(PIN(CPLD_CURRENT_SIDE_PORT), CPLD_CURRENT_SIDE_PIN)))
				{			
					// reboot
					((void(*)(void))0)();					
 				}	
						
				if (bit_is_clear(PIN(PREV_BUTTON_PORT), PREV_BUTTON_PIN))
				{
					if (diskMenuSelection > 0)
					{
						diskMenuSelection--;
						DrawDiskMenu(sd);
						_delay_ms(200);
					}					
				}
				else if (bit_is_clear(PIN(NEXT_BUTTON_PORT), NEXT_BUTTON_PIN))
				{
					diskMenuSelection++;
					DrawDiskMenu(sd);
					_delay_ms(200);
				}
				else if (bit_is_clear(PIN(SELECT_BUTTON_PORT), SELECT_BUTTON_PIN))
				{
					if (selectedFileType == DISK_IMAGE_DIRECTORY)
					{						
						// remember where we came from, so we can get back later
						char* pSubdirName = ((char*)sectorBuf[23]) + ((SHORTFILENAME_LEN+1) * subdirDepth);
						strncpy(pSubdirName, selectedFile, SHORTFILENAME_LEN+1);
						subdirDepth++;
					
						sd.chdir(selectedFile, true);
													
						diskMenuSelection = 0;
						LcdClear();
						InitDiskMenu(sd);
						DrawDiskMenu(sd);
						_delay_ms(400);
					}
					else if (selectedFileType == DISK_IMAGE_UP_DIRECTORY)
					{
						subdirDepth--;
						sd.chdir(true); // go to root directory
						
						for (uint8_t i=0; i<subdirDepth; i++)
						{
							char* pSubdirName = ((char*)sectorBuf[23]) + ((SHORTFILENAME_LEN+1) * i);
							sd.chdir(pSubdirName, true);	
						}
						
						diskMenuSelection = 0;
						LcdClear();
						InitDiskMenu(sd);
						DrawDiskMenu(sd);
						_delay_ms(400);
					}
					else
					{			
						// enable the interrupts
						PCICR |= (1<<CPLD_STEP_REQ_INT_ENABLE);	
						PCICR |= (1<<CPLD_CURRENT_SIDE_INT_ENABLE);
						PCICR |= (1<<CPLD_WR_REQ_INT_ENABLE);
						PCICR |= (1<<CPLD_RD_ACK_WR_TICK_INT_ENABLE);
	
						currentTrack = 0;	
						LcdReset(); // also resets the CPLD, ensures it knows we're now at track 0
						LcdClear();
						
						// "insert" the disk
						if (OpenImageFile())
						{			
							// tell the CPLD whether the disk is read-only (bit 0, active low)
							uint8_t configByte = 0;
						
							if (!readOnly)
								configByte |= 0x01;

							if (!mfmMode)
								configByte |= 0x02;			
							
							PORT(CPLD_DATA_PORT) = configByte;
							// asserting RD_READY without DISK_IN causes the CPLD to load config options from the data bus
							PORT(CPLD_RD_READY_TK0_PORT) |= (1<<CPLD_RD_READY_TK0_PIN);	
							_delay_ms(1);
							PORT(CPLD_RD_READY_TK0_PORT) &= ~(1<<CPLD_RD_READY_TK0_PIN);
							_delay_us(10);
						
							// tell the CPLD there is a disk	
							PORT(CPLD_STEP_ACK_DISK_IN_PORT) &= ~(1<<CPLD_STEP_ACK_DISK_IN_PIN);			
							diskInserted = true;
						}
					}						
					break;			
				}
			}
		}	
	}			
}
