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

# Copyright CERN, 2011 - 2013 (INCAA Computers BV)
# Author: Carlos Gil Soriano <cgilsori@cern.ch>
# Author: Julian Lewis (Julian.Lewis@cern.ch)
# Author: INCAA Computers BV (info@incaacomputers.com)
# Licence: GPL v2 or later.
# Website: http://www.ohwr.org
# Website: http://www.incaacomputers.com
# Version: 2.0 (Last modifications: 13/06/2013)
#
# FPGA firmware: spec_pts_gtp_clkfmc.bin and spec_pts_gtp_clkpll.bin
#
# Description
#  Testing of the Gigabit Tranceivers of the SPEC board: GTP1 (SATA0), GTP2 (SATA1), 
#  SFP, FMC High Speed Links.
#  For this test an "FMC tester mezzanine board" (EDA-02327) needs to be plugged in
#  the FMC slot; the SFP Loopback Adapter Module needs to be plugged on the SFP
#  connector and the SATA cable connected between the GTP1 and GTP2 ports.
#  The idea of the test is to loopback the transmitter and the receiver of each link
#  and check if the link is established.
#  For the FMCs the looping-back takes place through the 4.25Gbps Crosspoint Switches
#  ADN4600. The switches can be configured through an I2C interface.
#  For the SFP, the looping-back takes place through the Loopback Adapter.
#  GTP1 is straight, GTP2 is crossed-over and the cable connected between them is
#  straight.

import ctypes

import sys
import rr
import time
import os

from i2c import COpenCoresI2C
from si570 import CSI570

from ptsexcept import *

"""
test05: check SATA ports, SFP port and high speed pins on FMC connector (low count connector).
"""

##-------------------------------------------------------------------------------------------------
##                                     SWITCH ADN4600 class                                      --
##-------------------------------------------------------------------------------------------------

# Only basic mode implemented

class SWITCH_ADN4600:
    RST        = 0x0;
    XPT_CFG    = 0x40;
    XPT_UPDATE = 0x41;
    XPT_STAT_0 = 0x50;    XPT_STAT_1 = 0x51;    XPT_STAT_2 = 0x52;    XPT_STAT_3 = 0x53
    XPT_STAT_4 = 0x54;    XPT_STAT_5 = 0x55;    XPT_STAT_6 = 0x56;    XPT_STAT_7 = 0x57
    RX0_CONF   = 0x80;    RX1_CONF   = 0x88;    RX2_CONF   = 0x90;    RX3_CONF   = 0x98;
    RX4_CONF   = 0xA0;    RX5_CONF   = 0xA8;    RX6_CONF   = 0xB0;    RX7_CONF   = 0xB8;
    TX0_CONF   = 0xC0;    TX1_CONF   = 0xC8;    TX2_CONF   = 0xD0;    TX3_CONF   = 0xD8;
    TX4_CONF   = 0xE0;    TX5_CONF   = 0xE8;    TX6_CONF   = 0xF0;    TX7_CONF   = 0xF8;

    def __init__(self, i2c, addr):
        self.i2c = i2c;
        self.addr = addr;
        self.wr_reg8(0, 1); # soft reset

    def wr_reg8(self, bank, value):
        self.i2c.start(self.addr, True); # write cycle
        self.i2c.write(bank, False);
        self.i2c.write(value, True);

    def rd_reg8(self, bank):
        self.i2c.start(self.addr, True);
        self.i2c.write(bank, False);
        self.i2c.start(self.addr, False);
        return self.i2c.read(True);

    def passby_input_output(self, channel_input, channel_output):
        # If the channel_output is -1, it means broadcast to all output channels!
        if (channel_output == -1):
            data = (channel_input << 4) | (1 << 3);
        else :
            data = (channel_input << 4) | (channel_output);
            
        self.wr_reg8(self.XPT_CFG, data);
        self.wr_reg8(self.XPT_UPDATE, 0x1);

    def rx_logic(self, channel, negate) :
        self.wr_reg8(0x80 + 0x8*channel, (negate << 6) | (1 << 5) | (1 << 4))

    def enable_tx(self, channel) :
        self.wr_reg8(0xC0 + 0x8*channel, (1 << 5))

