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

# Copyright CERN, 2011
# Author: Marek Guminski<marek.guminski@creotech.pl>
# Licence: GPL v2 or later.
# Website: http://www.ohwr.org

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

import utilities as util
TOPDIRNAME=util.TOPDIRNAME
abspath = util.find_prj_topdir(TOPDIRNAME);


# helper libraries
import numpy 
# import matplotlib.pyplot as plt
import shutil
import psutil
import subprocess
# Import specific modules

sys.path.append(abspath+util.fmcmasterfip_commonpath)
sys.path.append(abspath+util.fmcmasterfip_regspath)
# sys.path.append('regs')
import onewire as ow
import ds18b20 
import spi as ospi
import wbslave
import eeprom_24aa64
import i2c

# useful functions


# addressing
import masterfip_addrtable;
import carrier_addrtable;




"""
FMC masterFIP class
"""

class adc_conf:
    def __init__(self):
        self.samples_before     = 256;
        self.samples_after      = 0xffff; 
        self.trigger_channel    = 1;
        self.trigger_threshold  = 2048;
        self.undersampling      = 1;
        self.output_file        = "/tmp/data.bin";
        self.output_img        = "/tmp/image.png";
        
    

class fmcmasterfip:

    
    EEPROM_ADDR                 = 0x50
    
    # Carrier specific components
    #------------------------------------
    # Carrier CSR address
    CARRIER_CSR_ADDR            = 0x00001200
    # Carrier One Wire
    CARR_ONEWIRE_ADDR           = 0x00001100

    CROSSBAR_MAIN               = 0x20000
    
    #------------------------------------
    # MasterFIP FMC address
    # eeprom
    MASTERFIP_I2C_EEPROM        = CROSSBAR_MAIN + 0x1000
    # ADC configuration
    MASTERFIP_SPI_ADC           = CROSSBAR_MAIN + 0x1100
    # SI570 configuration (not mount)
    MASTERFIP_I2C_SI570         = CROSSBAR_MAIN + 0x1200
    # ADC core registers
    MASTERFIP_CSR_ADC           = CROSSBAR_MAIN + 0x1300
    # Thermometer
    MASTERFIP_OW_THERM          = CROSSBAR_MAIN + 0x1400
    # Masterfip core registers
    MASTERFIP_CORE_ADDR         = CROSSBAR_MAIN + 0x8000

    SAMPLING_FREQ_ADDR_IN_BINARY_DATA = 72;

    freq_options    = [ 31250, 1e6, 25e5, 5e6 ]
    adc_sampling_freq = 100e6

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


    def __init__(self, carrier, topdirpath, firmware = None ):
        
        ###################################################################
        ###################### module initialization ####################
        ###################################################################
        self.carrier        = carrier;
        self.firmware       = topdirpath + firmware;
        self.topdirpath     = topdirpath;
        
        ###################################################################
        #################### interfaces initialization ####################
        ###################################################################
        
        # creation of interfaces to modules controlling external devices
        self.carr_onewire   = ow.COpenCoresOneWire(self.carrier, self.CARR_ONEWIRE_ADDR, 199, 39) #for 40 MHz clock
        self.carr_ds18b20   = ds18b20.CDS18B20(self.carr_onewire, 0)
        
        self.adc_spi        = ospi.COpenCoresSPI(self.carrier, self.MASTERFIP_SPI_ADC, 15)
        
        self.fmc_onewire    = ow.COpenCoresOneWire(self.carrier, self.MASTERFIP_OW_THERM, 499, 99) #for 40 MHz clock
        self.fmc_ds18b20    = ds18b20.CDS18B20(self.fmc_onewire, 0)

        self.fmc_sys_i2c    = i2c.COpenCoresI2C(self.carrier, self.MASTERFIP_I2C_EEPROM, 199)
        self.fmc_eeprom_24aa64  = eeprom_24aa64.C24AA64(self.fmc_sys_i2c, self.EEPROM_ADDR)

        # creation of interfaces to modules controlling main functionality
        self.fipcore        = wbslave.wbslave(carrier, self.MASTERFIP_CORE_ADDR, masterfip_addrtable.addr)
        self.carrier_csr    = wbslave.wbslave(carrier, self.CARRIER_CSR_ADDR, carrier_addrtable.addr)
        
                ###################################################################
        ###################### firmware initialization ####################
        ###################################################################
        self.verify_bitstream_type();
        self.verify_carrier_status();
        
        self.rst_core()
        time.sleep(0.5)
        
        
        ###################################################################
        ################# ADC interrupt initialization ####################
        ###################################################################
        #should be done by ADC driver, but for some reason its not
        self.carrier.write( 0x01404,1 )
        self.carrier.write( 0x21504,1 )
        
