#!/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 Audio Jack Re-tasking Utility
#
# Description:
#       Utility for re-tasking HDA audio jack mode.
#
# Authors:
# 	2022    Wilson Huang <WilsonYS.Huang@moxa.com>
#

TARGET_PRODUCT=""
SOUND_DEV_SYSPATH=""
DMIDECODE="/usr/sbin/dmidecode"
AUDIO_FW_FILE=/lib/firmware/mx-audio-retask.fw
AUDIO_PATCH_FILE=/etc/modprobe.d/mx-audio-retask.conf

_product_V2406C() {
	# pin id is based on hardware design.
	JACKS_PIN_ID_TBL=("0x14" "0x18")
}

_product_V2403C() {
	# pin id is based on hardware design.
	JACKS_PIN_ID_TBL=("0x14" "0x18")
}

_product_MC7400() {
	# pin id is based on hardware design.
	JACKS_PIN_ID_TBL=("0x14" "0x18")
}

declare -A PRODUCT_PROFILE=(
	["V2406C"]=_product_V2406C
	["V2403C"]=_product_V2403C
        ["MC7400"]=_product_MC7400
)

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
		NUM_OF_JACK_PORTS=${#JACKS_PIN_ID_TBL[@]}

		if ! load_sound_pin_mode_cfg; then
			echo "Error: sound chip is not support."
			exit 1
		fi
	else
		echo "Error: model profile not found."
		exit 1
	fi
}

