/* -*- tab-width: 4 -*-
 *
 * Electric(tm) VLSI Design System
 *
 * File: Netscan4Driver.java
 * Written by Tom O'Neill, Sun Microsystems.
 *
 * Copyright (c) 2004, Oracle and/or its affiliates. All rights reserved.
 *
 * Electric(tm) is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 *
 * Electric(tm) 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package com.sun.electric.tool.simulation.test;

/**
 * Low-level drvier providing initialization, configuration, and connection for
 * all four ports on Corelis NETUSB-1149.1/E boundary scan controller (JTAG
 * tester device). Shifting data in and out should instead be performed using
 * {@link ChainControl}. Class is static and non-instantiable.
 */
class Netscan4Driver extends Logger {

    /** Minumum allowed value of the JTAG port (TAP) parameter */
    public static final int MIN_TAP = 1;

    /** Maximum allowed value of the JTAG port (TAP) parameter */
    public static final int MAX_TAP = 4;

    /** Number of GPIO pins per JTAG port (TAP) */
    public static final int NUM_OUTPUT_PINS = 3;

    /** Identifier for accessing GPIO in output mode */
    public static final int POD_IO_OUTPUT = 0x01;

    /** Identifiers for the 3 GPIO pins */
    public static final int POD_GPIO[] = new int[] { 0x0D, 0x0E, 0x0F };

    /** IP address of JTAG tester. */
    private static String addressIP;

    /** Number of ports in use on the Netscan4 device. */
    private static int numPortsRegistered = 0;

    /** Suppress default constructor to make class non-instantiable */
    private Netscan4Driver() {
    }

    /**
     * Registers to use a port on the Netscan4 JTAG tester and, if it hasn't
     * done so already, connects to it by using the NetUSB_Connect routine in
     * the scan function library. The Corelis Netscan SFL library only supports
     * one JTAG tester at a time, a restriction we enforce in Java for better
     * error messages. The tester must then be configured before use, by calling
     * {@link #configure}.
     * 
     * @param addressIP
     *            IP address of JTAG tester
     */
    static void registerPort(String addressIP, int jtagPort) {
        checkJtagPort(jtagPort);
        numPortsRegistered++;

        // If already connected to the device, we can return
        if (Netscan4Driver.addressIP != null) {
            if (addressIP.equals(Netscan4Driver.addressIP)) {
                return;
            }
            Infrastructure.fatal("Attempt to connect to Netscan4 at IP"
                    + " address " + addressIP + ", when one at "
                    + Netscan4Driver.addressIP
                    + " is already connected.  Corelis Netscan SFL"
                    + " library only supports one JTAG tester at a time");
        }

        Netscan4Driver.addressIP = addressIP;
        NetscanGeneric.incrementNumTesters();

        // Connect to tester via ethernet
        Logger.logInit("Connecting to Netscan4 at " + addressIP);
        int status = Netscan4JNI.netUSB_Connect(addressIP);
        if (status <= 0) {
            Infrastructure.fatal("Netscan4JNI.net_connect returned error code "
                    + status);
        }
    }

    /**
     * Check if jtagPort is in the allowed range. Routine should be overridden
     * if multiple ports are supported.
     * 
     * @param jtagPort
     *            proposed port on the JTAG tester
     */
    static void checkJtagPort(int jtagPort) {
        if (jtagPort < MIN_TAP || jtagPort > MAX_TAP) {
            Infrastructure.fatal("JTAG port number " + jtagPort
                    + " not in allowed range " + MIN_TAP + ".." + MAX_TAP);
        }
    }

    /**
     * Configures the JTAG tester, setting its parameters, and resets the JTAG
     * controller (clears TRSTb briefly). Can be run at any time after
     * initialization to change settings.
     * 
     * @param tapVolt
     *            signal (TAP) voltage in Volts
     * @param kiloHerz
     *            the TCK frequency in kHz (from 391 kHz to 40 MHz)
     * @param logger
     *            Object with logging properties to use
     */
    static void configure(float tapVolt, long kiloHerz, Logger logger) {
        int milliVolt = Math.round(tapVolt * 1000.f);

        logger.logSet("Netscan4 configuring " + kiloHerz + " kHz and "
                + milliVolt + " mV");
        int status = Netscan4JNI.netUSB_hard_reset(kiloHerz, milliVolt);

        // N.B.: For some reason, Corelis used ZERO for the ERROR condition!
        if (status == 0)
            Infrastructure.fatal("Netscan4JNI.netUSB_hard_reset returned "
                    + status);
        status = Netscan4JNI.netUSB_set_scan_clk(kiloHerz);

        if (status != 0)
            Infrastructure.fatal("Netscan4JNI.netUSB_set_scan_clk returned "
                    + status);

        // Reset the JTAG controller
        reset();
    }

    /**
     * Return the IP address of the Netscan4
     * 
     * @return Returns the IP address of the Netscan4
     */
    static String getAddressIP() {
        return addressIP;
    }

