#!/usr/bin/python

# 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 os
import sys
sys.path.append("../ei2c")
sys.path.append("lib-multiboot")
from ei2cexcept import *
from comm_type import *
from xil_multiboot import *
import binascii
from optparse import OptionParser

GW_DIR = "/acc/local/share/firmware/"

# When you add new board type, search for where "ADDBOARD" appears as a comment
# and make the proper additions there
BOARDIDS = ["TBLO", "T485"]

# ADDBOARD: board ID string under mandatory arguments
USAGE = """Usage: %prog [options] elma-crate board-id

Mandatory:
  elma-crate  Hostname or IP address of ELMA crate to address
  board-id    ID of board to program; must be one of
    "TBLO" -> CONV-TTL-BLO
    "T485" -> CONV-TTL-RS485"""

# Return the list of gateware files at the requested path; each board should
# have a folder under "path" with its specific name. Files under this folder
# with the board's name should be bitstream files only.
def get_gw_list(path, board):
    path = path+board+'/'
    return [path+f for f in os.listdir(path) if not os.path.isdir(f)]

# Write bitstream and perform IPROG, depending on requested operation.
#
# The write address is inferred from whether the bitstream has a "golden" string
# in it, which denotes a golden bitsream, which should always be programmed from
# address 0x0
def mass_multiboot(write, gw_path, iprog, iprogaddr):
    for slot in slots:
        # The MultiBoot base address is normally 0x100
        mb_base = 0x100

        # ... but let's make sure we get the right address on older gateware or
        # test gateware of blocking converters
        if (boardid == "TBLO"):
            gwvers = elma.read(slot, 0x4) & 0xff
            if (gwvers == 0xff):
                mb_base = 0x300
            elif ((gwvers >= 0x10) and (gwvers <= 0x22)) or \
                 (((gwvers & 0xf0) == 0x00) and (gwvers < 0x02)):
                mb_base = 0x40

        # Run MultiBoot
        mb = XilMultiboot(ELMA_I2C_MULTIBOOT, elma, slot, mb_base, gw_path)
        if (write):
            if ("golden" in gw_path):
                waddr = 0x0
            else:
                waddr = iprogaddr
            mb.write(waddr)

        # Send IPROG command if requested
        if (iprog):
            mb.iprog(iprogaddr)


# main "function"
if __name__ == "__main__":
    parser = OptionParser(usage=USAGE)
    parser.add_option("-s", "--start", help="Start running programmed bitstream",
            action="store_true", dest="iprogafter")
    parser.add_option("-i", "--iprog", help="Run bitstream (IPROG) at a specific address and exit",
            action="store", type=int, dest="iprogaddr", default=None)
    (opts, args) = parser.parse_args()

    if (len(args) < 2):
        print("ERROR: wrong or missing arguments")
        parser.print_help()
        sys.exit(2)

    hname = args[0]
    board = args[1]
    board = board.upper()

    elma = EI2C(hname, "", "")
    elma.open()

    if not (board in BOARDIDS):
        print("ERROR: Invalid board ID")
        parser.print_help()
        sys.exit(2)

    # Walk a 17-slot crate to see if any boards of the requested type are found
    slots = []
    for i in range(1, 18):
        try:
            boardid = elma.read(i, 0x0);
            boardid = binascii.unhexlify("%s" % "{0:x}".format(boardid))
            if (boardid == board):
                print("Board with ID \"%s\" found in slot %d" % (board, i))
                slots.append(i)
        except NAckError:
            pass

    # Exit if slots array is not populated
    if (len(slots) == 0):
        print("ERROR: no boards found in crate or crate power not turned on")
        sys.exit(2)

    # New line before babbling more
    print("")

    # Run IPROG and exit program, as promised by the option
    if (opts.iprogaddr != None):
        while(1):
            inp = raw_input("Are you sure you want to run IPROG on ALL cards above? (y/n) ")
            inp = inp.lower()
            if (inp == 'y'):
                # Run mass MultiBoot without writing, only with IPROG from
                # defined address
                mass_multiboot(0, None, 1, opts.iprogaddr)
                break
            elif (inp == 'n'):
                break

        sys.exit(0)

    # Change board name to be according to bitstream folder names
    # ADDBOARD: add new if branches with folder names where you store the bitstreams
    # and start addresses for the release bitstreams on new FPGAs. Note that
    # folder names have to be under the GW_DIR definition above
    if (board == "TBLO"):
        board = "conv-ttl-blo"
        opts.iprogaddr = 0x170000
    elif (board == "T485"):
        board = "conv-ttl-rs485"
        opts.iprogaddr = 0x170000

    # Get gateware list
    print("Which gateware would you like to program?")
    gw_list = sorted(get_gw_list(GW_DIR, board))
    for (i, gw) in enumerate(gw_list):
        print("  %2d -> %s" % (i, os.path.basename(gw)))

    start = False
    while (not start):
        is_valid = False
        while(not is_valid):
            try :
                print("")
                choice = int(raw_input("Enter your choice : "))
                if choice < len(gw_list):
                    is_valid = 1
                else:
                    print("%d is not in range 0-%d"%(choice, len(gw_list)-1))
            except ValueError as e :
                print ("%s is not a valid integer." % e.args[0].split(": ")[1])

        gw_path = gw_list[choice]
        print("Selected gateware: %s" % os.path.basename(gw_path))
        inp = raw_input("Start writing bitstream? (y/n) ");
        if (inp.lower() == 'y'):
            start = True

    # Start writing the boards in the crate
    mass_multiboot(1, gw_path, opts.iprogafter, opts.iprogaddr)

    elma.close()
