##________________________________________________________________________________________________
##
##                                      CONV-TTL-RS485 PTS
##
##                                         CERN,BE/CO-HT
##________________________________________________________________________________________________
##
##------------------------------------------------------------------------------------------------
##
##                            CONV-TTL-RS485 DAC, VCXO, PLL test
##
##------------------------------------------------------------------------------------------------
##
## Description  Testing of the VCXO, the 125-MHz PLL and the DAC chips that control operation of
##              these two clocks. One DAC controls the operation of the 20MHz VCXO oscillator.
##              This DAC shall be henceforth referred to as DAC_VCXO.
##
##              Another DAC (DAC_PLL) controls a 25MHz VCXO, which in turn controls a TI PLL chip.
##
##              The clocking scheme is shown below
##                          __________        ________
##                   ------|          |      |        |
##               SPI ------| DAC_VCXO |----->|  VCXO  |--------------------> 20MHz clk
##                   ------|__________|      |________|
##                          __________        ________        ________
##                   ------|          |      |        |      |        |
##               SPI ------| DAC_PLL  |----->|  OSC   |----->|  PLL   |----> PLL 125 MHz clk
##                   ------|__________|      |________|      |________|
##
##              The gateware loaded to the FPGA implements the interface for the communication
##              between the DACs and the I2C bus on the VME P1 connector. The DAC interface is an
##              SPI Wishbone master, running at a system clock of 20MHz. The SPI Wishbone master
##              for the DAC_VCXO can be accessed at base address 0x080. The SPI Wishbone master
##              for the DAC_PLL  can be accessed at base address 0x020.
##
##              The FPGA receives the VCXO and PLL clock signals dedicated counters are used to
##              count clock cycles, which can be controlled and checked via the Wishbone
##              interface. The base address for the VCXO counter is 0x120. The base address for
##              the PLL counter is 0x100.
##
##              The following counter registers are used for controlling and evaluating the clks:
##               base_addr + 0x10, bits 31..0: number of cycles counted so far
##               base_addr + 0x14, bit      0: counter reset,  active high
##               base_addr + 0x18, bit      0: counter enable, active high
##
##              The test starts by programming the DACs. The VCXO and the PLL counters are
##              enabled and count clock cycles for ~2secs; the amount of counted cycles is an
##              indication of the clock's frequency. The test continues with reprogramming the
##              DACs and confirming that the amount of counted cycles by the counters changes
##              accordingly; the higher the DAC value the faster the oscillators become and the
##              larger the amount of cycles counted by the counters.
##
##
## Authors      Julian Lewis (Julian.Lewis@cern.ch)
##              Theodor Stana (t.stana@cern.ch)
## Website      http://www.ohwr.org/projects/conv-ttl-rs485
## Date         31/10/2014
##------------------------------------------------------------------------------------------------
##
##------------------------------------------------------------------------------------------------
##                               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
##-------------------------------------------------------------------------------------------------

# Import system modules

import sys
sys.path.append("pyts")
import time
import os
import math

# Import common modules

from ctypes import *
from ptsexcept import *
from vv_pts import *
from ptsdefine import *

##-------------------------------------------------------------------------------------------------
##                                           SPI class                                           --
##-------------------------------------------------------------------------------------------------

class COpenCoresSPI:

    R_RX   = [0x00, 0x04, 0x08, 0x0C]
    R_TX   = [0x00, 0x04, 0x08, 0x0C]
    R_CTRL = 0x10
    R_DIV  = 0x14
    R_SS   = 0x18

    LGH_MASK   = (0x7F)
    CTRL_GO    = (1<< 8)
    CTRL_BSY   = (1<< 8)
    CTRL_RXNEG = (1<< 9)
    CTRL_TXNEG = (1<<10)
    CTRL_LSB   = (1<<11)
    CTRL_IE    = (1<<12)
    CTRL_ASS   = (1<<13)

    DIV_MASK = (0xFFFF)

    SS_SEL = [0x1, 0x2, 0x4, 0x8, 0x10, 0x20, 0x40]

    conf = 0x0

    def wr_reg(self, addr, val):
        self.bus.vv_write(self.base + addr,val)

    def rd_reg(self,addr):
        return self.bus.vv_read(self.base + addr)

    def __init__(self, bus, base, divider):
        self.bus = bus;
        self.base = base;
        self.wr_reg(self.R_DIV, (divider & self.DIV_MASK));
        # default configuration
        self.conf = self.CTRL_ASS # | self.CTRL_TXNEG

    def wait_busy(self):
        tmo = 1000
        while(self.rd_reg(self.R_CTRL) & self.CTRL_BSY):
            tmo = tmo -1
            if tmo <= 0:
                raise PtsError("Timeout waiting on busy flag")

    def config(self, ass, rx_neg, tx_neg, lsb, ie):
        self.conf = 0
        if(ass):
            self.conf |= self.CTRL_ASS
        if(tx_neg):
            self.conf |= self.CTRL_TXNEG
        if(rx_neg):
            self.conf |= self.CTRL_RXNEG
        if(lsb):
            self.conf |= self.CTRL_LSB
        if(ie):
            self.conf |= self.CTRL_IE

    # slave = slave number (0 to 7)
    # data = byte data array to send, in case if read fill with dummy data of the right size
    # This transaction has been modified for this test!!

    def transaction(self, slave, data):
        self.wr_reg(self.R_SS, self.SS_SEL[slave])

        txrx = [0x00000000, 0x00000000, 0x00000000, 0x00000000]
        txrx[0] = 0x00FFFFFF & data
        self.wr_reg(self.R_TX[0], txrx[0])

        ctrl_reg = self.CTRL_ASS | self.CTRL_GO | 24

        self.wr_reg(self.R_CTRL, 2018)
        self.wr_reg(self.R_CTRL, 2118)
        tmp = (self.rd_reg(self.R_CTRL) >> 8) & 0x00000001

        tmo = 100
        while(tmp == 1):
            tmp = (self.rd_reg(self.R_CTRL) >> 8) & 0x00000001
            tmo = tmo -1
            if tmo <= 0:
                msg = "ERROR: DAC IC52 or IC53: Not responding"
                raise PtsError(msg)

        return txrx

