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

#
# $Id: init_card_7_29 3793 2008-02-04 23:00:48Z tzafrir $
#

#
# 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:
#	XPD_BUS		- bus name
#	XPD_NAME	- xpd name
#	XPD_UNIT	- xpd unit number
#	XPD_SUBUNIT	- xpd subunit number
#	XPD_TYPE	- xpd type number (from protocol reply):
#			3 - FXS
#			4 - FXO
#			6 - BRI_TE
#			7 - BRI_NT
#	XPD_REVISION	- xpd revision number
#
# 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;

my $program = basename("$0");
my $init_dir = dirname("$0");
my $unit_id;

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

# Arrange for error logging
if (-t STDERR) {
	$unit_id = 'Interactive';
	logit "Interactive startup\n";
} else {
	$unit_id = "$ENV{XPD_BUS}/$ENV{XPD_NAME}";
	open (STDERR, "| logger -t $program -p kern.info") || die;
	logit "Non Interactive startup\n";
}

package BRI;

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

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};
	#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 "0	WD	02	04";		
	BRI::gen "0	WD	50	00"; # disable PLL		
	BRI::gen "0	WD	51	02";		
	BRI::gen "0	WD	52	06";		
	BRI::gen "0	WD	53  	04";		
	BRI::gen "0	WD	50	01"; # Enable PLL		
	BRI::gen "0	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 "0	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 "0	WD	35	%02X", $port->{CTRL3};	# A_ST_CTRL3
	BRI::gen "0	WD	31	%02X", $port->{CTRL0};	# A_SU_CTRL0
	BRI::gen "0	WD	35	F8";			# A_ST_CTRL3 = set end of pulse control to 0xF8
	BRI::gen "0	WD	32	08";			# A_SU_CTRL1 = Ignore E-channel data
	BRI::gen "0	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;
	}
	#logit "clk_dly=$clk_dly";
	BRI::gen "0	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 "0	WD	16	%02X", $portnum;	# R_SU_SEL
}

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

# zap_xhfc_su.c:295
sub xhfc_resetfifo() {
	#logit "xhfc_resetfifo()";
	# A_INC_RES_FIFO = M_RES_FIFO | M_RES_FIFO_ERR
	BRI::gen "0	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
	
	#logit "setup_fifo($fifonum)";
	xhfc_selfifo $fifonum;
	# A_CON_HDLC: transparent mode selection
	BRI::gen "0	WD	FA	%02X", $conhdlc;
	# A_SUBCH_CFG: subchnl params
	BRI::gen "0	WD	FB	%02X", $subcfg;
	# A_FIFO_CTRL: FIFO Control Register
	BRI::gen "0	WD	FF	%02X", $fifoctrl;
	xhfc_resetfifo;
	xhfc_selfifo $fifonum;  # wait for busy is builtin in this command
	BRI::gen "0	WD	10	%02X", $r_slot;	# R_SLOT 		
	BRI::gen "0	WD	D0	%02X", $a_sl_cfg;	# A_SL_CFG		

	#system("/bin/echo \"----=====NT\" \"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)";
	#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 "0	WD	31	%02X", $port->{CTRL0};		# A_SU_CTRL0: V_B1_TX_EN | V_SU_MD | (NT/TE)
	BRI::gen "0	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};
	#logit "xhfc_ph_command(portnum=$portnum)";
	if ("$cmd" eq "HFC_L1_ACTIVATE_TE") {
		su_sel($portnum);	# Select port
		BRI::gen "0	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 "0	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 "0	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 "0	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};
	#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
	}

	# Zaptel 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	"0	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;
use Getopt::Std;

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

$ENV{XPP_BASE} = '/proc/xpp';
my $cmd = "$ENV{XPP_BASE}/$ENV{XPD_BUS}/command"; 
my $output;
if ($opts{o}) {
	$output = $opts{o};
} else {
	$ENV{XPD_BUS} || die "Missing XPD_BUS environment variable\n";
	$ENV{XPD_NAME} || die "Missing XPD_NAME environment variable\n";
	$ENV{XPD_TYPE} || die "Missing XPD_TYPE environment variable\n";
	$ENV{XPD_REVISION} || die "Missing XPD_REVISION environment variable\n";
	$output = "$ENV{XPP_BASE}/$ENV{XPD_BUS}/$ENV{XPD_NAME}/slics";
}

