#! /usr/bin/perl -w
use strict;

# Make warnings fatal
local $SIG{__WARN__} = sub { die @_ };

#
# $Id$
#

#
# Written by Oron Peled <oron@actcom.co.il>
# Copyright (C) 2006, Xorcom
#
# All rights reserved.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# See the file LICENSE in the top level of this tarball.
#

# This script is run from the xpp kernel module upon detection
# of a new XPD.
#
# Expects the following environment variables to be set:
#	XBUS_NAME	- bus name
#	UNIT_NUMBER	- xpd unit number
#	UNIT_SUBUNITS	- number of subunits in this xpd
#	UNIT_TYPE	- xpd type number (from protocol reply):
#			1 - FXS
#			2 - FXO
#			3 - BRI
#			4 - PRI
#	XBUS_REVISION	- xbus revision number
#	XBUS_CONNECTOR	- xbus connector string
#
# Output data format:
#	- An optional comment start with ';' or '#' until the end of line
#	- Optional Blank lines are ignored
#	- Fields are whitespace separated (spaces or tabs)
#
# The fields are (in command line order):
#	1. CHIP select in decimal (ignored, taken from 3 LSB's of subunit number)
#	2. Command word:
#		- RD	Read Direct register.
#		- RS	Read Sub-register.
#		- WD	Write Direct register.
#		- WS	Write Sub-register.
#	3. Register number in hexadecimal.
#	4. Subregister number in hexadecimal. (for RS and WS commands).
#	5. Data byte in hexadecimal. (for WD and WS commands only).
#

package main;
use File::Basename;
use Getopt::Std;

my $program = basename("$0");
my $init_dir = dirname("$0");
BEGIN { $init_dir = dirname($0); unshift(@INC, "$init_dir"); }
use XppConfig $init_dir;
my $unit_id;
my %opts;

getopts('o:', \%opts);

my %settings;

sub logit {
	print STDERR "$unit_id: @_\n";
}

sub debug {
	logit @_ if $settings{debug};
}

# Arrange for error logging
if (-t STDERR) {
	$unit_id = 'Interactive';
	debug "Interactive startup";
} else {
	$unit_id = "$ENV{XBUS_NAME}/UNIT-$ENV{UNIT_NUMBER}";
	open (STDERR, "| logger -t $program -p kern.info") || die;
	debug "Non Interactive startup";
	foreach my $k (qw(
			XBUS_NAME
			XBUS_NUMBER
			UNIT_NUMBER
			UNIT_TYPE
			UNIT_SUBUNITS
			UNIT_SUBUNITS_DIR
			XBUS_REVISION
			XBUS_CONNECTOR
			XBUS_LABEL)) {
		unless(defined $ENV{$k}) {
			logit "Missing ENV{$k}\n";
			die;
		}
	}
}

sub select_subunit($) {
	my $subunit = shift;
	die unless defined $subunit;
	my $output;

	if($opts{o}) {
		$output = $opts{o};
	} else {
		$output = sprintf "/sys/bus/xpds/devices/%02d:%1d:%1d/chipregs",
				$ENV{XBUS_NUMBER}, $ENV{UNIT_NUMBER}, $subunit;
		if(! -f $output) {
			my $xpd_name = sprintf("XPD-%1d%1d", $ENV{UNIT_NUMBER}, $subunit);
			$output = "/proc/xpp/$ENV{XBUS_NAME}/$xpd_name/chipregs";
			logit "OLD DRIVER: does not use /sys chipregs. Falling back to /proc"
				if -f $output;
		}
	}
	open(REG, ">$output") || die "Failed to open '$output': $!\n";
	my $oldfh = select REG;
	print "# Selecting subunit $subunit\n" if $opts{o};
	return $oldfh;
}

package BRI;

sub gen {
	my $fmt = shift;
	$| = 1;
	printf "$fmt\n", @_;
}

