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

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

#
# 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$
#
# 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.
#		- RS	Read Sub-register.
#		- WD	Write Direct register.
#		- WS	Write Sub-register.
#	3. Register number in hexadecimal.
#	4. Low data byte in hexadecimal. (for WD and WS commands).
#	5. High data byte in hexadecimal. (for WS 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"); }
use XppConfig $init_dir;
my $unit_id;
my %opts;
$Getopt::Std::STANDARD_HELP_VERSION = 1;
our $VERSION = '$Id$';

sub usage() {
	print <<"EOF";
$0 [-L] [-v verify_file] [-o output_file]
	-L: List all available opermodes and exit
	-v: verify opermodes and exit
	-o: simulate: output to file instead of astribank
EOF
	exit 1;
}

sub HELP_MESSAGE() {
	eval {usage};
	return 0;
}

getopts('Lo:v:', \%opts) || usage;

my %settings;
$settings{debug} = 0;
my $chipregs;

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

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

# Arrange for error logging
if (-t STDERR || $opts{v}) {
	$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;
		}
	}
	$chipregs = sprintf "/sys/bus/xpds/devices/%02d:%1d:0/chipregs",
		$ENV{XBUS_NUMBER}, $ENV{UNIT_NUMBER};
	if(! -f $chipregs) {
		my $xpd_name = sprintf("XPD-%1d0", $ENV{UNIT_NUMBER});
		$chipregs = "/proc/xpp/$ENV{XBUS_NAME}/$xpd_name/chipregs";
		logit "OLD DRIVER: does not use /sys chipregs. Falling back to /proc"
			if -f $chipregs;
	}
}

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;
	main::logit "# Setting output" if $opts{o};
	return $oldfh;
}

package FXO;

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

my $OPERMODE = 'FCC';

sub turn_off_leds() {
	# Turning off red LEDs
	# Warning: do not send WD 31 20 A0 !
	foreach my $i (0..7) {
		FXO::gen "$i WD 20 A0";
	}
}