##-------------------------------------------------------------------------------------------------
##                                          Minic class                                          --
##-------------------------------------------------------------------------------------------------

class CMinic:

    OFFSET_EP    = 0x0000
    OFFSET_PMEM  = 0x10000
    OFFSET_MINIC = 0x8000

    EP_REG_ECR       = 0x0
    EP_REG_MACL      = 0x28
    EP_REG_MDIO_CR   = 0x2c
    EP_REG_MDIO_SR   = 0x30
    EP_REG_IDR       = 0x34
    EP_MDIO_SR_READY = 0x80000000
    EP_ECR_TX_EN     = 0x40
    EP_ECR_RX_EN     = 0x80
    MDIO_MCR         = 0
    MDIO_MSR         = 1
    MDIO_MCR_PDOWN   = (1<<11)
    MDIO_MCR_RESET   = (1<<15)
    MDIO_MCR_AUTONEG = (1<<12)
    MDIO_MSR_LSTATUS = (1<<2)

    def __init__(self, bus, base):
        self.base = base;
        self.bus = bus;
        self.init_ep();

    def wr_reg(self, addr, val):
        self.bus.iwrite(0,self.base + addr,4,val)

    def rd_reg(self,addr):
        return self.bus.iread(0,self.base + addr,4)

    def ep_readl(self, addr):
        return self.rd_reg(self.OFFSET_EP + addr)

    def ep_writel(self, addr, val):
        self.wr_reg(self.OFFSET_EP + addr, val)

    def pcs_readl(self, addr):
        val = 0
        self.ep_writel(self.EP_REG_MDIO_CR, addr << 16);

        tmo = 10000
        while (val & self.EP_MDIO_SR_READY) == 0:
            val = self.ep_readl(self.EP_REG_MDIO_SR)
            tmo = tmo -1
            if tmo <= 0:
                msg = "ERROR: MINIC: Not responding"
                raise PtsError(msg)

        return (val & 0xffff)

    def pcs_writel(self, addr, val):
        val = 0
        self.ep_writel(self.EP_REG_MDIO_CR, (addr << 16) | (1 << 31) | val);

        tmo = 10000
        while (val & self.EP_MDIO_SR_READY) == 0:
            val = self.ep_readl(self.EP_REG_MDIO_SR)
            tmo = tmo -1
            if tmo <= 0:
                msg = "ERROR: MINIC: Not responding"
                raise PtsError(msg)

    def init_ep(self):
        idr =  self.ep_readl(self.EP_REG_IDR)
        self.ep_writel(self.EP_REG_ECR, self.EP_ECR_TX_EN | self.EP_ECR_RX_EN)

    def reset_phy(self):
        self.pcs_writel(self.MDIO_MCR, self.MDIO_MCR_PDOWN);
        self.pcs_writel(self.MDIO_MCR, self.MDIO_MCR_RESET);
        self.pcs_writel(self.MDIO_MCR, 0);

    def link_up(self):
        return self.pcs_readl(self.MDIO_MSR) & self.MDIO_MSR_LSTATUS

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

