#!/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_fmcconnect.bin
#
# Description
#  Testing of the pins of the FMC connector of the SPEC board. Note that there
#  are different tests for the Power Supplies and the High Speed pins of the FMC
#  connector. For this test an "FMC tester mezzanine board" (EDA-02327) needs to be
#  plugged in the FMC slot.
#              _______________________    __
#             | FMC tester            |  |  |            _____________
#             |       ________        |  |  |           |             |   P
#             |      |        |-------|->|  |---------->|             |   C
#             |    __| IO exp |  ...  |  |  |   ...     |             |   I
#             |   |  |________|-------|->|  |---------->|             |
#             |   |                   |  |F |           |     FPGA    |   E
#             |   |   ________        |  |M |           |             |   x
#             |   |  |        |-------|->|C |---------->|             |   p
#             |   |__| IO exp |  ...  |  |1 |   ...     |             |   r
#             |   |  |________|-------|->|  |---------->|             |   e
#             |   |                   |  |  |           |_____________|   s
#             |   |___________________|__|__|__________________| I2C      s
#             |                       |  |  |
#             |_______________________|  |__|
#
#  As the figure above shows, the pins of the FPGA are connected to the pins of the
#  IO Expanders of the "FMC tester mezzanine boards". The IO Expanders can be
#  configured through an I2C interface; the main idea of this test is to configure
#  the IO Expanders to output a certain pattern and then confirm that what arrives
#  to the pins of the FPGA is identical to what has been set to the Expanders.
#  The firmware loaded to the FPGA implements the interface for the communication
#  between the IO Expanders and the PCI Express bus.
#
#  For the reading of the inputs of the FPGA the firmware implements a WISHBONE
#  module that retreives the values from the pins and stores them in registers.
#  In total 6 IO Expanders are used for the FMC.    |
#  The Expanders are distinguised by their I2C address: x27, x24, x22, x26, x21, x25.
#  Each IO expander has 16 ports, which gives in total 96 pins = 3x32-bits-registers
#  to test. We have grouped together the ports of 2 Expanders at a time so as to
#  form registers of 32 bits on the FPGA side. Expanders x27 and x24, x22 and x26,
#  x21 and x25 are grouped together.
#
#  Check the SPEC_FMC_pinout.xlsx for the details on the mapping between the GPIO
#  registers and IO Expander pins.

import sys
import rr
import time
import os
import errno

from i2c import COpenCoresI2C

from ptsexcept import *    # jdgc

"""
test03: checks the low speed pins of FMC connector (low count connector).
"""

##-------------------------------------------------------------------------------------------------
##                                       IO Expander class                                       --
##-------------------------------------------------------------------------------------------------