# This data is manually taken from utils/init_fxo_modes which is generated
# during build.
# Running this script with a single 'verify' argument, during build,
# compare this data to a (possibly updated) utils/init_fxo_modes file.
my $OPERMODE_DATA = "
FCC            	reg16=01	reg26=C0	reg30=00	reg31=20		
TBR21          	reg16=00	reg26=C2	reg30=02	reg31=20	ring_osc=7E6C	ring_x=023A
ARGENTINA      	reg16=00	reg26=C0	reg30=00	reg31=20		
AUSTRALIA      	reg16=40	reg26=30	reg30=03	reg31=20		
AUSTRIA        	reg16=00	reg26=C2	reg30=03	reg31=28		
BAHRAIN        	reg16=00	reg26=C2	reg30=02	reg31=20		
BELGIUM        	reg16=00	reg26=C2	reg30=02	reg31=28		
BRAZIL         	reg16=00	reg26=30	reg30=00	reg31=20		
BULGARIA       	reg16=00	reg26=C2	reg30=03	reg31=20		
CANADA         	reg16=00	reg26=C0	reg30=00	reg31=20		
CHILE          	reg16=00	reg26=C0	reg30=00	reg31=20		
CHINA          	reg16=00	reg26=30	reg30=0F	reg31=20		
COLOMBIA       	reg16=00	reg26=C0	reg30=00	reg31=20		
CROATIA        	reg16=00	reg26=C2	reg30=02	reg31=20		
CYPRUS         	reg16=00	reg26=C2	reg30=02	reg31=20		
CZECH          	reg16=00	reg26=C2	reg30=02	reg31=20		
DENMARK        	reg16=00	reg26=C2	reg30=02	reg31=28		
ECUADOR        	reg16=00	reg26=C0	reg30=00	reg31=20		
EGYPT          	reg16=00	reg26=30	reg30=00	reg31=20		
ELSALVADOR     	reg16=00	reg26=C0	reg30=00	reg31=20		
FINLAND        	reg16=00	reg26=C2	reg30=02	reg31=28		
FRANCE         	reg16=00	reg26=C2	reg30=02	reg31=28		
GERMANY        	reg16=00	reg26=C2	reg30=03	reg31=28		
GREECE         	reg16=00	reg26=C2	reg30=02	reg31=28		
GUAM           	reg16=00	reg26=C0	reg30=00	reg31=20		
HONGKONG       	reg16=00	reg26=C0	reg30=00	reg31=20		
HUNGARY        	reg16=00	reg26=C0	reg30=00	reg31=20		
ICELAND        	reg16=00	reg26=C2	reg30=02	reg31=28		
INDIA          	reg16=00	reg26=C0	reg30=04	reg31=20		
INDONESIA      	reg16=00	reg26=C0	reg30=00	reg31=20		
IRELAND        	reg16=00	reg26=C2	reg30=02	reg31=28		
ISRAEL         	reg16=00	reg26=C2	reg30=02	reg31=20		
ITALY          	reg16=00	reg26=C2	reg30=02	reg31=28		
JAPAN          	reg16=00	reg26=30	reg30=00	reg31=20		
JORDAN         	reg16=00	reg26=30	reg30=00	reg31=20		
KAZAKHSTAN     	reg16=00	reg26=C0	reg30=00	reg31=20		
KUWAIT         	reg16=00	reg26=C0	reg30=00	reg31=20		
LATVIA         	reg16=00	reg26=C2	reg30=02	reg31=20		
LEBANON        	reg16=00	reg26=C2	reg30=02	reg31=20		
LUXEMBOURG     	reg16=00	reg26=C2	reg30=02	reg31=28		
MACAO          	reg16=00	reg26=C0	reg30=00	reg31=20		
MALAYSIA       	reg16=00	reg26=30	reg30=00	reg31=20		
MALTA          	reg16=00	reg26=C2	reg30=02	reg31=20		
MEXICO         	reg16=00	reg26=C0	reg30=00	reg31=20		
MOROCCO        	reg16=00	reg26=C2	reg30=02	reg31=20		
NETHERLANDS    	reg16=00	reg26=C2	reg30=02	reg31=28		
NEWZEALAND     	reg16=00	reg26=C0	reg30=04	reg31=20		
NIGERIA        	reg16=00	reg26=C2	reg30=02	reg31=20		
NORWAY         	reg16=00	reg26=C2	reg30=02	reg31=28		
OMAN           	reg16=00	reg26=30	reg30=00	reg31=20		
PAKISTAN       	reg16=00	reg26=30	reg30=00	reg31=20		
PERU           	reg16=00	reg26=C0	reg30=00	reg31=20		
PHILIPPINES    	reg16=00	reg26=30	reg30=00	reg31=20		
POLAND         	reg16=03	reg26=C0	reg30=00	reg31=20		
PORTUGAL       	reg16=00	reg26=C2	reg30=02	reg31=28		
ROMANIA        	reg16=00	reg26=C0	reg30=00	reg31=20		
RUSSIA         	reg16=00	reg26=30	reg30=00	reg31=20		
SAUDIARABIA    	reg16=00	reg26=C0	reg30=00	reg31=20		
SINGAPORE      	reg16=00	reg26=C0	reg30=00	reg31=20		
SLOVAKIA       	reg16=00	reg26=C0	reg30=03	reg31=20		
SLOVENIA       	reg16=00	reg26=C0	reg30=02	reg31=20		
SOUTHAFRICA    	reg16=42	reg26=C0	reg30=03	reg31=20		
SOUTHKOREA     	reg16=00	reg26=C0	reg30=00	reg31=20		
SPAIN          	reg16=00	reg26=C2	reg30=02	reg31=28		
SWEDEN         	reg16=00	reg26=C2	reg30=02	reg31=28		
SWITZERLAND    	reg16=00	reg26=C2	reg30=02	reg31=28		
SYRIA          	reg16=00	reg26=30	reg30=00	reg31=20		
TAIWAN         	reg16=00	reg26=30	reg30=00	reg31=20		
THAILAND       	reg16=00	reg26=30	reg30=00	reg31=20		
UAE            	reg16=00	reg26=C0	reg30=00	reg31=20		
UK             	reg16=00	reg26=C2	reg30=05	reg31=28		
USA            	reg16=00	reg26=C0	reg30=00	reg31=20		
YEMEN          	reg16=00	reg26=C0	reg30=00	reg31=20		
	";

my %opermode_table;

sub opermode_setup() {
	main::logit "Setting OPERMODE=$OPERMODE";
	# Several countries (South Africa, UAE, anybody else)
	# require a shorter delay:
	if($OPERMODE eq 'SOUTHAFRICA' or $OPERMODE eq 'UAE') {
		FXO::gen "* WD 17 2B";
	}
	# defaults, based on fxo_modes from wctdm.c . 
	# Decimal register numbers!
	my %regs = (
			16	=> 0,
			26	=> 0,
			30	=> 0,
			31	=> 0x20,
		);
	my $mode = $opermode_table{$OPERMODE};
	if(defined $mode) {
		foreach my $k (keys %regs) {
			my $fullkey = "reg$k";
			$regs{$k} = $mode->{$fullkey};
		}
	}
	foreach my $k (keys %regs) {
		# Our values are HEXADECIMAL without a 0x prefix!!!
		my $cmd = sprintf "* WD %02X %02X", $k, hex($regs{$k});
		main::debug "    regs: '$cmd'";
		FXO::gen "$cmd";
	}
	main::debug "Finished Opermode";
}

sub parse_opermode_line($) {
	my $line = shift or return();

	chomp $line;
	$line =~ s/#.*//;
	my @params = split(/\s+/, $line);
	my $location = shift @params;
	my $entry = {};
	foreach my $p (@params) {
		my ($key, $val) = split(/=/, $p, 2);
		$entry->{$key} = $val;
	}
	return ($location, $entry);
}

