#!/bin/bash
# Copyright (C) 2024 MOXA Inc. All rights reserved.
# This software is distributed under the terms of the MOXA SOFTWARE NOTICE.
# See the file LICENSE for details.
#
# Name:
#       MOXA USB Power Control Utility
#
# Authors:
#       2024    Elvis Yao <ElvisCW.Yao@moxa.com>
#

source "/usr/lib/mx-gpio-lib"
source "/usr/lib/mx-common-lib"

MODEL_NAME=""
USB_PWR_STATE_STR=("off" "on")
USB_PORT_STR=("front" "rear" "internal")
NUM_OF_USB=""
NUM_OF_PWR_STATE=${#USB_PWR_STATE_STR[@]}
TARGET_STATE=""
TARGET_OPCODE=0
TARGET_GET_USB_STATE_OPCODE=1
TARGET_SET_USB_PWR_STATE_OPCODE=3
IPMI_GET_OPT="0 1"
IPMI_SET_OPT="1"

OS_ID="$(awk -v opt="ID" -F= '$1==opt { print $2 ;}' /etc/os-release | tr -d '"')"

# V2202D settings
function V2202D::profile() {
        # USB_PWE1: GP41, USB_PWE2: GP40
        USB_IT87_GPIO_TBL=(41 40)
        USB_PORT_STR=("left" "right")
}

function V2202D::init() {
        NUM_OF_USB=${#USB_IT87_GPIO_TBL[@]}
}

function V2202D::get_usb_pwr_state() {
        get_usb_pwr_state_it87 "$1"
}

function V2202D::set_usb_pwr_state() {
        set_usb_pwr_state_it87 "$1" "$2"
}

# DA-920E settings
function DA920E::profile() {
	# Use BMC kcs to control USB power
	# spv_ipmi raw 0x3E 0xC0 0x06 0xF5 [get(0)/set(1)] [USB port: 1:4] [status: 0(turn off)|1(turn on)]
	# spv_ipmi means Insyde BMC tool
	# USB port define:
	# #1 for rear+lower usb port, #2 for rear+upper usb port.
	# #3 for front+lower usb port, #4 for front+upper usb port.
	# e.g.
	# Set turn on #1 usb port:	spv_ipmi raw 0x3E 0xC0 0x06 0xF5 1 1 1
	# Set turn off #3 usb port:	spv_ipmi raw 0x3E 0xC0 0x06 0xF5 1 3 0
	# Get #3 usb port state:	spv_ipmi raw 0x3E 0xC0 0x06 0xF5 0 1 3
	IPMI_CMD="spv_ipmi"
	IPMI_OPT="-I open raw"
	IPMI_RAW="0x3E 0xC0 0x06 0xF5"
}

function DA920E::init() {
	NUM_OF_USB=4
}

function DA920E::get_usb_pwr_state() {
	get_usb_state_ipmi "$1"
}

function DA920E::set_usb_pwr_state() {
	set_usb_state_ipmi "$1" "$2"
}

# DA-681C settings
function DA681C::profile() {
        # "front: GP46", "rear: GP27", "internal: GP41"
        USB_IT87_GPIO_TBL=(46 27 41)
}

function DA681C::init() {
        NUM_OF_USB=${#USB_IT87_GPIO_TBL[@]}
}

function DA681C::get_usb_pwr_state() {
        get_usb_pwr_state_it87 "$1"
}

function DA681C::set_usb_pwr_state() {
        set_usb_pwr_state_it87 "$1" "$2"
}

# DA-682C settings
function DA682C::profile() {
        # "front: GP46", "rear: GP27", "internal: GP41"
        USB_IT87_GPIO_TBL=(46 27 41)
}

function DA682C::init() {
        NUM_OF_USB=${#USB_IT87_GPIO_TBL[@]}
}

function DA682C::get_usb_pwr_state() {
        get_usb_pwr_state_it87 "$1"
}

function DA682C::set_usb_pwr_state() {
        set_usb_pwr_state_it87 "$1" "$2"
}

# DA-820C settings
function DA820C::profile() {
        # "front: GP46", "rear: GP27", "internal: GP41"
        USB_IT87_GPIO_TBL=(46 27 41)
}

function DA820C::init() {
        NUM_OF_USB=${#USB_IT87_GPIO_TBL[@]}
}

function DA820C::get_usb_pwr_state() {
        get_usb_pwr_state_it87 "$1"
}

function DA820C::set_usb_pwr_state() {
        set_usb_pwr_state_it87 "$1" "$2"
}

# DA-820E settings
function DA820E::profile() {
        # "front: GP46", "rear: GP41", "internal: GP66"
        USB_IT87_GPIO_TBL=(46 41 66)
}

function DA820E::init() {
        NUM_OF_USB=${#USB_IT87_GPIO_TBL[@]}
}

function DA820E::get_usb_pwr_state() {
        get_usb_pwr_state_it87 "$1"
}

function DA820E::set_usb_pwr_state() {
        set_usb_pwr_state_it87 "$1" "$2"
}

# main functions
function get_usb_state_ipmi() {
	local idx=$1
	local ipmi_idx
	local state
	local ret

	ipmi_idx=$((idx + 1))
	ret=$(${IPMI_CMD} ${IPMI_OPT} ${IPMI_RAW} ${IPMI_GET_OPT} ${ipmi_idx})

	[[ $? -ne 0 ]] && exit 1
	# convert ipmi return string '00/01' to '0/1'
	state="$((ret))"
	echo "Current USB port ${idx} power state is '${USB_PWR_STATE_STR[state]}'"
}

function set_usb_state_ipmi() {
	local idx=$1
	local state=$2
	local ret
	local ret_state

	ipmi_idx=$((idx + 1))
	ret="$(${IPMI_CMD} ${IPMI_OPT} ${IPMI_RAW} ${IPMI_SET_OPT} ${ipmi_idx} ${state})"
	[[ $? -ne 0 ]] && exit 1

	# convert ipmi return string '00/01' to '0/1'
	ret_state="$((ret))"
	if [[ "${ret_state}" == "0" || "${ret_state}" == "1" ]]; then
		if [[ "${ret_state}" == "${state}" ]]; then
			echo "Set USB port ${idx} power state '${USB_PWR_STATE_STR[state]}' OK"
		else
			echo "Set state is failed"
			exit 1
		fi
	else
		echo "Unknown ipmi return string '$ret'"
		exit 1
	fi
}

function get_usb_pwr_state_it87() {
        local usb_port

        usb_port=${1}

        if [[ "${OS_ID}" == "centos" ]]; then
                get_usb_pwr_state_it87_via_sysfs $usb_port
        else
                get_usb_pwr_state_it87_via_libgpiod $usb_port
        fi
}

function set_usb_pwr_state_it87() {
        local usb_port
        local usb_pwr_state

        usb_port=${1}
        usb_pwr_state=${2}

        if [[ "${OS_ID}" == "centos" ]]; then
                set_usb_pwr_state_it87_via_sysfs $usb_port $usb_pwr_state
        else
                set_usb_pwr_state_it87_via_libgpiod $usb_port $usb_pwr_state
        fi
}

function get_usb_pwr_state_it87() {
        local usb_port

        usb_port=${1}

        if [[ "${OS_ID}" == "centos" ]]; then
                get_usb_pwr_state_it87_via_sysfs $usb_port
        else
                get_usb_pwr_state_it87_via_libgpiod $usb_port
        fi
}

function set_usb_pwr_state_it87() {
        local usb_port
        local usb_pwr_state

        usb_port=${1}
        usb_pwr_state=${2}

        if [[ "${OS_ID}" == "centos" ]]; then
                set_usb_pwr_state_it87_via_sysfs $usb_port $usb_pwr_state
        else
                set_usb_pwr_state_it87_via_libgpiod $usb_port $usb_pwr_state
        fi
}

function get_usb_pwr_state_it87() {
        local usb_port

        usb_port=${1}

        if [[ "${OS_ID}" == "centos" ]]; then
                get_usb_pwr_state_it87_via_sysfs $usb_port
        else
                get_usb_pwr_state_it87_via_libgpiod $usb_port
        fi
}

function set_usb_pwr_state_it87() {
        local usb_port
        local usb_pwr_state

        usb_port=${1}
        usb_pwr_state=${2}

        if [[ "${OS_ID}" == "centos" ]]; then
                set_usb_pwr_state_it87_via_sysfs $usb_port $usb_pwr_state
        else
                set_usb_pwr_state_it87_via_libgpiod $usb_port $usb_pwr_state
        fi
}

function get_usb_pwr_state_it87_via_sysfs() {
        local usb_port
        local usb_pwr_state
        local sio_gpio_pin
        local ret

        ret=0

        if ! is_module_loaded gpio_it87; then
                echo "gpio_it87 driver is not loaded"
                exit 1
        fi

        usb_port=${1}
        sio_gpio_pin=$(gpc_it8786 ${USB_IT87_GPIO_TBL[$usb_port]})
        [[ $? -ne 0 ]] && ret=1
        usb_pwr_state=$(gpio_get_value_sysfs $sio_gpio_pin)
        [[ $? -ne 0 ]] && ret=1

        if [[ $ret -ne 0 ]]; then
                echo "Get USB power state Failed"
                exit $ret
        fi

        echo "Current [${USB_PORT_STR[$usb_port]}] USB power state is ${USB_PWR_STATE_STR[$usb_pwr_state]}."
}

function get_usb_pwr_state_it87_via_libgpiod() {
        local usb_port
        local usb_pwr_state
        local sio_gpio_pin
        local gpc_name
        local ret

        ret=0

        if ! is_module_loaded gpio_it87; then
                echo "gpio_it87 driver is not loaded"
                exit 1
        fi

        usb_port=${1}
        sio_gpio_pin=$(gpc_it8786_remap ${USB_IT87_GPIO_TBL[$usb_port]})
        gpc_name=$(gpio_get_gpiochip_name gpio_it87)
        [[ $? -ne 0 ]] && ret=1
        usb_pwr_state=$(gpio_get_value_libgpiod $sio_gpio_pin $gpc_name)
        [[ $? -ne 0 ]] && ret=1

        if [[ $ret -ne 0 ]]; then
                echo "Get State Failed"
                exit $ret
        fi

        echo "Current [${USB_PORT_STR[$usb_port]}] USB power state is ${USB_PWR_STATE_STR[$usb_pwr_state]}."
}

function set_usb_pwr_state_it87_via_sysfs() {
        local usb_port
        local usb_pwr_state
        local check_state
        local sio_gpio_pin

        if ! is_module_loaded gpio_it87; then
                echo "gpio_it87 driver is not loaded"
                exit 1
        fi

        usb_port=${1}
        usb_pwr_state=${2}
        sio_gpio_pin=$(gpc_it8786 ${USB_IT87_GPIO_TBL[$usb_port]})
        [[ $? -ne 0 ]] && ret=1

        gpio_set_value_sysfs $sio_gpio_pin $usb_pwr_state
        [[ $? -ne 0 ]] && ret=1

        if [[ $ret -ne 0 ]]; then
                echo "Set State Failed"
                exit $ret
        fi

        check_state=$(gpio_get_value_sysfs $sio_gpio_pin)
        [[ $? -ne 0 ]] && ret=1
        if [[ $ret -ne 0 || "$usb_pwr_state" -ne "$check_state" ]]; then
                echo "Set State Failed"
                exit $ret
        fi

        echo "Set OK."
}

function set_usb_pwr_state_it87_via_libgpiod() {
        local usb_port
        local usb_pwr_state
        local check_state
        local sio_gpio_pin
        local gpc_name
        local ret

        ret=0

        if ! is_module_loaded gpio_it87; then
                echo "gpio_it87 driver is not loaded"
                exit 1
        fi

        usb_port=${1}
        usb_pwr_state=${2}
        sio_gpio_pin=$(gpc_it8786_remap ${USB_IT87_GPIO_TBL[$usb_port]})
        gpc_name=$(gpio_get_gpiochip_name gpio_it87)
        [[ $? -ne 0 ]] && ret=1

        gpio_set_value_libgpiod $sio_gpio_pin $usb_pwr_state $gpc_name
        [[ $? -ne 0 ]] && ret=1

        check_state=$(gpio_get_value_libgpiod $sio_gpio_pin $gpc_name)
        [[ $? -ne 0 ]] && ret=1
        if [[ $ret -ne 0 || "$usb_pwr_state" -ne "$check_state" ]]; then
                echo "Set State Failed"
                exit $ret
        fi

        echo "Set OK."
}

function load_model_name() {
        for name in $(get_model_name_from_dmi_type12); do
                if [[ "$(type -t "${name}::profile")" = 'function' ]]; then
                        MODEL_NAME="${name}"
                        break
                fi
        done

        if [[ -z "${MODEL_NAME}" ]]; then
                for name in $(get_model_name_from_dmi_type1); do
                        if [[ "$(type -t "${name}::profile")" = 'function' ]]; then
                                MODEL_NAME="${name}"
                                break
                        fi
                done
        fi

        if [[ -z "${MODEL_NAME}" ]]; then
                echo "Unsupported model"
                exit 38
        fi
}

function script_usage() {
        cat <<EOF
USAGE:
	mx-usb-power-ctl -i <usb_port> [-s <state>]

OPTIONS:
	-i <usb_port>
                Get USB port power state

        -s <state>
                Set USB port power state
                        0: off
                        1: on

EXAMPLE:
	Get USB port 0 power state
	mx-usb-power-ctl -i 0

	Get USB port 1 power state
	mx-usb-power-ctl -i 1

	Set USB port 0 power state to off
	mx-usb-power-ctl -i 0 -s 0

	Set USB port 2 power state to on
	mx-usb-power-ctl -i 2 -s 1

EOF
}

function script_params() {
        if [[ $# -eq 0 ]]; then
                script_usage
                exit 1
        fi

        while getopts "hi:s:" opt; do
                case "${opt}" in
                i)
                        TARGET_USB_PORT="$OPTARG"
                        TARGET_OPCODE=$((TARGET_OPCODE += 1))
                        ;;
                s)
                        TARGET_STATE="$OPTARG"
                        TARGET_OPCODE=$((TARGET_OPCODE += 2))
                        ;;

                h)
                        script_usage
                        exit 0
                        ;;
                \?)
                        script_usage
                        exit 22
                        ;;
                :)
                        echo "Option -$OPTARG requires an argument." >&2
                        script_usage
                        exit 22
                        ;;
                esac
        done
}

function script_init() {
        load_model_name

        if [[ ! $(type -t "${MODEL_NAME}"::profile) == function ]]; then
                echo "${MODEL_NAME} profile function is not define"
                exit 1
        fi

        "${MODEL_NAME}"::profile

        if [[ ! $(type -t "${MODEL_NAME}"::init) == function ]]; then
                echo "${MODEL_NAME} init function is not define"
                exit 1
        fi

        "${MODEL_NAME}"::init
}

function verify_usb_port() {
        if ! check_leading_zero_digit $TARGET_USB_PORT; then
                echo "Invaild USB port format."
                exit 1
        fi

        if [[ $TARGET_USB_PORT -lt 0 || $TARGET_USB_PORT -ge $NUM_OF_USB ]]; then
                echo "Invalid USB port."
                exit 1
        fi
}

function verify_state() {
        if ! check_leading_zero_digit $TARGET_STATE; then
                echo "Invalid power state format."
                exit 1
        fi

        if [[ $TARGET_STATE -lt 0 || $TARGET_STATE -ge $NUM_OF_PWR_STATE ]]; then
                echo "Invalid power state."
                exit 1
        fi
}

function main() {
        script_params "$@"
        script_init

        case $TARGET_OPCODE in
        $TARGET_GET_USB_STATE_OPCODE)
                verify_usb_port

                if [[ ! $(type -t "${MODEL_NAME}"::get_usb_pwr_state) == function ]]; then
                        echo "${MODEL_NAME} get_usb_pwr_state function is not define"
                        exit 1
                fi

                "${MODEL_NAME}"::get_usb_pwr_state $TARGET_USB_PORT
                ;;
        $TARGET_SET_USB_PWR_STATE_OPCODE)
                verify_usb_port
                verify_state

                if [[ ! $(type -t "${MODEL_NAME}"::set_usb_pwr_state) == function ]]; then
                        echo "${MODEL_NAME} set_usb_pwr_state function is not define"
                        exit 1
                fi

                "${MODEL_NAME}"::set_usb_pwr_state $TARGET_USB_PORT $TARGET_STATE
                ;;
        *)
                script_usage
                exit 1
                ;;
        esac
}

main $@
