40f4f86ffa
add them back.
This reverts commit a36d266254
.
1171 lines
29 KiB
Perl
Executable File
1171 lines
29 KiB
Perl
Executable File
#! /usr/bin/perl -w
|
|
use strict;
|
|
|
|
# Make warnings fatal
|
|
local $SIG{__WARN__} = sub { die @_ };
|
|
|
|
#
|
|
# Written by Oron Peled <oron@actcom.co.il>
|
|
# Copyright (C) 2017, 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;
|
|
my $command;
|
|
my $expander_cmd;
|
|
my $ring_registers;
|
|
|
|
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
|
|
XBUS_MODEL_STRING
|
|
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;
|
|
}
|
|
}
|
|
logit "XBUS_MODEL_STRING='$ENV{XBUS_MODEL_STRING}'";
|
|
$chipregs = sprintf "/sys/bus/xpds/devices/%02d:%1d:0/chipregs",
|
|
$ENV{XBUS_NUMBER}, $ENV{UNIT_NUMBER};
|
|
$command = "/proc/xpp/$ENV{XBUS_NAME}/command";
|
|
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;
|
|
}
|
|
$ring_registers = sprintf "/sys/bus/xpds/devices/%02d:%1d:0/fxs_ring_registers",
|
|
$ENV{XBUS_NUMBER}, $ENV{UNIT_NUMBER};
|
|
logit "OLD DRIVER: missing '$ring_registers' -- fallback to hard-coded defaults"
|
|
unless -f $ring_registers;
|
|
}
|
|
|
|
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;
|
|
|
|
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 write_to_ring_register($) {
|
|
my $write_str = shift;
|
|
|
|
open(SLICS,">$ring_registers") or
|
|
die("Failed writing to ring_registers file $ring_registers");
|
|
print SLICS $write_str;
|
|
close(SLICS) or die "Failed writing '$write_str' to '$ring_registers': $!";
|
|
main::mysleep(0.001);
|
|
}
|
|
|
|
sub read_reg($$$;$) {
|
|
my $slic = shift;
|
|
my $addr = shift;
|
|
my $direct = shift;
|
|
my $bits_shift = shift;
|
|
$bits_shift = 0 unless defined $bits_shift;
|
|
my $addr_low;
|
|
my $addr_high;
|
|
main::debug("read_reg: $slic, $addr, $direct");
|
|
$addr_low = $addr & 0xFF;
|
|
$addr_high = $addr >> 8;
|
|
|
|
my $str;
|
|
if ($direct eq 'D') {
|
|
$str = sprintf "%s RD %02X",
|
|
$slic, $addr_low;
|
|
} else {
|
|
$str = sprintf "%s RR %02X %02X",
|
|
$slic, $addr_low, $addr_high;
|
|
}
|
|
write_to_slic_file($str);
|
|
|
|
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/;
|
|
if ($direct eq 'D') {
|
|
@results = /^\s*(\d+)\s+[RW][DR]\s+([[:xdigit:]]+)\s+([[:xdigit:]]+)\s+([[:xdigit:]]*)/;
|
|
} else {
|
|
@results = /^\s*(\d+)\s+[RW][DR]\s+([[:xdigit:]]+)\s+([[:xdigit:]]+)\s+([[:xdigit:]]+)\s+([[:xdigit:]]+)\s+([[:xdigit:]]+)\s+([[:xdigit:]]*)/;
|
|
}
|
|
if(($direct eq 'D' && @results != 4) || ($direct eq 'R' && @results != 7)) {
|
|
main::logit "Failed reading from '$chipregs' ($direct, $slic,$addr)";
|
|
die;
|
|
}
|
|
}
|
|
close(SLICS);
|
|
my $read_addr;
|
|
if ($direct eq 'D') {
|
|
$read_addr = hex($results[1]);
|
|
} else {
|
|
$read_addr = hex(sprintf "0x$results[2]$results[1]");
|
|
}
|
|
if($results[0] ne $slic || $read_addr ne $addr) {
|
|
# We read obsolete values, need to wait some more
|
|
if(--$retries) {
|
|
main::debug "$slic R$direct $addr -- retry (@results, $read_addr)";
|
|
redo WAIT_RESULTS;
|
|
} else {
|
|
main::logit "Failed: $slic R$direct $addr returned @results, $addr";
|
|
die;
|
|
}
|
|
}
|
|
# Good.
|
|
$results[0] = "";
|
|
$results[1] = "";
|
|
if ($direct eq 'R') {
|
|
$results[2] = "";
|
|
}
|
|
foreach my $val (@results) {
|
|
$reply = sprintf("%s%s", $val, $reply);
|
|
}
|
|
$reply = hex(sprintf("0x%s", $reply));
|
|
|
|
}
|
|
return $reply >> $bits_shift;
|
|
}
|
|
|
|
# TODO: rearange arguments
|
|
sub write_reg{#($$$) {
|
|
my $slic = shift;
|
|
my $reg = shift;
|
|
my $val = shift;
|
|
|
|
my $str = sprintf "%s WD %02X %02X",
|
|
$slic, $reg, $val;
|
|
main::debug("write_reg: $slic, $reg, $val");
|
|
write_to_slic_file($str);
|
|
}
|
|
|
|
#
|
|
# BITS:
|
|
# 3 - port num
|
|
# 1 - read/write
|
|
# 1 - Register / RAM
|
|
# 11 - address
|
|
# 29 - data
|
|
sub write_ram($$$$) {
|
|
my $slic = shift;
|
|
my $addr = shift;
|
|
my $value = shift;
|
|
my $bits_shift = shift;
|
|
my $addr_low;
|
|
my $addr_high;
|
|
my $value_0;
|
|
my $value_1;
|
|
my $value_2;
|
|
my $value_3;
|
|
my $log_output = sprintf("write_ram: %s, %4d, 0x%08X", $slic, $addr, $value);
|
|
main::debug($log_output);
|
|
$value = $value << $bits_shift;
|
|
$addr_low = $addr & 0xFF;
|
|
$addr_high = $addr >> 8;
|
|
|
|
$value_0 = $value & 0xFF;
|
|
$value >>= 8;
|
|
$value_1 = $value & 0xFF;
|
|
$value >>= 8;
|
|
$value_2 = $value & 0xFF;
|
|
$value >>= 8;
|
|
$value_3 = $value & 0xFF;
|
|
|
|
my $str = sprintf "%s WR %02X %02X %02X %02X %02X %02X",
|
|
$slic, $addr_low, $addr_high,
|
|
$value_0, $value_1, $value_2, $value_3;
|
|
write_to_slic_file($str);
|
|
}
|
|
|
|
sub set_user_mode {
|
|
my $slic = shift;
|
|
my $on = shift;
|
|
my $current = read_reg($slic, 0x7E, 'D');
|
|
$current &= 0x1;
|
|
$on &= 0x1;
|
|
main::debug("::set_user_mode($slic, $on): " . $current . " -> " . $on . "\n");
|
|
return 1 if $current == $on;
|
|
write_reg($slic, 126, 0x02);
|
|
write_reg($slic, 126, 0x08);
|
|
write_reg($slic, 126, 0x0E);
|
|
write_reg($slic, 126, 0x00);
|
|
return 1;
|
|
}
|
|
|
|
sub init_early() {
|
|
# I/O Expander initialization
|
|
$expander_cmd = sprintf "echo 0A 00 0F %1d0 05 08 00 00 00 FF > %s",
|
|
$ENV{UNIT_NUMBER}, $command; # enable outputs for Greed LEDs
|
|
system("$expander_cmd");
|
|
$expander_cmd = sprintf "echo 0A 00 0F %1d0 05 08 01 00 AA 55 > %s",
|
|
$ENV{UNIT_NUMBER}, $command; # enable outputs for Red LEDs and relays
|
|
system("$expander_cmd");
|
|
$expander_cmd = sprintf "echo 0A 00 0F %1d0 05 08 0D 00 AA AA > %s",
|
|
$ENV{UNIT_NUMBER}, $command; # enable pull-ups for inputs
|
|
system("$expander_cmd");
|
|
}
|
|
|
|
sub load_patch($) {
|
|
my $slics_ref = shift;
|
|
my @slics = @{ $slics_ref };
|
|
my $slic;
|
|
|
|
main::debug "Loading patch based on si3226x_patch_C_TSS_ISO_2014JUN18.c";
|
|
foreach $slic (@slics) {
|
|
FXS::set_user_mode($slic, 1); # Turn on user mode
|
|
}
|
|
write_reg('*', 81, 0x00); # JMPEN, disable the patch
|
|
|
|
main::debug "=====Patch data======";
|
|
my @patch_data_array = (
|
|
141541,
|
|
540867,
|
|
141541,
|
|
543427,
|
|
141541,
|
|
553155,
|
|
141541,
|
|
577731,
|
|
141541,
|
|
579779,
|
|
141541,
|
|
581315,
|
|
141541,
|
|
592579,
|
|
141541,
|
|
633027,
|
|
141541,
|
|
637635,
|
|
141541,
|
|
639683,
|
|
141541,
|
|
650947,
|
|
141541,
|
|
651459,
|
|
141541,
|
|
651971,
|
|
141541,
|
|
652483,
|
|
141541,
|
|
652995,
|
|
141541,
|
|
653507,
|
|
736,
|
|
491712,
|
|
452200,
|
|
141541,
|
|
491200,
|
|
5733,
|
|
524290,
|
|
142565,
|
|
550083,
|
|
3685,
|
|
519266,
|
|
5220,
|
|
144098,
|
|
550083,
|
|
3685,
|
|
524291,
|
|
141541,
|
|
551619,
|
|
5221,
|
|
3682,
|
|
524292,
|
|
5,
|
|
141541,
|
|
135362,
|
|
98021,
|
|
727745,
|
|
474213,
|
|
17637,
|
|
557251,
|
|
101093,
|
|
557251,
|
|
473701,
|
|
515653,
|
|
843365,
|
|
188002,
|
|
843355,
|
|
145125,
|
|
560835,
|
|
524290,
|
|
660069,
|
|
518053,
|
|
517224,
|
|
518244,
|
|
142565,
|
|
564419,
|
|
524288,
|
|
521733,
|
|
843365,
|
|
188002,
|
|
524315,
|
|
145125,
|
|
568003,
|
|
843365,
|
|
522850,
|
|
523387,
|
|
147685,
|
|
573123,
|
|
522363,
|
|
145125,
|
|
575171,
|
|
521826,
|
|
141541,
|
|
575683,
|
|
518757,
|
|
521826,
|
|
141541,
|
|
575683,
|
|
521824,
|
|
522245,
|
|
522338,
|
|
141541,
|
|
716481,
|
|
173669,
|
|
523845,
|
|
141541,
|
|
730304,
|
|
523877,
|
|
141541,
|
|
690368,
|
|
614117,
|
|
588995,
|
|
457221,
|
|
558181,
|
|
457122,
|
|
457333,
|
|
143077,
|
|
588995,
|
|
144608,
|
|
587971,
|
|
524292,
|
|
141541,
|
|
588483,
|
|
524304,
|
|
671746,
|
|
558181,
|
|
410018,
|
|
437365,
|
|
143586,
|
|
100034,
|
|
141541,
|
|
98498,
|
|
550117,
|
|
619715,
|
|
558181,
|
|
410018,
|
|
403061,
|
|
143077,
|
|
619715,
|
|
524290,
|
|
143589,
|
|
608963,
|
|
402533,
|
|
524290,
|
|
400901,
|
|
29189,
|
|
431717,
|
|
408133,
|
|
432741,
|
|
406085,
|
|
392805,
|
|
407621,
|
|
792165,
|
|
405573,
|
|
406629,
|
|
792133,
|
|
408677,
|
|
431680,
|
|
432645,
|
|
409189,
|
|
392785,
|
|
402949,
|
|
141541,
|
|
630979,
|
|
560741,
|
|
400482,
|
|
398852,
|
|
143077,
|
|
615107,
|
|
402533,
|
|
398946,
|
|
400901,
|
|
29186,
|
|
400389,
|
|
141541,
|
|
630979,
|
|
400997,
|
|
262242,
|
|
143077,
|
|
618691,
|
|
524291,
|
|
400901,
|
|
29189,
|
|
141541,
|
|
630979,
|
|
558181,
|
|
407458,
|
|
524309,
|
|
694789,
|
|
558085,
|
|
694789,
|
|
403045,
|
|
524290,
|
|
143077,
|
|
630979,
|
|
405605,
|
|
792133,
|
|
408165,
|
|
431685,
|
|
406117,
|
|
432709,
|
|
407653,
|
|
392768,
|
|
402949,
|
|
694789,
|
|
560645,
|
|
694789,
|
|
743525,
|
|
119426,
|
|
141541,
|
|
1003201,
|
|
560741,
|
|
524290,
|
|
143584,
|
|
636099,
|
|
141541,
|
|
191682,
|
|
694789,
|
|
141541,
|
|
859842,
|
|
171109,
|
|
170565,
|
|
141541,
|
|
963776,
|
|
524291,
|
|
144613,
|
|
641731,
|
|
199685,
|
|
667365,
|
|
644803,
|
|
431717,
|
|
197189,
|
|
136805,
|
|
198725,
|
|
170597,
|
|
262242,
|
|
524291,
|
|
144613,
|
|
647875,
|
|
170501,
|
|
667365,
|
|
886464,
|
|
136805,
|
|
180293,
|
|
141541,
|
|
886464,
|
|
524293,
|
|
524293,
|
|
524293,
|
|
524293,
|
|
524293,
|
|
524293,
|
|
);
|
|
|
|
write_ram('*', 1358, 0x00000000, 3); # PRAM_ADDR, reset the patch RAM address
|
|
foreach my $val (@patch_data_array) {
|
|
write_ram('*', 1359, $val, 12); # PRAM_DATA, shl 12, addr auto inc
|
|
}
|
|
|
|
main::debug "=====Patch entries======"; # lowest 8 entries are registers
|
|
my @patch_entries_array = (
|
|
950,
|
|
4347,
|
|
3431,
|
|
1425,
|
|
1347,
|
|
4287,
|
|
4006,
|
|
4469,
|
|
1881,
|
|
1720,
|
|
);
|
|
|
|
my $jmp_table_low = 82;
|
|
my $jmp_table_high = 1597;
|
|
foreach my $val (@patch_entries_array) {
|
|
last if $val == 0;
|
|
if ($jmp_table_low <= 97) {
|
|
write_reg('*', $jmp_table_low, $val & 0xFF);# JMPnLO
|
|
$jmp_table_low++;
|
|
write_reg('*', $jmp_table_low, $val >> 8 );# JMPnHI
|
|
$jmp_table_low++;
|
|
} else {
|
|
write_ram('*', $jmp_table_high, $val & 0x1FFF, 3); # shl 3
|
|
$jmp_table_high++;
|
|
}
|
|
}
|
|
|
|
write_ram('*', 448, 0x06182014, 3); # PATCH_ID, shl 3, a unique code which is a hash of the patch.
|
|
|
|
main::debug "=====Patch support======";
|
|
my @patch_support_addr_array = (
|
|
800,
|
|
795,
|
|
799,
|
|
798,
|
|
794,
|
|
787,
|
|
786,
|
|
782,
|
|
892,
|
|
893,
|
|
925,
|
|
926,
|
|
1014,
|
|
1020,
|
|
1021,
|
|
1022,
|
|
333,
|
|
334,
|
|
352,
|
|
);
|
|
my @patch_support_data_array = (
|
|
0x200000,
|
|
0x7D80000,
|
|
0x69580EA,
|
|
0x82C58C,
|
|
0x1500000,
|
|
0x0,
|
|
0x320000,
|
|
0x0,
|
|
0x400000,
|
|
0x0,
|
|
0xA00000,
|
|
0x1F00000,
|
|
0x2D8000,
|
|
0x0,
|
|
0x2075F60,
|
|
0x220335B,
|
|
0x0,
|
|
0x0,
|
|
0x0,
|
|
);
|
|
for (my $i = 0; $i < @patch_support_addr_array; $i++) {
|
|
my $addr = $patch_support_addr_array[$i];
|
|
my $val = $patch_support_data_array[$i];
|
|
write_ram('*', $addr, $val, 3);
|
|
}
|
|
|
|
if ($settings{debug}) {
|
|
foreach $slic (@slics) {
|
|
main::debug "=====Verify patch=======";
|
|
my $read_val;
|
|
# PRAM_ADDR, reset the patch RAM address:
|
|
write_ram($slic, 1358, 0x00000000, 3);
|
|
foreach my $val (@patch_data_array) {
|
|
# PRAM_DATA, shr 12, addr auto inc
|
|
$read_val = read_reg($slic, 1359, 'R', 12);
|
|
if ($val != $read_val) {
|
|
printf "0x%X =? 0x%X\n", $val, $read_val;
|
|
printf "patch verification failed\n";
|
|
exit 0;
|
|
}
|
|
}
|
|
main::debug "Patch has been verified!";
|
|
|
|
main::debug "=====Verify patch entries=======";
|
|
$jmp_table_low = 82;
|
|
$jmp_table_high = 1597;
|
|
foreach my $val (@patch_entries_array) {
|
|
last if $val == 0;
|
|
if ($jmp_table_low <= 97) {
|
|
$read_val = read_reg($slic, $jmp_table_low, 'D');# JMPnLO
|
|
$jmp_table_low++;
|
|
$read_val |= read_reg($slic, $jmp_table_low, 'D') << 8;# JMPnHI
|
|
$jmp_table_low++;
|
|
} else {
|
|
$read_val = read_reg($slic, $jmp_table_high, 'R', 3); # PRAM_DATA, shr 3
|
|
$read_val &= 0x1FFF;
|
|
$jmp_table_high++;
|
|
}
|
|
if ($val != $read_val) {
|
|
printf "0x%X =? 0x%X\n", $val, $read_val;
|
|
printf "patch entries verification failed\n";
|
|
exit 0;
|
|
}
|
|
}
|
|
main::debug "Patch entries has been verified!";
|
|
|
|
main::debug "=====Verify patch support=======";
|
|
for (my $i = 0; $i < @patch_support_addr_array; $i++) {
|
|
my $addr = $patch_support_addr_array[$i];
|
|
my $val = $patch_support_data_array[$i];
|
|
$read_val = read_reg($slic, $addr, 'R', 3);
|
|
if ($val != $read_val) {
|
|
printf "0x%X =? 0x%X\n", $val, $read_val;
|
|
printf "Patch support verification failed\n";
|
|
exit 0;
|
|
}
|
|
}
|
|
main::debug "patch support has been verified!";
|
|
}
|
|
}
|
|
|
|
write_reg('*', 81, 0x01); # JMPEN, enable the patch
|
|
write_reg('*', 05, 0x00); # RAM_ADDR_HI, back to normal RAM access
|
|
foreach $slic (@slics) {
|
|
FXS::set_user_mode($slic, 0); # Turn off user mode
|
|
}
|
|
}
|
|
|
|
sub load_general_params($) {
|
|
my $slics_ref = shift;
|
|
my @slics = @{ $slics_ref };
|
|
my $slic;
|
|
|
|
main::debug "Loading patch based on si3226x_patch_C_TSS_ISO_2014JUN18.c";
|
|
foreach $slic (@slics) {
|
|
FXS::set_user_mode($slic, 1); # Turn on user mode
|
|
}
|
|
|
|
write_reg('*', 47, 0x00); # ENHANCE
|
|
write_reg('*', 80, 0x2F); # AUTO
|
|
|
|
write_ram('*', 764, 0x0050C480, 3); #BAT_HYST
|
|
# Ringing parameters 65Vrms 0Vdc 20Hz LPR
|
|
# Loop = 500.0 ft @ 0.044 ohms/ft, REN = 5, Rcpe = 600 ohms
|
|
# Rprot = 30 ohms, Type = LPR, Waveform = SINE
|
|
write_ram('*', 637, 0x15E5200E, 3); #ringing impedance = 100 ohms
|
|
write_ram('*', 766, 0xA3D705, 3); #VBATL_EXPECT 10 V
|
|
write_ram('*', 767, 0xA3D705, 3); #VBATH_EXPECT 10 V
|
|
write_ram('*', 768, 0x5ED285F, 3); #VBATR_EXPECT 92.6 V
|
|
write_ram('*', 768, 0xFFF0000, 3); #PWRSAVE_TIMER
|
|
write_ram('*', 916, 0x0F5C28F, 3); #OFFHOOK_THRESH
|
|
write_ram('*', 919, 0x00F00000, 3); #VBAT_TRACK_MIN
|
|
write_ram('*', 920, 0x02000000, 3); #VBAT_TRACK_MIN_RNG
|
|
write_ram('*', 970, 0x00800000, 3); #THERM_DBI
|
|
|
|
write_ram('*', 1004, 0x00F18900, 3); #DCDC_VERR
|
|
write_ram('*', 1005, 0x0080C480, 3); #DCDC_VERR_HYST
|
|
write_ram('*', 1006, 0x00800000, 3); #DCDC_OITHRESH_LO
|
|
write_ram('*', 1007, 0x01F00000, 3); #DCDC_OITHRESH_HI
|
|
write_ram('*', 1540, 0x00200000, 3); #PD_UVLO
|
|
write_ram('*', 1541, 0x00300000, 3); #PD_OVLO
|
|
write_ram('*', 1542, 0x00200000, 3); #PD_OCLO
|
|
|
|
write_ram('*', 1545, 0x00C00000, 3); #DCDC_UVHYST, (94v - 90v -1) / 0.257v = 12
|
|
write_ram('*', 1546, 0x03D00000, 3); #DCDC_UVTHRESH, 94v / 1.543v = 61
|
|
write_ram('*', 1547, 0x1200000, 3); #DCDC_OVTHRESH
|
|
|
|
write_ram('*', 1554, 0x07700000, 3); #DCDC_UVPOL
|
|
|
|
write_ram('*', 1558, 0x00000000, 3); #DCDC_VREF_CTRL
|
|
write_ram('*', 1560, 0x00200000, 3); #DCDC_RNGTYPE
|
|
write_ram('*', 1585, 0x00300000, 3); #DCDC_ANA_GAIN
|
|
write_ram('*', 1586, 0x00300000, 3); #DCDC_ANA_TOFF
|
|
write_ram('*', 1587, 0x00100000, 3); #DCDC_ANA_TONMIN
|
|
write_ram('*', 1588, 0x00FFC000, 3); #DCDC_ANA_TONMAX
|
|
write_ram('*', 1589, 0x00F00000, 3); #DCDC_ANA_DSHIFT
|
|
write_ram('*', 1590, 0x0FDA4000, 3); #DCDC_ANA_LPOLY
|
|
|
|
write_ram('*', 759, 0x07FEB800, 3); # COEF_P_HVIC
|
|
write_ram('*', 756, 0x0048D15B, 3); # P_TH_HVIC
|
|
|
|
write_ram('*', 967, 0x03A2E8BA, 3); # SCALE_KAUDIO
|
|
|
|
# /* GC RAM locations that moved from RevB to RevC */
|
|
write_ram('*', 1018, 0x03000000, 3); # LKG_OFHK_OFFSET
|
|
write_ram('*', 1017, 0x05000000, 3); # LKG_LB_OFFSET
|
|
write_ram('*', 1013, 0x02200000, 3); # VBATH_DELTA
|
|
write_ram('*', 1012, 0x03700000, 3); # UVTHRESH_MAX
|
|
write_ram('*', 1011, 0x04F80200, 3); # UVTHRESH_SCALE
|
|
write_ram('*', 1010, 0x00A23000, 3); # UVTHRESH_BIAS
|
|
|
|
# /* Hardcoded mods to default settings */
|
|
write_reg('*', 98, 0x80); #PDN
|
|
write_ram('*', 626, 0x723F235, 3); # ROW0_C2
|
|
write_ram('*', 627, 0x57A9804, 3); # ROW1_C2
|
|
write_ram('*', 918, 0x36000, 3); # XTALK_TIMER
|
|
write_ram('*', 1616, 0x1100000, 3); # DCDC_CPUMP_LP_MASK
|
|
|
|
#/* Smart VOV Default Settings - set here in case no ring preset is loaded */
|
|
write_ram('*', 973, 0xFFFFFF, 3); # VOV_DCDC_SLOPE
|
|
write_ram('*', 974, 0xA18937, 3); # VOV_DCDC_OS
|
|
write_ram('*', 975, 0xE49BA5, 3); # VOV_RING_BAT_MAX
|
|
|
|
write_ram('*', 516, 0x10038D, 3); # VDIFFLPF
|
|
write_ram('*', 513, 0x4EDDB9, 3); # ILOOPLPF
|
|
write_ram('*', 514, 0x806D6, 3); # ILONGLPF
|
|
write_ram('*', 517, 0x10059F, 3); # VCMLPF
|
|
write_ram('*', 708, 0xF0000, 3); # CM_SPEEDUP_TIMER
|
|
write_ram('*', 709, 0x106240, 3); # VCM_TH
|
|
|
|
# /* Prevent Ref Osc from powering down in PLL Freerun mode (pd_ref_osc) */
|
|
# write_ram('*', 709, 0x106240, 3); # PWRSAVE_CTRL_LO shall be arithmetic here
|
|
|
|
# batType == VCM_HYST
|
|
write_ram('*', 1565, 0xC00000, 3); #
|
|
write_ram('*', 750, 0x0306280, 3); #VCM_HYST
|
|
write_ram('*', 971, 0x2A00000, 3); #LPR_SCALE
|
|
write_ram('*', 972, 0x061EB80, 3); #LPR_CM_OS
|
|
write_ram('*', 1565, 0x0A00000, 3); #DCDC_OIMASK
|
|
write_ram('*', 1643, 0x000000, 3); #DCDC_OV_DEBOUNCE
|
|
write_ram('*', 1641, 0x0D00000, 3); #DCDC_UV_DEBOUNCE
|
|
|
|
write_ram('*', 1512, 0x00200000, 3); # PD_OFFLD_DAC
|
|
write_ram('*', 1513, 0x00200000, 3); # PD_OFFLD_GM
|
|
write_ram('*', 1592, 0x0300000, 3); #DCDC_PD_ANA
|
|
write_ram('*', 897, 0x0480CBF, 3); #P_TH_OFFLOAD
|
|
|
|
write_reg('*', 35, 0x03); #OFFLOAD
|
|
|
|
write_ram('*', 1553, 0x00000000, 3); #DCDC_SWDRV_POL
|
|
write_ram('*', 860, 0x008B9ACA, 3); # IRING_LIM (90.000 mA)
|
|
|
|
foreach $slic (@slics) {
|
|
FXS::set_user_mode($slic, 0); # Turn on user mode
|
|
}
|
|
|
|
# Loading Coeff before cal to ensure accurate zsynth results in OHT
|
|
# This set of coefficients are for the following input parameters:
|
|
# Device = Si3217x, R1 = 600, R2 = 0, C = 0, R_surge = 20, R_fuse = 24, 10.txt
|
|
# Generated on 9/30/2009 2:51:17 PM
|
|
# TXACEQ
|
|
write_ram('*', 540, 0x07F55480, 3); #TXACEQ_CO
|
|
write_ram('*', 541, 0x000D6400, 3); #TXACEQ_C1
|
|
write_ram('*', 542, 0x00011A00, 3); #TXACEQ_C2
|
|
write_ram('*', 543, 0x1FFD7F00, 3); #TXACEQ_C3
|
|
|
|
# RXACEQ
|
|
write_ram('*', 546, 0x07F04900, 3); #RXACEQ_CO
|
|
write_ram('*', 547, 0x00126A00, 3); #RXACEQ_C1
|
|
write_ram('*', 548, 0x1FFE1D00, 3); #RXACEQ_C2
|
|
write_ram('*', 549, 0x1FFC9480, 3); #RXACEQ_C3
|
|
|
|
# ECFIR/ECIIR
|
|
write_ram('*', 563, 0x0012A580, 3); #ECFIR_C2
|
|
write_ram('*', 564, 0x1FE59900, 3); #ECFIR_C3
|
|
write_ram('*', 565, 0x01BCB180, 3); #ECFIR_C4
|
|
write_ram('*', 566, 0x00790780, 3); #ECFIR_C5
|
|
write_ram('*', 567, 0x02113380, 3); #ECFIR_C6
|
|
write_ram('*', 568, 0x1F172380, 3); #ECFIR_C7
|
|
write_ram('*', 569, 0x00805080, 3); #ECFIR_C8
|
|
write_ram('*', 570, 0x1FD6E600, 3); #ECFIR_C9
|
|
write_ram('*', 571, 0x1FFDF800, 3); #ECIIR_B0
|
|
write_ram('*', 572, 0x00010980, 3); #ECIIR_B1
|
|
write_ram('*', 573, 0x0E46D280, 3); #ECIIR_A1
|
|
write_ram('*', 574, 0x19B4F900, 3); #ECIIR_A2
|
|
|
|
# ZSYNTH
|
|
write_ram('*', 653, 0x007A8C00, 3); #ZSYNTH_B0
|
|
write_ram('*', 654, 0x1F0BB880, 3); #ZSYNTH_B1
|
|
write_ram('*', 655, 0x0079BD00, 3); #ZSYNTH_B2
|
|
write_ram('*', 656, 0x0FF66D00, 3); #ZSYNTH_A1
|
|
write_ram('*', 657, 0x18099080, 3); #ZSYNTH_A2
|
|
|
|
write_reg('*', 45, 0x59); #RA
|
|
|
|
# TXGAIN/RXGAIN
|
|
write_ram('*', 544, 0x08769A80, 3); #TXACGAIN
|
|
write_ram('*', 545, 0x0141E080, 3); #RXACGAIN
|
|
write_ram('*', 906, 0x0141E080, 3); #RXACGAIN_SAVE
|
|
|
|
# RXACHPF
|
|
write_ram('*', 658, 0x07AC0400, 3); #RXACHPF_B0_1
|
|
write_ram('*', 659, 0x1853FC80, 3); #RXACHPF_B1_1
|
|
write_ram('*', 660, 0x07580880, 3); #RXACHPF_A1_1
|
|
|
|
write_ram('*', 666, 0x8000000, 3); #RXACHPF_GAIN
|
|
|
|
foreach $slic (@slics) {
|
|
FXS::set_user_mode($slic, 1); # Turn on user mode
|
|
}
|
|
write_reg('*', 98, 0x80); # Power up MADC
|
|
foreach $slic (@slics) {
|
|
FXS::set_user_mode($slic, 0); # Turn off user mode
|
|
}
|
|
}
|
|
|
|
sub calibrate_slics($$$) {
|
|
my $cal0 = shift;
|
|
my $cal1 = shift;
|
|
my $cal2 = shift;
|
|
|
|
main::debug "Calibrating channels @SlicNums";
|
|
write_reg('*', 26, $cal0); # CAL0
|
|
write_reg('*', 27, $cal1); # CAL1
|
|
write_reg('*', 28, $cal2); # CAL2
|
|
write_reg('*', 29, 0x80); # CAL3, CAL_EN
|
|
|
|
# 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) {
|
|
my $val = read_reg($slic, 29, 'D');
|
|
main::debug("checking slic $slic: $val.");
|
|
push(@next_slics, $slic) if ($val & 0x80) != 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 dc_powerup($) {
|
|
my $slics_ref = shift;
|
|
my @slics = @{ $slics_ref };
|
|
|
|
foreach my $slic (@slics) {
|
|
FXS::set_user_mode($slic, 1); # Turn on user mode
|
|
}
|
|
write_ram('*', 1538, 0x700000, 3); # PD_DCDC, In case OV or UV previously occurred
|
|
# write_ram('*', 1555, 0x100000, 3); # DCDC_CPUMP, Turn on charge pump
|
|
main::mysleep(0.010);
|
|
# write_ram('*', 1538, 0x00600000, 3); # start up converter
|
|
main::mysleep(0.500);
|
|
write_ram('*', 1555, 0x000000, 3); # DCDC_CPUMP, Turn off charge pump
|
|
write_ram('*', 1542, 0x300000, 3); # PD_OCLO
|
|
write_ram('*', 1551, 0x00000000, 3); # sClear DCDC status
|
|
main::mysleep(0.030);
|
|
write_ram('*', 1538, 0x00400000, 3);
|
|
foreach my $slic (@slics) {
|
|
FXS::set_user_mode($slic, 0); # Turn off user mode
|
|
}
|
|
}
|
|
|
|
sub read_defaults() {
|
|
if(XppConfig::read_config(\%settings)) {
|
|
main::logit "Defaults from $settings{xppconf}";
|
|
} else {
|
|
main::logit "No defaults file, use hard-coded defaults.";
|
|
}
|
|
}
|
|
|
|
sub soft_reset() {
|
|
write_reg('*', 1, 0x03); # RESET
|
|
}
|
|
|
|
# Try to identify which slics are valid
|
|
sub check_slics() {
|
|
my @slics;
|
|
main::debug "check_slics()";
|
|
foreach my $slic (0 .. 7) {
|
|
my $value = read_reg($slic, 0, 'D');
|
|
#main::logit sprintf "Slic($slic): RD 00 0x%X\n", $value;
|
|
next if($value != 0xC3);
|
|
write_reg($slic, 14, 0x40);
|
|
$value = read_reg($slic, 14, 'D');
|
|
#main::logit sprintf "Slic($slic): RD 14 0x%X\n", $value;
|
|
next if($value != 0x40);
|
|
$value = read_reg($slic, 3, 'D') & 0x1F;
|
|
#main::logit sprintf "Slic($slic): RD 03 0x%X\n", $value;
|
|
push(@slics, $slic) if $value == 0x1F;
|
|
}
|
|
main::logit "Found " . scalar(@slics) . " SLICS (@slics)";
|
|
return @slics;
|
|
}
|
|
|
|
sub load_custom_preset($) {
|
|
my $slics_ref = shift;
|
|
my @slics = @{ $slics_ref };
|
|
my $slic;
|
|
|
|
main::debug "Loading patch based on si3226x_patch_C_TSS_ISO_2014JUN18.c";
|
|
foreach $slic (@slics) {
|
|
FXS::set_user_mode($slic, 1); # Turn on user mode
|
|
}
|
|
write_ram('*', 755, 0x00050000, 3); #RTPER
|
|
write_ram('*', 844, 0x7EFE000, 3); #RINGFR = 20Hz
|
|
write_ram('*', 845, 0x00208847, 3); #RINGAMP = 53 Vrms open circuit with 100ohm ring impedance
|
|
write_ram('*', 846, 0x00000000, 3); #RINGPHAS
|
|
write_ram('*', 843, 0x00000000, 3); #RINGOF
|
|
write_ram('*', 637, 0x15E5200E, 3); #SLOPE_RING (100.000 ohms)
|
|
write_ram('*', 848, 0x007B9068, 3); #RTACTH (68.236 mA)
|
|
write_ram('*', 847, 0x0FFFFFFF, 3); #RTDCTH (450.000 mA) */
|
|
write_ram('*', 850, 0x00006000, 3); #RTACDB (75.000 ms)
|
|
write_ram('*', 849, 0x00006000, 3); #RTDCDB (75.000 ms)
|
|
write_ram('*', 753, 0x00C49BA0, 3); # VOV_RING_BAT (12.000 v)
|
|
write_ram('*', 896, 0x00000000, 3); # VOV_RING_GND (0.000 v)
|
|
write_ram('*', 975, 0xE49BA5, 3);#vov_ring_bat_max = ~14V
|
|
|
|
write_reg('*', 39, 0x80); #RINGTALO
|
|
write_reg('*', 40, 0x3E); #RINGTAHI
|
|
write_reg('*', 41, 0x00); #RINGTILO
|
|
write_reg('*', 42, 0x7D); #RINGTIHI
|
|
write_reg('*', 38, 0x80); #RINGCON Both timers are disabled; enable LPR
|
|
write_ram('*', 483, 0x28C000, 3);#delta_vcm: This means ring side needs 2v extra.
|
|
write_ram('*', 973, 0xFFFFFF, 3);# slope_vov_dcdc
|
|
write_ram('*', 974, 0xA18937, 3);# vov_dcdc_os = ~10v
|
|
write_ram('*', 509, 0x00C49BA0, 3);# VOV_RING_BAT (12.000 v)
|
|
write_ram('*', 975, 0xE49BA5, 3);#vov_ring_bat_max = ~14V
|
|
write_ram('*', 971, 0x2A00000, 3);# scale for LPR amplitude, full scale 28-bit
|
|
write_ram('*', 972, 0x61EB80, 3);# 6v CM offset
|
|
write_ram('*', 970, 0x800000, 3);#
|
|
|
|
|
|
# DC_FEED:
|
|
write_ram('*', 634, 0x1C8A024C, 3); # SLOPE_VLIM
|
|
write_ram('*', 635, 0x1E3081AA, 3); # SLOPE_RFEED
|
|
write_ram('*', 636, 0x0040A0E0, 3); # SLOPE_ILIM
|
|
write_ram('*', 638, 0x1B27130B, 3); # SLOPE_DELTA1
|
|
write_ram('*', 639, 0x1DD87A3E, 3); # SLOPE_DELTA2
|
|
write_ram('*', 640, 0x034A1036, 3); # V_VLIM (28.000 v)
|
|
write_ram('*', 641, 0x03A446AC, 3); # V_RFEED (31.000 v)
|
|
write_ram('*', 642, 0x034A0E48, 3); # V_ILIM (28.000 v)
|
|
write_ram('*', 643, 0x01363429, 3); # CONST_RFEED (15.000 mA)
|
|
write_ram('*', 644, 0x0045CBC2, 3); # CONST_ILIM = 15 mA
|
|
write_ram('*', 645, 0x00222A30, 3); # I_VLIM = 0.000 mA
|
|
write_ram('*', 853, 0x005B0AFB, 3); # LCRONHK (10.000 mA)
|
|
write_ram('*', 852, 0x006D4060, 3); # LCROFFHK (12.000 mA)
|
|
write_ram('*', 701, 0x00008000, 3); # LCRDBI (5.000 ms)
|
|
write_ram('*', 858, 0x0048D595, 3); # LONGHITH (8.000 mA)
|
|
write_ram('*', 859, 0x003FBAE2, 3); # LONGLOTH (7.000 mA)
|
|
write_ram('*', 702, 0x00008000, 3); # LONGDBI (5.000 ms)
|
|
write_ram('*', 854, 0x000F0000, 3); # LCRMASK (150.000 ms)
|
|
write_ram('*', 855, 0x00080000, 3); # LCRMASK_POLREV (80.000 ms)
|
|
write_ram('*', 856, 0x00140000, 3); # LCRMASK_STATE (200.000 ms)
|
|
write_ram('*', 857, 0x00140000, 3); # LCRMASK_LINECAP (200.000 ms)
|
|
write_ram('*', 748, 0x01BA5E35, 3); # VCM_OH (27.000 v)
|
|
write_ram('*', 752, 0x0051EB85, 3); # VOV_BAT (5.000 v)
|
|
write_ram('*', 751, 0x00415F45, 3); # VOV_GND (3.990 v)
|
|
|
|
write_reg('*', 46, 0x04); # zcal_en
|
|
write_reg('*', 23, 0x10); # DTMF_EN = 1 Enable Interupts in IRQ2 Register 19
|
|
write_reg('*', 24, 0x03); # P_HVIC_IE = 1, P_THERM_IE = 1 Enable Interupts in IRQ3 Register 20
|
|
|
|
write_reg('*', 66, 0x00); # USERSTAT
|
|
write_reg('*', 71, 0x00); # DIAG1
|
|
write_ram('*', 799, 95 * 1074 * 1000, 3); #PRAM_VBATH_NEON = -95 V
|
|
write_ram('*', 786, 70 * 65536, 3); #PRAM_LCRMASK_MWI = 70 mSec
|
|
|
|
foreach $slic (@slics) {
|
|
FXS::set_linefeed($slic, 1);
|
|
}
|
|
write_ram('*', 918, 0x36000, 3);
|
|
|
|
foreach $slic (@slics) {
|
|
FXS::set_user_mode($slic, 1); # Turn on user mode
|
|
}
|
|
write_ram('*', 1018, 0x03000000, 3); # LKG_OFHK_OFFSET
|
|
write_ram('*', 1017, 0x05000000, 3); # LKG_LB_OFFSET
|
|
|
|
foreach $slic (@slics) {
|
|
if (($slic & 0x01) == 0) {
|
|
FXS::set_linefeed($slic, 0);
|
|
write_ram($slic, 1538, 0x00600000, 3);
|
|
} else {
|
|
write_ram($slic & 0xFE, 1538, 0x00400000, 3);
|
|
}
|
|
FXS::set_user_mode($slic, 0); # Turn off user mode
|
|
}
|
|
}
|
|
|
|
sub configure_pcm($) {
|
|
my $slic = shift;
|
|
main::debug "Configure_pcm()";
|
|
my $pcm_offset = $slic * 8;
|
|
write_reg($slic, 12, $pcm_offset); #PCMTXLO
|
|
write_reg($slic, 13, 0x00); #PCMTXHI
|
|
write_reg($slic, 14, $pcm_offset); #PCMTXLO
|
|
write_reg($slic, 15, 0x00); #PCMTXHI
|
|
|
|
write_reg($slic, 11, 0x11); #PCMMODE, u-Law
|
|
|
|
# write_reg($slic, 43, 0x00); #LOOPBACK
|
|
# write_reg($slic, 44, 0x00); #DIGCON
|
|
}
|
|
|
|
sub set_linefeed($$) {
|
|
my $slic = shift;
|
|
my $lfd = shift;
|
|
write_reg($slic, 30, $lfd); #LINEFEED
|
|
}
|
|
|
|
sub overwrite_ring_registers() {
|
|
}
|
|
|
|
package main;
|
|
|
|
main::logit "Starting '$0' (@SlicNums)\n";
|
|
|
|
FXS::read_defaults();
|
|
FXS::soft_reset();
|
|
@SlicNums = FXS::check_slics();
|
|
FXS::init_early();
|
|
FXS::load_patch(\@SlicNums);
|
|
FXS::load_general_params(\@SlicNums);
|
|
|
|
if($settings{fxs_skip_calib}) {
|
|
main::logit "==== WARNING: SKIPPED SLIC CALIBRATION =====";
|
|
} else {
|
|
FXS::calibrate_slics(0x00, 0x00, 0x01);
|
|
main::mysleep(0.060);
|
|
FXS::dc_powerup(\@SlicNums);
|
|
FXS::calibrate_slics(0x00, 0xC0, 0x18); # remaining cals (except mads, lb)
|
|
main::mysleep(0.700);
|
|
foreach my $slic (@SlicNums) {
|
|
FXS::set_linefeed($slic, 0);
|
|
FXS::set_linefeed($slic, 1);
|
|
}
|
|
main::mysleep(1.000);
|
|
|
|
}
|
|
FXS::load_custom_preset(\@SlicNums);
|
|
foreach my $slic (@SlicNums) {
|
|
FXS::configure_pcm($slic);
|
|
FXS::set_linefeed($slic, 1);
|
|
}
|
|
|
|
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;
|
|
FXS::overwrite_ring_registers();
|
|
|
|
main::debug "Ending '$0'";
|
|
close STDERR;
|
|
exit 0;
|
|
|
|
# ----------------------------------==== 8-channel FXS unit initialization ===-----------------------------------------
|
|
|
|
__DATA__
|