sub opermode_preprocess() {
	undef %opermode_table;
	foreach my $line (split(/\n/, $OPERMODE_DATA)) {
		my ($location, $entry) = parse_opermode_line($line);
		next unless defined $location;
		#print "$location\t", ref($entry), "\n";
		die "An entry for '$location' already exists\n"
			if exists $opermode_table{$location};
		$opermode_table{$location} = $entry;
	}
}

sub opermode_to_string($) {
	my $mode = shift or die;
	my @params;

	foreach my $k (sort keys %{$mode}) {
		push(@params, "$k=$mode->{$k}");
	}
	return join(" ", @params);
}

sub opermode_list() {
	my $l = join("\n", sort keys %opermode_table);
	print "$l\n";
}

sub opermode_verify($) {
	my $input = shift or die;
	my %verification_table;
	my %location_lines;
	my $mismatches = 0;

	open(F, $input) or die "$0: Failed opening '$input': $!\n";
	while(<F>) {
		chomp;
		#print "$_\n";
		s/#.*//;
		my @params = split;
		my $location = shift @params;
		foreach my $p (@params) {
			my ($key, $val) = split(/=/, $p, 2);
			$verification_table{$location}{$key} = $val;
		}
		$location_lines{$location} = $.;
	}
	close F;
	# First test: check for missing data in our program
	foreach my $location (sort keys %verification_table) {
		my $mode = $opermode_table{$location};
		if(! defined $mode) {
			printf STDERR  "Missing from $0: '$location' at $input:$location_lines{$location}\n";
			$mismatches++;
			next;
		}
		my $verify_mode = $verification_table{$location};
		my $str1 = opermode_to_string($mode);
		my $str2 = opermode_to_string($verify_mode);
		if($str1 ne $str2) {
			print STDERR  "DIFF: '$location' at $input:$location_lines{$location}\n";
			printf STDERR  "\t%-20s: %s\n", "program", $str1;
			printf STDERR  "\t%-20s: %s\n", "verify", $str2;
			$mismatches++;
		}
	}
	# Second test: check for extra data in our program
	foreach my $location (sort keys %opermode_table) {
		my $mode = $verification_table{$location};
		if(! defined $mode) {
			printf STDERR  "Extra in $0 '$location'\n";
			$mismatches++;
			next;
		}
	}
	print STDERR "Total $mismatches mismatches\n" if $mismatches;
	return $mismatches;
}

sub read_defaults() {
	if(XppConfig::read_config(\%settings)) {
		main::logit "Defaults from $settings{xppconf}";
		my $o = $settings{opermode};
		if(defined($o)) {
			# Verify
			$o = uc($o);	# Uppercase
			my $mode = $opermode_table{$o};
			if(! defined $mode) {
				main::logit "Unknown opermode='$o'";
				die;
			}
			$OPERMODE = $o;
			main::logit "Set OPERMODE = $o";
		}
	} else {
		main::logit "No defaults file, use hard-coded defaults.";
	}
}

package main;

FXO::opermode_preprocess;	# Must be first

if($opts{v}) {
	my $verify_file = $opts{v};
	usage unless $verify_file;
	main::debug "$0: opermode verification (input='$verify_file')";
	my $mismatches = FXO::opermode_verify($verify_file);
	die "$0: Verification against $verify_file failed\n" if $mismatches != 0;
	exit 0;
} elsif($opts{L}) {
	FXO::opermode_list();
	exit 0;
}

main::debug "Starting";

FXO::read_defaults;
die "OPERMODE is undefined" unless $OPERMODE;
set_output;
FXO::turn_off_leds;
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: '$_'";
	FXO::gen "$_";
}
FXO::opermode_setup;
close REG;

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

__DATA__
*	WD	12	02	# Full wave rectified ring detection
*	WD	03	01	# Polarity reversal detect mask
*	WD	04	00	# Clear interrupt status
*	WD	21	08	# Disable PCM transfers
*	WD	18	99
*	WD	06	00

# ----------- DAA PCM start offset ----------

*	WD	23	00
*	WD	25	00

0	WD	22	00
0	WD	24	00
0	WD	21	28	# Enable PCM transfers, when offsets are set

1	WD	22	08
1	WD	24	08
1	WD	21	28

2	WD	22	10
2	WD	24	10
2	WD	21	28

3	WD	22	18
3	WD	24	18
3	WD	21	28

4	WD	22	20
4	WD	24	20
4	WD	21	28

5	WD	22	28
5	WD	24	28
5	WD	21	28

6	WD	22	30
6	WD	24	30
6	WD	21	28

7	WD	22	38
7	WD	24	38
7	WD	21	28

# ----------- DAA ONHOOK --------------------
*	WD	05	00

# Set tip to ring voltage to 3.5 volts while off-hook
# instead of default of 3.1
*	WD	1A	C0
