#!/bin/bash
#
# Copyright (C) 2023 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 Module Control Utility
#
# Description:
#       Utility for controlling modules.
#
# Authors:
# 	2022    Wilson Huang <WilsonYS.Huang@moxa.com>
#

TARGET_PRODUCT=""
DIP_SWITCH_SELECT_WIFI=0
DIP_SWITCH_SELECT_LTE=1
DMIDECODE="/usr/sbin/dmidecode"

# default setup
SIM_CARD_A_VAL=1
SIM_CARD_B_VAL=0
GET_SIM_CARD_A_MSG="Get SIM side A"
SET_SIM_CARD_A_MSG="Set SIM side A"
GET_SIM_CARD_B_MSG="Get SIM side B"
SET_SIM_CARD_B_MSG="Set SIM side B"

_product_V2403C() {
	GPIO_MODULE="gpio_it87"
	NUM_OF_MODULE_SLOTS=2

	# If the function is not support, it should keep array empty.
	# Slot 1  2
	DIP_SWITCH_SELECT_TBL=(27 67)
	POWER_CONTROL_TBL=(81 83)
	SIM_CARD_SELECT_TBL=(80 82)
	RESET_PIN_TBL=()
}

_product_MC1200() {
	GPIO_MODULE="gpio_it87"
	NUM_OF_MODULE_SLOTS=2

	# If the function is not support, it should keep array empty.
	# Slot 1  2
	DIP_SWITCH_SELECT_TBL=(27 67)
	POWER_CONTROL_TBL=(81 83)
	SIM_CARD_SELECT_TBL=(80 82)
	RESET_PIN_TBL=()
}

_product_V3000() {
	GPIO_MODULE="gpio_pca953x"
	NUM_OF_MODULE_SLOTS=1

	# If the function is not support, it should keep array empty.
	# Slot 1: mPCIe Socket (LTE)
	# PWR=GP14=8+4=12, SIM=GP16=8+6=14, DIP=GP17=8+7=15
	DIP_SWITCH_SELECT_TBL=(15)
	POWER_CONTROL_TBL=(12)
	SIM_CARD_SELECT_TBL=(14)
	RESET_PIN_TBL=()
	SIM_CARD_A_VAL=0
	SIM_CARD_B_VAL=1
	GET_SIM_CARD_A_MSG="Get SIM side A1"
	SET_SIM_CARD_A_MSG="Set SIM side A1"
	GET_SIM_CARD_B_MSG="Get SIM side A2"
	SET_SIM_CARD_B_MSG="Set SIM side A2"
}

_product_V2406C() {
	GPIO_MODULE="gpio_it87"
	NUM_OF_MODULE_SLOTS=2

	# If the function is not support, it should keep array empty.
			 # Slot 1  2
	DIP_SWITCH_SELECT_TBL=(27 67)
	POWER_CONTROL_TBL=(81 83)
	SIM_CARD_SELECT_TBL=(80 82)
	RESET_PIN_TBL=()
}

_product_V2201() {
	GPIO_MODULE="gpio_it87"
	NUM_OF_MODULE_SLOTS=2

	# If the function is not support, it should keep array empty.
			 # Slot 1  2
	DIP_SWITCH_SELECT_TBL=()
	POWER_CONTROL_TBL=(22 24)
	SIM_CARD_SELECT_TBL=()
	RESET_PIN_TBL=(24 26)
}

declare -A PRODUCT_PROFILE=(
	["V2403C"]=_product_V2403C
	["V3000"]=_product_V3000
	["V2406C"]=_product_V2406C
	["V2201"]=_product_V2201
	["MC1200"]=_product_MC1200
)

load_product_name_from_dmi() {
	local ret=1

	for m in $($DMIDECODE -t 12 | grep "Option " | awk -F ':' '{print substr($2,1,11)}' | sed 's/ //g');
	do
		if [[ ${PRODUCT_PROFILE[$m]} ]]; then
				TARGET_PRODUCT=$m
				${PRODUCT_PROFILE[$TARGET_PRODUCT]}
				ret=0
				break
		fi
	done

	for m in $($DMIDECODE -t 1 | grep "Product Name" | awk -F ':' '{print $2}' | sed 's/ //g');
	do
		if [[ ${PRODUCT_PROFILE[$m]} ]]; then
			TARGET_PRODUCT=$m
			${PRODUCT_PROFILE[$TARGET_PRODUCT]}
			ret=0
			break
		fi
	done

	return $ret
}

load_product_config() {
	if load_product_name_from_dmi; then
		# Determine gpio chip profile
		gpc=${GPIO_CHIP[$GPIO_MODULE]}
	else
		echo "Error: model profile not found."
		exit 1
	fi
}

# GPIO function
gpio_request() {
	local gpio=${1}

	if [ ! -e "/sys/class/gpio/gpio${gpio}" ]
	then
		echo "${gpio}" > /sys/class/gpio/export
	fi
}

