"""
#    TOP2049 Open Source programming suite
#
#    Atmel Tiny13 DIP8
#
#    Copyright (c) 2010 Michael Buesch <m@bues.ch>
#
#    This program is free software; you can redistribute it and/or modify
#    it under the terms of the GNU General Public License as published by
#    the Free Software Foundation; either version 2 of the License, or
#    (at your option) any later version.
#
#    This program is distributed in the hope that it will be useful,
#    but WITHOUT ANY WARRANTY; without even the implied warranty of
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#    GNU General Public License for more details.
#
#    You should have received a copy of the GNU General Public License along
#    with this program; if not, write to the Free Software Foundation, Inc.,
#    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
"""

from libtoprammer.chip import *


class Chip_AtTiny13dip8(Chip):
	PROGCMD_SENDINSTR	= 1 # Send an instruction to the chip

	STAT_BUSY		= 0x01 # Programmer is running a command
	STAT_SDO		= 0x02 # Raw SDO pin state

	def __init__(self):
		Chip.__init__(self,
			      chipPackage = "DIP8",
			      chipPinVCC = 8,
			      chipPinsVPP = 1,
			      chipPinGND = 4)
		self.signature = "\x1E\x90\x07"
		self.flashPageSize = 16
		self.flashPages = 32
		self.eepromPageSize = 4
		self.eepromPages = 16

	def readSignature(self):
		self.__enterPM()
		self.progressMeterInit("Reading signature", 0)
		signature = self.__readSignature()
		self.progressMeterFinish()
		return signature

	def erase(self):
		self.__enterPM()
		self.progressMeterInit("Erasing chip", 0)
		self.__sendInstr(SDI=0x80, SII=0x4C)
		self.__sendInstr(SDI=0x00, SII=0x64)
		self.__sendInstr(SDI=0x00, SII=0x6C)
		self.__waitHighSDO()
		self.__sendNOP()
		self.progressMeterFinish()

	def readProgmem(self):
		nrWords = self.flashPages * self.flashPageSize
		image = ""
		self.__enterPM()
		self.progressMeterInit("Reading flash", nrWords)
		self.__sendReadFlashInstr()
		currentHigh = -1
		bufferedBytes = 0
		for word in range(0, nrWords):
			self.progressMeter(word)
			low = word & 0xFF
			high = (word >> 8) & 0xFF
			self.__sendInstr(SDI=low, SII=0x0C)
			if high != currentHigh:
				self.__sendInstr(SDI=high, SII=0x1C)
				currentHigh = high
			self.__sendInstr(SDI=0x00, SII=0x68)
			self.__sendInstr(SDI=0x00, SII=0x6C)
			self.__readSDOBufferHigh()
			bufferedBytes += 1
			self.__sendInstr(SDI=0x00, SII=0x78)
			self.__sendInstr(SDI=0x00, SII=0x7C)
			self.__readSDOBufferHigh()
			bufferedBytes += 1
			if bufferedBytes == self.top.getBufferRegSize():
				image += self.top.cmdReadBufferReg(bufferedBytes)
				bufferedBytes = 0
		image += self.top.cmdReadBufferReg(bufferedBytes)
		self.progressMeterFinish()
		return image

	def writeProgmem(self, image):
		nrWords = self.flashPages * self.flashPageSize
		if len(image) > nrWords * 2 or len(image) % 2 != 0:
			self.throwError("Invalid flash image size %d (expected <=%d and word aligned)" %\
				(len(image), nrWords * 2))
		self.__enterPM()
		self.progressMeterInit("Writing flash", len(image) // 2)
		self.__sendWriteFlashInstr()
		currentHigh = -1
		for word in range(0, len(image) // 2):
			self.progressMeter(word)
			low = word & 0xFF
			high = (word >> 8) & 0xFF
			self.__sendInstr(SDI=low, SII=0x0C)
			self.__sendInstr(SDI=byte2int(image[word * 2 + 0]), SII=0x2C)
			self.__sendInstr(SDI=byte2int(image[word * 2 + 1]), SII=0x3C)
			self.__sendInstr(SDI=0x00, SII=0x7D)
			self.__sendInstr(SDI=0x00, SII=0x7C)
			if ((word + 1) % self.flashPageSize == 0) or word == len(image) // 2 - 1:
				if currentHigh != high:
					self.__sendInstr(SDI=high, SII=0x1C)
					currentHigh = high
				self.__sendInstr(SDI=0x00, SII=0x64)
				self.__sendInstr(SDI=0x00, SII=0x6C)
				self.__waitHighSDO()
		self.__sendNOP()
		self.progressMeterFinish()

	def readEEPROM(self):
		nrBytes = self.eepromPages * self.eepromPageSize
		image = ""
		self.__enterPM()
		self.progressMeterInit("Reading EEPROM", nrBytes)
		self.__sendReadEEPROMInstr()
		currentPage = -1
		bufferedBytes = 0
		for i in range(0, nrBytes):
			self.progressMeter(i)
			low = i & 0xFF
			high = (i >> 8) & 0xFF
			self.__sendInstr(SDI=low, SII=0x0C)
			if currentPage != high:
				self.__sendInstr(SDI=high, SII=0x1C)
				currentPage = high
			self.__sendInstr(SDI=0x00, SII=0x68)
			self.__sendInstr(SDI=0x00, SII=0x6C)
			self.__readSDOBufferHigh()
			bufferedBytes += 1
			if bufferedBytes == self.top.getBufferRegSize():
				image += self.top.cmdReadBufferReg(bufferedBytes)
				bufferedBytes = 0
		image += self.top.cmdReadBufferReg(bufferedBytes)
		self.progressMeterFinish()
		return image

	def writeEEPROM(self, image):
		nrBytes = self.eepromPages * self.eepromPageSize
		if len(image) > nrBytes:
			self.throwError("Invalid EEPROM image size %d (expected <=%d)" %\
				(len(image), nrBytes))
		self.__enterPM()
		self.progressMeterInit("Writing EEPROM", len(image))
		self.__sendWriteEEPROMInstr()
		for i in range(0, len(image)):
			self.progressMeter(i)
			self.__sendInstr(SDI=i, SII=0x0C)
			self.__sendInstr(SDI=byte2int(image[i]), SII=0x2C)
			self.__sendInstr(SDI=0x00, SII=0x6D)
			self.__sendInstr(SDI=0x00, SII=0x6C)
			if ((i + 1) % self.eepromPageSize == 0) or i == len(image) - 1:
				self.__sendInstr(SDI=0x00, SII=0x64)
				self.__sendInstr(SDI=0x00, SII=0x6C)
				self.__waitHighSDO()
		self.__sendNOP()
		self.progressMeterFinish()

	def readFuse(self):
		fuses = []
		self.__enterPM()
		self.progressMeterInit("Reading fuses", 0)
		self.__sendInstr(SDI=0x04, SII=0x4C)
		self.__sendInstr(SDI=0x00, SII=0x68)
		self.__sendInstr(SDI=0x00, SII=0x6C)
		self.__readSDOBufferHigh()
		fuses.append(self.top.cmdReadBufferReg(1))
		self.__sendInstr(SDI=0x04, SII=0x4C)
		self.__sendInstr(SDI=0x00, SII=0x7A)
		self.__sendInstr(SDI=0x00, SII=0x7E)
		self.__readSDOBufferHigh()
		fuses.append(int2byte(self.top.cmdReadBufferReg8() | 0xE0))
		self.progressMeterFinish()
		return b"".join(fuses)

	def writeFuse(self, image):
		if len(image) != 2:
			self.throwError("Invalid Fuses image size %d (expected %d)" %\
				(len(image), 2))
		self.__enterPM()
		self.progressMeterInit("Writing fuses", 0)
		self.__sendInstr(SDI=0x40, SII=0x4C)
		self.__sendInstr(SDI=byte2int(image[0]), SII=0x2C)
		self.__sendInstr(SDI=0x00, SII=0x64)
		self.__sendInstr(SDI=0x00, SII=0x6C)
		self.__waitHighSDO()
		self.__sendInstr(SDI=0x40, SII=0x4C)
		self.__sendInstr(SDI=(byte2int(image[1]) & 0x1F), SII=0x2C)
		self.__sendInstr(SDI=0x00, SII=0x74)
		self.__sendInstr(SDI=0x00, SII=0x7C)
		self.__waitHighSDO()
		self.progressMeterFinish()

	def readLockbits(self):
		self.__enterPM()
		self.progressMeterInit("Reading lockbits", 0)
		self.__sendInstr(SDI=0x04, SII=0x4C)
		self.__sendInstr(SDI=0x00, SII=0x78)
		self.__sendInstr(SDI=0x00, SII=0x7C)
		self.__readSDOBufferHigh()
		lockbits = int2byte(self.top.cmdReadBufferReg8() | 0xFC)
		self.progressMeterFinish()
		return lockbits

	def writeLockbits(self, image):
		if len(image) != 1:
			self.throwError("Invalid Lockbits image size %d (expected %d)" %\
				(len(image), 1))
		self.__enterPM()
		self.progressMeterInit("Writing lockbits", 0)
		self.__sendInstr(SDI=0x20, SII=0x4C)
		self.__sendInstr(SDI=(byte2int(image[0]) & 3), SII=0x2C)
		self.__sendInstr(SDI=0x00, SII=0x64)
		self.__sendInstr(SDI=0x00, SII=0x6C)
		self.__waitHighSDO()
		self.progressMeterFinish()

	def __readSignature(self):
		self.__sendInstr(SDI=0x08, SII=0x4C)
		for i in range(0, 3):
			self.__sendInstr(SDI=i, SII=0x0C)
			self.__sendInstr(SDI=0x00, SII=0x68)
			self.__sendInstr(SDI=0x00, SII=0x6C)
			self.__readSDOBufferHigh()
		return self.top.cmdReadBufferReg()[0:3]

	def __enterPM(self):
		"Enter HV programming mode."
		self.applyVCC(False)
		self.applyVPP(False)
		self.applyGND(False)
		self.top.cmdSetVCCVoltage(5)
		self.top.cmdSetVPPVoltage(0)
		self.top.cmdSetVPPVoltage(12)
		self.applyGND(True)
		self.applyVCC(True)

		self.__setPins(SCI=0, SDO_en=0, RST_en=1, RST=0)
		for i in range(0, 6):
			self.__setPins(SCI=0, SDO_en=0, RST_en=1, RST=0)
			self.__setPins(SCI=1, SDO_en=0, RST_en=1, RST=0)
		self.__setPins(SCI=0, SDO_en=1, SDO=0, RST_en=1, RST=0)
		self.top.hostDelay(0.001)
		self.__setPins(SDO_en=1, SDO=0, RST_en=0)
		self.applyVPP(True)
		self.top.hostDelay(0.001)
		self.__setPins(SDO_en=0)
		self.top.hostDelay(0.01)

		signature = self.__readSignature()
		if signature != self.signature:
			msg = "Unexpected device signature. " +\
			      "Want %02X%02X%02X, but got %02X%02X%02X" % \
				(byte2int(self.signature[0]), byte2int(self.signature[1]),
				 byte2int(self.signature[2]),
				 byte2int(signature[0]), byte2int(signature[1]),
				 byte2int(signature[2]))
			if self.top.getForceLevel() >= 1:
				self.printWarning(msg)
			else:
				self.throwError(msg)

	def __sendReadEEPROMInstr(self):
		self.__sendInstr(SDI=0x03, SII=0x4C)

	def __sendWriteEEPROMInstr(self):
		self.__sendInstr(SDI=0x11, SII=0x4C)

	def __sendReadFlashInstr(self):
		self.__sendInstr(SDI=0x02, SII=0x4C)

	def __sendWriteFlashInstr(self):
		self.__sendInstr(SDI=0x10, SII=0x4C)

	def __sendNOP(self):
		self.__sendInstr(SDI=0x00, SII=0x4C)

	def __sendInstr(self, SDI, SII):
		self.__setSDI(SDI)
		self.__setSII(SII)
		self.__loadCommand(self.PROGCMD_SENDINSTR)
		# We do not poll the busy flag, because that would result
		# in a significant slowdown. We delay long enough for the
		# command to finish execution, instead.
		self.top.hostDelay(0.001)

	def __setSDI(self, sdi):
		self.top.cmdFPGAWrite(0x13, sdi & 0xFF)

	def __setSII(self, sii):
		self.top.cmdFPGAWrite(0x14, sii & 0xFF)

	def __loadCommand(self, command):
		self.top.cmdFPGAWrite(0x12, command & 0xFF)

	def __runCommandSync(self, command):
		self.__loadCommand(command)
		self.__busyWait()

	def __setPins(self, SCI=0, SDO_en=0, SDO=0, RST_en=0, RST=0):
		data = 0
		if SCI:
			data |= 1
		if SDO_en:
			data |= 2
		if SDO:
			data |= 4
		if RST_en:
			data |= 8
		if RST:
			data |= 16
		self.top.cmdFPGAWrite(0x15, data)

	def __getStatusFlags(self):
		self.top.cmdFPGARead(0x12)
		stat = self.top.cmdReadBufferReg()
		return byte2int(stat[0])

	def __readSDOBufferHigh(self):
		self.top.cmdFPGARead(0x10)

	def __rawSDOState(self):
		return bool(self.__getStatusFlags() & self.STAT_SDO)

	def __busy(self):
		return bool(self.__getStatusFlags() & self.STAT_BUSY)

	def __busyWait(self):
		for i in range(0, 100):
			if not self.__busy():
				return
			self.top.hostDelay(0.01)
		self.throwError("Timeout in busywait.")

	def __waitHighSDO(self):
		for i in range(0, 100):
			if self.__rawSDOState():
				return
			self.top.hostDelay(0.01)
		self.throwError("Timeout waiting for SDO.")

ChipDescription(
	Chip_AtTiny13dip8,
	bitfile = "attiny13dip8",
	runtimeID = (0x0001, 0x01),
	chipVendors = "Atmel",
	description = "AtTiny13",
	packages = ( ("DIP8", ""), ),
)
