# Author: Theodor Stana <theodor.stana@gmail.com>
# Date: October 2014
#
# 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 3
# 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, you may find one here:
# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
# or you may search the http://www.gnu.org website for the version 2 license,
# or you may write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
#

import sys
sys.path.append("../../ei2c")
from ei2c import *
from ei2cexcept import *
import time
from functools import partial
from comm_type import *
from flash_m25p import *
import os

#===============================================================================
MB_CR_OFS = 0x00
MB_SR_OFS = 0x04
MB_GBBAR_OFS = 0x08
MB_MBBAR_OFS = 0x0C
MB_FAR_OFS   = 0x10
#===============================================================================

class XilMultiboot:

    # Constructor
    def __init__(self, *param):
        if param[0] == ELMA_I2C_MULTIBOOT:
            self.comm = param[0]
            self.elma = param[1]
            self.slot = param[2]
            self.mb_base = param[3]
            self.bitstream = param[4]
            self.flash = FlashM25P(self.comm, self.elma, self.slot, self.mb_base)

    #
    # Print read or write progress to screen
    #
    def _progress(self, sa, ea, ca):
        actual = ca - sa
        total = ea - sa
        progress = (float(actual)/float(total)) * 100
        if (progress > 100.00):
            progress = 100.00
        sys.stdout.write("\r  %3.2f%% (0x%06x)" % (progress, ca))
        sys.stdout.flush()

    #
    # Read from flash
    #
    def read(self, sa, ea):
        # Ask for and open bitstream file
        fname = raw_input("Output file name for flash readout: ")
        f = open(fname,'wb')

        # Read the data and dump to file
        print("Reading flash contents from board in slot %d" % self.slot)
        dat = []
        for i in range(sa, ea, 256):
            dat += self.flash.read(i, 256)
            self._progress(sa, ea, i)
        i += 256
        self._progress(sa, ea, i)
        print("")
        dat = ''.join(map(chr,dat))
        f.write(dat)
        f.close()

    #
    # Write to flash
    #
    def write(self, addr):

        print("Writing bitstream to board in slot %d" % self.slot)

        # Ask for and open bitstream file
        f = open(self.bitstream,'rb')
        sta = addr
        tot = os.path.getsize(self.bitstream)
        end = sta + tot

        # Start reading input file 256 bytes at a time and write to flash
        for dat in iter(partial(f.read, 256), ''):
            dat = [int(d.encode("hex"), 16) for d in dat]
            self._progress(sta, end, addr)

            # Erase on sector boundary
            if not (addr % 0x10000):
                self.flash.serase(addr)
                while (self.flash.rsr() & 0x01):
                    pass

            # Write data to page
            self.flash.write(addr, dat)
            while (self.flash.rsr() & 0x01):
                pass

            # increment to next page address
            addr += 256

        self._progress(sta, end, addr)
        print("")

        # Close file handle
        f.close()
        print("DONE!")

    #
    # Start IPROG sequence
    #
    def iprog(self, addr):
        if (self.comm == ELMA_I2C_MULTIBOOT):
            print("Issuing IPROG command to board in slot %d..." % self.slot)
            self.elma.write(self.slot, self.mb_base+MB_GBBAR_OFS, 0x44 | (0x0b << 24))
            self.elma.write(self.slot, self.mb_base+MB_MBBAR_OFS, addr | (0x0b << 24))
            self.elma.write(self.slot, self.mb_base+MB_CR_OFS, 0x10000)
            try:
                self.elma.write(self.slot, self.mb_base+MB_CR_OFS, 0x20000)
            except NAckError:
                pass

            # Set timeout...
            t0 = time.time()
            t1 = t0 + 60

            # and wait for the FPGA to gracefully respond, or die trying
            while (1):
                try:
                    if (time.time() >= t1):
                        print("Timeout, IPROG unsuccessful!")
                        break
                    if ((self.elma.read(self.slot, 0x4) & 0xf0) == 0x00):
                        print("IPROG unsuccessful, fallback to Golden bitstream occured!")
                        break
                    else:
                        print("IPROG successful!")
                        break
                except NAckError:
                    continue

    #
    # Sequence to read FPGA configuration register
    #
    def rdcfgreg(self):
        print("Press 'q' to end config reg readout")
        while 1:
            try:
                reg = raw_input('Address (hex): ')
                reg = int(reg, 16)
                if (reg < 0x00) or (reg > 0x22):
                    raise ValueError
                if (self.comm == ELMA_I2C_MULTIBOOT):
                    self.elma.write(self.slot, self.mb_base+MB_CR_OFS, (1 << 6) | reg)
                    val = self.elma.read(self.slot, self.mb_base+MB_SR_OFS)
                if (val & (1 << 16)):
                    print("REG(0x%02X) = 0x%04X" % (reg, val & 0xffff))
                else:
                    print("CFGREGIMG invalid!")
            except ValueError:
                if (reg == 'q'):
                    break
                print("Please input a hex value in the range [0x00, 0x22] or 'q' to quit")

    #
    # Set bitstream file path
    #
    def set_bitstream_file(self, path):
        self.bitstream = path