# Turning on/off multi-byte packet reception.
sub multibyte($) {
	my $active = (shift) ? 'M' : 'm';
	for my $subunit (0 .. $ENV{UNIT_SUBUNITS} - 1) {
		#main::logit "multibyte(): $subunit -> $active";
		main::select_subunit($subunit);

		BRI::gen "$subunit W$active";
	}
}

sub read_defaults() {
	if(XppConfig::read_config(\%settings)) {
		main::logit "Defaults from $settings{xppconf}";
	} else {
		main::logit "No defaults file, use hard-coded defaults.";
	}
}

package BRI::Port;

sub new {
	my $pack = shift;
	my $port = { @_ };
	bless $port, $pack;
}

# zap_xhfc_su.c:995
sub init_su {
	my $port = shift;
	my $portnum = $port->{PORT_NUM};
	my $port_mode_up = $port->{PORT_MODE_UP};
	my $port_mode_exch = $port->{PORT_MODE_EXCH};
	my $bri_nt = $port->{BRI_NT};
	#main::logit "init_su(portnum=$portnum, port_mode_up=$port_mode_up, bri_nt=$bri_nt)";
	
	# Setting PLL
	# R_PLL_CTRL = 0      (V_PLL_M = 0, Reset PLL, Disable PLL_
	# R_CLK_CFG  = 05     (PLL clock as system clock, output it to CLK_OUT pin)
	# R_PLL_P    = 1     
	# R_PLL_N    = 6
	# R_PLL_S    = 1
	# R_PLL_CTRL = 1 (V_PLL_M)
	
	BRI::gen "#--------------------------- init_su($portnum, $bri_nt, $port_mode_up, $port_mode_exch)";
	BRI::gen "$portnum	WD	02	04";
	BRI::gen "$portnum	WD	50	00"; # disable PLL
	BRI::gen "$portnum	WD	51	02";
	BRI::gen "$portnum	WD	52	06";
	BRI::gen "$portnum	WD	53  	04";
	BRI::gen "$portnum	WD	50	01"; # Enable PLL
	BRI::gen "$portnum	WD	02	05"; # Enable PLL
	
	su_sel($portnum);	# select port
	if ("$port_mode_up" == 1) {
		$port->{CTRL3} = 0x01;		# A_ST_CTRL3: V_ST_SEL = 1
		$port->{CTRL0} = 0x10;		# A_SU_CTRL0: V_ST_SQ_EN = 1
		BRI::gen "$portnum	WD	34	0F";	# A_MS_TX:
				# (multiframe/superframe transmit register)
	} else {
		$port->{CTRL3} = 0x00;	# A_ST_CTRL3: V_ST_SEL = 0
		$port->{CTRL0} = 0x00;	# A_SU_CTRL0: V_ST_SQ_EN = 0
	}
	if ("$bri_nt" == 1) {
		$port->{CTRL0} |= 0x04;	# V_SU_MD
	}
	# ((V_SU_EXCH)?0x80:00) (change polarity)
	if($port_mode_exch) {
		$port->{CTRL2} = 0x80;
	} else {
		$port->{CTRL2} = 0x00;
	}
	BRI::gen "$portnum	WD	35	%02X", $port->{CTRL3};	# A_ST_CTRL3
	BRI::gen "$portnum	WD	31	%02X", $port->{CTRL0};	# A_SU_CTRL0
	BRI::gen "$portnum	WD	35	F8";			# A_ST_CTRL3 = set end of pulse control to 0xF8
	BRI::gen "$portnum	WD	32	09";			# A_SU_CTRL1 = Ignore E-channel data, Force automatic transition from G2 to G3
	BRI::gen "$portnum	WD	33	%02X", $port->{CTRL2};	# A_SU_CTRL2

	# zap_xhfc_su.c:1030 in init_su()
	# A_SU_CLK_DLY
	my $clk_dly;
	if ("$bri_nt" == 1) {
		$clk_dly = 0x6C;
	} else {
		$clk_dly = 0x0E;
	}
	#main::logit "clk_dly=$clk_dly";
	BRI::gen "$portnum	WD	37	%02X", "$clk_dly";
}