class EXPANDER_MCP23017:

    # The MCP23017 device is a 16-bit remote bidirectional I/O port. It consists of 8-bit configuration
    # registers for input/output/polarity/etc selection. The system master can enable the I/Os as either
    # inputs or outputs by writing the I/O configuration bits (IODIRA/B). The data for each input or
    # output is kept in the corresponding input or output register. The 16-bit I/O port functionally
    # consists of two 8-bit ports (PORTA and PORTB). The MCP23X17 here is configured to operate in
    # 16-bit mode, via the IOCON.BANK register.

    IODIRA   = 0x0;  # Controls the PORTA direction (0: pin configured as output)
    IODIRB   = 0x01  # Controls the PORTB direction (0: pin configured as output)
    IPOLA    = 0x02; # Controls the PORTA polarity (0: register bit reflects the same logic state of input pin)
    IPOLB    = 0x03; # Controls the PORTB polarity (0: register bit reflects the same logic state of input pin)
    GPINTENA = 0x04; # Controls the PORTA interrupts (not used)
    GPINTENB = 0x05; # Controls the PORTB interrupts (not used)  
    DEFVALA  = 0x06; # Refers to PORTA interrupts (not used)
    DEFVALB  = 0x07; # Refers to PORTB interrupts (not used)
    INTCONA  = 0x08; # Refers to PORTA interrupts (not used)
    INTCONB  = 0x09; # Refers to PORTA interrupts (not used) 
    IOCONA   = 0x0A; # Shared register between the two ports
    IOCONB   = 0x0B; # Shared register between the two ports
    GPPUA    = 0x0C; # Controls the PORTA pull-up resistors for the port pins (0: pull-up disabled)
    GPPUB    = 0x0D; # Controls the PORTA pull-up resistors for the port pins (0: pull-up disabled)
    INTFA    = 0x0E; # Refers to PORTA interrupts (not used)
    INTFB    = 0x0F; # Refers to PORTB interrupts (not used)
    INTCAPA  = 0x10; # Refers to PORTA interrupts (not used)
    INTCAPB  = 0x11; # Refers to PORTB interrupts (not used)  
    GPIOA    = 0x12; # Reflects the value of the PORTA
    GPIOB    = 0x13; # Reflects the value of the PORTB       
    OLATA    = 0x14; # Output latch for PORTA (a write modifies the output pins)
    OLATB    = 0x15; # Output latch for PORTB (a write modifies the output pins)

    IOCON_BANK   = 7;
    IOCON_MIRROR = 6;
    IOCON_SEQOP  = 5;
    IOCON_DISSLW = 4;
    IOCON_HAEN   = 3;
    IOCON_ODR    = 2;
    IOCON_INTPOL = 1;

    def __init__(self, i2c, addr):
        self.i2c = i2c;
        self.addr = addr; 
 
        # Setup configuration register

        iocon_value  = (0 << EXPANDER_MCP23017.IOCON_BANK) #BANK=0: addresses are sequential
        iocon_value += (1 << EXPANDER_MCP23017.IOCON_MIRROR)
        iocon_value += (1 << EXPANDER_MCP23017.IOCON_SEQOP)
        iocon_value += (1 << EXPANDER_MCP23017.IOCON_DISSLW)
        iocon_value += (1 << EXPANDER_MCP23017.IOCON_HAEN)
        iocon_value += (0 << EXPANDER_MCP23017.IOCON_ODR)
        iocon_value += (1 << EXPANDER_MCP23017.IOCON_INTPOL)
        self.i2c.start(self.addr, True);
        self.i2c.write(self.IOCONA, False);
        self.i2c.write(iocon_value, True);
        self.i2c.start(self.addr, True);
        self.i2c.write(self.IOCONB, False);
        self.i2c.write(iocon_value, True);

        # Setup every port as output

        self.i2c.start(self.addr, True);
        self.i2c.write(self.IODIRA, False);
        self.i2c.write(0x00, True); # 0: output
        self.i2c.start(self.addr, True);
        self.i2c.write(self.IODIRB, False);
        self.i2c.write(0x00, True); # 0: output

    # Configures the Expander's ports as inputs or outputs (0:pin becomes output, 1:pin becomes input)

    def set_port_direct(self, bank, mask):
        self.i2c.start(self.addr, True);
        if (bank == EXPANDER_MCP23017.GPIOA):
            self.i2c.write(self.IODIRA, False);
        else:
            self.i2c.write(self.IODIRB, False);
        self.i2c.write(mask, True);

    # Sets Expander outputs

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

    # Reads expander ports

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

##-------------------------------------------------------------------------------------------------
##                                       GPIO class                                              --
##-------------------------------------------------------------------------------------------------

class GPIO_slave:
    R_GPIO_CODR = 0x0
    R_GPIO_SODR = 0x4
    R_GPIO_DDR  = 0x8
    R_GPIO_PSR  = 0xC

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

    def set_GPIO_inout(self, value):
        self.bus.iwrite(0, self.base +  self.R_GPIO_DDR, 4, value)

    def set_GPIO_in(self):
        self.set_GPIO_inout(0x00000000)

    def set_GPIO_out(self):
        self.set_GPIO_inout(0xFFFFFFFF)

    def wr_GPIO_block(self, value):
        self.set_GPIO_out()
        self.bus.iwrite(0, self.base +  self.R_GPIO_CODR, 4, ~value)
        self.bus.iwrite(0, self.base +  self.R_GPIO_SODR, 4, value)

    def rd_GPIO_block(self):
        self.set_GPIO_in()
        return self.bus.iread(0, self.base +  self.R_GPIO_PSR, 4)

##-------------------------------------------------------------------------------------------------
##                                     File with Pinout List                                     --
##-------------------------------------------------------------------------------------------------