    /**
     * Reset the finite state machine of the chip's JTAG controller by briefly
     * setting the TRSTb signal <tt>LO</tt>. The IR becomes bypass
     * automatically.
     */
    static void reset() {
        int status = Netscan4JNI.netUSB_set_trst(0);
        if (status != 0)
            Infrastructure
                    .fatal("Netscan4JNI.net_set_trst(0) returned error code "
                            + status);
        status = Netscan4JNI.netUSB_set_trst(1);
        if (status != 0)
            Infrastructure
                    .fatal("Netscan4JNI.net_set_trst(1) returned error code "
                            + status);
    }

    /**
     * Reset the finite state machine of the chip's JTAG controller by briefly
     * setting the TMS signal <tt>HI</tt> for five cycles. The IR becomes bypass
     * automatically.
     */
    static void tms_reset(int jtagPort) {
        int status = Netscan4JNI.netUSB_tms_reset(jtagPort);
        if (status != 0)
            Infrastructure
                    .fatal("Netscan4JNI.net_tms_reset("+jtagPort+") returned error code "
                            + status);
    }

    /**
     * Deregisters to use a port on the JTAG tester. If no more ports are
     * registered, disconnect from the JTAG tester. Should be called for each
     * registered port before exiting JVM.
     */
    static void deregisterPort() {
        numPortsRegistered--;
        if (numPortsRegistered == 0) {
            int status = Netscan4JNI.netUSB_Disconnect();
            if (status != 0) {
                Infrastructure.fatal("Netscan4JNI.net_disconnect() "
                        + "returned error code " + status);
            }
        }
    }

    /**
     * Write the bits <code>scanIn</code> to the JTAG controller's instruction
     * register. The first bit scanned in to the chip is the LSB of
     * <code>scanIn[0]</code>, and the first bit scanned out from the chip is
     * the LSB of <code>scanOut[0]</code>.
     * 
     * @param jtagPort
     *            TAP port to use
     * @param numBits
     *            The number of bits to shift
     * @param scanIn
     *            Bit sequence to write to instruction register
     * @param scanOut
     *            Bits scanned out of instruction register
     * @param logger
     *            Object with logging properties to use
     * 
     * @return 0x00 (success), 0x11 (transmit error), 0x33 (receive error)
     */
    protected static int hw_net_scan_ir(int jtagPort, int numBits,
            short[] scanIn, short[] scanOut, Logger logger) {
        checkJtagPort(jtagPort);
        logger.logOther("IR into TAP " + jtagPort + ": # shorts="
                + scanIn.length + ", # bits="+ numBits+", " + NetscanGeneric.shortsToString(scanIn));
        int result = Netscan4JNI.netUSB_scan_ir(scanIn, numBits, scanOut, jtagPort);
        logger.logOther("IR out: # shorts=" + scanOut.length + ", "
                + NetscanGeneric.shortsToString(scanOut));
        return result;
    }

    /**
     * Write the bits <code>scanIn</code> to the JTAG controller's data
     * register, and read back the bits <code>scanOut</code>. The first bit
     * scanned in to the chip is the LSB of <code>scanIn[0]</code>, and the
     * first bit scanned out from the chip is the LSB of <code>scanOut[0]</code>.
     * 
     * @param jtagPort
     *            TAP port to use
     * @param numBits
     *            The number of bits to shift
     * @param scanIn
     *            Bit sequence to write to data register
     * @param scanOut
     *            Bits scanned out of data register
     * @param logger
     *            Object with logging properties to use
     * 
     * @return 0x00 (success), 0x11 (transmit error), 0x33 (receive error)
     */
    protected static int hw_net_scan_dr(int jtagPort, int numBits,
            short[] scanIn, short[] scanOut, Logger logger) {
        checkJtagPort(jtagPort);
        logger.logOther("DR into TAP " + jtagPort + ":  # shorts="
                + scanIn.length + ", " + NetscanGeneric.shortsToString(scanIn));
        logger.logOther("   lentgth=" + numBits);
        int result = Netscan4JNI.netUSB_scan_dr(scanIn, numBits,
                scanOut, jtagPort);
        logger.logOther("DR out: # shorts=" + scanOut.length + ", "
                + NetscanGeneric.shortsToString(scanOut));
        return result;
    }

    /** Checks if parallel I/O port and index are in the allowed ranges */
    static void checkGpioID(int jtagPort, int index) {
        Netscan4Driver.checkJtagPort(jtagPort);
        if (index < 0 || index >= POD_GPIO.length) {
            Infrastructure.fatal("Index " + index + " not in allowed range 0.."
                    + (POD_GPIO.length - 1));
        }
    }

    /**
     * Set the logic level for a single channel of the parallel programmable
     * output signals from the JTAG tester to the chip.
     * 
     * @param jtagPort
     *            Which JTAG port ({@link #MIN_TAP}..{@link #MAX_TAP}) to
     *            set output on
     * @param index
     *            Which of the TAP's parallel outputs (0..2) to set
     * @param newLevel
     *            set parallel output <tt>HI</tt>?
     */
    static void setParallelIO(int jtagPort, int index, boolean newLevel) {
        checkGpioID(jtagPort, index);
        int value;
        if (newLevel) {
            value = 1;
        } else {
            value = 0;
        }
        int status = Netscan4JNI.netUSB_AccessScanGPIO(jtagPort,
                POD_GPIO[index], POD_IO_OUTPUT, value);
        if (status != 0) {
            Infrastructure
                    .fatal("net_access_gpio returned error code" + status);
        }
    }
}