gpio_set_value() {
	local gpio=${1}
	local value=${2}

	gpio_request "${gpio}"
	case "${value}" in
	0)
		echo "low" > "/sys/class/gpio/gpio${gpio}/direction"
		;;
	1)
		echo "high" > "/sys/class/gpio/gpio${gpio}/direction"
		;;
	*)
		usage
	;;
	esac
}

gpio_get_value() {
	local gpio=${1}

	gpio_request "${gpio}"
	cat "/sys/class/gpio/gpio${gpio}/value"
}

gpc_it8786() {
	local gpio=$1
	local GPIO_TABLE=(
		10 11 12 13 14 15 16 17
		20 21 22 23 24 25 26 27
		30 31 32 33 34 35 36 37
		40 41 42 43 44 45 46 47
		50 51 52 53 54 55 56 57
		60 61 62 63 64 65 66 67
		70 71 72 73 74 75 76 77
		80 81 82 83 84 85 86 87
		90 91 92 93 94 95 96 97
                100 101 102 103 104 105
	)

	if ! $(is_module_loaded gpio_it87); then
		echo "gpio_it87 driver is not loaded."
		exit 1
	fi

	[[ ! ${GPIO_TABLE[*]} =~ $gpio ]] && \
			echo "Invalid gpio number." && exit 1

	for gpiochip in /sys/class/gpio/gpiochip*
	do
		if cat "${gpiochip}"/label | grep -q "gpio_it87"
		then
			base=$(cat "${gpiochip}"/base)
			break
		fi
	done

	group=$(($gpio / 10 - 1))
	bit=$(($gpio % 10))
	echo $((base + 8 * group + bit))
}

gpc_cp2112() {
	local gpio=$1
	local GPIO_TABLE=(0 1 2 3 4 5 6 7)

	[[ ! ${GPIO_TABLE[*]} =~ $gpio ]] && \
			echo "Invalid gpio number." && exit 1

	for gpiochip in /sys/class/gpio/gpiochip*
	do
		if cat "${gpiochip}"/label | grep -q "cp2112_gpio"
		then
			base=$(cat "${gpiochip}"/base)
			break
		fi
	done

	echo $((base + $gpio))
}

gpc_pca953x() {
	local gpio=$1
	local chip_addr="null"

	if ! $(is_module_loaded gpio_pca953x); then
		echo "gpio_pca953x driver is not loaded."
		exit 1
	fi

	for gpc in /sys/class/gpio/gpiochip*
	do
		if [ -f "${gpc}/device/name" ]; then
			if [[ $(cat $gpc/device/name) == "pca9535" ]]; then
				base=$(cat $gpc/base)
				break
			fi
		fi
	done

	echo $((base + $gpio))
}

declare -A GPIO_CHIP=(
	["gpio_it87"]=gpc_it8786
	["cp2112_gpio"]=gpc_cp2112
	["gpio_pca953x"]=gpc_pca953x
)

usage() {
cat << EOF
Usage:
       mx-module-ctl [Options]

Operations:
       -s, --slot <module_slot_id>
               Select module slot
       -p, --power [on|off]
               Get/Set power on/off module
       -r, --reset [on|off]
               Get/Set reset pin to high(on)/low(off) to slot
       -i, --sim 1|2
               Get/Set sim card slot

Example:
       Power on module 1
       # mx-module-ctl -s 1 -p on

       Set module 2 reset pin to high
       # mx-module-ctl -s 2 -r on

       Select SIM 2 for module 1
       # mx-module-ctl -s 1 -i 2

       Get power status of module 1
       # mx-module-ctl -s 1 -p

       Get current SIM slot of module 1
       # mx-module-ctl -s 1 -i
EOF
}

is_module_loaded() {
	local mod_name=$1
	if lsmod | grep -w $mod_name &> /dev/null; then
		return 0
	else
		return 1
	fi
}

is_number() {
        local input=${1}
        local num_regex='^[0-9]+$'

        if [ -z "${input}" ]; then
                return 1
        fi

        if ! [[ ${input} =~ ${num_regex} ]]; then
                return 1
        fi
        return 0
}

is_slot_valid() {
        local slot=${1}

        if ! is_number "${slot}"; then
                return 1
        fi

        if [ "${slot}" -le 0 ] || [ "${slot}" -gt "${NUM_OF_MODULE_SLOTS}" ]; then
                return 1
        fi
        return 0
}

not_support() {
        echo "Operation not supported"
}