##-------------------------------------------------------------------------------------------------
##                                       DAC AD5662 class                                        --
##-------------------------------------------------------------------------------------------------

class DAC_AD5662:

    PD0 = 16
    PD1 = 17

    OM_NORMAL   = 0
    OM_1K       = 1
    OM_100K     = 2
    OM_TRISTATE = 3

    V_REF = 3.3

    def __init__(self, spi):
        self.dac_value = 0x0000
        self.op = self.OM_NORMAL
        self.spi = spi

    def set_dac_voltage(self, voltage):
        if (voltage >= self.V_REF):
            "FAIL: please set a valid DAC_AD5662 voltage"
        value = int(voltage / self.V_REF * 65536)
        self.dac_value = value

    def operation_mode(self, op):
        self.op = op

    def update_output(self):
        data = (self.op << 16) + (self.dac_value)
        self.spi.transaction(0, data)

##-------------------------------------------------------------------------------------------------
##                                             main                                              --
##-------------------------------------------------------------------------------------------------

def main(bus,tname,inf,log):

    """
    tests : DAC IC52, VCXO OSC3, DAC IC53, VCXO OSC1, PLL IC29
    uses  : pts.bit and dac_vcxo_pll.py
    """

    VCXO_COUNTER_MARGIN  =   2000000  # Expected value of a 20MHz counter after 100msec
    VCXO_COUNTER_2SEC    =  40000000  # Expected value of a 20MHz counter after 2sec
    PLL_COUNTER_MARGIN   =  12500000  # Expected value of a 125MHz counter after 100msec
    PLL_COUNTER_2SEC     = 250000000  # Expected value of a 125MHz counter after 2sec

    pel = PTS_ERROR_LOGGER(inf,log)

    dac_volts = [0.1, 0.8, 1.65, 2.4, 3.2]
    vcxo = []
    pll125 = []

    for dac_volt in dac_volts:

        # Setting DAC_VCXO
        dac_vcxo_spi = COpenCoresSPI(bus, VCXO_DAC_BASE, 0x014) # divider=0x014: SCLK would be 5 MHz
        dac = DAC_AD5662(dac_vcxo_spi)
        dac.operation_mode(dac.OM_NORMAL)
        dac.set_dac_voltage(dac_volt)
        dac.update_output()

        # Setting DAC_PLL
        dac_pll_spi = COpenCoresSPI(bus, PLL_DAC_BASE, 0x014)   # divider=0x014: SCLK woulb be 5 MHz
        dac = DAC_AD5662(dac_pll_spi)
        dac.operation_mode(dac.OM_NORMAL)
        dac.set_dac_voltage(dac_volt)
        dac.update_output()

        # Resetting VCXO & PLL Counters
        bus.vv_write(VCXO_CLKINFO_BASE + CLKINFO_RST_OFS, 1)              # Reset VCXO counter
        bus.vv_write(PLL_CLKINFO_BASE + CLKINFO_RST_OFS, 1)               # Reset PLL IC29 counter
        vcxo_count = bus.vv_read(VCXO_CLKINFO_BASE + CLKINFO_VALUE_OFS)   # Read VCXO counter, counter is under reset
        pll125_count = bus.vv_read(PLL_CLKINFO_BASE + CLKINFO_VALUE_OFS)  # Read PLL IC29 counter, counter is under reset

        # Enabling VCXO & PLL Counters
        bus.vv_write(VCXO_CLKINFO_BASE + CLKINFO_ENABLE_OFS, 1)            # Enable VCXO counter
        bus.vv_write(PLL_CLKINFO_BASE + CLKINFO_ENABLE_OFS, 1)             # Enable PLL IC29 counter
        bus.vv_write(VCXO_CLKINFO_BASE + CLKINFO_RST_OFS, 0)              # Release VCXO counter reset
        bus.vv_write(PLL_CLKINFO_BASE + CLKINFO_RST_OFS, 0)               # Release PLL IC29 counter reset

        # VCXO & PLL Counters counting for 2 secs..
        time.sleep(2)
        bus.vv_write(VCXO_CLKINFO_BASE + CLKINFO_ENABLE_OFS, 0)            # Disable VCXO counter
        bus.vv_write(PLL_CLKINFO_BASE + CLKINFO_ENABLE_OFS, 0)             # Disable PLL IC29 counter

        # Reading VCXO & PLL Counters
        vcxo_count = bus.vv_read(VCXO_CLKINFO_BASE + CLKINFO_VALUE_OFS)   # Read VCXO counter
        pll125_count = bus.vv_read(PLL_CLKINFO_BASE + CLKINFO_VALUE_OFS)  # Read PLL IC29 counter
        vcxo.append(vcxo_count)
        pll125.append(pll125_count)

        # Evaluating the VCXO and PLL clock frequencies at DAC middle value 1.65
        if dac_volt == 1.65:
            if (vcxo_count > (VCXO_COUNTER_2SEC-VCXO_COUNTER_MARGIN)) and  (vcxo_count < (VCXO_COUNTER_2SEC+VCXO_COUNTER_MARGIN)):
                msg = "VCXO OSC3: Frequency OK; Counter value after 2sec: %d is within range[%d .. %d]" % (vcxo_count, VCXO_COUNTER_2SEC-VCXO_COUNTER_MARGIN, VCXO_COUNTER_2SEC+VCXO_COUNTER_MARGIN)
                inf.write("%s\n" % (msg))
            else:
                msg = "ERROR: VCXO OSC3: Wrong frequency; Counter value after 2sec: %d out of range[%d .. %d]" % (vcxo_count, VCXO_COUNTER_2SEC-VCXO_COUNTER_MARGIN, VCXO_COUNTER_2SEC+VCXO_COUNTER_MARGIN)
                pel.set(msg)

            if (pll125_count > (PLL_COUNTER_2SEC-PLL_COUNTER_MARGIN)) and  (pll125_count < (PLL_COUNTER_2SEC+PLL_COUNTER_MARGIN)):
                msg = "PLL IC29: Frequency OK; Counter value after 2sec: %d is within range[%d .. %d]"  % (pll125_count, PLL_COUNTER_2SEC-PLL_COUNTER_MARGIN, PLL_COUNTER_2SEC+PLL_COUNTER_MARGIN)
                inf.write("%s\n" % (msg))
            else:
                msg = "ERROR: PLL IC29: Wrong frequency; Counter value after 2sec: %d out of range[%d .. %d]" % (pll125_count, PLL_COUNTER_2SEC-PLL_COUNTER_MARGIN, PLL_COUNTER_2SEC+PLL_COUNTER_MARGIN)
                pel.set(msg)

    inf.write("\n")

    # Evaluating the VCXO and PLL influence from DAC
    for i in range(len(dac_volts)):
        grad = vcxo[i] / dac_volts[i]
        if i > 0:
            diff = grad - ograd
            if diff < 0:
                inf.write("VCXO OSC3: Frequency responds to change in DAC IC52 OK. DAC=%3.2fV\n" % (dac_volts[i]))
            else:
                msg = "ERROR: VCXO OSC3: Frequency not responding to change in DAC IC52 control value"
                pel.set(msg)
        ograd = grad

    inf.write("\n")

    for i in range(len(dac_volts)):
        grad = pll125[i] / dac_volts[i]
        if i > 0:
            diff = grad - ograd
            if diff < 0:
                inf.write("PLL IC29: Frequency responds to change in DAC IC53 OK.  DAC=%3.2fV\n" % (dac_volts[i]))
            else:
                msg = "ERROR: PLL IC29: Frequency not responding to change in DAC IC53 control value"
                pel.set(msg)
        ograd = grad

    inf.write("\n")

    # Set DAC_VCXO to mid-oscillator range (1.65 V)
    dac_vcxo_spi = COpenCoresSPI(bus, VCXO_DAC_BASE, 0x014) # divider=0x014: SCLK would be 5 MHz
    dac = DAC_AD5662(dac_vcxo_spi)
    dac.operation_mode(dac.OM_NORMAL)
    dac.set_dac_voltage(1.65)
    dac.update_output()

    # Set DAC_PLL to mid-oscillator range (1.65 V)
    dac_pll_spi = COpenCoresSPI(bus, PLL_DAC_BASE, 0x014)   # divider=0x014: SCLK woulb be 5 MHz
    dac = DAC_AD5662(dac_pll_spi)
    dac.operation_mode(dac.OM_NORMAL)
    dac.set_dac_voltage(1.65)
    dac.update_output()

    return pel.get()

