/*
 * isp.c - part of USBasp
 *
 * Autor..........: Thomas Fischl <tfischl@gmx.de>
 * Description....: Provides functions for communication/programming
 *                  over ISP interface
 * Licence........: GNU GPL v2 (see Readme.txt)
 * Creation Date..: 2005-02-23
 * Last change....: 2010-01-19
 */

#include <avr/io.h>
#include "isp.h"
#include "clock.h"
#include "usbasp.h"

#define spiHWdisable() SPCR = 0

uchar sck_sw_delay;
uchar sck_spcr;
uchar sck_spsr;
uchar isp_hiaddr;

void spiHWenable() {
	SPCR = sck_spcr;
	SPSR = sck_spsr;
}

void ispSetSCKOption(uchar option) {

	if (option == USBASP_ISP_SCK_AUTO)
		option = USBASP_ISP_SCK_375;

	if (option >= USBASP_ISP_SCK_93_75) {
		ispTransmit = ispTransmit_hw;
		sck_spsr = 0;
		sck_sw_delay = 1;	/* force RST#/SCK pulse for 320us */

		switch (option) {

		case USBASP_ISP_SCK_1500:
			/* enable SPI, master, 1.5MHz, XTAL/8 */
			sck_spcr = (1 << SPE) | (1 << MSTR) | (1 << SPR0);
			sck_spsr = (1 << SPI2X);
		case USBASP_ISP_SCK_750:
			/* enable SPI, master, 750kHz, XTAL/16 */
			sck_spcr = (1 << SPE) | (1 << MSTR) | (1 << SPR0);
			break;
		case USBASP_ISP_SCK_375:
		default:
			/* enable SPI, master, 375kHz, XTAL/32 (default) */
			sck_spcr = (1 << SPE) | (1 << MSTR) | (1 << SPR1);
			sck_spsr = (1 << SPI2X);
			break;
		case USBASP_ISP_SCK_187_5:
			/* enable SPI, master, 187.5kHz XTAL/64 */
			sck_spcr = (1 << SPE) | (1 << MSTR) | (1 << SPR1);
			break;
		case USBASP_ISP_SCK_93_75:
			/* enable SPI, master, 93.75kHz XTAL/128 */
			sck_spcr = (1 << SPE) | (1 << MSTR) | (1 << SPR1) | (1 << SPR0);
			break;
		}

	} else {
		ispTransmit = ispTransmit_sw;
		switch (option) {

		case USBASP_ISP_SCK_32:
			sck_sw_delay = 3;

			break;
		case USBASP_ISP_SCK_16:
			sck_sw_delay = 6;

			break;
		case USBASP_ISP_SCK_8:
			sck_sw_delay = 12;

			break;
		case USBASP_ISP_SCK_4:
			sck_sw_delay = 24;

			break;
		case USBASP_ISP_SCK_2:
			sck_sw_delay = 48;

			break;
		case USBASP_ISP_SCK_1:
			sck_sw_delay = 96;

			break;
		case USBASP_ISP_SCK_0_5:
			sck_sw_delay = 192;

			break;
		}
	}
}

void ispDelay() {

	uint8_t starttime = TIMERVALUE;
	while ((uint8_t) (TIMERVALUE - starttime) < sck_sw_delay) {
	}
}

void ispConnect() {

	/* all ISP pins are inputs before */
	/* now set output pins */
	ISP_DDR |= (1 << ISP_RST) | (1 << ISP_SCK) | (1 << ISP_MOSI);

	/* reset device */
	ISP_OUT &= ~(1 << ISP_RST); /* RST low */
	ISP_OUT &= ~(1 << ISP_SCK); /* SCK low */

	/* positive reset pulse > 2 SCK (target) */
	ispDelay();
	ISP_OUT |= (1 << ISP_RST); /* RST high */
	ispDelay();
	ISP_OUT &= ~(1 << ISP_RST); /* RST low */

	if (ispTransmit == ispTransmit_hw) {
		spiHWenable();
	}
	
	/* Initial extended address value */
	isp_hiaddr = 0;
}

void ispDisconnect() {

	/* set all ISP pins inputs */
	ISP_DDR &= ~((1 << ISP_RST) | (1 << ISP_SCK) | (1 << ISP_MOSI));
	/* switch pullups off */
	ISP_OUT &= ~((1 << ISP_RST) | (1 << ISP_SCK) | (1 << ISP_MOSI));

	/* disable hardware SPI */
	spiHWdisable();
}