#         Make sure that serial data output is enabled
        self.fipcore.write_regname('test',0)
        
        self.adc_def_conf = adc_conf()
        
        self.busfreq = self.get_bus_freq()
        
    # Returns FMC unique ID
    def get_unique_id(self):
        return self.fmc_ds18b20.read_serial_number()

    # Returns FMC temperature
    def get_temp(self):
        serial_number = self.fmc_ds18b20.read_serial_number()
        if(serial_number == -1):
            return -1
        else:
            return self.fmc_ds18b20.read_temp(serial_number)
    
    # write to EEPROM on system i2c bus
    def sys_i2c_eeprom_write(self, addr, data):
        return self.fmc_eeprom_24aa64.wr_data(addr, data)

    # read from  EEPROM on system i2c bus
    def sys_i2c_eeprom_read(self, addr, size):
        return self.fmc_eeprom_24aa64.rd_data(addr, size)
    
    def adc_init(self):
        self.adc_power(1)
        self.adc_configure();
        
    
    def adc_configure(self):
        # software reset
        # should be done after power up
        # self cleaning
        self.write_adc_reg_spi( 0, 0x80 )
        
        time.sleep(0.01)
        
        # set up singed data format
        self.write_adc_reg_spi( 1, 0x20 )
    
    def adc_single_sample(self):
        return numpy.int16( util.u2int16arr( numpy.uint16( [ self.carrier.read(self.MASTERFIP_CSR_ADC+0x38), self.carrier.read(self.MASTERFIP_CSR_ADC+0x4c) ] ))) 
    
    def read_adc_reg_spi(self, addr ):
        # frame format:
        # BIT:            15   14 13 12 11 10 9  8  7  6  5  4  3  2  1  0  
        # FUNCTION:     rd/nwr A6 A5 A4 A3 A2 A1 A0 D7 D6 D5 D4 D3 D2 D1 D0
        return self.adc_spi.transaction16(0, 0x8000 | ((addr&0x7f)<<8) )
    
    def write_adc_reg_spi(self, addr, data ):
        # frame format:
        # BIT:            15   14 13 12 11 10 9  8  7  6  5  4  3  2  1  0  
        # FUNCTION:     rd/nwr A6 A5 A4 A3 A2 A1 A0 D7 D6 D5 D4 D3 D2 D1 D0
        return self.adc_spi.transaction16( 0, ((addr&0x7f)<<8) + (data&0xff) )
    
            
    def load_firmware(self):
        self.carrier.program(self.firmware);
    
    def set_firmware_path(self, firmware):
        self.firmware = firmware;

        
    # set up acquisition
    def setup_acquisition( self, customconfig = 0 ):
        
        # if customconfig is not specified default will be used
        if customconfig == 0:
            cfg = self.adc_def_conf
        elif isinstance(customconfig, adc_conf):
            util.dbg_msg("Using custom config")
            cfg = customconfig
        else:
            util.crit_msg("Incorrect call of setup_acquisition")
        
        # kill acquisition process if for some reason it is already running
        for proc in psutil.process_iter():
            if proc.name == "fald-acq":
                proc.kill()
                
        # if tmp file exist
        if os.path.isfile(cfg.output_file):
            # remove it
            os.remove(cfg.output_file)
        
        # make id use form understandable by driver
        idstr = "0x%x" % (self.carrier.bus << 8)
        util.info_msg("Setting up acquisition on carrier connected to bus %s" % idstr )
        
        call = [self.topdirpath + util.faldacq_path, 
            "-b", str(cfg.samples_before), 
            "-a", str(cfg.samples_after), 
            "-u", str(cfg.undersampling),
            "-c", str(cfg.trigger_channel),
            "-t", str(cfg.trigger_threshold), 
            "-r", "10", 
            "-B", cfg.output_file, 
            idstr ]
            
        util.info_msg("Starting acquisition with following call:")
        util.info_msg("\t%s\n" % ' '.join(call) )
        
        # start new acquisition
        proc = subprocess.Popen( call,
            stderr=subprocess.PIPE,
            stdout=subprocess.PIPE
            )
         
        time.sleep(1)
        
        return proc
        
    def customize_adc_settings(self, transmission_bytes, samplesperbit=20, margin=1.2):

        oversampling=int(self.adc_sampling_freq/self.busfreq)
        
        undersampling=float(oversampling)/float(samplesperbit)
        
        util.dbg_msg("Initial undersampling: %f" % undersampling )
        
        # in order to keep the same number of samples for all bus speeds
        # samples per bit must be limited to maximum value achievable for fastest bus
        if undersampling > 1:
            samples = transmission_bytes*8*samplesperbit*margin
            undersampling = int(undersampling)
        else :
            util.crit_msg("Wrong samples per bit value selected. Can't set such value! Following subtests would fail!" )
        
        util.dbg_msg("Undersampling: %d, samples %d" % ( undersampling, samples ) )
        
        custom_conf = adc_conf()
        custom_conf.undersampling = undersampling;
        custom_conf.samples_after = int( samples )
        
        return custom_conf

    def save_sampling_freq( self, freq ):
    # modify standard binary output file to store inforation about sampling frequency
        try:
            with open( self.adc_def_conf.output_file, "r+b") as fin:
                fin.seek( self.SAMPLING_FREQ_ADDR_IN_BINARY_DATA)
                fin.write(freq)
                fin.close()
        except EnvironmentError: # parent of IOError, OSError *and* WindowsError where available
            util.info_msg('File containing ADC samples not found')

    def read_adc_samples( self ):
        
        try:
        
            with open( self.adc_def_conf.output_file, "rb") as fin:
                

                # header
                header=fin.read(512) 
                # get number of samples
                nsamples = struct.unpack("I",header[8:12])
                # unpack gives structure
                # integer (that is interesting) is stored in address 0
                # number of samples must be divided by number of channels
                nsamples = nsamples[0]/4

                # in order to know timescale i had to add information about sampling frequency to binary file
                # it is stred in uint32_t reserverd on byte 76
                sampling_freq = struct.unpack("I",header[ self.SAMPLING_FREQ_ADDR_IN_BINARY_DATA : self.SAMPLING_FREQ_ADDR_IN_BINARY_DATA+4 ] )
                if ( sampling_freq[0] == 0):
                    util.info_msg( "Sampling frequency is not stored in given file." )
                    sampling_period = 1    
                else:
                    sampling_period = 1e6 / sampling_freq[0];

                # read data from file
                d=numpy.fromfile(file=fin,dtype=numpy.uint16).reshape(nsamples,4)
                
                # helper vector
                samples_range=range(0,nsamples-1)
            
                # positive line is connected to ADC channel 0
                positive_samples = numpy.int16( util.u2int16arr( numpy.uint16( d[samples_range, 0] ) ) )

                # negative line is connected to ADC channel 1
                negative_samples = numpy.int16( util.u2int16arr( numpy.uint16( d[samples_range, 1] ) ) )
                
                transmission = numpy.int32( numpy.int32(positive_samples) - numpy.int32(negative_samples) );
                
                time = numpy.linspace(0, sampling_period*len(transmission),len(transmission))

                return [transmission, positive_samples, negative_samples, time ]

        except EnvironmentError: # parent of IOError, OSError *and* WindowsError where available
            util.info_msg('File containing ADC samples not found')
            return [ -1, -1, -1, -1]


    def save_transmission(self, path ):
        if os.path.exists(self.adc_def_conf.output_file) :
            shutil.move(self.adc_def_conf.output_file, path+".bin" )
        if os.path.exists(self.adc_def_conf.output_img) :
            shutil.move(self.adc_def_conf.output_img, path+".png" )
        
        