sub su_sel {
	if (@_ != 1 ) {
		main::logit "ERROR: su_sel() called with " . scalar(@_) . " parameters";
		exit 1;
	}
	my $portnum = shift;
	BRI::gen "$portnum	WD	16	%02X", $portnum;	# R_SU_SEL
}

# zap_xhfc_su.c:281
sub xhfc_selfifo {
	my $port = shift;
	my $portnum = $port->{PORT_NUM};

	if (@_ != 1 ) {
		main::logit "ERROR: xhfc_selfifo() called with " . scalar(@_) . " parameters";
		exit 1;
	}
	my $fifonum = shift;
	#main::logit "xhfc_selfifo($fifonum)";
	BRI::gen "$portnum	WD	0F	%02X", $fifonum;
	#	--> WAIT UNTIL (R_STATUS & M_BUSY) == 0
}

# zap_xhfc_su.c:295
sub xhfc_resetfifo() {
	my $port = shift;
	my $portnum = $port->{PORT_NUM};

	#main::logit "xhfc_resetfifo()";
	# A_INC_RES_FIFO = M_RES_FIFO | M_RES_FIFO_ERR
	BRI::gen "$portnum	WD	0E	0A";
	#	--> WAIT UNTIL (R_STATUS & M_BUSY) == 0
}

# zap_xhfc_su.c:1040
# Initialize fifo (called for each portnum, channel, direction)
sub setup_fifo {
	my $port = shift;
	my $chan = shift;
	my $direction = shift;
	my $conhdlc = shift;
	my $subcfg = shift;
	my $fifoctrl = shift;
	my $portnum = $port->{PORT_NUM};
	my $port_mode_up = $port->{PORT_MODE_UP};
	my $port_mode_exch = $port->{PORT_MODE_EXCH};
	my $bri_nt = $port->{BRI_NT};

	BRI::gen "#--------------------------- setup_fifo($portnum, $chan, $direction)";
	# my $fifonum = 0x80 | ($portnum << 3) | ($chan << 1) | ($direction); # # MSB first
	my $fifonum = ($portnum << 3) | ($chan << 1) | ($direction); # # MSB first
	my $r_slot = ($portnum << 3) | ($chan << 1) | ($direction);

        # channel order workaround, swap odd and even portnums in $r_slot for PCM (chan 0, 1) only
        if ("$chan" == 0 || "$chan" == 1) {
                $r_slot = $r_slot ^ 0x08;
        }

	my $short_portnum = $portnum & 0x03;
	my $a_sl_cfg = (0x80 | ($short_portnum << 3) | ($chan << 1) | ($direction)); # receive data from STIO2, transmit to STIO1
	
	#main::logit "setup_fifo($fifonum)";
	$port->xhfc_selfifo($fifonum);
	# A_CON_HDLC: transparent mode selection
	BRI::gen "$portnum	WD	FA	%02X", $conhdlc;
	# A_SUBCH_CFG: subchnl params
	BRI::gen "$portnum	WD	FB	%02X", $subcfg;
	# A_FIFO_CTRL: FIFO Control Register
	BRI::gen "$portnum	WD	FF	%02X", $fifoctrl;
	$port->xhfc_resetfifo();
	$port->xhfc_selfifo($fifonum);  # wait for busy is builtin in this command
	BRI::gen "$portnum	WD	10	%02X", $r_slot;	# R_SLOT 		
	BRI::gen "$portnum	WD	D0	%02X", $a_sl_cfg;	# A_SL_CFG		

	#system("/bin/echo \"----=====TE\" \"short_portnum=\"$short_portnum \"portnum=\" $portnum \"chan=\" $chan\"======----\n\" >>/root/xortel/test_init");
}

