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

#
# 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.
#

#
# $Id: init_card_1_30 4266 2008-05-13 21:08:09Z tzafrir $
#
# Data format:
#	- A comment start with ';' or '#' until the end of line
#	- Blank lines are ignored
#	- Fields are whitespace separated (spaces or tabs)
#
# The fields are (in command line order):
#	1. SLIC select in decimal (range 0-7).
#	   * is a special value which means ALL SLICS (only some registers
#	   accept settings for ALL SLICS).
#	2. Command word:
#		- RD	Read Direct register.
#		- RI	Read Indirect register.
#		- WD	Write Direct register.
#		- WI	Write Indirect register.
#	3. Register number in hexadecimal.
#	4. Low data byte in hexadecimal. (for WD and WI commands).
#	5. High data byte in hexadecimal. (for WI command 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", "$init_dir/zconf"); }
use Zaptel::Config::Defaults;
my $unit_id;
my %opts;
$ENV{XPP_BASE} = '/proc/xpp';

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

my $debug;
my $skip_calib;

my $xpd_name = sprintf("XPD-%1d0", $ENV{UNIT_NUMBER});
my $chipregs = "$ENV{XPP_BASE}/$ENV{XBUS_NAME}/$xpd_name/chipregs";

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

sub debug {
	logit @_ if $debug;
}

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

sub set_output() {
	my $output;

	if($opts{o}) {
		$output = $opts{o};
	} else {
		# No subunits in FXS (everything is subunit 0)
		$output = $chipregs;
	}
	open(REG, ">$output") || die "Failed to open '$output': $!\n";
	my $oldfh = select REG;
	print "# Setting output\n" if $opts{o};
	return $oldfh;
}

sub mysleep($) {
	my $timeout = shift;
	select(undef,undef,undef,$timeout);
}

package FXS;

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

my @SlicNums = (0 .. 7);

sub write_to_slic_file($) {
	my $write_str = shift;

	open(SLICS,">$chipregs") or 
		die("Failed writing to chipregs file $chipregs");
	print SLICS $write_str;
	close(SLICS) or die "Failed writing '$write_str' to '$chipregs': $!";
	main::mysleep(0.001);
	
}

sub read_reg($$$) {
	my $read_slic = shift;
	my $read_reg = shift;
	my $direct = shift;
	
	write_to_slic_file(
		sprintf("%s R%s %02X", $read_slic, $direct, $read_reg));
	main::mysleep(0.005);
	open(SLICS,$chipregs) or 
		die("Failed reading from chipregs file $chipregs");
	#awk '/^SLIC_REPLY:/{print $5}' $SLICS | cut -dx -f2
	my @reply = ();
	while(<SLICS>){
		#if (/^ /) {
		#	main::debug "answer line: $_";
		#}
		s/#.*//;
		next unless /\S/;
		if (/^ \d*\s+[RW][DI]\s+[[:xdigit:]]+\s+([[:xdigit:]]+)\s+([[:xdigit:]]*)/){
			@reply = (hex($1), hex($2)); 
			#main::debug "got [$reply]";
			last;
		} else {
			main::logit("Got from '$chipregs' a non-matching line '$_'");
		}
	}
	close(SLICS);
	die("Failed reading from '$chipregs' ($read_slic,$read_reg,$direct)")
		unless @reply;
	if ($direct eq 'I') {
		return @reply;
	} else {
		return $reply[0];
	}
}

# TODO: rearange arguments
sub write_reg{#($$$$$) {
	my $read_slic = shift;
	my $read_reg = shift;
	my $direct = shift;
	my $reg_val_low = shift;
	my $reg_val_hi = shift;
	
	my $str  = sprintf "%s W%s %02X %02X", 
		$read_slic, $direct, $read_reg, $reg_val_low;
	if ($direct eq 'I') {
		$str .= sprintf " %02X", $reg_val_hi;
	}
	write_to_slic_file($str);
}

sub log_calib_params() {
	for my $i (100 .. 107) {
		my $line="Calib Reg $i:  ";
		for my $slic (@SlicNums) {
			$line .= " ".read_reg($slic, $i, 'D');
		}
		main::debug($line);
	}
}

