#!   /usr/bin/env   python
#    coding: utf8

# Copyright CERN, 2011
# Author: Matthieu Cattin <matthieu.cattin@cern.ch>
# Licence: GPL v2 or later.
# Website: http://www.ohwr.org
# Last modifications: 16/5/2012

# Import system modules
import sys
import time
import datetime
import os

# Add common modules and libraries location to path
sys.path.append('../../../')
sys.path.append('../../../gnurabbit/python/')
sys.path.append('../../../common/')
sys.path.append('../../fmceeprom/python/')

# Import common modules
from ptsexcept import *
from fmc_eeprom import *
import rr

# Import specific modules
from fmc_adc_spec import *
from fmc_adc import *


"""
test31: Fix EEPROM content:
         - wrong IPMI format
         - add 'name' file
         - add 'data/' dir
         - rename sdb files
         - change sdb layout

        Serial number and manufacturing date are taken from the wrong EEPROM.

Note: Requires test00.py to run first to load the firmware!
"""

def main (default_directory='.', offset="0x1000", filename="calibration.sdb"):

    # Constants declaration
    TEST_NB = 31
    EXPECTED_BITSTREAM_TYPE = 0x1
    MANUFACTURER = "CERN"
    PRODUCT_NAME = "FmcAdc100m14b4cha"
    PART_NUMBER = "EDA-02063-V5-0"
    NAME = 'adc_100m'
    SDBFS_DIR = "sdbfs/"
    SDBFS_DIR = os.path.join(default_directory, SDBFS_DIR)
    NAME_BIN_FILENAME = SDBFS_DIR + "name"
    IPMI_BIN_FILENAME = SDBFS_DIR + "IPMI-FRU"
    CALIBR_BIN_FILENAME = SDBFS_DIR + "calib"
    BAD_EEPROM_BIN_FILENAME = "bad_eeprom_content.out"
    BAD_EEPROM_BIN_FILENAME = os.path.join(default_directory, BAD_EEPROM_BIN_FILENAME)
    NEW_EEPROM_BIN_FILENAME = "new_eeprom_content.out"
    NEW_EEPROM_BIN_FILENAME = os.path.join(default_directory, NEW_EEPROM_BIN_FILENAME)
    EEPROM_SIZE = 8192 # in Bytes

    BAD_SDB_DIR_OFFSET = offset
    BAD_CALIBR_BIN_FILENAME = filename


    start_test_time = time.time()
    print "================================================================================"
    print "Test%02d start\n" % TEST_NB

    # SPEC object declaration
    print "Loading hardware access library and opening device.\n"
    spec = rr.Gennum()

    # Carrier object declaration (SPEC board specific part)
    try:
        carrier = CFmcAdc100mSpec(spec, EXPECTED_BITSTREAM_TYPE)
    except FmcAdc100mSpecOperationError as e:
        raise PtsCritical("Carrier init failed, test stopped: %s" % e)

    # Mezzanine object declaration (FmcAdc100m14b4cha board specific part)
    try:
        fmc = CFmcAdc100m(spec)
    except FmcAdc100mOperationError as e:
        raise PtsCritical("Mezzanine init failed, test stopped: %s" % e)


    ###########################################################################
    # Real test stuff here
    try:

        #==================================================
        # Get data from bad eeprom
        #==================================================
        print("\n==================================================")
        print("Get bad image from mezzanine EEPROM.\n")

        # Calculate number of minutes since 0:00 1/1/96
        current_date = datetime.datetime.now()
        ref_date = datetime.datetime(1996, 1, 1)
        diff_date = current_date - ref_date
        current_date_min = int(diff_date.total_seconds()//60)
        print("Current date/time: %s"%(str(current_date)))
        print(" -> 0x%06X = %d minutes (since 0:00 1/1/96)\n" % (current_date_min, current_date_min))

        # Read entire bad EEPROM
        eeprom_data_read = fmc.sys_i2c_eeprom_read(0, EEPROM_SIZE)

        # Write EEPROM data to binary file
        print("Dumping bad EEPROM content (length=0x%x, %d):"%(len(eeprom_data_read), len(eeprom_data_read)))
        f_eeprom = open(BAD_EEPROM_BIN_FILENAME, "wb")
        for i,byte in enumerate(eeprom_data_read):
            f_eeprom.write(chr(byte))
            if not(i%16):
                print("")
            print("%02x"%(byte)),
        print("\n")
        f_eeprom.close()

        # Get calibration date from bad ipmi data
        eeprom_data = open(BAD_EEPROM_BIN_FILENAME, "rb").read()
        mfg_date_min = ipmi_get_mfg_date(eeprom_data)
        ref_date = datetime.datetime(1996, 1, 1)
        mfg_date = ref_date + datetime.timedelta(minutes=mfg_date_min)
        print("Mfg date read from bad EEPROM image: %s"%(str(mfg_date)))
        print(" -> 0x%06X = %d minutes (since 0:00 1/1/96)\n"%(mfg_date_min, mfg_date_min))

        # Get serial number from bad ipmi data
        serial_offset = 0x26
        serial_length = 19
        serial = ""
        for i in range(serial_length):
            serial += chr(eeprom_data_read[i+serial_offset])
        print("Serial number read from bad EEPROM image: %s"%serial)
        if serial == '':
            raise PtsCritical("Invalid serial number read in EEPROM!")

        BAK_CALIBR_BIN_FILENAME = "calibr_bak/calibr_" + serial + ".bin"
        BAK_CALIBR_BIN_FILENAME = os.path.join(default_directory, BAK_CALIBR_BIN_FILENAME)

        # Check that the manufacturing date is valid
        first_prod_date = datetime.datetime(2013, 3, 4)
        diff_date = first_prod_date - ref_date
        first_prod_date_min = int(diff_date.total_seconds()//60)
        if(mfg_date_min == 0 | mfg_date_min == 0xffffff):
            raise PtsCritical("No manufacturing date found in the EEPROM!")
        elif(mfg_date_min > current_date_min):
            raise PtsCritical("Date found in the EEPROM is in the future!")
        elif(mfg_date_min < first_prod_date_min):
            raise PtsCritical("Date found in the EEPROM is older than the first production!")
        else:
            print("Valid manufacturing date found in EEPROM: %s (will be preserved)\n" % str(mfg_date))


        #==================================================
        # Build the new ipmi file
        #==================================================
        print("\n==================================================")

        # Create Board Info Area
        # FRU field is used to store the date of generation of the eeprom content
        # This could be used later to determine if the content has to be udated (bug fix, ...)
        print("EEPROM content generation date: %s\n" % str(current_date))

        print("IPMI: Board Info Area")
        print(" - Mfg. Date/Time     : 0x%06X" % mfg_date_min)
        print(" - Board Manufacturer : %s" % MANUFACTURER)
        print(" - Board Product Name : %s" % PRODUCT_NAME)
        print(" - Board Serial Number: %s" % serial)
        print(" - Board Part Number  : %s" % PART_NUMBER)
        print(" - FRU File ID        : %s" % str(current_date))
        print("")
        fru = "%s" % str(current_date)
        bia = BoardInfoArea(mfg_date_min, MANUFACTURER, PRODUCT_NAME, serial, PART_NUMBER, fru)

        # Multirecords Area
        print("IPMI: Multi-record area")

        # output number, vnom, vmin, vmax, ripple, imin, imax
        vnom=2.5; vmin=2.375; vmax=2.625; ripple=0.0; imin=0; imax=4000
        print(" - DC Load (VADJ): vnom=%2.3fV, vmin=%2.3fV, vmax=%2.3fV, ripple=%2.3fV, imin=%dmA, imax=%dmA"%(vnom, vmin, vmax, ripple, imin, imax))
        dcload0 = DCLoadRecord(0, vnom, vmin, vmax, ripple, imin, imax) # VADJ

        vnom=3.3; vmin=3.135; vmax=3.465; ripple=0.0; imin=0; imax=3000
        print(" - DC Load (P3V3): vnom=%2.3fV, vmin=%2.3fV, vmax=%2.3fV, ripple=%2.3fV, imin=%dmA, imax=%dmA"%(vnom, vmin, vmax, ripple, imin, imax))
        dcload1 = DCLoadRecord(1, vnom, vmin, vmax, ripple, imin, imax) # P3V3

        vnom=12.0; vmin=11.4; vmax=12.6; ripple=0.0; imin=0; imax=1000
        print(" - DC Load (P12V): vnom=%2.3fV, vmin=%2.3fV, vmax=%2.3fV, ripple=%2.3fV, imin=%dmA, imax=%dmA"%(vnom, vmin, vmax, ripple, imin, imax))
        dcload2 = DCLoadRecord(2, vnom, vmin, vmax, ripple, imin, imax) # P12V

        dcload = [ dcload0, dcload1, dcload2 ]

        # output number, vnom, vmin, vmax, ripple, imin, imax
        vnom=0.0; vmin=0.0; vmax=0.0; ripple=0.0; imin=0; imax=0
        print(" - DC Out (VIO_B_M2C): vnom=%2.3fV, vmin=%2.3fV, vmax=%2.3fV, ripple=%2.3fV, imin=%dmA, imax=%dmA"%(vnom, vmin, vmax, ripple, imin, imax))
        dcout0 = DCOutputRecord(3, vnom, vmin, vmax, ripple, imin, imax) # VIO_B_M2C

        print(" - DC Out (VREF_A_M2C): vnom=%2.3fV, vmin=%2.3fV, vmax=%2.3fV, ripple=%2.3fV, imin=%dmA, imax=%dmA"%(vnom, vmin, vmax, ripple, imin, imax))
        dcout1 = DCOutputRecord(4, vnom, vmin, vmax, ripple, imin, imax) # VREF_A_M2C

        print(" - DC Out (VREF_B_M2C): vnom=%2.3fV, vmin=%2.3fV, vmax=%2.3fV, ripple=%2.3fV, imin=%dmA, imax=%dmA"%(vnom, vmin, vmax, ripple, imin, imax))
        dcout2 = DCOutputRecord(5, vnom, vmin, vmax, ripple, imin, imax) # VREF_B_M2C

        dcout = [ dcout0, dcout1, dcout2 ]

        # module size  : 0=single width, 1=double width
        # P1 size      : 0=LPC, 1=HPC
        # P2 size      : 0=LPC, 1=HPC, 3=not fitted
        # clock dir    : 0=M2C, 1=C2M
        # nb sig P1 A  : number
        # nb sig P1 B  : number
        # nb sig P2 A  : number
        # nb sig P2 B  : number
        # nb GBT P1    : number
        # nb GBT P2    : number
        # max TCK freq : frequency in MHz
        size=0; p1_size=0; p2_size=3; clk_dir=0; sig_p1_a=68; sig_p1_b=0; sig_p2_a=0; sig_p2_b=0; gbt_p1=0; gbt_p2=0; tck=0
        print(" - OEM: Module Size=%d, P1 Connector Size=%d, P2 Connector Size=%d, Clock Direction=%d, \
P1 Bank A nb signal=%d, P1 Bank B nb signal=%d, P2 Bank A nb signal=%d, P2 Bank B nb signal=%d, P1 GBT nb=%d, P2 GBT nb=%d, TCK max freq=%d\n"%(size, p1_size, p2_size, clk_dir, sig_p1_a, sig_p1_b, sig_p2_a, sig_p2_b, gbt_p1, gbt_p2, tck))
        oem = OEMRecord(size, p1_size, p2_size, clk_dir, sig_p1_a, sig_p1_b, sig_p2_a, sig_p2_b, gbt_p1, gbt_p2, tck)

        # Write ipmi content to a binary file
        print("Generate binary file with IPMI crap.\n -> Filename: %s"%IPMI_BIN_FILENAME)
        ipmi_open_file(IPMI_BIN_FILENAME)
        ipmi_set(bia, dcload, dcout, oem)
        ipmi_write()
        ipmi_close_file()

        #==================================================
        # Write 'name' file
        #==================================================
        print("\n==================================================")
        print("Writing %s with: %s"%(NAME_BIN_FILENAME, NAME))
        f_name = open(NAME_BIN_FILENAME, "wb")
        for char in NAME:
            f_name.write(char)
        f_name.close()

        #==================================================
        # Extract calibration data from bad eeprom image
        #==================================================
        print("\n==================================================")
        cmd = 'sdb-read -e ' + BAD_SDB_DIR_OFFSET + ' ' + BAD_EEPROM_BIN_FILENAME + ' ' + BAD_CALIBR_BIN_FILENAME + ' > ' + CALIBR_BIN_FILENAME
        print("Exctract calibration binary file from bad EEPROM image, cmd: %s"%(cmd))
        os.system(cmd)
        cmd = "cp " + CALIBR_BIN_FILENAME + " " + BAK_CALIBR_BIN_FILENAME
        print("Copy calibration binary, cmd: %s"%(cmd))
        os.system(cmd)

        #==================================================
        # Check that valid calibration data were extracted
        #==================================================
        print("\n==================================================")
        print("Check calibration data")
        calib_data = open(CALIBR_BIN_FILENAME, "rb").read()
        if calib_data == '':
            raise PtsCritical("Invalid calibration data read in EEPROM!")

        #==================================================
        # Generate eeprom image with gensdbfs
        #==================================================
        print("\n==================================================")
        cmd = "gensdbfs " + SDBFS_DIR + " " + NEW_EEPROM_BIN_FILENAME
        print("Generate new EEPROM image, cmd: %s\n"%(cmd))
        os.system(cmd)

        #==================================================
        # Read eeprom content from binary file
        #==================================================
        print("\n==================================================")
        f_bin_eeprom = open(NEW_EEPROM_BIN_FILENAME, "rb")
        eeprom_data = []
        byte = f_bin_eeprom.read(1) # reads one byte
        while byte:
            eeprom_data.append(ord(byte))
            byte = f_bin_eeprom.read(1) # reads one byte
        f_bin_eeprom.close()

        # Dump EEPROM content to log
        print("Dumping new EEPROM content (length=0x%x, %d):"%(len(eeprom_data), len(eeprom_data)))
        for i,byte in enumerate(eeprom_data):
            if not(i%16):
                print("")
            print("%02x"%(byte)),
        print("\n")

        #==================================================
        # Erase EEPROM content
        #==================================================
        print("\n==================================================")
        print "Erase EEPROM content."
        fmc.sys_i2c_eeprom_write([0x0] * EEPROM_SIZE)

        #==================================================
        # Write content to EEPROM via I2C
        #==================================================
        print("\n==================================================")
        print "Write EEPROM content."
        if eeprom_data != []:
            fmc.sys_i2c_eeprom_write(eeprom_data)

        #==================================================
        # Read back EEPROM content via I2C
        #==================================================
        print("\n==================================================")
        eeprom_data_read = fmc.sys_i2c_eeprom_read(0, len(eeprom_data))
        mismatch = 0
        for i in range(len(eeprom_data)):
            wr_data = eeprom_data[i]
            rd_data = eeprom_data_read[i]
            if wr_data == rd_data:
                check = "OK"
            else:
                check = "FAILED"
                mismatch += 1
                print "0x%02X 0x%02X => %s" % (wr_data, rd_data, check)
            #print "0x%02X 0x%02X => %s" % (wr_data, rd_data, check)
        print "EEPROM content comparison => ",
        if(mismatch == 0):
            print "OK"
        else:
            print "FAILED"

    except FmcAdc100mOperationError as e:
        raise PtsError("Test failed: %s" % e)

    ###########################################################################

    print ""
    print "==> End of test%02d" % TEST_NB
    print "================================================================================"
    end_test_time = time.time()
    print "Test%02d elapsed time: %.2f seconds\n" % (TEST_NB, end_test_time-start_test_time)

    # Check if an error occured during EEPROM verification
    if(mismatch != 0):
        raise PtsError("EEPROM comparison failed: %d mismatch found. Check log for details." % mismatch)


if __name__ == '__main__' :
    arg_nb = len(sys.argv[1:])
    if arg_nb == 2:
        offset = sys.argv[1]
        filename = sys.argv[2]
    elif arg_nb > 2:
        print("Too many arguments.")
        sys.exit()
    else:
        offset = "0x1000"
        filename = "calibration.sdb"

    main('.', offset, filename)