# zap_xhfc_su.c:1071
sub setup_su {
	my $port = shift;
	my $bchan = shift;
	my $portnum = $port->{PORT_NUM};
	my $port_mode_exch = $port->{PORT_MODE_EXCH};
	my $bri_nt = $port->{BRI_NT};

	BRI::gen "#--------------------------- setup_su($portnum, $bchan)";
	#main::logit "setup_su(portnum=$portnum, bchan=$bchan, port_mode_exch=$port_mode_exch, bri_nt=$bri_nt)";
	$port->{CTRL0} |= (1 << $bchan) | $bri_nt;
	$port->{CTRL2} |= ($port_mode_exch << 7) | (1 << $bchan);
	su_sel($portnum);	# Select port
	BRI::gen "$portnum	WD	31	%02X", $port->{CTRL0};		# A_SU_CTRL0: V_B1_TX_EN | V_SU_MD | (NT/TE)
	BRI::gen "$portnum	WD	33	%02X", $port->{CTRL2};		# A_SU_CTRL2: V_B1_RX_EN
}

sub xhfc_ph_command {
	my $port = shift;
	my $cmd = shift;
	my $portnum = $port->{PORT_NUM};
	#main::logit "xhfc_ph_command(portnum=$portnum)";
	if ("$cmd" eq "HFC_L1_ACTIVATE_TE") {
		su_sel($portnum);	# Select port
		BRI::gen "$portnum	WD	30	60";		# A_SU_WR_STA = (M_SU_ACT & 0x03)
									# (set activation)
	} elsif ("$cmd" eq "HFC_L1_FORCE_DEACTIVATE_TE") {
		su_sel($portnum);	# Select port
		BRI::gen "$portnum	WD	30	40";		# A_SU_WR_STA = (M_SU_ACT & 0x02)
									# (set deactivation)
	} elsif ("$cmd" eq "HFC_L1_ACTIVATE_NT") {
		su_sel($portnum);	# Select port
		BRI::gen "$portnum	WD	30	E0";		# A_SU_WR_STA = (M_SU_ACT & 0x03) | 0x80
									# (set activation + NT)
	} elsif ("$cmd" eq "HFC_L1_DEACTIVATE_NT") {
		su_sel($portnum);	# Select port
		BRI::gen "$portnum	WD	30	40";		# A_SU_WR_STA = (M_SU_ACT & 0x02)
									# (set deactivation)
	}
}


sub zthfc_startup {
	my $port = shift;
	my $portnum = $port->{PORT_NUM};
	my $port_mode_exch = $port->{PORT_MODE_EXCH};
	my $bri_nt = $port->{BRI_NT};
	#main::logit "zthfc_startup(portnum=$portnum, port_mode_exch=$port_mode_exch, bri_nt=$bri_nt)";

	# PCM <-> ST/Up Configuration
	foreach my $chan ( 0, 1 ) {
		$port->setup_fifo($chan, 0, 0xFE, 0, 0);# Transparent mode, FIFO EN, ST->PCM 
		$port->setup_fifo($chan, 1, 0xFE, 0, 0);# Transparent mode, FIFO EN, ST->PCM 
		$port->setup_su($chan);			# zap_xhfc_su.c:194
	}

	# Dahdi chan 2 used as HDLC D-Channel
	$port->setup_fifo(2, 0, 0x05, 2, 0);		# D-TX: zap_xhfc_su.c:205
	$port->setup_fifo(2, 1, 0x05, 2, 0);		# D-RX: zap_xhfc_su.c:206
	# E-chan, Echo channel is ignored
	
	
	# enable this port's state machine
	su_sel($portnum);	# Select port
	# A_SU_WR_STA: reset port state machine
	BRI::gen	"$portnum	WD	30	00";
	if ("$bri_nt" == 0) {
		$port->xhfc_ph_command("HFC_L1_ACTIVATE_TE");
	} else {
		$port->xhfc_ph_command("HFC_L1_ACTIVATE_NT");
	}
}

package main;

debug "Starting '$0'";

BRI::read_defaults;

#------------------------------------------- Instance detection