uchar ispTransmit_sw(uchar send_byte) {

	uchar rec_byte = 0;
	uchar i;
	for (i = 0; i < 8; i++) {

		/* set MSB to MOSI-pin */
		if ((send_byte & 0x80) != 0) {
			ISP_OUT |= (1 << ISP_MOSI); /* MOSI high */
		} else {
			ISP_OUT &= ~(1 << ISP_MOSI); /* MOSI low */
		}
		/* shift to next bit */
		send_byte = send_byte << 1;

		/* receive data */
		rec_byte = rec_byte << 1;
		if ((ISP_IN & (1 << ISP_MISO)) != 0) {
			rec_byte++;
		}

		/* pulse SCK */
		ISP_OUT |= (1 << ISP_SCK); /* SCK high */
		ispDelay();
		ISP_OUT &= ~(1 << ISP_SCK); /* SCK low */
		ispDelay();
	}

	return rec_byte;
}

uchar ispTransmit_hw(uchar send_byte) {
	SPDR = send_byte;

	while (!(SPSR & (1 << SPIF)))
		;
	return SPDR;
}

uchar ispEnterProgrammingMode() {
	uchar check;
	uchar count = 32;

	while (count--) {
		ispTransmit(0xAC);
		ispTransmit(0x53);
		check = ispTransmit(0);
		ispTransmit(0);

		if (check == 0x53) {
			return 0;
		}

		spiHWdisable();

		/* pulse RST */
		ispDelay();
		ISP_OUT |= (1 << ISP_RST); /* RST high */
		ispDelay();
		ISP_OUT &= ~(1 << ISP_RST); /* RST low */
		ispDelay();

		if (ispTransmit == ispTransmit_hw) {
			spiHWenable();
		}

	}

	return 1; /* error: device dosn't answer */
}

static void ispUpdateExtended(unsigned long address)
{
	uchar curr_hiaddr;

	curr_hiaddr = (address >> 17);

	/* check if extended address byte is changed */
	if(isp_hiaddr != curr_hiaddr)
	{
		isp_hiaddr = curr_hiaddr;
		/* Load Extended Address byte */
		ispTransmit(0x4D);
		ispTransmit(0x00);
		ispTransmit(isp_hiaddr);
		ispTransmit(0x00);
	}
}

uchar ispReadFlash(unsigned long address) {

	ispUpdateExtended(address);

	ispTransmit(0x20 | ((address & 1) << 3));
	ispTransmit(address >> 9);
	ispTransmit(address >> 1);
	return ispTransmit(0);
}

uchar ispWriteFlash(unsigned long address, uchar data, uchar pollmode) {

	/* 0xFF is value after chip erase, so skip programming
	 if (data == 0xFF) {
	 return 0;
	 }
	 */

	ispUpdateExtended(address);

	ispTransmit(0x40 | ((address & 1) << 3));
	ispTransmit(address >> 9);
	ispTransmit(address >> 1);
	ispTransmit(data);

	if (pollmode == 0)
		return 0;

	if (data == 0x7F) {
		clockWait(15); /* wait 4,8 ms */
		return 0;
	} else {

		/* polling flash */
		uchar retries = 30;
		uint8_t starttime = TIMERVALUE;
		while (retries != 0) {
			if (ispReadFlash(address) != 0x7F) {
				return 0;
			};

			if ((uint8_t) (TIMERVALUE - starttime) > CLOCK_T_320us) {
				starttime = TIMERVALUE;
				retries--;
			}

		}
		return 1; /* error */
	}

}

uchar ispFlushPage(unsigned long address, uchar pollvalue) {

	ispUpdateExtended(address);
	
	ispTransmit(0x4C);
	ispTransmit(address >> 9);
	ispTransmit(address >> 1);
	ispTransmit(0);

	if (pollvalue == 0xFF) {
		clockWait(15);
		return 0;
	} else {

		/* polling flash */
		uchar retries = 30;
		uint8_t starttime = TIMERVALUE;

		while (retries != 0) {
			if (ispReadFlash(address) != 0xFF) {
				return 0;
			};

			if ((uint8_t) (TIMERVALUE - starttime) > CLOCK_T_320us) {
				starttime = TIMERVALUE;
				retries--;
			}

		}

		return 1; /* error */
	}

}

uchar ispReadEEPROM(unsigned int address) {
	ispTransmit(0xA0);
	ispTransmit(address >> 8);
	ispTransmit(address);
	return ispTransmit(0);
}

uchar ispWriteEEPROM(unsigned int address, uchar data) {

	ispTransmit(0xC0);
	ispTransmit(address >> 8);
	ispTransmit(address);
	ispTransmit(data);

	clockWait(30); // wait 9,6 ms

	return 0;
}