def pin_str(bit):
    btxt = tuple(open("./SPEC_FMC_pinout.txt","r"))
    for i in range(1,len(btxt)):
        mch = "FMC %d" % (bit)
        if btxt[i].find(mch) != -1:
            return btxt[i][8:].strip()
    return "Not Used"

##-------------------------------------------------------------------------------------------------
##                                  IO Expanders Initialization                                  --
##-------------------------------------------------------------------------------------------------

def x_init(x27, x24, x22, x26, x21, x25):
    # I2C configuration of all the pins of the 6 IO Expanders of an FMC tester board as outputs;
    # exeption are the IO Expander x22 GPIOB bits: 2(connected to GA1), 5(connected to GA0), 6(connected to P3V3) 
    x21.set_port_direct(EXPANDER_MCP23017.GPIOA, 0x00)
    x21.set_port_direct(EXPANDER_MCP23017.GPIOB, 0x00)
    x25.set_port_direct(EXPANDER_MCP23017.GPIOA, 0x00)
    x25.set_port_direct(EXPANDER_MCP23017.GPIOB, 0x00)
    x22.set_port_direct(EXPANDER_MCP23017.GPIOA, 0x00)
    x22.set_port_direct(EXPANDER_MCP23017.GPIOB, 0x64) # 0x64: bits 2, 5, 6 are hardwired to GND or P3V3
    x26.set_port_direct(EXPANDER_MCP23017.GPIOA, 0x00)
    x26.set_port_direct(EXPANDER_MCP23017.GPIOB, 0x00)
    x27.set_port_direct(EXPANDER_MCP23017.GPIOA, 0x00)
    x27.set_port_direct(EXPANDER_MCP23017.GPIOB, 0x00)
    x24.set_port_direct(EXPANDER_MCP23017.GPIOA, 0x00)
    x24.set_port_direct(EXPANDER_MCP23017.GPIOB, 0x00)
    time.sleep(1)

##-------------------------------------------------------------------------------------------------
##                     Setting one of the bits of the 6 IO Expanders to '1'                      --
##-------------------------------------------------------------------------------------------------

def get_port(port,bit):
    p = bit/8
    if p != port:
        return 0
    return 1 << (bit - p*8)

def test_bit(x27, x24, x22, x26, x21, x25, fpga_inputs0, fpga_inputs1, fpga_inputs2, bit):
    # Setting the output of all the ports of the 6 IO Expanders
    byte_0b = get_port(0,bit)
    byte_0a = get_port(1,bit)
    byte_1b = get_port(2,bit)
    byte_1a = get_port(3,bit)
    byte_2b = get_port(4,bit)
    byte_2a = get_port(5,bit)
    byte_3b = get_port(6,bit)
    byte_3a = get_port(7,bit)
    byte_4b = get_port(8,bit)
    byte_4a = get_port(9,bit)
    byte_5b = get_port(10,bit)
    byte_5a = get_port(11,bit)

    x21.set_output(EXPANDER_MCP23017.GPIOA, byte_5a)
    x21.set_output(EXPANDER_MCP23017.GPIOB, byte_5b)
    x25.set_output(EXPANDER_MCP23017.GPIOA, byte_4a)
    x25.set_output(EXPANDER_MCP23017.GPIOB, byte_4b)
    x22.set_output(EXPANDER_MCP23017.GPIOA, byte_3a)
    x22.set_output(EXPANDER_MCP23017.GPIOB, byte_3b)
    x26.set_output(EXPANDER_MCP23017.GPIOA, byte_2a)
    x26.set_output(EXPANDER_MCP23017.GPIOB, byte_2b)
    x27.set_output(EXPANDER_MCP23017.GPIOA, byte_1a)
    x27.set_output(EXPANDER_MCP23017.GPIOB, byte_1b)
    x24.set_output(EXPANDER_MCP23017.GPIOA, byte_0a)
    x24.set_output(EXPANDER_MCP23017.GPIOB, byte_0b)

    # Reading the input of each port of the FPGA
    fpga_pins_x27_x24 = fpga_inputs0.rd_GPIO_block()
    fpga_pins_x22_x26 = fpga_inputs1.rd_GPIO_block()
    fpga_pins_x21_x25 = fpga_inputs2.rd_GPIO_block()
    received  = abs((fpga_pins_x21_x25 << 64) | (fpga_pins_x22_x26 << 32) | fpga_pins_x27_x24)

    expected = 1 << bit

    # Dealing with exeptions on Bits 50, 53, 54, 88
    # Bit 50 corresponds to GA1

    if bit == 50:
        expected = 0

    # Bit 53 corresponds to GA0

    if bit == 53:
        expected = 0

    # Bit 54 corresponds to P3V3

    expected |= 1 << 54

    # Bit 88 is connected to 7 other locations on the FPGA side

    if bit == 88:
        expected |= 1 << 73
        expected |= 1 << 74
        expected |= 1 << 75
        expected |= 1 << 76
        expected |= 1 << 77
        expected |= 1 << 78
        expected |= 1 << 79

    x21.rd_input(0) # somehow it's needed; the i2c signals are connected to the GPIO ports of this chip

    # Evaluating the results

    if received != expected:
        msg = "ERROR: FMC Connector: pin %s, received=%x expected=%x" % (pin_str(bit), received, expected)
        print(msg)
        return msg

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

