#! /usr/bin/env python3

# SPDX-FileCopyrightText: Copyright (c) 2022-2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: LicenseRef-NvidiaProprietary
#
# NVIDIA CORPORATION, its affiliates and licensors retain all intellectual
# property and proprietary rights in and to this material, related
# documentation and any modifications thereto. Any use, reproduction,
# disclosure or distribution of this material and related documentation
# without an express license agreement from NVIDIA CORPORATION or
# its affiliates is strictly prohibited.
# Copyright (c) 2022-2024, NVIDIA CORPORATION.  All Rights Reserved.

import os
import sys
import subprocess
from optparse import OptionParser
try:
    from supported_targets_all import supported_targets
except:
    from supported_targets import supported_targets


class rf_exception(Exception):
    def __init__(self, value):
        self.value = value

    def __str__(self):
        return repr(self.value)


def require_bmc():
    if options.bmc is None:
        print("ERROR: bmc functionality requires --bmc or else BMC_URL envvar", file=sys.stderr)
        sys.exit(1)


def rf_command(rfcmd):
    require_bmc()
    if options.index is not None:
        print("ERROR: index not supported; assuming 1 device per BMC.\n")
        sys.exit(1)
    cmdline = ["rf_power_reset.py"]
    echo_cmdline = cmdline.copy()
    creds = ["-u", "root", "-p", "0penBmc"]
    cmdline += creds
    echo_cmdline += ["<CREDENTIALS>"]

    bmc_url = ["-r", options.bmc]
    cmdline += bmc_url
    echo_cmdline += bmc_url
    cmdline += rfcmd
    echo_cmdline += rfcmd
    if options.debug:
        print("\n DEBUG: " + ' '.join(echo_cmdline) + "\n")
    rf_status = subprocess.Popen(cmdline).wait()
    if rf_status != 0:
        raise rf_exception("Return code " + str(rf_status) + " from `rf_power_reset.py " + ' '.join(rfcmd))


def require_topo():
    if options.remote:
        print("ERROR: topo functionality is not available in --remote mode", file=sys.stderr)
        sys.exit(1)


from _targets._common import sleep_progress
def th500_delay(time=None):
    if time is None:
        time = options.delay
    sleep_progress(time)


TCA9535_REGMAP = [
        {'CONFIG' : 0x06, 'OUTPUT' : 0x02, 'INPUT' : 0x00},
        {'CONFIG' : 0x07, 'OUTPUT' : 0x03, 'INPUT' : 0x01}
        ]

def set_bit(value, bit_pos, bit_state):
    mask = 1 << bit_pos
    value &= ~mask
    if bit_state:
        value |= mask
    return value

import time
from ctypes import *
from nvtopo import nv_topo


def configureTCA9535(topo, addr, port, pin_index, pin_state, write_read):
    regval = 0
    if (write_read):
        #Configure Port Pins for I/P
        data = (c_ubyte * 2)()
        data[0] = TCA9535_REGMAP[port]['CONFIG']
        data[1] = 0xFF
        topo.i2c_write(addr, 2, data)

        #Read Port State
        rd_reg = (c_ubyte * 1)()
        rd_reg[0] = TCA9535_REGMAP[port]['INPUT']
        rdval = int(topo.i2c_write_read(addr, 1, 1, rd_reg)[0])
        time.sleep(0.05)
        return ((rdval >> pin_index) & 0x1)
    else:
        #Configure Port Pin for O/P (Read-Write)
        rd_reg = (c_ubyte * 1)()
        rd_reg[0] = TCA9535_REGMAP[port]['CONFIG']
        rdval = int(topo.i2c_write_read(addr, 1, 1, rd_reg)[0])

        data = (c_ubyte * 2)()
        data[0] = TCA9535_REGMAP[port]['CONFIG']
        data[1] = set_bit(rdval, pin_index, 0)
        topo.i2c_write(addr, 2, data)

        #Write Output Port Pin value (Read-Write)
        rd_reg = (c_ubyte * 1)()
        rd_reg[0] = TCA9535_REGMAP[port]['OUTPUT']
        rdval = int(topo.i2c_write_read(addr, 1, 1, rd_reg)[0])

        data = (c_ubyte * 2)()
        data[0] = TCA9535_REGMAP[port]['OUTPUT']
        data[1] = set_bit(rdval, pin_index, pin_state)
        topo.i2c_write(addr, 2, data)
        time.sleep(0.05)
        return 0


