591 lines
12 KiB
Plaintext
591 lines
12 KiB
Plaintext
|
#! /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;
|
||
|
|
||
|
getopts('o:', \%opts);
|
||
|
|
||
|
my %settings;
|
||
|
$settings{debug} = 0;
|
||
|
$settings{fxs_skip_calib} = 0;
|
||
|
my $chipregs;
|
||
|
|
||
|
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;
|
||
|
}
|
||
|
}
|
||
|
$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;
|
||
|
}
|
||
|
|
||
|
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));
|
||
|
my $retries = 10;
|
||
|
my @reply;
|
||
|
# If the command queue is long, we may need to wait...
|
||
|
WAIT_RESULTS:
|
||
|
{
|
||
|
my @results;
|
||
|
|
||
|
# The time to sleep is a tradeoff:
|
||
|
# - Too long is a waste of time.
|
||
|
# - Too short will cause many retries, wastes time.
|
||
|
# So the current value (after trial and error) is...
|
||
|
main::mysleep(0.013);
|
||
|
open(SLICS,$chipregs) or
|
||
|
die("Failed reading from chipregs file $chipregs");
|
||
|
while(<SLICS>){
|
||
|
s/#.*//;
|
||
|
next unless /\S/;
|
||
|
@results = /^\s*(\d+)\s+[RW][DI]\s+([[:xdigit:]]+)\s+([[:xdigit:]]+)\s+([[:xdigit:]]*)/;
|
||
|
if(@results != 4) {
|
||
|
main::logit "Failed reading from '$chipregs' ($read_slic,$read_reg,$direct)";
|
||
|
die;
|
||
|
}
|
||
|
}
|
||
|
close(SLICS);
|
||
|
my $reg = hex($results[1]);
|
||
|
if($results[0] ne $read_slic || $reg ne $read_reg) {
|
||
|
# We read obsolete values, need to wait some more
|
||
|
if(--$retries) {
|
||
|
main::debug "$read_slic RD $read_reg -- retry ($results[0], $reg)";
|
||
|
redo WAIT_RESULTS;
|
||
|
} else {
|
||
|
main::logit "Failed: $read_slic RD $read_reg returned $results[0], $reg";
|
||
|
die;
|
||
|
}
|
||
|
}
|
||
|
# Good.
|
||
|
@reply = (hex($results[2]), hex($results[3]));
|
||
|
|
||
|
}
|
||
|
if ($direct eq 'S') {
|
||
|
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 'S') {
|
||
|
$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("#
|
||
|
* WS 1E 00 C2 55
|
||
|
* WS 1E 01 E6 51
|
||
|
* WS 1E 02 85 4B
|
||
|
* WS 1E 03 37 49
|
||
|
|
||
|
* WS 1E 04 33 33
|
||
|
* WS 1E 05 02 02
|
||
|
* WS 1E 06 02 02
|
||
|
* WS 1E 07 98 01
|
||
|
|
||
|
* WS 1E 08 98 01
|
||
|
* WS 1E 09 11 06
|
||
|
* WS 1E 0A 02 02
|
||
|
* WS 1E 0B E5 00
|
||
|
|
||
|
* WS 1E 0C 1C 0A
|
||
|
* WS 1E 0D 30 7B
|
||
|
* WS 1E 0E 63 00
|
||
|
* WS 1E 0F 00 00
|
||
|
|
||
|
* WS 1E 10 70 78
|
||
|
* WS 1E 11 7D 00
|
||
|
* WS 1E 12 00 00
|
||
|
* WS 1E 13 00 00
|
||
|
|
||
|
* WS 1E 14 FD 7E
|
||
|
* WS 1E 15 77 01
|
||
|
* WS 1E 16 00 00
|
||
|
* WS 1E 17 00 20
|
||
|
|
||
|
* WS 1E 18 00 20
|
||
|
* WS 1E 19 00 00
|
||
|
* WS 1E 1A 00 20
|
||
|
* WS 1E 1B 00 40
|
||
|
|
||
|
* WS 1E 1C 00 10
|
||
|
* WS 1E 1D 00 36
|
||
|
* WS 1E 1E 00 10
|
||
|
* WS 1E 1F 00 02
|
||
|
|
||
|
* WS 1E 20 C0 07
|
||
|
* WS 1E 21 6F 37
|
||
|
* WS 1E 22 80 1B
|
||
|
* WS 1E 23 00 80
|
||
|
|
||
|
* WS 1E 24 00 08
|
||
|
* WS 1E 25 00 08
|
||
|
* WS 1E 26 00 08
|
||
|
* WS 1E 27 00 08
|
||
|
|
||
|
* WS 1E 28 00 00
|
||
|
* WS 1E 2B 00 08 # LCRTL = 5.08 mA
|
||
|
|
||
|
* WS 1E 63 DA 00
|
||
|
* WS 1E 64 60 6B
|
||
|
* WS 1E 65 74 00
|
||
|
* WS 1E 66 C0 79
|
||
|
|
||
|
* WS 1E 67 20 11
|
||
|
* WS 1E 68 E0 3B
|
||
|
#");
|
||
|
}
|
||
|
|
||
|
sub init_early_direct_regs() {
|
||
|
return write_to_slic_file("#
|
||
|
* WD 08 00 # Audio Path Loopback Control
|
||
|
* WD 6C 01
|
||
|
* WD 4A 3F # 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, 'S')];
|
||
|
write_reg($slic, $reg, 'S', 0, 0x80);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
sub restore_indirect_filter_params() {
|
||
|
for my $slic (@SlicNums) {
|
||
|
for my $reg (35 .. 39) {
|
||
|
write_reg($slic, $reg, 'S',
|
||
|
@{$FilterParams[$slic][$reg]});
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
my $ManualCalibrationSleepTime = 0.04; # 40ms
|
||
|
|
||
|
sub manual_calibrate_loop($$) {
|
||
|
my $write_reg = shift;
|
||
|
my $read_reg = shift;
|
||
|
my @curr_slics = @SlicNums;
|
||
|
|
||
|
# initialize counters
|
||
|
my @slic_counters = map { 0x1F } @curr_slics;
|
||
|
|
||
|
# wait until all slics have finished calibration, or for timeout
|
||
|
while (@curr_slics) {
|
||
|
my $debug_calib_str = "ManualCalib:: ";
|
||
|
my @next_slics;
|
||
|
|
||
|
for my $slic (@curr_slics) {
|
||
|
write_reg($slic,$write_reg,'D',$slic_counters[$slic]);
|
||
|
}
|
||
|
main::mysleep $ManualCalibrationSleepTime;
|
||
|
for my $slic (@curr_slics) {
|
||
|
my $value = read_reg($slic, $read_reg, 'D');
|
||
|
$debug_calib_str .= sprintf " [%d:%d:%X]",
|
||
|
$slic, $slic_counters[$slic], $value;
|
||
|
next if $value == 0; # This one is calibrated.
|
||
|
if ($slic_counters[$slic] > 0) {
|
||
|
$slic_counters[$slic]--;
|
||
|
push(@next_slics, $slic);
|
||
|
} else {
|
||
|
main::logit("ERROR: SLIC $slic reached 0 during manual calibration");
|
||
|
}
|
||
|
}
|
||
|
@curr_slics = @next_slics;
|
||
|
main::debug($debug_calib_str);
|
||
|
}
|
||
|
main::debug("No more slics to calibrate");
|
||
|
}
|
||
|
|
||
|
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:
|
||
|
for my $slic(@SlicNums) {
|
||
|
write_to_slic_file(
|
||
|
sprintf
|
||
|
"$slic WD 61 %02X\n".
|
||
|
"$slic WD 60 %02X\n".
|
||
|
"", $calib_97, $calib_96
|
||
|
|
||
|
);
|
||
|
}
|
||
|
|
||
|
# wait until all slics have finished calibration, or for timeout
|
||
|
# time periods in seconds:
|
||
|
my $sleep_time = 0.001;
|
||
|
my $timeout_time = 0.600; # Maximum from the spec
|
||
|
my @curr_slics = @SlicNums;
|
||
|
my $sleep_cnt = 0;
|
||
|
CALIB_LOOP:
|
||
|
while(1) {
|
||
|
main::mysleep($sleep_time);
|
||
|
my @next_slics;
|
||
|
for my $slic (@curr_slics) {
|
||
|
main::debug("checking slic $slic");
|
||
|
my $val = read_reg($slic, 96, 'D');
|
||
|
push(@next_slics, $slic) if $val != 0;
|
||
|
}
|
||
|
@curr_slics = @next_slics;
|
||
|
last unless @curr_slics;
|
||
|
if ($sleep_cnt * $sleep_time > $timeout_time) {
|
||
|
main::logit("Auto Calibration: Exiting on timeout: $timeout_time.");
|
||
|
last CALIB_LOOP;
|
||
|
}
|
||
|
main::debug("auto_calibrate not done yet($sleep_cnt): @curr_slics");
|
||
|
$sleep_cnt++;
|
||
|
}
|
||
|
#log_calib_params();
|
||
|
}
|
||
|
|
||
|
sub calibrate_slics() {
|
||
|
main::debug "Calibrating '$0'";
|
||
|
auto_calibrate(0x40, 0x1E);
|
||
|
main::debug "after auto_calibrate";
|
||
|
manual_calibrate();
|
||
|
main::debug "after manul_calibrate";
|
||
|
auto_calibrate(0x40, 0x01);
|
||
|
main::debug "after auto_calibrate 2";
|
||
|
main::debug "Continue '$0'";
|
||
|
}
|
||
|
|
||
|
sub read_defaults() {
|
||
|
if(XppConfig::read_config(\%settings)) {
|
||
|
main::logit "Defaults from $settings{xppconf}";
|
||
|
} else {
|
||
|
main::logit "No defaults file, use hard-coded defaults.";
|
||
|
}
|
||
|
}
|
||
|
|
||
|
# Try to identify which slics are valid
|
||
|
sub check_slics() {
|
||
|
my @slics;
|
||
|
foreach my $slic (0 .. 7) {
|
||
|
my $value = read_reg($slic, 0, 'D');
|
||
|
push(@slics, $slic) if $value != 0xFF;
|
||
|
}
|
||
|
main::logit "Found " . scalar(@slics) . " SLICS (@slics)";
|
||
|
return @slics;
|
||
|
}
|
||
|
|
||
|
package main;
|
||
|
|
||
|
main::debug "Starting '$0'";
|
||
|
|
||
|
FXS::read_defaults;
|
||
|
@SlicNums = FXS::check_slics;
|
||
|
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($settings{fxs_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::debug "Ending '$0'";
|
||
|
close STDERR;
|
||
|
exit 0;
|
||
|
|
||
|
# ----------------------------------==== 8-channel FXS unit initialization ===-----------------------------------------
|
||
|
|
||
|
__DATA__
|
||
|
|
||
|
# Flush out energy accumulators
|
||
|
* WS 1E 58 00 00
|
||
|
* WS 1E 59 00 00
|
||
|
* WS 1E 5A 00 00
|
||
|
* WS 1E 5B 00 00
|
||
|
* WS 1E 5C 00 00
|
||
|
* WS 1E 5D 00 00
|
||
|
* WS 1E 5E 00 00
|
||
|
* WS 1E 5F 00 00
|
||
|
|
||
|
* WS 1E 61 00 00
|
||
|
|
||
|
* WS 1E C1 00 00
|
||
|
* WS 1E C2 00 00
|
||
|
* WS 1E C3 00 00
|
||
|
* WS 1E C4 00 00
|
||
|
* WS 1E C5 00 00
|
||
|
* WS 1E C6 00 00
|
||
|
* WS 1E C7 00 00
|
||
|
* WS 1E C8 00 00
|
||
|
* WS 1E C9 00 00
|
||
|
* WS 1E CA 00 00
|
||
|
* WS 1E CB 00 00
|
||
|
* WS 1E CC 00 00
|
||
|
* WS 1E CD 00 00
|
||
|
* WS 1E CE 00 00
|
||
|
* WS 1E CF 00 00
|
||
|
* WS 1E D0 00 00
|
||
|
* WS 1E D1 00 00
|
||
|
* WS 1E D2 00 00
|
||
|
* WS 1E D3 00 00
|
||
|
|
||
|
# Clear and disable interrupts
|
||
|
* WD 12 FF
|
||
|
* WD 13 FF
|
||
|
* WD 14 FF
|
||
|
* WD 15 00
|
||
|
* WD 16 00
|
||
|
* WD 17 00
|
||
|
|
||
|
## Mode(8-bit,u-Law,1 PCLK )
|
||
|
* WD 01 08 # Disable PCM transfers
|
||
|
|
||
|
# Setting of SLICs offsets
|
||
|
# New card initialization
|
||
|
|
||
|
* WD 03 00
|
||
|
* WD 05 00
|
||
|
|
||
|
0 WD 02 00
|
||
|
0 WD 04 00
|
||
|
0 WD 01 28 # Enable PCM transfers
|
||
|
1 WD 02 08
|
||
|
1 WD 04 08
|
||
|
1 WD 01 28
|
||
|
2 WD 02 10
|
||
|
2 WD 04 10
|
||
|
2 WD 01 28
|
||
|
3 WD 02 18
|
||
|
3 WD 04 18
|
||
|
3 WD 01 28
|
||
|
4 WD 02 20
|
||
|
4 WD 04 20
|
||
|
4 WD 01 28
|
||
|
5 WD 02 28
|
||
|
5 WD 04 28
|
||
|
5 WD 01 28
|
||
|
6 WD 02 30
|
||
|
6 WD 04 30
|
||
|
6 WD 01 28
|
||
|
7 WD 02 38
|
||
|
7 WD 04 38
|
||
|
7 WD 01 28
|
||
|
|
||
|
# Audio path. (also initialize 0A and 0B here if necessary)
|
||
|
* WD 08 00
|
||
|
* WD 09 00
|
||
|
* WD 0A 08
|
||
|
* WD 0B 33
|
||
|
|
||
|
#------ Metering tone
|
||
|
* WD 2C 00 # Timer dL
|
||
|
* WD 2D 03 # Timer dH
|
||
|
* WS 1E 17 61 15 # Amplitue Ramp-up
|
||
|
* WS 1E 18 61 15 # Max Amplitude
|
||
|
* WS 1E 19 FB 30 # Frequency
|
||
|
|
||
|
# Ring regs are set by driver
|
||
|
|
||
|
# 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
|
||
|
|
||
|
# On-Hook Line Voltage (VOC)
|
||
|
* WD 48 20
|
||
|
|
||
|
# Common Mode Voltage (VCM)
|
||
|
* WD 49 03
|
||
|
|
||
|
* WS 1E 23 00 80
|
||
|
* WS 1E 24 20 03
|
||
|
* WS 1E 25 8C 00
|
||
|
* WS 1E 26 00 00
|
||
|
* WS 1E 27 10 00
|
||
|
|
||
|
* WD 0E 00
|