do_reset() {
	if [[ ${#RESET_PIN_TBL[@]} -eq 0 ]]; then
		not_support
		return
	fi
}

do_power() {
	if [[ ${#POWER_CONTROL_TBL[@]} -eq 0 ]]; then
		not_support
		return
	fi

	local slot=$(($1 - 1))
	local state=$2
	local pwr_gpio=$($gpc ${POWER_CONTROL_TBL[$slot]})

	if [[ ${#DIP_SWITCH_SELECT_TBL[@]} -ne 0 ]]; then
		local dip_switch_gpio=$($gpc ${DIP_SWITCH_SELECT_TBL[$slot]})

		if [ -z $dip_switch_gpio ] || [ -z $pwr_gpio ]; then
        	        echo "Unknown Module Slot $slot"
        	        return 1
        	elif [ $(gpio_get_value $dip_switch_gpio) -eq $DIP_SWITCH_SELECT_WIFI ]; then
        	        not_support
        	        return
        	fi
	fi

	case $state in
		# Set
		on)
			echo "Power on slot #${1}"
			gpio_set_value $pwr_gpio 1
			;;
		off)
			echo "Power off slot #${1}"
			gpio_set_value $pwr_gpio 0
			;;
		# Get
		"")
			ret=$(gpio_get_value $pwr_gpio)
			case $ret in
				0)
					echo "Slot #${1} power status is off"
					;;
				1)
					echo "Slot #${1} power status is on"
					;;
				*)
					echo "unknown status"
					return 1
					;;
			esac
			;;
		*)
			echo "Unknown power state"
			return 1
			;;
	esac
}

do_sim() {
	if [[ ${#SIM_CARD_SELECT_TBL[@]} -eq 0 ]]; then
		not_support
		return
	fi

	local slot=$(($1 - 1))
	local sim_slot_num=$2
	local sim_slot_gpio=$($gpc ${SIM_CARD_SELECT_TBL[$slot]})

	if [ -z $sim_slot_gpio ]
        then
                echo "Unknown Module Slot '$1'"
                return 1
        fi

	case $sim_slot_num in
		# Set SIM slot A/B
		1)
			echo "${SET_SIM_CARD_A_MSG} on slot #${1}"
			gpio_set_value $sim_slot_gpio $SIM_CARD_A_VAL
			;;
		2)
			echo "${SET_SIM_CARD_B_MSG} on slot #${1}"
			gpio_set_value $sim_slot_gpio $SIM_CARD_B_VAL
			;;
		# Get SIM slot A/B
		"")
			ret=$(gpio_get_value $sim_slot_gpio)
			case $ret in
				$SIM_CARD_A_VAL)
					echo "${GET_SIM_CARD_A_MSG} on slot #${1}"
					;;
				$SIM_CARD_B_VAL)
					echo "${GET_SIM_CARD_B_MSG} on slot #${1}"
					;;
				*)
					echo "unknown"
					return 1
					;;
			esac
			;;
		*)
			echo "Unknown SIM ID '$1'"
                	return 1
			;;
	esac
}

do_action() {
	local action=$1
	local slot=$2
	local arg=$3

	if [ $NUM_OF_MODULE_SLOTS -eq 0 ]; then
                echo "No module slot on this device." >&2
                return 1
        fi

        if ! is_slot_valid $slot; then
                echo "Error: Invalid slot index: $slot" >&2
                return 1
        fi


	if [[ $action == "RESET" ]]; then
		do_reset $slot $arg
	elif [[ $action == "POWER" ]]; then
		do_power $slot $arg
	elif [[ $action == "SIM" ]]; then
		do_sim $slot $arg
	else
		echo "Error: Unknown action: ${action}" >&2
		return 1
	fi
}

# Bind CP2112 USB-to-I2C driver for PCA9535
_bind_cp2112_driver() {
	local addr=$1

	if ! $(is_module_loaded hid_cp2112); then
		echo "hid_cp2112 driver is not loaded."
		exit 1
	fi

	if ! $(is_module_loaded gpio_pca953x); then
		echo "gpio_pca953x driver is not loaded."
		exit 1
	fi

	for filename in /sys/bus/i2c/devices/i2c-*/name; do
		i2c_devname=$(cat ${filename})
		if [[ $i2c_devname == *"CP2112"* ]]; then
			i2c_devpath=$(echo ${filename%/*})
			i2c_num=$(echo ${i2c_devpath} | cut -d '-' -f2)
			if [[ ! -e "${i2c_devpath}/${i2c_num}-00${addr##0x}" ]]; then
				echo "pca9535 $addr" > ${i2c_devpath}/new_device
			fi
		fi
	done
}

init_product() {
	case $TARGET_PRODUCT in
		V3000)
			_bind_cp2112_driver 0x26
			_bind_cp2112_driver 0x27
			;;
	esac
}

while true
do
        case $1 in
                -s|--slot)
			slot=$2
                        shift 2
                        ;;
                -r|--reset)
			action="RESET"
			arg=$2
			shift 2
                        ;;
                -p|--power)
                        case $2 in
                                "") arg=""; shift ;;
                                *)  arg=$2; shift 2 ;;
                        esac
			action="POWER"
                        ;;
                -i|--sim)
			case $2 in
                                "") arg=""; shift ;;
                                *)  arg=$2; shift 2 ;;
                        esac
			action="SIM"
                        ;;
                "")
                        break
                        ;;
                *)
                        usage
			exit 1
                        ;;
        esac
done

load_product_config
init_product

if [ -z "${slot}" ] || [ -z "${action}" ]; then
	usage
	exit 1
fi

do_action $action $slot $arg
