#!/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.
#
# Authors:
#	2022	Elvis Yao <ElvisCW.Yao@moxa.com>
# Description:
#	For controlling gpio-sysfs value for Digital IO
#

TARGET_PRODUCT=""
DIO_MODE_STR=("LOW" "HIGH")
DMIDECODE="/usr/sbin/dmidecode"
LIBGPIOD_TOOLS_PATH="/usr/local/bin"

## Product Profile
#
# _product_<model name>() {
# }
#

_product_DA682C() {
	GPIO_MODULE="gpio_it87"
	DIN_GPIO_TBL=("82" "83" "84" "85" "36" "37")
	DOUT_GPIO_TBL=("33" "34")
	DIO_MODE_TBL=("0" "1")
}

_product_DA681C() {
	GPIO_MODULE="gpio_it87"
	DIN_GPIO_TBL=("82" "83" "84" "85" "36" "37")
	DOUT_GPIO_TBL=("33" "34")
	DIO_MODE_TBL=("0" "1")
}

_product_DA820C() {
	GPIO_MODULE="gpio_it87"
	DIN_GPIO_TBL=("82" "83" "84" "85" "36" "37")
	DOUT_GPIO_TBL=("33" "34")
	DIO_MODE_TBL=("0" "1")
}

_product_V3000() {
	GPIO_MODULE="gpio_it87"
	DIN_GPIO_TBL=("36" "37")
	DOUT_GPIO_TBL=("33" "34")
	DIO_MODE_TBL=("0" "1")
}

_product_V2403C() {
	GPIO_MODULE="gpio_it87"
	DIN_GPIO_TBL=("70" "71" "72" "73")
	DOUT_GPIO_TBL=("74" "75" "76" "77")
	DIO_MODE_TBL=("0" "1")
}

_product_V2201() {
	GPIO_MODULE="gpio_it87"
	DIN_GPIO_TBL=("64" "65" "66" "67")
	DOUT_GPIO_TBL=("60" "61" "62" "63")
	DIO_MODE_TBL=("0" "1")
}

_product_V2406C() {
	GPIO_MODULE="gpio_it87"
	DIN_GPIO_TBL=("70" "71" "72" "73" "74" "75")
	DOUT_GPIO_TBL=("76" "77")
	DIO_MODE_TBL=("0" "1")
}

_product_DA680() {
	GPIO_MODULE="gpio_it87"
	DIN_GPIO_TBL=()
	DOUT_GPIO_TBL=("33" "34")
	DIO_MODE_TBL=("0" "1")
}

_product_BXPC100() {
	GPIO_MODULE="gpio_it87"
	DIN_GPIO_TBL=("70" "71" "72" "73")
	DOUT_GPIO_TBL=("74" "75" "76" "77")
	DIO_MODE_TBL=("0" "1")
}

_product_RKPA110() {
	GPIO_MODULE="gpio_it87"
	DIN_GPIO_TBL=("70" "71" "72" "73" "80" "81" "82" "83")
	DOUT_GPIO_TBL=("74" "75" "76" "77" "84" "85" "86" "87")
	DIO_MODE_TBL=("0" "1")
}

_product_BXPA100() {
	GPIO_MODULE="gpio_it87"
	DIN_GPIO_TBL=("70" "71" "72" "73")
	DOUT_GPIO_TBL=("74" "75" "76" "77")
	DIO_MODE_TBL=("0" "1")
}

_product_RKPC110() {
	GPIO_MODULE="gpio_it87"
	DIN_GPIO_TBL=("70" "71" "72" "73" "80" "81" "82" "83")
	DOUT_GPIO_TBL=("74" "75" "76" "77" "84" "85" "86" "87")
	DIO_MODE_TBL=("0" "1")
}

_product_MPC3000() {
	GPIO_MODULE="gpio_it87"
	DIN_GPIO_TBL=("70" "71" "72" "73")
	DOUT_GPIO_TBL=("74" "75" "76" "77")
	DIO_MODE_TBL=("0" "1")
}

declare -A PRODUCT_PROFILE=(
	["DA682C"]=_product_DA682C
	["DA681C"]=_product_DA681C
	["DA820C"]=_product_DA820C
	["V3000"]=_product_V3000
	["V2403C"]=_product_V2403C
	["V2201"]=_product_V2201
	["V2406C"]=_product_V2406C
	["DA680"]=_product_DA680
	["BXPC100"]=_product_BXPC100
	["RKPA110"]=_product_RKPA110
	["BXPA100"]=_product_BXPA100
	["RKPC110"]=_product_RKPC110
	["MPC3000"]=_product_MPC3000
)

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
}