# zap_xhfc_su.c:895
sub init_xhfc($) {
	my $portnum = shift;
	main::debug "init_xhfc($portnum)";
	BRI::gen "#--------------------------- init_xhfc";
	BRI::gen "$portnum	WD	0D	00";	# r_FIFO_MD: 16 fifos,
							# 64 bytes for TX and RX each (FIFO mode config)

	# software reset to enable R_FIFO_MD setting
	BRI::gen "$portnum	WD	00	08";	# R_CIRM = M_SRES (soft reset)
	#	--> WAIT 5u
	BRI::gen "$portnum	WD	00	00";	# R_CIRM = 0 (zero it to deactivate reset)

	# amplitude
	BRI::gen "$portnum	WD	46	80";	# R_PWM_MD: (PWM output mode register)
							#           PWM push to zero only
	BRI::gen "$portnum	WD	39	18";	# R_PWM1: (modulator register for PWM1)
							#          set duty cycle

	BRI::gen "$portnum	WD	0C	11";	# R_FIFO_THRES: (FIFO fill lvl control register)
							#               RX/TX threshold = 16 bytes

	# set PCM bus mode to slave by default
	BRI::gen "$portnum	WD	14	08";	# R_PCM_MD0 = PCM slave mode, F0IO duration is 2 HFC_PCLK's
							# (C4IO, F0IO are inputs)

	BRI::gen "$portnum	WD	14	98";	# R_PCM_MD0: Index value to select
							#            the register at address 15
	BRI::gen "$portnum	WD	15	20";	# R_PCM_MD1: V_PLL_ADJ (DPLL adjust speed), 
							#            in the last slot of PCM frame
							#            V_PCM_DR, C4IO  is 16.384MHz(128 time slots) 

	BRI::gen "$portnum	WD	4C	07";	# GPIOGPIO function (not PWM) on GPIO0, GPIO1 and GPIO2 pins
	BRI::gen "$portnum	WD	4A	07";	# Output enable for GPIO0, GPIO1 and GPIO2 pins
	BRI::gen "$portnum	WD	48	01";	# GPIO output data bits

}

my @port_type = (
		{ 'BRI_NT' => 1 },
		{ 'BRI_NT' => 0 }
	);

# zap_xhfc_su.c:175
sub main() {
	my $subunit;
	my $subunits_mask = pack("C", $ENV{UNIT_SUBUNITS_DIR});
	my @direction = split(//, unpack("b*", $subunits_mask));

	#logit "main(): UNIT_TYPE=$ENV{UNIT_TYPE} UNIT_SUBUNITS_DIR=[@direction]";
	if(!$opts{o}) {
		foreach my $var (qw(XBUS_NAME UNIT_NUMBER UNIT_TYPE UNIT_SUBUNITS UNIT_SUBUNITS_DIR XBUS_REVISION XBUS_CONNECTOR)) {
			die "Missing mandatory '$var' environment variable" unless defined $var;
		}
	}
	# Turn off multi-byte packet reception before initialization started 
	# Otherwise we mess with registers while the FPGA firmware tries to
	# send us packets.
	BRI::multibyte(0);

	# Port initialization
	for($subunit = 0; $subunit < $ENV{UNIT_SUBUNITS}; $subunit++) {
		my $is_nt = $direction[$subunit];

		main::select_subunit($subunit);
		if(($subunit % 4) == 0) {	# A new xhfc chip
			#logit "main(): Initializing chip";
			init_xhfc($subunit);	# zap_xhfc_su.c:1173 in setup_instance()
		}
		#logit "main(): Initializing subunit $subunit is_nt=$is_nt";
		my $p = BRI::Port->new(
				'PORT_NUM'		=> $subunit,
				'BRI_NT'		=> $is_nt,
				'PORT_MODE_UP'		=> 0,
				'PORT_MODE_EXCH'	=> 0
				);
		# zap_XHfc_su.c:1186 in setup_instance()
		$p->init_su;
		$p->zthfc_startup;
	}
	# Turn on multi-byte packet reception when ports initialization finished
	BRI::multibyte(1);
}

main;

debug "Ending '$0'";

close REG;
close STDERR;
exit 0;