def main (default_directory='.'):
    # Configure the FPGA using the program fpga_loader
    path_fpga_loader = '../firmwares/fpga_loader';
    path_firmware = '../firmwares/spec_pts_gtp_clkfmc.bin';
    firmware_loader = os.path.join(default_directory, path_fpga_loader)
    bitstream = os.path.join(default_directory, path_firmware)
    print "Loading firmware: %s" % (firmware_loader + ' ' + bitstream)
    os.system( firmware_loader + ' ' + bitstream)
    #time.sleep(2);

    FMC_SWITCH_I2C_WB_ADDR = 0x40000
    EP_BASE_FMC            = 0x60000
    EP_BASE_SFP            = 0x80000
    EP_BASE_SATA1          = 0xA0000
    EP_BASE_SATA0          = 0x20000

    # Load board library and open the corresponding device
    print "Loading hardware access library and opening devices\n"
    spec = rr.Gennum();
    i2c = COpenCoresI2C(spec, FMC_SWITCH_I2C_WB_ADDR, 99); # Prescaler value calculated using 50 MHz clock
    adn4600 = SWITCH_ADN4600(i2c, 0x48);

    print "Test05 Start"
    init_test_time = time.time()
    ret_error = None

    adn4600.rx_logic(0, 0)
    adn4600.rx_logic(1, 0)
    adn4600.rx_logic(2, 0)
    adn4600.rx_logic(3, 0)
    adn4600.rx_logic(4, 0)
    adn4600.rx_logic(5, 0)
    adn4600.rx_logic(6, 1)
    adn4600.rx_logic(7, 0)

    # print "-------------------------------------------------"
    # print "Setup ADN4600 chip (crossing point) \n"
    # print "adn4600 rx logic 1-0: " + hex(adn4600.rd_reg8(0x88)) + " " + hex(adn4600.rd_reg8(0x80))
    # print "adn4600 rx logic 3-2: " + hex(adn4600.rd_reg8(0x98)) + " " + hex(adn4600.rd_reg8(0x90))
    # print "adn4600 rx logic 5-4: " + hex(adn4600.rd_reg8(0xA8)) + " " + hex(adn4600.rd_reg8(0xA0))
    # print "adn4600 rx logic 7-6: " + hex(adn4600.rd_reg8(0xB8)) + " " + hex(adn4600.rd_reg8(0xB0))

    adn4600.enable_tx(0)
    adn4600.enable_tx(1)
    adn4600.enable_tx(2)
    adn4600.enable_tx(3)
    adn4600.enable_tx(4) # channel 6
    adn4600.enable_tx(5) # channel 7
    adn4600.enable_tx(6) # channel 5
    adn4600.enable_tx(7) # channel 4

    # print "adn4600 tx 1-0: " + hex(adn4600.rd_reg8(0xC8)) + " " + hex(adn4600.rd_reg8(0xC0))
    # print "adn4600 tx 3-2: " + hex(adn4600.rd_reg8(0xD8)) + " " + hex(adn4600.rd_reg8(0xD0))
    # print "adn4600 tx 5-4: " + hex(adn4600.rd_reg8(0xF0)) + " " + hex(adn4600.rd_reg8(0xF8))
    # print "adn4600 tx 7-6: " + hex(adn4600.rd_reg8(0xE0)) + " " + hex(adn4600.rd_reg8(0xE8))

    time.sleep(1)
    adn4600.passby_input_output(0x4, 0x1);
    adn4600.passby_input_output(0x6, 0x6);
    # print "adn4600 1-0: " + hex(adn4600.rd_reg8(0x51)) + " " + hex(adn4600.rd_reg8(0x50))
    # print "adn4600 3-2: " + hex(adn4600.rd_reg8(0x53)) + " " + hex(adn4600.rd_reg8(0x52))
    # print "adn4600 5-4: " + hex(adn4600.rd_reg8(0x55)) + " " + hex(adn4600.rd_reg8(0x54))
    # print "adn4600 7-6: " + hex(adn4600.rd_reg8(0x57)) + " " + hex(adn4600.rd_reg8(0x56))

    time.sleep(1)

    # The address of the SI570 is fixed in the part number
    si570 = CSI570(i2c, 0x55);

    print "------------------------------------"
    si570.setup_frequency(125)
    si570.read_setup_oscillator()
    print "After setup of FMC Carrier tester's Si570 for f_out = 125 MHz:"
    print "HS DIV: " + str(si570.hs_div);
    print "N1: " + str(si570.n1);
    print "RFREQ: " + str(si570.rfreq);
    print "------------------------------------"

    #------------------------------------------------------------
    # Test for the FMC1 High speed links
    # I. Setup of the rx<->tx loopback through the ADN4600 Switch
    # I2C for FMC1 ADN4600 Switch

    adn4600 = SWITCH_ADN4600(i2c, 0x48);

    # Enable Switch receiver input6

    adn4600.rx_logic(6, 1)

    # Enable Switch transmitter output1

    adn4600.enable_tx(1)

    # Connect Switch input6: HS_rx to Switch output1: HS_tx

    adn4600.passby_input_output(0x6, 0x1);

    # FMC high speed link test

    msg = "Begin FMC link test (GBTCLK_M2C reference)"
    print("\n%s" % (msg))

    minic = CMinic(spec, EP_BASE_FMC);
    minic.init_ep()
    minic.reset_phy()
    link_up = 0;
    for i in range(0,10):
        if minic.link_up() :
            link_up = 1
            break;

    if link_up :
        msg = "FMC link Up OK"
        print("%s\n" % (msg))
    else:
        msg = "ERROR: FMC high speed link Down"
        print(msg)
        ret_error = msg

    si570.setup_frequency(10)

    minic.init_ep()
    minic.reset_phy()
    link_up = 0;
    for i in range(0,10):
        if minic.link_up() :
            link_up = 1
            break;

    if link_up :
        msg = "ERROR: FMC high speed link UP but should be Down"
        print(msg)
        ret_error = msg
    else:
        msg = "FMC link DOWN OK"
        print("%s\n" % (msg))


    #============================================================
    # Configure the FPGA using the program fpga_loader
    path_fpga_loader = '../firmwares/fpga_loader';
    path_firmware = '../firmwares/spec_pts_gtp_clkpll.bin';
    firmware_loader = os.path.join(default_directory, path_fpga_loader)
    bitstream = os.path.join(default_directory, path_firmware)
    print "Loading firmware: %s" % (firmware_loader + ' ' + bitstream)
    os.system( firmware_loader + ' ' + bitstream)
    #time.sleep(2);

    #------------------------------------------------------------
    # SFP link test

    msg = "Begin SFP link test (REF_CLK0_123 reference)"
    print("\n%s" % (msg))

    minic = CMinic(spec, EP_BASE_SFP);
    minic.init_ep()
    minic.reset_phy()
    minic.link_up()

    if minic.link_up() :
        msg = "SFP link Up OK"
        print("%s\n" % (msg))
    else:
        msg = "ERROR: SFP high speed link Down"
        print(msg)
        ret_error = msg

    #------------------------------------------------------------
    # Test for the FMC1 High speed links
    # I. Setup of the rx<->tx loopback through the ADN4600 Switch
    # I2C for FMC1 ADN4600 Switch

    adn4600 = SWITCH_ADN4600(i2c, 0x48);

    # Enable Switch receiver input6

    adn4600.rx_logic(6, 1)

    # Enable Switch transmitter output1

    adn4600.enable_tx(1)

    # Connect Switch input6: HS_rx to Switch output1: HS_tx

    adn4600.passby_input_output(0x6, 0x1);

    # FMC high speed link test

    msg = "Begin FMC link test (REF_CLK_101 reference)"
    print("\n%s" % (msg))

    minic = CMinic(spec, EP_BASE_FMC);
    minic.init_ep()
    minic.reset_phy()
    minic.link_up()

    if minic.link_up() :
        msg = "FMC link Up OK"
        print("%s\n" % (msg))
    else:
        msg = "ERROR: FMC high speed link Down"
        print(msg)
        ret_error = msg

    #------------------------------------------------------------
    # Test for the SATA0, SATA1 links

    msg = "Begin SATA0 SATA1 link test (REF_CLK_101 and REF_CLK0_123 reference)"
    print("\n%s" % (msg))

    minic_s0 = CMinic(spec, EP_BASE_SATA0);
    minic_s0.init_ep()
    minic_s1 = CMinic(spec, EP_BASE_SATA1);
    minic_s1.init_ep()
    minic_s0.reset_phy()
    minic_s1.reset_phy()
    minic_s0.link_up()
    minic_s1.link_up()

    if minic_s0.link_up() :
        msg = "SATA0 link Up OK"
        print("%s\n" % (msg))
    else:
        msg = "ERROR: SATA0 link Down"
        print(msg)
        ret_error = msg

    if minic_s1.link_up() :
        msg = "SATA1 link Up OK"
        print("%s\n" % (msg))
    else:
        msg = "ERROR: SATA1 link Down"
        print(msg)
        ret_error = msg

    end_test_time = time.time()
    print "End of Test06"
    print "RESULT: [{}]".format("FAIL" if ret_error else "OK")
    print 'Test06 elapsed time: {:.2f} seconds'.format(end_test_time-init_test_time)
    if ret_error:
        raise PtsError(ret_error)


if __name__ == '__main__' :
    main();