load_sound_pin_mode_cfg() {
	local ret=1

	for path in /sys/class/sound/hwC0D*/chip_name; do
		chip_name=$(cat $path)
		if [[ ${SOUND_CHIP[$chip_name]} ]]; then
			${SOUND_CHIP[$chip_name]}
			SOUND_DEV_SYSPATH=${path%/*}
			ret=0
			break
		fi
	done

	return $ret
}

# Sound chip
sndc_alc262() {
	# Codec
	CODEC="0x10ec0888 0x00000000 0"

	# output modes
	LINE_OUT="0x01014010"
	SPEAKER="0x90170150"
	HEADPHONE="0x0321403f"

	# input modes
	LINE_IN="0x0181304f"
	MICROPHONE="0x03a19020"

	JACK_MODE=($LINE_OUT $SPEAKER $HEADPHONE $LINE_IN $MICROPHONE)
	JACK_MODE_STR=("Line out" "Speaker" "Headphone" "Line in" "Microphone")
}

sndc_alc888() {
	# Codec
	CODEC="0x10ec0888 0x00000000 0"

	# output modes
	LINE_OUT="0x01014010"
	SPEAKER="0x90170150"
	HEADPHONE="0x02211010"

	# input modes
	LINE_IN="0x02811020"
	MICROPHONE="0x03a19020"

	JACK_MODE=($LINE_OUT $SPEAKER $HEADPHONE $LINE_IN $MICROPHONE)
	JACK_MODE_STR=("Line out" "Speaker" "Headphone" "Line in" "Microphone")
}

declare -A SOUND_CHIP=(
	["ALC262"]=sndc_alc262
	["ALC888-VD"]=sndc_alc888
)

jack_mode_to_string() {
	local mode=$1

	for ((i = 0 ;i < ${#JACK_MODE[@]}; i++)); do
		if [[ ${JACK_MODE[$i]} == $mode ]]; then
			echo ${JACK_MODE_STR[$i]}
			return 0
		fi
	done

	echo "Unknown"
	return 1
}

usage() {
	cat << EOF
Usage:
	mx-audio-retask [Options]

Operations:
	-p,--port <port_index>
		Select Jack Index (e.g. 1, 2, ...)
	-m,--mode <jack_mode>"
		Select Jack Mode [0|1|2|3|4]

Jack Mode:
	0 --> Set to LINE_OUT mode
	1 --> Set to SPEAKER mode
	2 --> Set to HEADPHONE mode
	3 --> Set to LINE_IN mode
	4 --> Set to MICROPHONE mode

Example:
	Get Jack 1 mode
	# mx-audio-retask -p 1
	Set Jack 2 to HEADPHONE mode
	# mx-audio-retask -p 2 -m 2
EOF
}

generate_hda_fw_patch() {
	cat << EOF > $AUDIO_PATCH_FILE
# This file was added by the program 'mx-audio-retask'.
# If you want to revert the changes made by this program, you can simply erase this file and reboot your computer.
options snd-hda-intel patch=mx-audio-retask.fw
EOF
}

get_sound_pin_mode() {
	local port=$1
	local jack_mode=""
	local pin_index
	local pin_id

	pin_index=$((port - 1))
	pin_id=${JACKS_PIN_ID_TBL[$pin_index]}

	if [ -z "${pin_id}" ]; then
		echo "Error: Unknown jack pin id at port $port" >&2
		return 1
	fi

	# 1. AUDIO_FW_FILE
	if [[ -f $AUDIO_FW_FILE ]]; then
		jack_mode=$(grep -w "^$pin_id" $AUDIO_FW_FILE | awk '{print $2}')

		if [[ -n $jack_mode ]]; then
			echo "Jack $port is in $(jack_mode_to_string $jack_mode) mode"
			return 0
		fi
	fi

	# # 2. user_pin_configs
	jack_mode=$(cat $SOUND_DEV_SYSPATH/user_pin_configs | grep -w "^$pin_id" | awk '{print $2}')

	if [[ -n $jack_mode ]]; then
		echo "Jack $port is in $(jack_mode_to_string $jack_mode) mode"
		return 0
	fi
	# 3. init_pin_configs
	jack_mode=$(cat $SOUND_DEV_SYSPATH/init_pin_configs | grep -w "^$pin_id" | awk '{print $2}')

	if [[ -n $jack_mode ]]; then
		echo "Jack $port is in $(jack_mode_to_string $jack_mode) mode"
		return 0
	fi
}

generate_hda_fw_file() {
	cat << EOF > $AUDIO_FW_FILE
[codec]
$CODEC

[pincfg]
EOF
}

set_sound_pin_mode() {
	local port=$1
	local mode=$2
	local pin_id
	local pin_index
	local pin_cfg

	if [ -z "${CODEC}" ]; then
		echo "Error: Unknown codec value" >&2
		return 1
	fi

	if [[ ! -f $AUDIO_PATCH_FILE ]]; then
		generate_hda_fw_patch
	fi

	if [[ ! -f $AUDIO_FW_FILE ]]; then
		generate_hda_fw_file
	fi

	pin_index=$((port - 1))
	pin_id=${JACKS_PIN_ID_TBL[$pin_index]}

	if [ -z "${pin_id}" ]; then
		echo "Error: Unknown jack pin id at port $port" >&2
		return 1
	fi

	if [[ $mode -lt 0 || $mode -ge ${#JACK_MODE[@]} ]]; then
		echo "Error: Unknown mode" >&2
		return 1
	fi

	pin_cfg=${JACK_MODE[$mode]}

	if ! grep -wq "$pin_id" $AUDIO_FW_FILE; then
		echo "$pin_id $pin_cfg" >> $AUDIO_FW_FILE;
	else
		sed -i "/$pin_id /c\\$pin_id $pin_cfg" $AUDIO_FW_FILE
	fi

	echo "Set jack $port to $(jack_mode_to_string $pin_cfg) mode"
	echo "If jack mode does not take effect, please reboot the system."
}

load_product_config

if [[ $# -lt 1 ]]; then
	usage
fi

while [[ "$#" -gt 0 ]]; do
	case $1 in
		-p)
			port="$2"
			TARGET_OPT="get"
			shift
			;;
		-m)
			mode="$2"
			TARGET_OPT="set"
			shift
			;;
		-h)
			usage
			exit 0
			;;
		*)
			echo "Unknown parameter passed"
			exit 1
			;;
		esac
		shift
done

[[ $port -lt 1 || $port -gt $NUM_OF_JACK_PORTS ]] && \
	echo "Invalid port." >&2 && exit 1

[[ $NUM_OF_JACK_PORTS -eq 0 ]] && \
	echo "No audio jacks on this device." >&2 && exit 1

case $TARGET_OPT in
	get)
		get_sound_pin_mode $port
		;;
	set)
		set_sound_pin_mode $port $mode
		;;
	*)
		exit 1
		;;
esac