###############################################################################################
    def verify_carrier_status(self):
        if ( self.carrier_csr.read_regname("stat.fmc_pres") ):
            util.crit_msg("FMC slot NOT populated");
        else:
            util.info_msg("FMC slot populated");

        if ( self.carrier_csr.read_regname("stat.p2l_pll_lck") ):
            util.info_msg("GN4142 core PLL locked");
        else:
            util.crit_msg("GN4142 core PLL NOT locked");
        
        if ( self.carrier_csr.read_regname("stat.sys_pll_lck") ):
            util.info_msg("System PLL locked");
        else:
            util.crit_msg("System PLL NOT locked");
        

        if ( self.carrier_csr.read_regname("stat.ddr3_cal_done") ):
            util.info_msg("DDR3 calibration done");
        else:
            util.crit_msg("DDR3 calibration NOT done");
        



    # Read bitstream type
    def verify_bitstream_type(self):
        
        # read carrier status register 
        tmp = self.carrier_csr.read_regname("carrier");
        if( tmp == 0xFFFFFFFF):
            util.crit_msg( "No access to masterfip core" )
            
        elif( tmp != 0x10003):
            util.crit_msg( "Wrong bitstream type loaded 0x%8X" % tmp )
            
        else:
            util.info_msg( "Access to masterfip core OK 0x%8X" % tmp )
            
        return True 