## gpio
gpiochip_it8786_pin_remap() {
	local orig_pin
	local new_pin
	orig_pin=$1

	group=$(($orig_pin / 10 - 1))
	bit=$(($orig_pin % 10))
	new_pin=$((8 * group + bit))

	echo $new_pin
}

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
	)

	[[ ! ${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

	echo $(( base + $(gpiochip_it8786_pin_remap $gpio) ))
}

declare -A GPIO_CHIP=(
	["gpio_it87"]=gpc_it8786
)

load_product_name_from_file() {
	echo "TBD"
}

load_product_config() {
	if load_product_name_from_dmi; then
		# Determine gpio chip profile
		gpc=${GPIO_CHIP[$GPIO_MODULE]}
		# Determine number of serial and mode
		NUM_OF_DIN=$(( ${#DIN_GPIO_TBL[@]} ))
		NUM_OF_DOUT=$(( ${#DOUT_GPIO_TBL[@]} ))
		NUM_OF_DIO_MODE=$(( ${#DIO_MODE_TBL[@]} ))
	else
		echo "Error: model profile not found."
		exit 1
	fi
}

gpio_request_sysfs() {
	local gpio=${1}

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

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

	gpio_request_sysfs "${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_sysfs() {
	local gpio=${1}

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

get_gpiochip_name() {
	local driver
	driver=${1}

	$LIBGPIOD_TOOLS_PATH/gpiodetect | grep "${driver}" | awk -F " " '{print $1}'
}

gpio_set_value_libgpiod() {
	local gpio=${1}
	local value=${2}
	local gpc_name=${3}

	$LIBGPIOD_TOOLS_PATH/gpioset -p 20ms -t 0 -c $gpc_name "${gpio}=${value}"
}

gpio_get_value_libgpiod() {
	local gpio=${1}
	local gpc_name=${2}

	$LIBGPIOD_TOOLS_PATH/gpioget --numeric -c $gpc_name $gpio
}

_check_libgpiod_tools() {
	if [ ! -e "$LIBGPIOD_TOOLS_PATH/gpiodetect" ]
	then
		echo "Error: $LIBGPIOD_TOOLS_PATH/gpiodetect not found."
		exit 1
	fi
	if [ ! -e "$LIBGPIOD_TOOLS_PATH/gpioset" ]
	then
		echo "Error: $LIBGPIOD_TOOLS_PATH/gpioset not found."
		exit 1
	fi
	if [ ! -e "$LIBGPIOD_TOOLS_PATH/gpioget" ]
	then
		echo "Error: $LIBGPIOD_TOOLS_PATH/gpioget not found."
		exit 1
	fi
}

## DIO
_set_dout_mode_sysfs() {
	local do_num=$1
	local mode_num=$2

	gpio_set_value_sysfs $($gpc ${DOUT_GPIO_TBL[$do_num]}) $mode_num

	echo "Set OK."
}

_set_dout_mode_libgpiod() {
	local do_num=$1
	local mode_num=$2
	local gpc_name

	_check_libgpiod_tools

	gpc_name=$(get_gpiochip_name gpio_it87)

	gpio_set_value_libgpiod $(gpiochip_it8786_pin_remap ${DOUT_GPIO_TBL[$do_num]}) $mode_num $gpc_name
}

set_dout_mode() {
	local do_num=$1
	local mode_num=$2

	case $TARGET_PRODUCT in
		BXPC100 | RKPA110 | BXPA100 | RKPC110 | MPC3000)
			_set_dout_mode_libgpiod $do_num $mode_num
			;;
		*)
			_set_dout_mode_sysfs $do_num $mode_num
			;;
	esac
}

_get_dout_mode_sysfs() {
	local do_num=$1
	local mode_num=""

	mode_num=$(gpio_get_value_sysfs $($gpc ${DOUT_GPIO_TBL[$do_num]}))

	if [[ ${DIO_MODE_TBL[0]} -eq $mode_num ]]; then
		echo "Current DO status is ${DIO_MODE_STR[0]} status."
		return 0
	elif [[ ${DIO_MODE_TBL[1]} -eq $mode_num ]]; then
		echo "Current DO status is ${DIO_MODE_STR[1]} status."
		return 0
	fi

	echo "Unknown DOUT mode"
	return 1
}

_get_dout_mode_libgpiod() {
	local do_num=$1
	local mode_num=""
	local gpc_name

	_check_libgpiod_tools

	gpc_name=$(get_gpiochip_name gpio_it87)

	mode_num=$(gpio_get_value_libgpiod $(gpiochip_it8786_pin_remap ${DOUT_GPIO_TBL[$do_num]}) $gpc_name)

	if [[ ${DIO_MODE_TBL[0]} -eq $mode_num ]]; then
		echo "Current DO status is ${DIO_MODE_STR[0]} status."
		return 0
	elif [[ ${DIO_MODE_TBL[1]} -eq $mode_num ]]; then
		echo "Current DO status is ${DIO_MODE_STR[1]} status."
		return 0
	fi

	echo "Unknown DOUT mode"
	return 1
}

get_dout_mode() {
	local do_num=$1

	case $TARGET_PRODUCT in
		BXPC100 | RKPA110 | BXPA100 | RKPC110 | MPC3000)
			_get_dout_mode_libgpiod $do_num
			;;
		*)
			_get_dout_mode_sysfs $do_num
			;;
	esac
}

_get_din_mode_sysfs() {
	local di_num=$1
	local mode_num=""

	mode_num=$(gpio_get_value_sysfs $($gpc ${DIN_GPIO_TBL[$di_num]}))

	if [[ ${DIO_MODE_TBL[0]} -eq $mode_num ]]; then
		echo "Current DI status is ${DIO_MODE_STR[0]} status."
		return 0
	elif [[ ${DIO_MODE_TBL[1]} -eq $mode_num ]]; then
		echo "Current DI status is ${DIO_MODE_STR[1]} status."
		return 0
	fi

	echo "Unknown DIN mode"
	return 1
}

_get_din_mode_libgpiod() {
	local di_num=$1
	local mode_num=""
	local gpc_name

	_check_libgpiod_tools

	gpc_name=$(get_gpiochip_name gpio_it87)

	mode_num=$(gpio_get_value_libgpiod $(gpiochip_it8786_pin_remap ${DIN_GPIO_TBL[$di_num]}) $gpc_name)

	if [[ ${DIO_MODE_TBL[0]} -eq $mode_num ]]; then
		echo "Current DI status is ${DIO_MODE_STR[0]} status."
		return 0
	elif [[ ${DIO_MODE_TBL[1]} -eq $mode_num ]]; then
		echo "Current DI status is ${DIO_MODE_STR[1]} status."
		return 0
	fi

	echo "Unknown DIN mode"
	return 1
}

get_din_mode() {
	local di_num=$1

	case $TARGET_PRODUCT in
		BXPC100 | RKPA110 | BXPA100 | RKPC110 | MPC3000)
			_get_din_mode_libgpiod $di_num
			;;
		*)
			_get_din_mode_sysfs $di_num
			;;
	esac
}


usage() {
cat << EOF
Usage:
	mx-dio-ctl <-i|-o <#port number> [-s <#state>]>

OPTIONS:
	-i <#DIN port number>
	-o <#DOUT port number>
	-s <#state>
		Set state for target DOUT port
		0 --> LOW
		1 --> HIGH

Example:
	Get value from DIN port 0
	# mx-dio-ctl -i 0
	Get value from DOUT port 0
	# mx-dio-ctl -o 0

	Set DOUT port 0 value to LOW
	# mx-dio-ctl -o 0 -s 0
	Set DOUT port 0 value to HIGH
	# mx-dio-ctl -o 0 -s 1
EOF
}

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

load_product_config

# Module check
[[ $(is_module_loaded $GPIO_MODULE) -ne 0 ]] && \
	echo "$GPIO_MODULE is not loaded." && exit 1

# Parameter check
if [[ $# -lt 2 ]]; then
	usage
	exit 1
fi

while [[ "$#" -gt 0 ]]; do
	case $1 in
		-i)
			TARGET_DIN_PORT="$2"
			TARGET_OPT="get_din"
			break
			;;
		-o)
			TARGET_DOUT_PORT="$2"
			TARGET_OPT="get_dout"
			shift
			;;
		-s)
			[[ -z $TARGET_DOUT_PORT ]] && echo "Invalid DOUT port." && exit 1
			TARGET_DOUT_MODE="$2"
			TARGET_OPT="set_dout"
			shift
			;;
		*)
			echo "Unknown parameter passed"
			usage
			exit 1
			;;
		esac
		shift
done

case $TARGET_OPT in
	get_din)
		[[ $TARGET_DIN_PORT -lt 0 || $TARGET_DIN_PORT -ge $NUM_OF_DIN ]] && \
			echo "Invalid DIN port." && exit 1

		get_din_mode $TARGET_DIN_PORT
		;;
	get_dout)
		[[ $TARGET_DOUT_PORT -lt 0 || $TARGET_DOUT_PORT -ge $NUM_OF_DOUT ]] && \
			echo "Invalid DOUT port." && exit 1

		get_dout_mode $TARGET_DOUT_PORT
		;;
	set_dout)
		[[ -z $TARGET_DOUT_PORT || -z $TARGET_DOUT_MODE ]] && \
			echo "Invalid DOUT port or DOUT mode." && exit 1

		[[ $TARGET_DOUT_PORT -lt 0 || $TARGET_DOUT_PORT -ge $NUM_OF_DOUT ]] && \
			echo "Invalid DOUT port." && exit 1

		[[ $TARGET_DOUT_MODE -lt 0 || $TARGET_DOUT_MODE -ge $NUM_OF_DIO_MODE ]] && \
			echo "Invalid DOUT mode." && exit 1

		set_dout_mode $TARGET_DOUT_PORT $TARGET_DOUT_MODE
		get_dout_mode $TARGET_DOUT_PORT
		;;
	*)
		exit 1
		;;
esac
