#!   /usr/bin/env   python
#    coding: utf8
##_________________________________________________________________________________________________
##                                                                                                |
##                                           |TDC PTS|                                            |
##                                                                                                |
##                                         CERN,BE/CO-HT                                          |
##________________________________________________________________________________________________|

##-------------------------------------------------------------------------------------------------
##                                                                                                |
##                            Write Calibration values to TDC EEPROM                              |
##                                                                                                |
##-------------------------------------------------------------------------------------------------
##                                                                                                |
## Description  Writing of the IPMI information and calibration data to the FMC EEPROM.           |
##              This is done using the gensdbfs tool. The Serial number and the calibration data  |
##              are taken from text files. Note: Requires DAC_calibration.py and                  |
##              channels_calibration.py to run to get calibration data.                           |
##                                                                                                |
##                                                                                                |
## FW to load   .bin                                                                              |
## Authors      Evangelia Gousiou (Evangelia.Gousiou@cern.ch)                                     |
## Website      http://www.ohwr.org/projects/pts                                                  |
## Date         11/01/2013                                                                        |
##-------------------------------------------------------------------------------------------------

##-------------------------------------------------------------------------------------------------
##                               GNU LESSER GENERAL PUBLIC LICENSE                                |
##                              ------------------------------------                              |
## This source file is free software; you can redistribute it and/or modify it under the terms of |
## the GNU Lesser General Public License as published by the Free Software Foundation; either     |
## version 2.1 of the License, or (at your option) any later version.                             |
## This source 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 Lesser General Public License for more details.                                    |
## You should have received a copy of the GNU Lesser General Public License along with this       |
## source; if not, download it from http://www.gnu.org/licenses/lgpl-2.1.html                     |
##-------------------------------------------------------------------------------------------------


# Import system modules
import sys
import time
import datetime
import os
import numpy as np

# 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
import fmc_tdc