def boot_sel_qspi():
    print("BOOT_SELECT STRAPS SET TO QSPI")
    configureTCA9535(nvctl, 0x75, 1, 2, 0, 0)   #IO_C0_STRAP_BOOT_SELECT = 0
    configureTCA9535(nvctl, 0x75, 1, 6, 1, 0)   #CPU0_STRAP_LS_EN_L = 1     Disable strap change thru TOPO so HW strap will be used.
    configureTCA9535(nvctl, 0x75, 1, 5, 0, 0)   #IO_C1_STRAP_BOOT_SELECT = 0
    configureTCA9535(nvctl, 0x75, 1, 7, 0, 0)   #CPU1_STRAP_LS_EN_L = 0     Enable Strap change thru TOPO
    print("NOW: Reset CPU(s) using E4870 Reset button or thru TOPO for Boot strap to take effect")
    return


if __name__ == "__main__":
    rf_topo_targets = supported_targets["rf_topo"]
    commands = "status | dual_cpu_rcm_reset | rcm_power_on | power_{on,off} | force_{on,off} | os_restart | force_reset | topo_reset | warm_reset | rcm_{up,down} | boot_sel_{uart,qspi} | e4870_topo_uart1_en"

    parser = OptionParser(epilog="Available commands:  " + commands, usage="th500-ctl [options] <command>")
    parser.set_defaults()
    parser.add_option("--target", "-t", action="store", type="string",
                      dest="target", help="Target board [%s]" % " | ".join(rf_topo_targets))
    parser.add_option("--variant", "-v", action="store", type="string",
                      dest="variant", help="Target board variant [A00 | A01 | A02 | A03 | ...]")
    parser.add_option("--delay", "-d", action="store", type="int",
                      dest="delay", help="onkey/rcm press time in seconds",
                      default=30)
    parser.add_option("--index", "-i", action="store", type="int",
                      dest="index", help="Instance of debug board if there are more than one, starts with 0.",
                      default=None)
    parser.add_option("--bmc", "-b", action="store", type="string",
                      dest="bmc", help="URL (including protocol) of redfish BMC. Can also use 'BMC_URL' envvar.",
                      default=None)
    parser.add_option("--remote", action="store_true",
                      help="Skips initialization of topo, for remote (BMC-only) operation.",
                      default=False)
    parser.add_option("--debug", action="store_true",
                      help="Enable debug output of this script.",
                      default=False)

    (options, args) = parser.parse_args()

    if options.bmc is None:
        if "BMC_URL" in os.environ:
            options.bmc = os.environ["BMC_URL"]
            print("NOTE: Using --bmc=%s from 'BMC_URL' envvar" % options.bmc, file=sys.stderr)
        elif "BMCURL" in os.environ:
            options.bmc = os.environ["BMCURL"]
            print("NOTE: Using --bmc=%s from 'BMCURL' envvar" % options.bmc, file=sys.stderr)
        elif "BMCIP" in os.environ:
            options.bmc = "https://" + os.environ["BMCIP"]
            print("NOTE: Using --bmc=https://%s from 'BMCIP' envvar" % options.bmc, file=sys.stderr)
        else:
            print("NOTE: No bmc url provided; commands that require BMC will fail.", file=sys.stderr)

    if options.remote:
        print("NOTE: Running in '--remote' mode; commands that require topo will fail.", file=sys.stderr)

    if options.target in rf_topo_targets:
        if not options.remote:
            nvctl = nv_topo("topo", serial_number=None, index=None)
    else:
        print("ERROR: '%s' is not a supported target board\nUse one of following: [%s]" %
               (options.target, " | ".join(rf_topo_targets)))
        sys.exit(1)

    if len(args) != 1:
        print("ERROR: Board control command missing.  Must be one of: %s" % commands, file=sys.stderr)
        sys.exit(1)

    if args[0] == "status":
        rf_command(["--info"])
        if not options.remote:
            for gpio in sorted(nvctl.get_IO_names()):
                if "FRC_REC" in gpio or "NVJTAG" in gpio:
                    print(gpio + " is %d" % nvctl.get_IO_value(gpio))
    elif args[0] == "rcm_power_on":
        require_topo()
        boot_sel_qspi()
        print("Holding FRC_REC_N low via TOPO...")
        nvctl.hold_button("FORCE_RECOVERY")
        th500_delay(1)
        rf_command(["--type", "ForceOn"])
        th500_delay()
        print("Releasing FRC_REC_N via TOPO...")
        nvctl.release_button("FORCE_RECOVERY")
    elif args[0] == "power_on":  # for cold boot
        rf_command(["--type", "On"])
        th500_delay(1)
    elif args[0] == "power_off":
        rf_command(["--type", "GracefulShutdown"])
        th500_delay()
    elif args[0] == "force_on":
        rf_command(["--type", "ForceOn"])
        th500_delay(1)
    elif args[0] == "force_off":
        rf_command(["--type", "ForceOff"])
        th500_delay()
    elif args[0] == "os_restart": # for complete system reset (graceful)
        rf_command(["--type", "GracefulRestart"])
        th500_delay()
    elif args[0] == "force_reset" or args[0] == "warm_reset":  # harsher reset by BMC
        rf_command(["--type", "ForceRestart"])
        th500_delay()
    elif args[0] == "topo_reset":  # cpu only (TOPO)
        require_topo()
        print("TOPO: Asserting SYS_RST_N for {} second(s)...".format(options.delay))
        nvctl.target_reset(options.delay)
    elif args[0] == "warm_reset":  # cpu only (BMC)
        require_topo()
        print("Asserting SYS_RST_N for {} second(s)...".format(options.delay))
        nvctl.target_reset(options.delay)
    elif args[0] == "rcm_down":
        require_topo()
        print("Holding FRC_REC_N low via TOPO...")
        nvctl.hold_button("FORCE_RECOVERY")
    elif args[0] == "rcm_up":
        require_topo()
        print("Releasing FRC_REC_N via TOPO...")
        nvctl.release_button("FORCE_RECOVERY")
    elif args[0] == "boot_sel_uart":
        require_topo()
        print("BOOT_SELECT STRAPS SET TO UART")
        configureTCA9535(nvctl, 0x75, 1, 2, 1, 0)      #IO_C0_STRAP_BOOT_SELECT = 1
        configureTCA9535(nvctl, 0x75, 1, 6, 0, 0)      #CPU0_STRAP_LS_EN_L = 1     Enable Strap change thru TOPO
        configureTCA9535(nvctl, 0x75, 1, 5, 1, 0)      #IO_C1_STRAP_BOOT_SELECT = 1
        configureTCA9535(nvctl, 0x75, 1, 7, 0, 0)      #CPU1_STRAP_LS_EN_L = 0     Enable Strap change thru TOPO
        print("NOW: Reset CPU using E4870 Reset button or thru TOPO for Boot strap to take effect")
    elif args[0] == "boot_sel_qspi":
        require_topo()
        boot_sel_qspi()
    elif args[0] == "dual_cpu_rcm_reset" or args[0] == "c2_rcm_reset":  # Tegrashell called this `e4866rcm`
        require_topo()
        configureTCA9535(nvctl,0x74, 0, 2, 1, 0)    #IO_MUX_UPHY4_CPU1_SEL_H (P02) disable for UPHY4 L1 to USB3 Port. Required for E4866
        configureTCA9535(nvctl, 0x74, 0, 0, 1, 0)    #IO_MUX_UPHY4_CPU2_SEL_H (P00) enable for UPHY4 L1 to USB3 Port. Required for E4866
        configureTCA9535(nvctl,0x75, 1, 2, 0, 0)      #IO_STRAP_CPU1_BOOT_SELECT = 0
        configureTCA9535(nvctl,0x75, 1, 6, 1, 0)      #IO_STRAP_CPU1_EN_L = 1     Disable strap change thru TOPO so HW strap will be used.
        configureTCA9535(nvctl,0x75, 1, 5, 0, 0)      #IO_STRAP_CPU2_BOOT_SELECT = 0
        configureTCA9535(nvctl,0x75, 1, 7, 1, 0)      #IO_STRAP_CPU2_EN_L = 1     Disable strap change thru TOPO so HW strap will be used.
        print("Holding FRC_REC_N via TOPO...")
        nvctl.hold_button("FORCE_RECOVERY")
        th500_delay(5)
        print("NOW: Starting BMC ForceRestart")
        rf_command(["--type", "ForceRestart"])
        th500_delay(10)
        print("Releasing FRC_REC_N via TOPO...")
        nvctl.release_button("FORCE_RECOVERY")
    elif args[0] == "e4870_topo_uart1_en":
        require_topo()
        configureTCA9535(nvctl, 0x75, 0, 4, 0, 0)      #U124.P04 MCU_UART0_OE_L Low - Enable UART1 thru TOPO Path along w/ HDR
        time.sleep(0.1)
    else:
        print("Unknown board control command '%s'.  Must be one of: %s" % (args[0], commands), file=sys.stderr)
        sys.exit(1)

    sys.exit(0)

