#! /usr/bin/perl -w use strict; # Make warnings fatal local $SIG{__WARN__} = sub { die @_ }; # # Written by Oron Peled # 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(){ 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() { 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