sub init_indirect_registers() {
	return write_to_slic_file("#
*	WI	1E	00	C2	55
*	WI	1E	01	E6	51
*	WI	1E	02	85	4B
*	WI	1E	03	37	49
                          
*	WI	1E	04	33	33
*	WI	1E	05	02	02
*	WI	1E	06	02	02
*	WI	1E	07	98	01
                          
*	WI	1E	08	98	01
*	WI	1E	09	11	06
*	WI	1E	0A	02	02
*	WI	1E	0B	E5	00
                          
*	WI	1E	0C	1C	0A
*	WI	1E	0D	30	7B
*	WI	1E	0E	63	00
*	WI	1E	0F	00	00
                          
*	WI	1E	10	70	78
*	WI	1E	11	7D	00
*	WI	1E	12	00	00
*	WI	1E	13	00	00
                          
*	WI	1E	14	F0	7E
*	WI	1E	15	C0	01
*	WI	1E	16	00	00
*	WI	1E	17	00	20
                          
*	WI	1E	18	00	20
*	WI	1E	19	00	00
*	WI	1E	1A	00	20
*	WI	1E	1B	00	40
                          
*	WI	1E	1C	00	10
*	WI	1E	1D	00	36
*	WI	1E	1E	00	10
*	WI	1E	1F	00	02
                          
*	WI	1E	20	C0	07
*	WI	1E	21	00	26
*	WI	1E	22	F4	0F
*	WI	1E	23	00	80

#*	WI	1E	24	20	03
#*	WI	1E	25	8C	08
#*	WI	1E	26	00	01
#*	WI	1E	27	10	00
                          
*	WI	1E	24	00	08
*	WI	1E	25	00	08
*	WI	1E	26	00	08
*	WI	1E	27	00	08
                          
*	WI	1E	28	00	0C
*	WI	1E	29	00	0C
*	WI	1E	2B	00	01
                          
*	WI	1E	63	DA	00
*	WI	1E	64	60	6B
*	WI	1E	65	74	00
*	WI	1E	66	C0	79
                          
*	WI	1E	67	20	11
*	WI	1E	68	E0	3B	
#");
}

sub init_early_direct_regs() {
	return write_to_slic_file("#
*	WD	08	00	# Audio Path Loopback Control
*	WD	4A	34	# High Battery Voltage
*	WD	4B	10	# Low Battery Voltage
*	WD	40	00	# Line Feed Control
#")
}

my @FilterParams = ();

sub save_indirect_filter_params() {
	for my $slic (@SlicNums) {
		for my $reg (35 .. 39) {
			$FilterParams[$slic][$reg] = 
				[read_reg($slic, $reg, 'I')];
			write_reg($slic, $reg, 'I', 0, 0x80);
		}
	}
	
}

sub restore_indirect_filter_params() {
	for my $slic (@SlicNums) {
		for my $reg (35 .. 39) {
			write_reg($slic, $reg, 'I', 
				@{$FilterParams[$slic][$reg]});
		}
	}
}

my $ManualCalibrationSleepTime = 0.04; # 40ms

sub manual_calibrate_loop($$) {
	my $write_reg = shift;
	my $read_reg = shift;
	
	# counters to count down to (at most) 0
	my @slic_counters = ();
	for my $i (0 .. $#SlicNums) {
		$slic_counters[$i] = 0x1F;
	}
	
	# start calibration:
	my $calibration_in_progress = 1;
	write_reg('*', $write_reg, 'D', 0x1F);
	main::mysleep $ManualCalibrationSleepTime;
	
	# wait until all slics have finished calibration, or for timeout
	while ($calibration_in_progress) {
		$calibration_in_progress = 0; # until proven otherwise
		my $debug_calib_str = "ManualCalib:: ";
		for my $slic(@SlicNums) {
			my $value = read_reg($slic, $read_reg, 'D');
			$debug_calib_str .= " [$slic_counters[$slic]:$value]";
			if ($value != 0 && $slic_counters[$slic] > 0) {
				$calibration_in_progress = 1;
				$slic_counters[$slic]--;
				write_reg($slic,$write_reg,'D',$slic_counters[$slic]);
			}
		}
		main::debug($debug_calib_str);
		# TODO: unnecessary sleep in the last round:
		main::mysleep $ManualCalibrationSleepTime;
	}
}

sub manual_calibrate() {
	manual_calibrate_loop(98, 88);
	manual_calibrate_loop(99, 89);
}

sub auto_calibrate($$) {
	my $calib_96 = shift;
	my $calib_97 = shift;

	#log_calib_params();
	# start calibration:
	write_to_slic_file(
		sprintf
			"* WD 60 %02X\n".
			"* WD 61 %02X\n".
			"", $calib_96, $calib_97
			
	);
	# wait until all slics have finished calibration, or for timeout
	my $sleep_cnt = 0;
	# time periods in seconds:
	my $sleep_time = 0.1;
	my $timeout_time = 2; 
	CALIB_LOOP: for my $slic (@SlicNums) {
		main::debug("checking slic $slic");
		while(1) {
			if ((read_reg($slic, 60, 'D')) == 0) {
				# move to next register
				main::debug("slic $slic calibrated");
				last;
			}
			if ( $sleep_cnt > $timeout_time/$sleep_time) {
				main::debug("Auto Calibration: Exiting on timeout: $timeout_time.");
				last CALIB_LOOP;
			}
			main::debug("auto_calibrate not done yet: slic #$slic");
			main::mysleep(0.1);
			$sleep_cnt++;
		}
	}
	#log_calib_params();
}

sub calibrate_slics() {
	main::logit "Calibrating '$0'";
	auto_calibrate(0x47, 0x1E);
	main::debug "after auto_calibrate";
	manual_calibrate();
	main::debug "after manul_calibrate";
	auto_calibrate(0x40, 0x01);
	main::debug "after auto_calibrate 2";
	main::logit "Continue '$0'";
}

sub read_defaults() {
	# For lab tests
	my $labfile = "$init_dir/genzaptelconf.env";

	# Source default files
	$ENV{ZAPTEL_DEFAULTS} = "$labfile" if -r "$labfile";
	my $var_debug = 'DEBUG_INIT_FXS';
	my $var_skip_calib = 'INIT_FXS_SKIP_CALIB';
	my ($default_file, %source_defaults) =
		Zaptel::Config::Defaults::source_vars($var_debug, $var_skip_calib);
	$debug = $source_defaults{$var_debug};
	$skip_calib = $source_defaults{$var_skip_calib};
	main::logit "From $default_file: $var_debug=$debug $var_skip_calib=$skip_calib";
}

package main;

main::logit "Starting '$0'";

FXS::read_defaults;
main::debug "before init_indirect_registers";
FXS::init_indirect_registers();
main::debug "after init_indirect_registers";
FXS::init_early_direct_regs();
main::debug "after init_early_direct_regs";
if($skip_calib) {
	main::logit "==== WARNING: SKIPPED SLIC CALIBRATION =====";
} else {
	FXS::calibrate_slics;
}
set_output;
while(<DATA>) {
	chomp;
	s/[#;].*$//;		# remove comments
	s/^\s+//;		# trim whitespace
	s/\s+$//;		# trim whitespace
	s/\t+/ /g;		# replace tabs with spaces (for logs)
	next unless /\S/;	# Skip empty lines
	main::debug "writing: '$_'";
	print "$_\n";
}
close REG;

main::logit "Ending '$0'";
close STDERR;
exit 0;

# ----------------------------------==== 8-channel FXS unit initialization ===-----------------------------------------

__DATA__
# Change SLICs states to "Open state"s  (Off,all transfers tristated to avoid data collision), Voltage sense
*	WD	40	00

# Flush out energy accumulators
*	WI	1E	58	00 00
*	WI	1E	59	00 00
*	WI	1E	5A	00 00
*	WI	1E	5B	00 00
*	WI	1E	5C	00 00
*	WI	1E	5D	00 00
*	WI	1E	5E	00 00
*	WI	1E	5F	00 00
*	WI	1E	61	00 00
*	WI	1E	58	00 00
*	WI	1E	C1	00 00
*	WI	1E	C2	00 00
*	WI	1E	C3	00 00
*	WI	1E	C4	00 00
*	WI	1E	C5	00 00
*	WI	1E	C6	00 00
*	WI	1E	C7	00 00
*	WI	1E	C8	00 00
*	WI	1E	C9	00 00
*	WI	1E	CA	00 00
*	WI	1E	CB	00 00
*	WI	1E	CC	00 00
*	WI	1E	CD	00 00
*	WI	1E	CE	00 00
*	WI	1E	CF	00 00
*	WI	1E	D0	00 00
*	WI	1E	D1	00 00
*	WI	1E	D2	00 00
*	WI	1E	D3	00 00
                
# Setting of SLICs offsets
# New card initialization
0	WD	02	00
0	WD	04	00
1	WD	02	08
1	WD	04	08
2	WD	02	10
2	WD	04	10
3	WD	02	18
3	WD	04	18
4	WD	02	20
4	WD	04	20
5	WD	02	28
5	WD	04	28
6	WD	02	30
6	WD	04	30
7	WD	02	38
7	WD	04	38
*	WD	03	00
*	WD	05	00

# Audio path. (also initialize 0A and 0B here if necessary)
*	WD	08 00
*	WD	09 C0

# Automatic/Manual Control: defaults but:
#	Cancel AOPN - Power Alarm
#	Cancel ABAT - Battery Feed Automatic Select
*	WD	43 16

# Loop Closure Debounce Interval
*	WD	45 0A

# Ring Detect Debounce Interval
*	WD	46 47

# Battery Feed Control: Battery low (DCSW low)
*	WD	42 00

# Loop Current Limit
*	WD	47 00

# Ring VBath:
*	WD	4A 3F


*	WD	6C	01

*	WI	1E	23	00 80
*	WI	1E	24	20 03
*	WI	1E	25	8C 08
*	WI	1E	26	00 01
*	WI	1E	27	10 00

#------ Metering tone
*	WI	1E	17	61 15	# Amplitue Ramp-up
*	WI	1E	18	61 15	# Max Amplitude
*	WI	1E	19	FB 30	# Frequency
*	WD	2C	00	# Timer dL
*	WD	2D	03	# Timer dH

# ------------------------------------- Initialization of direct registers --------------------------------------------

# Mode(8-bit,u-Law,1 PCLK ) setting, Loopbacks and Interrupts clear

*	WD	01	29
#*	WD	0E	00  

#*	WD	15 00
#*	WD	16 03

# Clear pending interrupts
*	WD	12 FF
*	WD	13 FF
*	WD	14 FF 

#*	WD	4A 34
#*	WD	4B 10