###############################################################################################
    # Reset the masterFIP core
    def rst_core(self):
#         self.send_id_dat(0x7F14)
        self.fipcore.write_regname('tx_ctrl', 0 )
        time.sleep(0.01)
        self.fipcore.write_regname('rstn', 0xCAFE0003)
        time.sleep(0.5)
        self.fipcore.write_regname('rstn', 0xCAFE0000)
        time.sleep(0.5)
        self.fipcore.write_regname('rstn', 0xCAFE0003)
        time.sleep(1)
    
    def rst_fd(self):
        self.fipcore.write_regname('rstn', 0xCAFE0003)
        time.sleep(0.5)
        self.fipcore.write_regname('rstn', 0xCAFE0001)
        time.sleep(0.5)
        self.fipcore.write_regname('rstn', 0xCAFE0003)
        time.sleep(1)


    # Read masterFIP ID
    def read_masterfip_id(self):
        util.info_msg( "ID byte: 0x%08X"%(self.fipcore.read_regname('id')) )

    # Read WorldFIP speed    
    def read_speed(self):
        util.info_msg("Speed  : 0x%08X"%(self.fipcore.read_regname('speed')) )

    def get_bus_freq(self):
        print self.fipcore.read_regname('speed')
        return self.freq_options[ self.fipcore.read_regname('speed') ]

    
    def adc_power(self, val):
#         all power switches are enabled by low value
        val = 0 if val > 0 else 1;
        
        self.fipcore.write_regname('adc.1v8_shdn_n', val)
        self.fipcore.write_regname('adc.m5v_shdn_n', val)
        self.fipcore.write_regname('adc.5v_en', val)
    
    def close_relay(self, val):
        if val == 0 :
            self.fipcore.write_regname('adc.prim_conn', 0)
            self.fipcore.write_regname('adc.sec_conn', 0)
    
        elif val == 1 :
            self.fipcore.write_regname('adc.prim_conn', 1)
            self.fipcore.write_regname('adc.sec_conn', 0)
            
        elif val == 2 :
            self.fipcore.write_regname('adc.prim_conn', 0)
            self.fipcore.write_regname('adc.sec_conn', 1)
        else:
            util.dbg_msg("Wrong identifier" )
            return -1
        return 0



    # Disable EXT_SYNC test pulse
    def disable_ext_sync_tst(self):
        self.fipcore.write_regname('ext_sync', 0)

    # Send ID_DAT    
    def send_id_dat(self, varid):
        
        self.fipcore.write_regname('tx_ctrl', 1)
        self.fipcore.write_regname('tx_ctrl', 0)
        self.fipcore.write_regname('tx_data_ctrl', 0x3)
        self.fipcore.write_regname('tx_data_reg1', varid)
        self.fipcore.write_regname('tx_ctrl', 0x202)
    
    
    # Read RP_DAT
    def read_rp_dat(self):
        
        rx_stat      = self.fipcore.read_regname('rx_stat')
        rx_data_ctrl = self.fipcore.read_regname('rx_data_ctrl')
        rx_data_reg1 = self.fipcore.read_regname('rx_data_reg1')
        rx_data_reg2 = self.fipcore.read_regname('rx_data_reg2')
        rx_data_reg3 = self.fipcore.read_regname('rx_data_reg3')
        rx_data     = rx_data_reg3 << 32*2 | rx_data_reg2 << 32*1 | rx_data_reg1 << 32*0 
        return [ rx_stat, rx_data_ctrl, rx_data ]
#         all_bytes    = (rx_stat << 32*4) | (rx_data_ctrl << 32*3) | (rx_data_reg1 << 32*2) | (rx_data_reg2 << 32) | (rx_data_reg3)
#         print ("bytes stat : 0x%08X")%(rx_stat)
#         print ("byte ctrl  : 0x%08X")%(rx_data_ctrl)
#         print ("bytes 0-3  : 0x%08X")%(rx_data_reg1)
#         print ("bytes 4-7  : 0x%08X")%(rx_data_reg2)
#         print ("bytes 8-11 : 0x%08X")%(rx_data_reg3)