def main (default_directory='.'):

    # Constants declaration
    TEST_NB = 15
    MANUFACTURER = "CERN"
    PRODUCT_NAME = "FmcTdc1ns5cha"
    PART_NUMBER = "EDA-02290-V3-0"
    NAME = 'tdc_1ns_5cha'
    SERIAL_FILENAME = "../../../../serial.txt"
    SERIAL_FILENAME = os.path.join(default_directory, SERIAL_FILENAME)
    CH_CALIBR_FILENAME = "ch_calibration_data.txt"
    CH_CALIBR_FILENAME = os.path.join(default_directory, CH_CALIBR_FILENAME)
    DAC_CALIBR_FILENAME = "DAC_calibration_data.txt"
    DAC_CALIBR_FILENAME = os.path.join(default_directory, DAC_CALIBR_FILENAME)
    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"
    EEPROM_BIN_FILENAME = "eeprom_content.out"
    EEPROM_BIN_FILENAME = os.path.join(default_directory, EEPROM_BIN_FILENAME)
    EEPROM_SIZE = 8192 # in Bytes
    EEPROM_ADDR = 0x50

    start_test_time = time.time()

    # Constants declaration
    FMC_TDC_ADDR             = '1a39:0004/1a39:0004@0001:0000'#'1a39:0004/1a39:0004@000B:0000'
    FMC_TDC_BITSTREAM_PATH   = '../firmwares/tdc.bin'
    FPGA_LOADER_PATH         = '../../../../gnurabbit/user/fpga_loader'

    # SPEC object declaration
    spec = rr.Gennum()

    # Bind SPEC object to FMC TDC card
    print "\n-------------------------------------------------------------------"
    print "----------------------------- FMC TDC -----------------------------"
    print "-------------------- TDC mezz EEPROM writting ---------------------\n"

    print ("Writing of the IPMI information and the calibration data retreived\n"
           "through tests 00 and 01, to the FMC EEPROM.\n")


    print "\n_______________________________Info______________________________\n"
    print "FMC TDC address to parse: %s"%(FMC_TDC_ADDR)
    for name, value in spec.parse_addr(FMC_TDC_ADDR).iteritems():
        print "%s:0x%04X"%(name, value)
    spec.bind(FMC_TDC_ADDR)

    # Load FMC TDC firmware
    print "\n_______________________TDC Initialisations________________________\n"
    print "Loading FMC TDC firmware...",
    firmware_loader = os.path.join(default_directory, FPGA_LOADER_PATH)
    bitstream = os.path.join(default_directory, FMC_TDC_BITSTREAM_PATH)
    os.system(firmware_loader + ' ' + bitstream)
    time.sleep(1)
    print "Firmware loaded!"

    # TDC object declaration
    tdc = fmc_tdc.CFMCTDC(spec)

    # Check bitsteam type
    bitstream_type = tdc.read_bitstream_type()
    if(bitstream_type == 0xFFFFFFFF):
        msg = ("FATAL ERROR: No access to TDC core")
        print (msg)
        raise PtsError (msg)

    if(bitstream_type != 0x2):
        msg = ("FATAL ERROR: Wrong bitstream type loaded 0x%08X") %bitstream_type
        print (msg)
        raise PtsError (msg)
    else:
        print('Access to TDC core OK')


    ###########################################################################
    # Real test stuff here!

    #==================================================
    # Read serial number from a file
    # This file must be written by the shell script launching pts
    print "\n\n_____________Reading serial and calibration txt files_____________\n"
    try:
        f_serial = open(SERIAL_FILENAME, 'r+')
    except:
        msg = ("ERROR: Text file serial.txt: Not found")
        os.close(spec.fd)
        print (msg)
        raise PtsError (msg)

    serial = f_serial.readline()
    f_serial.close()
    serial = serial[:-1] # remove EOL char
    print "Board's serial number: %s" % serial

    #==================================================
    # Read calibration data from text files
    # These file must be written by the tests performing the calibration
    try:
        f_ch_calibr  = open(CH_CALIBR_FILENAME, 'r+')
    except:
        msg = ("ERROR: Text file ch_calibration.txt: Not found")
        os.close(spec.fd)
        print (msg)
        raise PtsError (msg)

    calibr_data = []
    for line in iter(f_ch_calibr):
        calibr_data.append(0xFF & int(line, 16))
        calibr_data.append(0xFF & (int(line, 16) >> 8))
        calibr_data.append(0xFF & (int(line, 16) >> 16))
        calibr_data.append(0xFF & (int(line, 16) >> 24))
    f_ch_calibr.close()

    try:
        f_dac_calibr = open(DAC_CALIBR_FILENAME, 'r+')
    except:
        msg = ("ERROR: Text file DAC_calibration.txt: Not found")
        os.close(spec.fd)
        print (msg)
        raise PtsError (msg)

    for line in iter(f_dac_calibr):
        calibr_data.append(0xFF & int(line, 16))
        calibr_data.append(0xFF & (int(line, 16) >> 8))
        calibr_data.append(0xFF & (int(line, 16) >> 16))
        calibr_data.append(0xFF & (int(line, 16) >> 24))
    f_dac_calibr.close()

    print "Calibration data bytes:",
    string = ('[' + ", ".join([hex(i) for i in calibr_data]) + ']')
    print string.upper()

    #==================================================
    # Write calibration data to binary file
    # This file will be fed to gensdbfs
    f_bin_calibr = open(CALIBR_BIN_FILENAME, 'wb')
    for byte in calibr_data:
        f_bin_calibr.write(chr(byte))
    f_bin_calibr.close()
    print "Creating binary file with all calibration data.."

    #==================================================
    # Scan FMC I2C bus
    print "\n\n_________________Reading existing EEPROM content__________________\n"
    try:
        periph_addr = tdc.mezz_i2c_scan()
    except:
        msg = ("ERROR: EEPROM IC5: No peripheral detected")
        os.close(spec.fd)
        print (msg)
        raise PtsError (msg)

    # Check that the EEPROM is detected on the I2C bus
    if(0 == len(periph_addr)):
        msg = ("ERROR: EEPROM IC5: No peripheral detected on system management I2C bus")
        os.close(spec.fd)
        print (msg)
        raise PtsError (msg)
    else:
        if(1 != len(periph_addr)):
            msg = ("ERROR: EEPROM IC5: Signal integrity problem detected on system management I2C bus, %d devices detected instead of 1"%(len(periph_addr)))
            os.close(spec.fd)
            print (msg)
            raise PtsError (msg)
        else:
            if(EEPROM_ADDR != periph_addr[0]):
                msg = ("ERROR: EEPROM IC5: Wrong device mounted on system management I2C bus, address is:0x%.2X expected:0x%.2X"%(periph_addr[0],EEPROM_ADDR))
                os.close(spec.fd)
                print (msg)
                raise PtsError (msg)

    #==================================================
    # Check if a manufacturing date is present in the EEPROM.
    # If not, put the current date (it means it's the first time the test is run).
    # If a date is present, keep it.

    # EEPROM clear code used to test the test!
    #eeprom_data = [0x0] * EEPROM_SIZE
    #fmc.sys_i2c_eeprom_write(eeprom_data)

    # Read entire EEPROM
    print "Reading EEPROM content.."
    try:
        eeprom_data_read = tdc.mezz_i2c_eeprom_read(0, EEPROM_SIZE)
    except:
        msg = ("ERROR: EEPROM IC5: Read operation faied")
        os.close(spec.fd)
        print (msg)
        raise PtsError (msg)

    # Write EEPROM data to binary file
    print "Writing EEPROM content to binary file.."
    f_eeprom = open(EEPROM_BIN_FILENAME, "wb")
    for byte in eeprom_data_read:
        f_eeprom.write(chr(byte))
    f_eeprom.close()

    # Get manufacturing date from EEPROM data, if exists
    print "Looking for manufacturing date in the EEPROM content.."
    eeprom_data = open(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 eeprom: %s"%(str(mfg_date)))
    print(" -> 0x%06X = %d minutes (since 0:00 1/1/96)\n"%(mfg_date_min, mfg_date_min))

    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)

    # No manufacturing date present in EEPROM, put the current date

    # 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))

    if(mfg_date_min == 0 | mfg_date_min == 0xffffff):
        print("No manufacturing date found in the EEPROM => taking current date: %s\n" % str(current_date))
        mfg_date_min = current_date_min
    elif(mfg_date_min > current_date_min):
        print("Date found in the EEPROM is in the future => taking current date: %s\n" % str(current_date))
        mfg_date_min = current_date_min
    elif(mfg_date_min < first_prod_date_min):
        print("Date found in the EEPROM is older than the first production => taking current date: %s\n" % str(current_date))
        mfg_date_min = current_date_min
    else:
        print("Valid manufacturing date found in EEPROM: %s (will be preserved)\n" % str(mfg_date))


    #==================================================
    # 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 "\n___________________Generating EEPROM content______________________\n"
    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("Generating binary file with IPMI stuff..\n")
    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()

    #==================================================
    # Create an empty 'data' folder
    data_dir = SDBFS_DIR + "data"
    if(not(os.path.exists(data_dir))):
        cmd = "mkdir " + data_dir
        print("Create empty data folder, cmd: %s\n"%(cmd))
        os.system(cmd)
    else:
        print("%s folder already exists.\n"%(data_dir))

    #==================================================
    # Read impi content from binary file
    f_ipmi = open(IPMI_BIN_FILENAME, "rb")
    ipmi_data = []
    byte = f_ipmi.read(1) # reads one byte
    while byte:
        ipmi_data.append(ord(byte))
        byte = f_ipmi.read(1) # reads one byte
    f_ipmi.close()

    #print "Raw IPMI data:"
    #for add, data in enumerate(ipmi_data):
    #    print "0x%02X(%3d): 0x%02X" % (add, add, data)

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

    #==================================================
    # Erase EEPROM content
    print "\n______________________Writing EEPROM content______________________\n"
    print "Erasing EEPROM content..\n"
    eeprom_data = [0x0] * EEPROM_SIZE
    tdc.mezz_i2c_eeprom_write_page(eeprom_data)

    #==================================================
    # Read eeprom content from binary file
    f_bin_eeprom = open(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("Content to be written to the eeprom (length=0x%X (%d)):"%(len(eeprom_data), len(eeprom_data)))
    for i in range(len(eeprom_data)/16):
        print("0x%04X"%(i*16)),
        for j in range(16):
            print("%02X"%eeprom_data[(i*16)+j]),
        print("")
    print("")

    #==================================================
    # Write content to EEPROM via I2C
    print "Writing now EEPROM content..\n"
    if eeprom_data != []:
        tdc.mezz_i2c_eeprom_write_page(eeprom_data)
    #==================================================
    # Read back EEPROM content via I2C
    print "Reading data back for validation..\n"
    eeprom_data_read = tdc.mezz_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"


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

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

    os.close(spec.fd)
    print "\n-----------------------------------------------------------------\n\n\n"
    end_test_time = time.time()
    print "\nTest elapsed time: %.2f seconds\n" % (end_test_time-start_test_time)

if __name__ == '__main__' :
    main()