BASE_I2C   = 0x20000
BASE_GPIO0 = 0x40000
BASE_GPIO1 = 0x60000
BASE_GPIO2 = 0x80000

def main (default_directory='.'):
    # Configure the FPGA using the program fpga_loader
    path_fpga_loader = '../firmwares/fpga_loader';
    path_firmware = '../firmwares/spec_pts_fmcconnect.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);

    # Load board library and open the corresponding device
    print "Loading hardware access library and opening devices\n"
    spec = rr.Gennum();

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

    print "Testing of the pins of the FMC connector"

    #---------------------------------------------------------------------------------------
    # For FMC 1

    # FPGA Inputs

    fmc_pins0 = GPIO_slave(spec, BASE_GPIO0);
    fmc_pins1 = GPIO_slave(spec, BASE_GPIO1);
    fmc_pins2 = GPIO_slave(spec, BASE_GPIO2);

    # I2C for IO Expanders

    i2c = COpenCoresI2C(spec, BASE_I2C, 99);
    i2c_x27 = EXPANDER_MCP23017(i2c, 0x27);
    i2c_x24 = EXPANDER_MCP23017(i2c, 0x24);
    i2c_x22 = EXPANDER_MCP23017(i2c, 0x22);
    i2c_x26 = EXPANDER_MCP23017(i2c, 0x26);
    i2c_x21 = EXPANDER_MCP23017(i2c, 0x21);
    i2c_x25 = EXPANDER_MCP23017(i2c, 0x25);

    # Initialization of the IO Expanders

    x_init(i2c_x27,i2c_x24,i2c_x22,i2c_x26,i2c_x21,i2c_x25)

    # One of the bits of the Expanders is set to '1' at a time & the inputs of the AFPGA are read
    # Note the following exceptional cases: bits 67..80, 92, 94 are not connected.

    for bit in range(0,64):
        ret_error = test_bit(i2c_x27,i2c_x24,i2c_x22,i2c_x26,i2c_x21,i2c_x25,fmc_pins0,fmc_pins1,fmc_pins2,bit)

    for bit in range(64,67):
        ret_error = test_bit(i2c_x27,i2c_x24,i2c_x22,i2c_x26,i2c_x21,i2c_x25,fmc_pins0,fmc_pins1,fmc_pins2,bit)

    for bit in range(81,92):
        ret_error = test_bit(i2c_x27,i2c_x24,i2c_x22,i2c_x26,i2c_x21,i2c_x25,fmc_pins0,fmc_pins1,fmc_pins2,bit)

    bit = 93
    ret_error = test_bit(i2c_x27,i2c_x24,i2c_x22,i2c_x26,i2c_x21,i2c_x25,fmc_pins0,fmc_pins1,fmc_pins2,bit)

    bit = 95
    ret_error = test_bit(i2c_x27,i2c_x24,i2c_x22,i2c_x26,i2c_x21,i2c_x25,fmc_pins0,fmc_pins1,fmc_pins2,bit)

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


if __name__ == '__main__':
    main(default_directory='.')