open(REG, ">$output") || die "Failed to open '$output': $!\n";
select REG;

logit "Starting '$0'";

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

# zap_xhfc_su.c:895
sub init_xhfc() {
	#logit "init_xhfc()";
	BRI::gen "#--------------------------- init_xhfc";
	BRI::gen "0	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 "0	WD	00	08";	# R_CIRM = M_SRES (soft reset)
	#	--> WAIT 5u
	BRI::gen "0	WD	00	00";	# R_CIRM = 0 (zero it to deactivate reset)

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

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

	#	--> Wait until (R_STATUS & (M_BUSY | M_PCM_INIT))
 	# M_BUSY status will be checked after fifo selection
	BRI::gen "0	WD	0F	80";
	# set PCM !master mode
	BRI::gen "0	WD	14	08";	# R_PCM_MD0 = PCM slave mode, F0IO duration is 2 HFC_PCLK's

					# (C4IO, F0IO are inputs)

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

	BRI::gen "0 	WD	4C	03";	# GPIOGPIO function (not PWM) on GPIO0 and GPIO1 pins
	BRI::gen "0 	WD	4A	03";	# Output enable for GPIO0 and GPIO1 pins
}

my %port_type = (
		6	=> { 'BRI_NT' => 0 },
		7	=> { 'BRI_NT' => 1 }
	);

# zap_xhfc_su.c:175
sub main() {
	#logit "main(): XPD_TYPE=$ENV{XPD_TYPE}";

	$ENV{XPD_TYPE} || die "Missing XPD_TYPE environment variable\n";
	my $type = $port_type{$ENV{XPD_TYPE}};
	die "Bad XPD_TYPE=$ENV{XPD_TYPE}\n" unless $type;
	die "Missing XPD_SUBUNIT information\n" unless defined($ENV{XPD_SUBUNIT});

	# We must first turn off packet reception.
	#
	# Otherwise we mess with registers while the FPGA firmware tries to
	# send us packets.
	BRI::gen "0 Wm";

	# Common initialization
	if(($ENV{XPD_SUBUNIT} % 4) eq '0') {
		# Turn off multi-byte packet reception before initialization started 
		my $chipnum = ($ENV{XPD_SUBUNIT} & 0x04) << 3;  # Max 2 XHFC chips
		for my $subunit (0 .. 3) {			# 4 subunits per XHFC chip
			my $addr = $chipnum | ($subunit << 3);
			my $str = sprintf("0A 00 0F %02X C0 00 00 00 00 00\n", $addr);
			open(CMD, ">$cmd") || die "Failed to open '$cmd': $!\n";
			print CMD $str;
			close CMD;
		}
		init_xhfc;	# zap_xhfc_su.c:1173 in setup_instance()
	}

	# Port initialization
	my $p = BRI::Port->new(
			'PORT_NUM'		=> $ENV{XPD_SUBUNIT},
			'BRI_NT'		=> $type->{BRI_NT},
			'PORT_MODE_UP'		=> 0,
			'PORT_MODE_EXCH'	=> 0
			);
	# zap_XHfc_su.c:1186 in setup_instance()
	$p->init_su;

	$p->zthfc_startup;
	
	if(($ENV{XPD_SUBUNIT} % 4) eq 3) {
		# Turn on multi-byte packet reception when ports initialization finished
		my $chipnum = ($ENV{XPD_SUBUNIT} & 0x04) << 3;  # Max 2 XHFC chips
		for my $subunit (0 .. 3) {			 # 4 subunits per XHFC chip
			my $addr = $chipnum | ($subunit << 3);
			my $str = sprintf("0A 00 0F %02X 80 00 00 00 00 00\n", $addr);
			open(CMD, ">$cmd") || die "Failed to open '$cmd': $!\n";
			print CMD $str;
			close CMD;
		}
	}
}

main;

logit "Ending '$0'";

close REG;
close STDERR;
exit 0;
