Revert "Remove support for xpp drivers."
authorKeith Morgan <kmorgan@digium.com>
Thu, 4 Oct 2018 17:51:54 +0000 (17:51 +0000)
committerKeith Morgan <kmorgan@digium.com>
Thu, 4 Oct 2018 17:51:54 +0000 (17:51 +0000)
add them back.
This reverts commit a36d2662546e56c5be44e610bd5fd9deed57a9bb.

68 files changed:
drivers/dahdi/Kbuild
drivers/dahdi/Kconfig
drivers/dahdi/xpp/Changelog_xpp [new file with mode: 0644]
drivers/dahdi/xpp/Kbuild [new file with mode: 0644]
drivers/dahdi/xpp/Kconfig [new file with mode: 0644]
drivers/dahdi/xpp/Makefile [new file with mode: 0644]
drivers/dahdi/xpp/XppConfig.pm [new file with mode: 0644]
drivers/dahdi/xpp/card_bri.c [new file with mode: 0644]
drivers/dahdi/xpp/card_bri.h [new file with mode: 0644]
drivers/dahdi/xpp/card_echo.c [new file with mode: 0644]
drivers/dahdi/xpp/card_echo.h [new file with mode: 0644]
drivers/dahdi/xpp/card_fxo.c [new file with mode: 0644]
drivers/dahdi/xpp/card_fxo.h [new file with mode: 0644]
drivers/dahdi/xpp/card_fxs.c [new file with mode: 0644]
drivers/dahdi/xpp/card_fxs.h [new file with mode: 0644]
drivers/dahdi/xpp/card_global.c [new file with mode: 0644]
drivers/dahdi/xpp/card_global.h [new file with mode: 0644]
drivers/dahdi/xpp/card_pri.c [new file with mode: 0644]
drivers/dahdi/xpp/card_pri.h [new file with mode: 0644]
drivers/dahdi/xpp/dahdi_debug.c [new file with mode: 0644]
drivers/dahdi/xpp/dahdi_debug.h [new file with mode: 0644]
drivers/dahdi/xpp/firmwares/FPGA_1141.hex [new file with mode: 0644]
drivers/dahdi/xpp/firmwares/FPGA_1151.hex [new file with mode: 0644]
drivers/dahdi/xpp/firmwares/FPGA_1161.201.hex [new file with mode: 0644]
drivers/dahdi/xpp/firmwares/FPGA_1161.202.hex [new symlink]
drivers/dahdi/xpp/firmwares/FPGA_1161.hex [new file with mode: 0644]
drivers/dahdi/xpp/firmwares/FPGA_FXS.hex [new file with mode: 0644]
drivers/dahdi/xpp/firmwares/LICENSE.firmware [new file with mode: 0644]
drivers/dahdi/xpp/firmwares/Makefile [new file with mode: 0644]
drivers/dahdi/xpp/firmwares/PIC_TYPE_1.hex [new file with mode: 0644]
drivers/dahdi/xpp/firmwares/PIC_TYPE_2.hex [new file with mode: 0644]
drivers/dahdi/xpp/firmwares/PIC_TYPE_3.hex [new file with mode: 0644]
drivers/dahdi/xpp/firmwares/PIC_TYPE_4.hex [new file with mode: 0644]
drivers/dahdi/xpp/firmwares/PIC_TYPE_6.hex [new file with mode: 0644]
drivers/dahdi/xpp/firmwares/README [new file with mode: 0644]
drivers/dahdi/xpp/firmwares/USB_FW.201.hex [new file with mode: 0644]
drivers/dahdi/xpp/firmwares/USB_FW.202.hex [new symlink]
drivers/dahdi/xpp/firmwares/USB_FW.hex [new file with mode: 0644]
drivers/dahdi/xpp/firmwares/USB_RECOV.hex [new file with mode: 0644]
drivers/dahdi/xpp/init_card_1_30 [new file with mode: 0755]
drivers/dahdi/xpp/init_card_2_30 [new file with mode: 0755]
drivers/dahdi/xpp/init_card_3_30 [new file with mode: 0755]
drivers/dahdi/xpp/init_card_4_30 [new file with mode: 0755]
drivers/dahdi/xpp/init_card_5_30 [new file with mode: 0755]
drivers/dahdi/xpp/init_card_6_30 [new file with mode: 0755]
drivers/dahdi/xpp/mmapbus.c [new file with mode: 0644]
drivers/dahdi/xpp/mmapbus.h [new file with mode: 0644]
drivers/dahdi/xpp/mmapdrv.c [new file with mode: 0644]
drivers/dahdi/xpp/param_doc [new file with mode: 0755]
drivers/dahdi/xpp/parport_debug.c [new file with mode: 0644]
drivers/dahdi/xpp/parport_debug.h [new file with mode: 0644]
drivers/dahdi/xpp/print_fxo_modes.c [new file with mode: 0644]
drivers/dahdi/xpp/xbus-core.c [new file with mode: 0644]
drivers/dahdi/xpp/xbus-core.h [new file with mode: 0644]
drivers/dahdi/xpp/xbus-pcm.c [new file with mode: 0644]
drivers/dahdi/xpp/xbus-pcm.h [new file with mode: 0644]
drivers/dahdi/xpp/xbus-sysfs.c [new file with mode: 0644]
drivers/dahdi/xpp/xdefs.h [new file with mode: 0644]
drivers/dahdi/xpp/xframe_queue.c [new file with mode: 0644]
drivers/dahdi/xpp/xframe_queue.h [new file with mode: 0644]
drivers/dahdi/xpp/xpd.h [new file with mode: 0644]
drivers/dahdi/xpp/xpp.conf [new file with mode: 0644]
drivers/dahdi/xpp/xpp_dahdi.c [new file with mode: 0644]
drivers/dahdi/xpp/xpp_dahdi.h [new file with mode: 0644]
drivers/dahdi/xpp/xpp_debug [new file with mode: 0755]
drivers/dahdi/xpp/xpp_usb.c [new file with mode: 0644]
drivers/dahdi/xpp/xproto.c [new file with mode: 0644]
drivers/dahdi/xpp/xproto.h [new file with mode: 0644]

index 6d9b91c..855e5bf 100644 (file)
@@ -41,6 +41,7 @@ obj-$(DAHDI_BUILD_ALL)$(CONFIG_DAHDI_WCB4XXP)         += wcb4xxp/
 
 endif
 
+obj-$(DAHDI_BUILD_ALL)$(CONFIG_DAHDI_XPP)              += xpp/
 
 obj-$(DAHDI_BUILD_ALL)$(CONFIG_DAHDI_ECHOCAN_JPAH)     += dahdi_echocan_jpah.o
 obj-$(DAHDI_BUILD_ALL)$(CONFIG_DAHDI_ECHOCAN_STEVE)    += dahdi_echocan_sec.o
index 2e9a6db..5b94bf1 100644 (file)
@@ -223,3 +223,4 @@ config DAHDI_DYNAMIC_LOC
          If unsure, say Y.
 
 
+source "drivers/dahdi/xpp/Kconfig"
diff --git a/drivers/dahdi/xpp/Changelog_xpp b/drivers/dahdi/xpp/Changelog_xpp
new file mode 100644 (file)
index 0000000..5408651
--- /dev/null
@@ -0,0 +1,31 @@
+Sun Mar  1 2009 Oron Peled <oron@actcom.co.il> - xpp.r6795
+  * Fix cases where the command_queue overflowed during initialization.
+    - Also add a 'command_queue_length' parameter to xpp.ko
+  * More migrations to sysfs:
+    - Add a 'transport' attribute to our astribank devices which
+      points to the usb device we use. E.g:
+       /sys/bus/astribanks/devices/xbus-00/transport is symlinked to
+       ../../../../../../devices/pci0000:00/0000:00:10.4/usb5/5-4
+    - Move /proc/xpp/XBUS-??/XPD-??/span to
+      /sys/bus/xpds/devices/??:?:?/span
+    - Migrate from /proc/xpp/sync to:
+      /sys/bus/astribanks/drivers/xppdrv/sync
+    - New 'offhook' attribute in:
+      /sys/bus/xpds/devices/??:?:?/offhook
+  * PRI: change the "timing" priority to match the convention used by
+         other PRI cards -- I.e: lower numbers (not 0) have higher
+        priority.
+  * FXO: 
+    - Power denial: create two module parameters instead of hard-coded
+      constants (power_denial_safezone, power_denial_minlen).
+      For sites that get non-standard power-denial signals from central
+      office on offhook.
+    - Don't hangup on power-denial, just notify Dahdi and wait for
+    - Fix caller-id detection for the case central office sends it before
+      first ring without any indication before.
+      Asterisk's desicion.
+  * USB_FW.hex:
+    - Fixes cases where firmware loading would fail.
+
+Thu, Aug 14 2008 Oron Peled <oron@actcom.co.il> - xpp.r6056
+  * First DAHDI-linux release.
diff --git a/drivers/dahdi/xpp/Kbuild b/drivers/dahdi/xpp/Kbuild
new file mode 100644 (file)
index 0000000..2965af0
--- /dev/null
@@ -0,0 +1,81 @@
+EXTRA_CFLAGS   =       $(XPP_LOCAL_CFLAGS)     \
+                       -DDEBUG                 \
+                       -DPOLL_DIGITAL_INPUTS   \
+                       -DDEBUG_PCMTX           \
+                       -DPROTOCOL_DEBUG        \
+                       -g
+                       #
+
+WITH_BRISTUFF  := $(shell grep -c '^[[:space:]]*\#[[:space:]]*define[[:space:]]\+CONFIG_DAHDI_BRI_DCHANS\>' $(src)/../../../include/dahdi/dahdi_config.h)
+
+obj-$(DAHDI_BUILD_ALL)$(CONFIG_DAHDI_XPP)              += xpp.o
+obj-$(DAHDI_BUILD_ALL)$(CONFIG_DAHDI_XPD_FXS)          += xpd_fxs.o
+obj-$(DAHDI_BUILD_ALL)$(CONFIG_DAHDI_XPD_FXO)          += xpd_fxo.o
+obj-$(DAHDI_BUILD_ALL)$(CONFIG_DAHDI_XPD_PRI)          += xpd_pri.o
+obj-$(DAHDI_BUILD_ALL)$(CONFIG_DAHDI_XPD_BRI)          += xpd_bri.o
+obj-$(DAHDI_BUILD_ALL)$(CONFIG_DAHDI_XPD_ECHO)         += xpd_echo.o
+
+# Build only supported modules
+ifneq  (,$(filter y m,$(CONFIG_USB)))
+obj-$(DAHDI_BUILD_ALL)$(CONFIG_XPP_USB)                        += xpp_usb.o
+endif
+ifneq  (,$(filter y m,$(CONFIG_BF537)))
+obj-$(DAHDI_BUILD_ALL)$(CONFIG_XPP_MMAP)               += xpp_mmap.o
+endif
+
+xpp-objs               += xbus-core.o xbus-sysfs.o xbus-pcm.o xframe_queue.o xpp_dahdi.o xproto.o card_global.o dahdi_debug.o
+xpd_fxs-objs           += card_fxs.o
+xpd_fxo-objs           += card_fxo.o
+xpd_bri-objs           += card_bri.o
+xpd_pri-objs           += card_pri.o
+xpd_echo-objs          += card_echo.o
+xpp_mmap-objs          += mmapbus.o mmapdrv.o
+
+ifeq   (y,$(PARPORT_DEBUG))
+EXTRA_CFLAGS   += -DDEBUG_SYNC_PARPORT
+obj-m          += parport_debug.o
+endif
+
+# Just in case it was left from an older version:
+clean-files    += xpp_version.h
+
+# Validations:
+#  - Syntactic verification of perl scripts
+#  - Handle country table validation for init_card_2_*
+
+XPP_PROTOCOL_VERSION   := $(shell grep XPP_PROTOCOL_VERSION $(src)/xproto.h | sed -e 's/^.*XPP_PROTOCOL_VERSION[ \t]*//')
+
+xpp_verifications              = \
+                               init_card_1_$(XPP_PROTOCOL_VERSION)     \
+                               init_card_2_$(XPP_PROTOCOL_VERSION)     \
+                               init_card_3_$(XPP_PROTOCOL_VERSION)     \
+                               init_card_4_$(XPP_PROTOCOL_VERSION)     \
+                               init_fxo_modes
+
+xpp_verified                   = $(foreach file, $(xpp_verifications), $(file).verified)
+
+FXO_MODES  = $(src)/../fxo_modes.h
+FXO_VERIFY = $(obj)/init_card_2_$(XPP_PROTOCOL_VERSION) -v $(obj)/init_fxo_modes
+
+hostprogs-y                    := print_fxo_modes
+always                         := $(xpp_verified)
+print_fxo_modes-objs           := print_fxo_modes.o
+HOSTCFLAGS_print_fxo_modes.o   += -include $(FXO_MODES)
+
+clean-files                    += print_fxo_modes init_fxo_modes $(xpp_verified)
+
+$(obj)/init_fxo_modes: $(obj)/print_fxo_modes
+       @echo '  GEN     $@'
+       $(Q)$(obj)/print_fxo_modes >$@ || (rm -f $@; exit 1)
+
+$(obj)/init_fxo_modes.verified: $(obj)/init_card_2_$(XPP_PROTOCOL_VERSION) $(obj)/init_fxo_modes
+       @echo '  CHECK   $(obj)/init_card_2_$(XPP_PROTOCOL_VERSION)'
+       $(Q)$(FXO_VERIFY) || (rm -f $@; exit 1)
+       $(Q)touch $@
+
+$(obj)/init_card_%_$(XPP_PROTOCOL_VERSION).verified: $(src)/init_card_%_$(XPP_PROTOCOL_VERSION)
+       @echo '  VERIFY  $<'
+       $(Q)perl -c $< 2> $@ || (cat $@; rm -f $@; exit 1)
+
+.PHONY: FORCE
+FORCE:
diff --git a/drivers/dahdi/xpp/Kconfig b/drivers/dahdi/xpp/Kconfig
new file mode 100644 (file)
index 0000000..aebd29c
--- /dev/null
@@ -0,0 +1,82 @@
+#
+# XPP configuration
+#
+
+menuconfig DAHDI_XPP
+       tristate "Xorcom Astribank Support"
+       depends on DAHDI
+       default DAHDI
+       ---help---
+         Infrastructure support for Xorcom Astribank products.
+
+         To compile this driver as a module, choose M here: the
+         module will be called xpp.
+
+         If unsure, say Y.
+
+config DAHDI_XPP_USB
+       tristate "Astribank USB transport"
+       depends on DAHDI_XPP && USB
+       default DAHDI_XPP
+       ---help---
+         To compile this driver as a module, choose M here: the
+         module will be called xpp_usb.
+
+         If unsure, say Y.
+
+config DAHDI_XPP_MMAP
+       tristate "Astribank Blackfin transport"
+       depends on DAHDI_XPP && BF537
+       default DAHDI_XPP
+       ---help---
+         To compile this driver as a module, choose M here: the
+         module will be called xpp_mmap.
+         
+         This module can be compiled only on Blackfin architecture
+         (with uClinux).
+
+         If unsure, say N.
+
+config DAHDI_XPD_FXS
+       tristate "FXS port Support"
+       depends on DAHDI_XPP && (DAHDI_XPP_USB || DAHDI_XPP_MMAP)
+       default DAHDI_XPP
+       ---help---
+         To compile this driver as a module, choose M here: the
+         module will be called xpd_fxs.
+
+         If unsure, say Y.
+
+config DAHDI_XPD_FXO
+       tristate "FXO port Support"
+       depends on DAHDI_XPP && (DAHDI_XPP_USB || DAHDI_XPP_MMAP)
+       default DAHDI_XPP
+       ---help---
+         To compile this driver as a module, choose M here: the
+         module will be called xpd_fxo.
+
+         If unsure, say Y.
+
+config DAHDI_XPD_BRI
+       tristate "BRI port Support"
+       depends on DAHDI_XPP && (DAHDI_XPP_USB || DAHDI_XPP_MMAP)
+       default DAHDI_XPP
+       ---help---
+         To compile this driver as a module, choose M here: the
+         module will be called xpd_pri.
+
+         Note: this driver will be automatically excluded
+               from compilation if dahdi driver does not
+               contain the "bristuff" patch.
+
+         If unsure, say Y.
+
+config DAHDI_XPD_PRI
+       tristate "PRI port Support"
+       depends on DAHDI_XPP && (DAHDI_XPP_USB || DAHDI_XPP_MMAP)
+       default DAHDI_XPP
+       ---help---
+         To compile this driver as a module, choose M here: the
+         module will be called xpd_pri.
+
+         If unsure, say Y.
diff --git a/drivers/dahdi/xpp/Makefile b/drivers/dahdi/xpp/Makefile
new file mode 100644 (file)
index 0000000..00fc5ee
--- /dev/null
@@ -0,0 +1,7 @@
+# We only get here on kernels 2.6.0-2.6.9 .
+# For newer kernels, Kbuild will be included directly by the kernel
+# build system.
+-include $(src)/Kbuild
+
+ctags:
+       ctags *.[ch]
diff --git a/drivers/dahdi/xpp/XppConfig.pm b/drivers/dahdi/xpp/XppConfig.pm
new file mode 100644 (file)
index 0000000..f00811f
--- /dev/null
@@ -0,0 +1,38 @@
+package XppConfig;
+#
+# Written by Oron Peled <oron@actcom.co.il>
+# Copyright (C) 2008, Xorcom
+# This program is free software; you can redistribute and/or
+# modify it under the same terms as Perl itself.
+#
+# $Id$
+#
+use strict;
+
+my $conf_file = "/etc/dahdi/xpp.conf";
+
+sub import {
+       my $pack = shift || die "Import without package?";
+       my $init_dir = shift || die "$pack::import -- missing init_dir parameter";
+       my $local_conf = "$init_dir/xpp.conf";
+       $conf_file = $local_conf if -r $local_conf;
+}
+
+sub read_config($) {
+       my $opts = shift || die;
+
+       open(F, $conf_file) || return ();
+       while(<F>) {
+               chomp;
+               s/#.*//;        # strip comments
+               next unless /\S/;
+               s/\s*$//;       # Trim trailing whitespace
+               my ($key, $value) = split(/\s+/, $_, 2);
+               $opts->{$key} = $value;
+       }
+       close F;
+       $opts->{'xppconf'} = $conf_file;
+       return %{$opts};
+}
+
+1;
diff --git a/drivers/dahdi/xpp/card_bri.c b/drivers/dahdi/xpp/card_bri.c
new file mode 100644 (file)
index 0000000..12efecf
--- /dev/null
@@ -0,0 +1,1808 @@
+/*
+ * Written by Oron Peled <oron@actcom.co.il>
+ * Copyright (C) 2004-2006, Xorcom
+ *
+ * Parts derived from Cologne demo driver for the chip.
+ *
+ * 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.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ *
+ */
+
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/fs.h>
+#include <linux/delay.h>
+#include <linux/seq_file.h>
+#include "xpd.h"
+#include "xproto.h"
+#include "xpp_dahdi.h"
+#include "card_bri.h"
+#include "dahdi_debug.h"
+#include "xbus-core.h"
+
+static const char rcsid[] = "$Id$";
+
+#ifndef        DAHDI_SIG_HARDHDLC
+#error Cannot build BRI without HARDHDLC supprt
+#endif
+
+/* must be before dahdi_debug.h */
+static DEF_PARM(int, debug, 0, 0644, "Print DBG statements");
+static DEF_PARM(uint, poll_interval, 500, 0644,
+               "Poll channel state interval in milliseconds (0 - disable)");
+static DEF_PARM_BOOL(nt_keepalive, 1, 0644,
+                    "Force BRI_NT to keep trying connection");
+
+enum xhfc_states {
+       ST_RESET = 0,           /* G/F0 */
+       /* TE */
+       ST_TE_SENSING = 2,      /* F2   */
+       ST_TE_DEACTIVATED = 3,  /* F3   */
+       ST_TE_SIGWAIT = 4,      /* F4   */
+       ST_TE_IDENT = 5,        /* F5   */
+       ST_TE_SYNCED = 6,       /* F6   */
+       ST_TE_ACTIVATED = 7,    /* F7   */
+       ST_TE_LOST_FRAMING = 8, /* F8   */
+       /* NT */
+       ST_NT_DEACTIVATED = 1,  /* G1   */
+       ST_NT_ACTIVATING = 2,   /* G2   */
+       ST_NT_ACTIVATED = 3,    /* G3   */
+       ST_NT_DEACTIVTING = 4,  /* G4   */
+};
+
+#ifdef CONFIG_PROC_FS
+static const char *xhfc_state_name(bool is_nt, enum xhfc_states state)
+{
+       const char *p;
+
+#define        _E(x)   [ST_ ## x] = #x
+       static const char *te_names[] = {
+               _E(RESET),
+               _E(TE_SENSING),
+               _E(TE_DEACTIVATED),
+               _E(TE_SIGWAIT),
+               _E(TE_IDENT),
+               _E(TE_SYNCED),
+               _E(TE_ACTIVATED),
+               _E(TE_LOST_FRAMING),
+       };
+       static const char *nt_names[] = {
+               _E(RESET),
+               _E(NT_DEACTIVATED),
+               _E(NT_ACTIVATING),
+               _E(NT_ACTIVATED),
+               _E(NT_DEACTIVTING),
+       };
+#undef _E
+       if (is_nt) {
+               if (state > ST_NT_DEACTIVTING)
+                       p = "NT ???";
+               else
+                       p = nt_names[state];
+       } else {
+               if (state > ST_TE_LOST_FRAMING)
+                       p = "TE ???";
+               else
+                       p = te_names[state];
+       }
+       return p;
+}
+#endif
+
+/* xhfc Layer1 physical commands */
+#define HFC_L1_ACTIVATE_TE             0x01
+#define HFC_L1_FORCE_DEACTIVATE_TE     0x02
+#define HFC_L1_ACTIVATE_NT             0x03
+#define HFC_L1_DEACTIVATE_NT           0x04
+
+#define HFC_L1_ACTIVATING      1
+#define HFC_L1_ACTIVATED       2
+#define        HFC_TIMER_T1            2500
+#define        HFC_TIMER_T3            8000    /* 8s activation timer T3 */
+#define        HFC_TIMER_OFF           -1      /* timer disabled */
+
+#define        A_SU_WR_STA             0x30    /* ST/Up state machine register */
+#define                V_SU_LD_STA     0x10
+#define        V_SU_ACT        0x60    /* start activation/deactivation        */
+#define        STA_DEACTIVATE  0x40    /* start deactivation in A_SU_WR_STA */
+#define        STA_ACTIVATE    0x60    /* start activation   in A_SU_WR_STA */
+#define        V_SU_SET_G2_G3  0x80
+
+#define        A_SU_RD_STA             0x30
+typedef union {
+       struct {
+               __u8 v_su_sta:4;
+               __u8 v_su_fr_sync:1;
+               __u8 v_su_t2_exp:1;
+               __u8 v_su_info0:1;
+               __u8 v_g2_g3:1;
+       } bits;
+       __u8 reg;
+} su_rd_sta_t;
+
+#define        REG30_LOST      3       /* in polls */
+#define        DCHAN_LOST      15000   /* in ticks */
+
+#define        BRI_DCHAN_SIGCAP        DAHDI_SIG_HARDHDLC
+#define        BRI_BCHAN_SIGCAP        (DAHDI_SIG_CLEAR | DAHDI_SIG_DACS)
+
+#define        IS_NT(xpd)              (PHONEDEV(xpd).direction == TO_PHONE)
+#define        BRI_PORT(xpd)           ((xpd)->addr.subunit)
+
+/* shift in PCM highway */
+#define        SUBUNIT_PCM_SHIFT       4
+#define        PCM_SHIFT(mask, sunit)  ((mask) << (SUBUNIT_PCM_SHIFT * (sunit)))
+
+/*---------------- BRI Protocol Commands ----------------------------------*/
+
+static int write_state_register(xpd_t *xpd, __u8 value);
+static bool bri_packet_is_valid(xpacket_t *pack);
+static void bri_packet_dump(const char *msg, xpacket_t *pack);
+#ifdef CONFIG_PROC_FS
+static const struct file_operations proc_bri_info_ops;
+#endif
+static int bri_spanconfig(struct file *file, struct dahdi_span *span,
+                         struct dahdi_lineconfig *lc);
+static int bri_chanconfig(struct file *file, struct dahdi_chan *chan,
+                         int sigtype);
+static int bri_startup(struct file *file, struct dahdi_span *span);
+static int bri_shutdown(struct dahdi_span *span);
+
+#define        PROC_BRI_INFO_FNAME     "bri_info"
+
+enum led_state {
+       BRI_LED_OFF = 0x0,
+       BRI_LED_ON = 0x1,
+       /*
+        * We blink by software from driver, so that
+        * if the driver malfunction that blink would stop.
+        */
+       // BRI_LED_BLINK_SLOW   = 0x2,  /* 1/2 a second blink cycle */
+       // BRI_LED_BLINK_FAST   = 0x3   /* 1/4 a second blink cycle */
+};
+
+enum bri_led_names {
+       GREEN_LED = 0,
+       RED_LED = 1
+};
+
+#define        NUM_LEDS        2
+#define        LED_TICKS       100
+
+struct bri_leds {
+       __u8 state:2;
+       __u8 led_sel:1;         /* 0 - GREEN, 1 - RED */
+       __u8 reserved:5;
+};
+
+#ifndef MAX_DFRAME_LEN_L1
+#define MAX_DFRAME_LEN_L1 300
+#endif
+
+#define        DCHAN_BUFSIZE   MAX_DFRAME_LEN_L1
+
+struct BRI_priv_data {
+       struct proc_dir_entry *bri_info;
+       su_rd_sta_t state_register;
+       bool initialized;
+       bool dchan_is_open;
+       int t1;                 /* timer 1 for NT deactivation */
+       int t3;                 /* timer 3 for TE activation */
+       ulong l1_flags;
+       bool reg30_good;
+       uint reg30_ticks;
+       bool layer1_up;
+
+       /*
+        * D-Chan: buffers + extra state info.
+        */
+       atomic_t hdlc_pending;
+       bool txframe_begin;
+
+       uint tick_counter;
+       uint poll_counter;
+       uint dchan_tx_counter;
+       uint dchan_rx_counter;
+       uint dchan_rx_drops;
+       bool dchan_alive;
+       uint dchan_alive_ticks;
+       uint dchan_notx_ticks;
+       uint dchan_norx_ticks;
+       enum led_state ledstate[NUM_LEDS];
+};
+
+static xproto_table_t PROTO_TABLE(BRI);
+
+DEF_RPACKET_DATA(BRI, SET_LED, /* Set one of the LED's */
+                struct bri_leds bri_leds;);
+
+static /* 0x33 */ DECLARE_CMD(BRI, SET_LED, enum bri_led_names which_led,
+                             enum led_state to_led_state);
+
+#define        DO_LED(xpd, which, tostate) \
+               CALL_PROTO(BRI, SET_LED, (xpd)->xbus, (xpd), (which), (tostate))
+
+#define DEBUG_BUF_SIZE (100)
+static void dump_hex_buf(xpd_t *xpd, char *msg, __u8 *buf, size_t len)
+{
+       char debug_buf[DEBUG_BUF_SIZE + 1];
+       int i;
+       int n = 0;
+
+       debug_buf[0] = '\0';
+       for (i = 0; i < len && n < DEBUG_BUF_SIZE; i++)
+               n += snprintf(&debug_buf[n], DEBUG_BUF_SIZE - n, "%02X ",
+                             buf[i]);
+       XPD_NOTICE(xpd, "%s[0..%zd]: %s%s\n", msg, len - 1, debug_buf,
+                  (n >= DEBUG_BUF_SIZE) ? "..." : "");
+}
+
+static void dump_dchan_packet(xpd_t *xpd, bool transmit, __u8 *buf, int len)
+{
+       struct BRI_priv_data *priv;
+       char msgbuf[MAX_PROC_WRITE];
+       char ftype = '?';
+       char *direction;
+       int frame_begin;
+
+       priv = xpd->priv;
+       BUG_ON(!priv);
+       if (transmit) {
+               direction = "TX";
+               frame_begin = priv->txframe_begin;
+       } else {
+               direction = "RX";
+               frame_begin = 1;
+       }
+       if (frame_begin) {      /* Packet start */
+               if (!IS_SET(buf[0], 7))
+                       ftype = 'I';    /* Information */
+               else if (IS_SET(buf[0], 7) && !IS_SET(buf[0], 6))
+                       ftype = 'S';    /* Supervisory */
+               else if (IS_SET(buf[0], 7) && IS_SET(buf[0], 6))
+                       ftype = 'U';    /* Unnumbered */
+               else
+                       XPD_NOTICE(xpd, "Unknown frame type 0x%X\n", buf[0]);
+
+               snprintf(msgbuf, MAX_PROC_WRITE, "D-Chan %s = (%c) ", direction,
+                        ftype);
+       } else {
+               snprintf(msgbuf, MAX_PROC_WRITE, "D-Chan %s =     ", direction);
+       }
+       dump_hex_buf(xpd, msgbuf, buf, len);
+}
+
+static void set_bri_timer(xpd_t *xpd, const char *name, int *bri_timer,
+                         int value)
+{
+       if (value == HFC_TIMER_OFF)
+               XPD_DBG(SIGNAL, xpd, "Timer %s DISABLE\n", name);
+       else
+               XPD_DBG(SIGNAL, xpd, "Timer %s: set to %d\n", name, value);
+       *bri_timer = value;
+}
+
+static void dchan_state(xpd_t *xpd, bool up)
+{
+       struct BRI_priv_data *priv;
+
+       BUG_ON(!xpd);
+       priv = xpd->priv;
+       BUG_ON(!priv);
+       if (priv->dchan_alive == up)
+               return;
+       if (up) {
+               XPD_DBG(SIGNAL, xpd, "STATE CHANGE: D-Channel RUNNING\n");
+               priv->dchan_alive = 1;
+       } else {
+               XPD_DBG(SIGNAL, xpd, "STATE CHANGE: D-Channel STOPPED\n");
+               priv->dchan_rx_counter = priv->dchan_tx_counter =
+                   priv->dchan_rx_drops = 0;
+               priv->dchan_alive = 0;
+               priv->dchan_alive_ticks = 0;
+       }
+}
+
+static void layer1_state(xpd_t *xpd, bool up)
+{
+       struct BRI_priv_data *priv;
+
+       BUG_ON(!xpd);
+       priv = xpd->priv;
+       BUG_ON(!priv);
+       if (priv->layer1_up == up)
+               return;
+       priv->layer1_up = up;
+       XPD_DBG(SIGNAL, xpd, "STATE CHANGE: Layer1 %s\n", (up) ? "UP" : "DOWN");
+       if (!up)
+               dchan_state(xpd, 0);
+}
+
+static void te_activation(xpd_t *xpd, bool on)
+{
+       struct BRI_priv_data *priv;
+       __u8 curr_state;
+
+       BUG_ON(!xpd);
+       priv = xpd->priv;
+       BUG_ON(!priv);
+       curr_state = priv->state_register.bits.v_su_sta;
+       XPD_DBG(SIGNAL, xpd, "%s\n", (on) ? "ON" : "OFF");
+       if (on) {
+               if (curr_state == ST_TE_DEACTIVATED) {
+                       XPD_DBG(SIGNAL, xpd, "HFC_L1_ACTIVATE_TE\n");
+                       set_bit(HFC_L1_ACTIVATING, &priv->l1_flags);
+                       write_state_register(xpd, STA_ACTIVATE);
+                       set_bri_timer(xpd, "T3", &priv->t3, HFC_TIMER_T3);
+               } else {
+                       XPD_DBG(SIGNAL, xpd,
+                               "HFC_L1_ACTIVATE_TE (state %d, ignored)\n",
+                               curr_state);
+               }
+       } else {                /* happen only because of T3 expiry */
+               switch (curr_state) {
+               case ST_TE_DEACTIVATED: /* F3   */
+               case ST_TE_SYNCED:      /* F6   */
+               case ST_TE_ACTIVATED:   /* F7   */
+                       XPD_DBG(SIGNAL, xpd,
+                               "HFC_L1_FORCE_DEACTIVATE_TE "
+                               "(state %d, ignored)\n",
+                               curr_state);
+                       break;
+               case ST_TE_SIGWAIT:     /* F4   */
+               case ST_TE_IDENT:       /* F5   */
+               case ST_TE_LOST_FRAMING:        /* F8   */
+                       XPD_DBG(SIGNAL, xpd, "HFC_L1_FORCE_DEACTIVATE_TE\n");
+                       write_state_register(xpd, STA_DEACTIVATE);
+                       break;
+               default:
+                       XPD_NOTICE(xpd, "Bad TE state: %d\n", curr_state);
+                       break;
+               }
+       }
+}
+
+static void nt_activation(xpd_t *xpd, bool on)
+{
+       struct BRI_priv_data *priv;
+       __u8 curr_state;
+
+       BUG_ON(!xpd);
+       priv = xpd->priv;
+       BUG_ON(!priv);
+       curr_state = priv->state_register.bits.v_su_sta;
+       XPD_DBG(SIGNAL, xpd, "%s\n", (on) ? "ON" : "OFF");
+       if (on) {
+               switch (curr_state) {
+               case ST_RESET:  /* F/G 0 */
+               case ST_NT_DEACTIVATED: /* G1 */
+               case ST_NT_DEACTIVTING: /* G4 */
+                       XPD_DBG(SIGNAL, xpd, "HFC_L1_ACTIVATE_NT\n");
+                       set_bri_timer(xpd, "T1", &priv->t1, HFC_TIMER_T1);
+                       set_bit(HFC_L1_ACTIVATING, &priv->l1_flags);
+                       write_state_register(xpd, STA_ACTIVATE);
+                       break;
+               case ST_NT_ACTIVATING:  /* G2 */
+               case ST_NT_ACTIVATED:   /* G3 */
+                       XPD_DBG(SIGNAL, xpd,
+                               "HFC_L1_ACTIVATE_NT (in state %d, ignored)\n",
+                               curr_state);
+                       break;
+               }
+       } else {
+               switch (curr_state) {
+               case ST_RESET:  /* F/G 0 */
+               case ST_NT_DEACTIVATED: /* G1 */
+               case ST_NT_DEACTIVTING: /* G4 */
+                       XPD_DBG(SIGNAL, xpd,
+                               "HFC_L1_DEACTIVATE_NT (in state %d, ignored)\n",
+                               curr_state);
+                       break;
+               case ST_NT_ACTIVATING:  /* G2 */
+               case ST_NT_ACTIVATED:   /* G3 */
+                       XPD_DBG(SIGNAL, xpd, "HFC_L1_DEACTIVATE_NT\n");
+                       write_state_register(xpd, STA_DEACTIVATE);
+                       break;
+               default:
+                       XPD_NOTICE(xpd, "Bad NT state: %d\n", curr_state);
+                       break;
+               }
+       }
+}
+
+/*
+ * D-Chan receive
+ */
+static int bri_check_stat(xpd_t *xpd, struct dahdi_chan *dchan, __u8 *buf,
+                         int len)
+{
+       struct BRI_priv_data *priv;
+       __u8 status;
+
+       priv = xpd->priv;
+       BUG_ON(!priv);
+       if (len <= 0) {
+               XPD_NOTICE(xpd, "D-Chan RX DROP: short frame (len=%d)\n", len);
+               dahdi_hdlc_abort(dchan, DAHDI_EVENT_ABORT);
+               return -EPROTO;
+       }
+       status = buf[len - 1];
+       if (status) {
+               int event = DAHDI_EVENT_ABORT;
+
+               if (status == 0xFF) {
+                       XPD_NOTICE(xpd, "D-Chan RX DROP: ABORT: %d\n", status);
+               } else {
+                       XPD_NOTICE(xpd, "D-Chan RX DROP: BADFCS: %d\n", status);
+                       event = DAHDI_EVENT_BADFCS;
+               }
+               dump_hex_buf(xpd, "D-Chan RX:    current packet", buf, len);
+               dahdi_hdlc_abort(dchan, event);
+               return -EPROTO;
+       }
+       return 0;
+}
+
+static int rx_dchan(xpd_t *xpd, reg_cmd_t *regcmd)
+{
+       struct BRI_priv_data *priv;
+       __u8 *src;
+       struct dahdi_chan *dchan;
+       uint len;
+       bool eoframe;
+       int ret = 0;
+
+       src = REG_XDATA(regcmd);
+       len = regcmd->h.bytes;
+       eoframe = regcmd->h.eoframe;
+       if (len <= 0)
+               return 0;
+       if (!SPAN_REGISTERED(xpd))      /* Nowhere to copy data */
+               return 0;
+       BUG_ON(!xpd);
+       priv = xpd->priv;
+       BUG_ON(!priv);
+       dchan = XPD_CHAN(xpd, 2);
+       if (!IS_OFFHOOK(xpd, 2)) {      /* D-chan is used? */
+               static int rate_limit;
+
+               if ((rate_limit++ % 1000) == 0)
+                       XPD_DBG(SIGNAL, xpd, "D-Chan unused\n");
+               goto out;
+       }
+       XPD_DBG(GENERAL, xpd, "D-Chan RX: eoframe=%d len=%d\n", eoframe, len);
+       dahdi_hdlc_putbuf(dchan, src, (eoframe) ? len - 1 : len);
+       if (!eoframe)
+               goto out;
+       if ((ret = bri_check_stat(xpd, dchan, src, len)) < 0)
+               goto out;
+       /*
+        * Tell Dahdi that we received len-1 bytes.
+        * They include the data and a 2-byte checksum.
+        * The last byte (that we don't pass on) is 0 if the
+        * checksum is correct. If it were wrong, we would drop the
+        * packet in the "if (src[len-1])" above.
+        */
+       dahdi_hdlc_finish(dchan);
+       priv->dchan_rx_counter++;
+       priv->dchan_norx_ticks = 0;
+out:
+       return ret;
+}
+
+/*
+ * D-Chan transmit
+ */
+/*
+ * DAHDI calls this when it has data it wants to send to
+ * the HDLC controller
+ */
+static void bri_hdlc_hard_xmit(struct dahdi_chan *chan)
+{
+       xpd_t *xpd = chan->pvt;
+       struct dahdi_chan *dchan;
+       struct BRI_priv_data *priv;
+
+       priv = xpd->priv;
+       BUG_ON(!priv);
+       dchan = XPD_CHAN(xpd, 2);
+       if (dchan == chan)
+               atomic_inc(&priv->hdlc_pending);
+}
+
+static int send_dchan_frame(xpd_t *xpd, xframe_t *xframe, bool is_eof)
+{
+       struct BRI_priv_data *priv;
+       int ret;
+
+       XPD_DBG(COMMANDS, xpd, "eoframe=%d\n", is_eof);
+       priv = xpd->priv;
+       if (!test_bit(HFC_L1_ACTIVATED, &priv->l1_flags)
+           && !test_bit(HFC_L1_ACTIVATING, &priv->l1_flags)) {
+               XPD_DBG(SIGNAL, xpd,
+                       "Want to transmit: Kick D-Channel transmiter\n");
+               if (!IS_NT(xpd))
+                       te_activation(xpd, 1);
+               else
+                       nt_activation(xpd, 1);
+       }
+       dump_xframe("send_dchan_frame", xpd->xbus, xframe, debug);
+       ret = send_cmd_frame(xpd->xbus, xframe);
+       if (ret < 0)
+               XPD_ERR(xpd, "%s: failed sending xframe\n", __func__);
+       if (is_eof) {
+               atomic_dec(&priv->hdlc_pending);
+               priv->dchan_tx_counter++;
+               priv->txframe_begin = 1;
+       } else
+               priv->txframe_begin = 0;
+       priv->dchan_notx_ticks = 0;
+       return ret;
+}
+
+/*
+ * Fill a single multibyte REGISTER_REQUEST
+ */
+static void fill_multibyte(xpd_t *xpd, xpacket_t *pack,
+       bool eoframe, char *buf, int len)
+{
+       reg_cmd_t *reg_cmd;
+       char *p;
+
+       XPACKET_INIT(pack, GLOBAL, REGISTER_REQUEST, xpd->xbus_idx, 0, 0);
+       XPACKET_LEN(pack) = XFRAME_CMD_LEN(REG);
+       reg_cmd = &RPACKET_FIELD(pack, GLOBAL, REGISTER_REQUEST, reg_cmd);
+       reg_cmd->h.bytes = len;
+       reg_cmd->h.is_multibyte = 1;
+       reg_cmd->h.portnum = xpd->addr.subunit;
+       reg_cmd->h.eoframe = eoframe;
+       p = REG_XDATA(reg_cmd);
+       memcpy(p, buf, len);
+       if (debug)
+               dump_dchan_packet(xpd, 1, p, len);
+}
+
+/*
+ * Transmit available D-Channel frames
+ *
+ * - FPGA firmware expect to get this as a sequence of REGISTER_REQUEST
+ *   multibyte commands.
+ * - The payload of each command is limited to MULTIBYTE_MAX_LEN bytes.
+ * - We batch several REGISTER_REQUEST packets into a single xframe.
+ * - The xframe is terminated when we get a bri "end of frame"
+ *   or when the xframe is full (should not happen).
+ */
+static int tx_dchan(xpd_t *xpd)
+{
+       struct BRI_priv_data *priv;
+       xframe_t *xframe;
+       xpacket_t *pack;
+       int packet_count;
+       int eoframe;
+       int ret;
+
+       priv = xpd->priv;
+       BUG_ON(!priv);
+       if (atomic_read(&priv->hdlc_pending) == 0)
+               return 0;
+       if (!SPAN_REGISTERED(xpd)
+           || !(PHONEDEV(xpd).span.flags & DAHDI_FLAG_RUNNING))
+               return 0;
+       /* Allocate frame */
+       xframe = ALLOC_SEND_XFRAME(xpd->xbus);
+       if (!xframe) {
+               XPD_NOTICE(xpd, "%s: failed to allocate new xframe\n",
+                          __func__);
+               return -ENOMEM;
+       }
+       for (packet_count = 0, eoframe = 0; !eoframe; packet_count++) {
+               int packet_len = XFRAME_CMD_LEN(REG);
+               char buf[MULTIBYTE_MAX_LEN];
+               int len = MULTIBYTE_MAX_LEN;
+
+               /* Reserve packet */
+               pack = xframe_next_packet(xframe, packet_len);
+               if (!pack) {
+                       BUG_ON(!packet_count);
+                       /*
+                        * A split. Send what we currently have.
+                        */
+                       XPD_NOTICE(xpd, "%s: xframe is full (%d packets)\n",
+                                  __func__, packet_count);
+                       break;
+               }
+               /* Get data from DAHDI */
+               eoframe = dahdi_hdlc_getbuf(XPD_CHAN(xpd, 2), buf, &len);
+               if (len <= 0) {
+                       /*
+                        * Already checked priv->hdlc_pending,
+                        * should never get here.
+                        */
+                       if (printk_ratelimit())
+                               XPD_ERR(xpd,
+                                       "%s: hdlc_pending, but nothing "
+                                       "to transmit?\n", __func__);
+                       FREE_SEND_XFRAME(xpd->xbus, xframe);
+                       return -EINVAL;
+               }
+               BUG_ON(len > MULTIBYTE_MAX_LEN);
+               fill_multibyte(xpd, pack, eoframe != 0, buf, len);
+       }
+       return send_dchan_frame(xpd, xframe, eoframe != 0);
+       return ret;
+}
+
+/*---------------- BRI: Methods -------------------------------------------*/
+
+static void bri_proc_remove(xbus_t *xbus, xpd_t *xpd)
+{
+       struct BRI_priv_data *priv;
+
+       BUG_ON(!xpd);
+       priv = xpd->priv;
+       XPD_DBG(PROC, xpd, "\n");
+#ifdef CONFIG_PROC_FS
+       if (priv->bri_info) {
+               XPD_DBG(PROC, xpd, "Removing '%s'\n", PROC_BRI_INFO_FNAME);
+               remove_proc_entry(PROC_BRI_INFO_FNAME, xpd->proc_xpd_dir);
+       }
+#endif
+}
+
+static int bri_proc_create(xbus_t *xbus, xpd_t *xpd)
+{
+       struct BRI_priv_data *priv;
+
+       BUG_ON(!xpd);
+       priv = xpd->priv;
+       XPD_DBG(PROC, xpd, "\n");
+#ifdef CONFIG_PROC_FS
+       XPD_DBG(PROC, xpd, "Creating '%s'\n", PROC_BRI_INFO_FNAME);
+       priv->bri_info = proc_create_data(PROC_BRI_INFO_FNAME, 0444,
+                                xpd->proc_xpd_dir, &proc_bri_info_ops, xpd);
+       if (!priv->bri_info) {
+               XPD_ERR(xpd, "Failed to create proc file '%s'\n",
+                       PROC_BRI_INFO_FNAME);
+               bri_proc_remove(xbus, xpd);
+               return -EINVAL;
+       }
+       SET_PROC_DIRENTRY_OWNER(priv->bri_info);
+#endif
+       return 0;
+}
+
+static xpd_t *BRI_card_new(xbus_t *xbus, int unit, int subunit,
+                          const xproto_table_t *proto_table,
+                          const struct unit_descriptor *unit_descriptor,
+                          bool to_phone)
+{
+       xpd_t *xpd = NULL;
+       int channels = min(3, CHANNELS_PERXPD);
+
+       if ((unit_descriptor->ports_per_chip < 1) ||
+                       (unit_descriptor->ports_per_chip > 4)) {
+               XBUS_ERR(xbus, "Bad ports_per_chip=%d\n",
+                               unit_descriptor->ports_per_chip);
+               return NULL;
+       }
+       if ((unit_descriptor->numchips) < 1 ||
+                       (unit_descriptor->numchips > 2)) {
+               XBUS_ERR(xbus, "Bad numchips=%d\n",
+                               unit_descriptor->numchips);
+               return NULL;
+       }
+       XBUS_DBG(GENERAL, xbus, "\n");
+       xpd =
+           xpd_alloc(xbus, unit, subunit,
+                     sizeof(struct BRI_priv_data), proto_table, unit_descriptor, channels);
+       if (!xpd)
+               return NULL;
+       PHONEDEV(xpd).direction = (to_phone) ? TO_PHONE : TO_PSTN;
+       xpd->type_name = (to_phone) ? "BRI_NT" : "BRI_TE";
+       xbus->sync_mode_default = SYNC_MODE_AB;
+       if (bri_proc_create(xbus, xpd) < 0)
+               goto err;
+       return xpd;
+err:
+       xpd_free(xpd);
+       return NULL;
+}
+
+static int BRI_card_init(xbus_t *xbus, xpd_t *xpd)
+{
+       struct BRI_priv_data *priv;
+
+       BUG_ON(!xpd);
+       XPD_DBG(GENERAL, xpd, "\n");
+       priv = xpd->priv;
+       DO_LED(xpd, GREEN_LED, BRI_LED_OFF);
+       DO_LED(xpd, RED_LED, BRI_LED_OFF);
+       set_bri_timer(xpd, "T1", &priv->t1, HFC_TIMER_OFF);
+       write_state_register(xpd, 0);   /* Enable L1 state machine */
+       priv->initialized = 1;
+       return 0;
+}
+
+static int BRI_card_remove(xbus_t *xbus, xpd_t *xpd)
+{
+       BUG_ON(!xpd);
+       XPD_DBG(GENERAL, xpd, "\n");
+       bri_proc_remove(xbus, xpd);
+       return 0;
+}
+
+#ifdef DAHDI_AUDIO_NOTIFY
+static int bri_audio_notify(struct dahdi_chan *chan, int on)
+{
+       xpd_t *xpd = chan->pvt;
+       int pos = chan->chanpos - 1;
+
+       BUG_ON(!xpd);
+       LINE_DBG(SIGNAL, xpd, pos, "BRI-AUDIO: %s\n", (on) ? "on" : "off");
+       mark_offhook(xpd, pos, on);
+       return 0;
+}
+#endif
+
+static const struct dahdi_span_ops BRI_span_ops = {
+       .owner = THIS_MODULE,
+       .spanconfig = bri_spanconfig,
+       .chanconfig = bri_chanconfig,
+       .startup = bri_startup,
+       .shutdown = bri_shutdown,
+       .hdlc_hard_xmit = bri_hdlc_hard_xmit,
+       .open = xpp_open,
+       .close = xpp_close,
+       .hooksig = xpp_hooksig, /* Only with RBS bits */
+       .ioctl = xpp_ioctl,
+       .maint = xpp_maint,
+       .echocan_create = xpp_echocan_create,
+       .echocan_name = xpp_echocan_name,
+       .assigned = xpp_span_assigned,
+#ifdef DAHDI_SYNC_TICK
+       .sync_tick = dahdi_sync_tick,
+#endif
+#ifdef CONFIG_DAHDI_WATCHDOG
+       .watchdog = xpp_watchdog,
+#endif
+
+#ifdef DAHDI_AUDIO_NOTIFY
+       .audio_notify = bri_audio_notify,
+#endif
+};
+
+static int BRI_card_dahdi_preregistration(xpd_t *xpd, bool on)
+{
+       xbus_t *xbus;
+       struct BRI_priv_data *priv;
+       int i;
+
+       BUG_ON(!xpd);
+       xbus = xpd->xbus;
+       priv = xpd->priv;
+       BUG_ON(!xbus);
+       XPD_DBG(GENERAL, xpd, "%s\n", (on) ? "on" : "off");
+       if (!on) {
+               /* Nothing to do yet */
+               return 0;
+       }
+       PHONEDEV(xpd).span.spantype =
+               (PHONEDEV(xpd).direction == TO_PHONE)
+                       ? SPANTYPE_DIGITAL_BRI_NT
+                       : SPANTYPE_DIGITAL_BRI_TE;
+       PHONEDEV(xpd).span.linecompat = DAHDI_CONFIG_AMI | DAHDI_CONFIG_CCS;
+       PHONEDEV(xpd).span.deflaw = DAHDI_LAW_ALAW;
+       BIT_SET(PHONEDEV(xpd).digital_signalling, 2);   /* D-Channel */
+       for_each_line(xpd, i) {
+               struct dahdi_chan *cur_chan = XPD_CHAN(xpd, i);
+
+               XPD_DBG(GENERAL, xpd, "setting BRI channel %d\n", i);
+               snprintf(cur_chan->name, MAX_CHANNAME, "XPP_%s/%02d/%1d%1d/%d",
+                        xpd->type_name, xbus->num, xpd->addr.unit,
+                        xpd->addr.subunit, i);
+               cur_chan->chanpos = i + 1;
+               cur_chan->pvt = xpd;
+               if (i == 2) {   /* D-CHAN */
+                       cur_chan->sigcap = BRI_DCHAN_SIGCAP;
+                       clear_bit(DAHDI_FLAGBIT_HDLC, &cur_chan->flags);
+                       priv->txframe_begin = 1;
+                       atomic_set(&priv->hdlc_pending, 0);
+               } else {
+                       cur_chan->sigcap = BRI_BCHAN_SIGCAP;
+               }
+       }
+       CALL_PHONE_METHOD(card_pcm_recompute, xpd, 0);
+       PHONEDEV(xpd).span.ops = &BRI_span_ops;
+       return 0;
+}
+
+static int BRI_card_dahdi_postregistration(xpd_t *xpd, bool on)
+{
+       xbus_t *xbus;
+
+       BUG_ON(!xpd);
+       xbus = xpd->xbus;
+       BUG_ON(!xbus);
+       XPD_DBG(GENERAL, xpd, "%s\n", (on) ? "on" : "off");
+       return (0);
+}
+
+static int BRI_card_hooksig(xpd_t *xpd, int pos, enum dahdi_txsig txsig)
+{
+       LINE_DBG(SIGNAL, xpd, pos, "%s\n", txsig2str(txsig));
+       return 0;
+}
+
+/*
+ * LED managment is done by the driver now:
+ *   - Turn constant ON RED/GREEN led to indicate NT/TE port
+ *   - Very fast "Double Blink" to indicate Layer1 alive (without D-Channel)
+ *   - Constant blink (1/2 sec cycle) to indicate D-Channel alive.
+ */
+static void handle_leds(xbus_t *xbus, xpd_t *xpd)
+{
+       struct BRI_priv_data *priv;
+       unsigned int timer_count;
+       int which_led;
+       int other_led;
+       int mod;
+
+       BUG_ON(!xpd);
+       if (IS_NT(xpd)) {
+               which_led = RED_LED;
+               other_led = GREEN_LED;
+       } else {
+               which_led = GREEN_LED;
+               other_led = RED_LED;
+       }
+       priv = xpd->priv;
+       BUG_ON(!priv);
+       timer_count = xpd->timer_count;
+       if (xpd->blink_mode) {
+               if ((timer_count % DEFAULT_LED_PERIOD) == 0) {
+                       // led state is toggled
+                       if (priv->ledstate[which_led] == BRI_LED_OFF) {
+                               DO_LED(xpd, which_led, BRI_LED_ON);
+                               DO_LED(xpd, other_led, BRI_LED_ON);
+                       } else {
+                               DO_LED(xpd, which_led, BRI_LED_OFF);
+                               DO_LED(xpd, other_led, BRI_LED_OFF);
+                       }
+               }
+               return;
+       }
+       if (priv->ledstate[other_led] != BRI_LED_OFF)
+               DO_LED(xpd, other_led, BRI_LED_OFF);
+       if (priv->dchan_alive) {
+               mod = timer_count % 1000;
+               switch (mod) {
+               case 0:
+                       DO_LED(xpd, which_led, BRI_LED_ON);
+                       break;
+               case 500:
+                       DO_LED(xpd, which_led, BRI_LED_OFF);
+                       break;
+               }
+       } else if (priv->layer1_up) {
+               mod = timer_count % 1000;
+               switch (mod) {
+               case 0:
+               case 100:
+                       DO_LED(xpd, which_led, BRI_LED_ON);
+                       break;
+               case 50:
+               case 150:
+                       DO_LED(xpd, which_led, BRI_LED_OFF);
+                       break;
+               }
+       } else {
+               if (priv->ledstate[which_led] != BRI_LED_ON)
+                       DO_LED(xpd, which_led, BRI_LED_ON);
+       }
+}
+
+static void handle_bri_timers(xpd_t *xpd)
+{
+       struct BRI_priv_data *priv;
+
+       BUG_ON(!xpd);
+       priv = xpd->priv;
+       BUG_ON(!priv);
+       if (IS_NT(xpd)) {
+               if (priv->t1 > HFC_TIMER_OFF) {
+                       if (--priv->t1 == 0) {
+                               set_bri_timer(xpd, "T1", &priv->t1,
+                                             HFC_TIMER_OFF);
+                               if (!nt_keepalive) {
+                                       /* G2 */
+                                       if (priv->state_register.bits.v_su_sta == ST_NT_ACTIVATING) {
+                                               XPD_DBG(SIGNAL, xpd,
+                                                       "T1 Expired. "
+                                                       "Deactivate NT\n");
+                                               clear_bit(HFC_L1_ACTIVATING,
+                                                         &priv->l1_flags);
+                                               /* Deactivate NT */
+                                               nt_activation(xpd, 0);
+                                       } else
+                                               XPD_DBG(SIGNAL, xpd,
+                                                       "T1 Expired. "
+                                                       "(state %d, ignored)\n",
+                                                       priv->state_register.
+                                                       bits.v_su_sta);
+                               }
+                       }
+               }
+       } else {
+               if (priv->t3 > HFC_TIMER_OFF) {
+                       /* timer expired ? */
+                       if (--priv->t3 == 0) {
+                               XPD_DBG(SIGNAL, xpd,
+                                       "T3 expired. Deactivate TE\n");
+                               set_bri_timer(xpd, "T3", &priv->t3,
+                                             HFC_TIMER_OFF);
+                               clear_bit(HFC_L1_ACTIVATING, &priv->l1_flags);
+                               te_activation(xpd, 0);  /* Deactivate TE */
+                       }
+               }
+       }
+}
+
+/* Poll the register ST/Up-State-machine Register, to see if the cable
+ * if a cable is connected to the port.
+ */
+static int BRI_card_tick(xbus_t *xbus, xpd_t *xpd)
+{
+       struct BRI_priv_data *priv;
+
+       BUG_ON(!xpd);
+       priv = xpd->priv;
+       BUG_ON(!priv);
+       if (!priv->initialized || !xbus->self_ticking)
+               return 0;
+       if (poll_interval != 0 && (priv->tick_counter % poll_interval) == 0) {
+               // XPD_DBG(GENERAL, xpd, "%d\n", priv->tick_counter);
+               priv->poll_counter++;
+               xpp_register_request(xbus, xpd,
+                       BRI_PORT(xpd),  /* portno       */
+                       0,              /* writing      */
+                       A_SU_RD_STA,    /* regnum       */
+                       0,              /* do_subreg    */
+                       0,              /* subreg       */
+                       0,              /* data_low     */
+                       0,              /* do_datah     */
+                       0,              /* data_high    */
+                       0,              /* should_reply */
+                       0               /* do_expander  */
+                   );
+
+               if (IS_NT(xpd) && nt_keepalive
+                   && !test_bit(HFC_L1_ACTIVATED, &priv->l1_flags)
+                   && !test_bit(HFC_L1_ACTIVATING, &priv->l1_flags)) {
+                       XPD_DBG(SIGNAL, xpd, "Kick NT D-Channel\n");
+                       nt_activation(xpd, 1);
+               }
+       }
+       /* Detect D-Channel disconnect heuristic */
+       priv->dchan_notx_ticks++;
+       priv->dchan_norx_ticks++;
+       priv->dchan_alive_ticks++;
+       if (priv->dchan_alive
+           && (priv->dchan_notx_ticks > DCHAN_LOST
+               || priv->dchan_norx_ticks > DCHAN_LOST)) {
+               /*
+                * No tx_dchan() or rx_dchan() for many ticks
+                * This D-Channel is probabelly dead.
+                */
+               dchan_state(xpd, 0);
+       } else if (priv->dchan_rx_counter > 1 && priv->dchan_tx_counter > 1) {
+               if (!priv->dchan_alive)
+                       dchan_state(xpd, 1);
+       }
+       /* Detect Layer1 disconnect */
+       if (priv->reg30_good && priv->reg30_ticks > poll_interval * REG30_LOST) {
+               /* No reply for 1/2 a second */
+               XPD_ERR(xpd, "Lost state tracking for %d ticks\n",
+                       priv->reg30_ticks);
+               priv->reg30_good = 0;
+               layer1_state(xpd, 0);
+       }
+       handle_leds(xbus, xpd);
+       handle_bri_timers(xpd);
+       tx_dchan(xpd);
+       priv->tick_counter++;
+       priv->reg30_ticks++;
+       return 0;
+}
+
+static int BRI_card_ioctl(xpd_t *xpd, int pos, unsigned int cmd,
+                         unsigned long arg)
+{
+       BUG_ON(!xpd);
+       if (!XBUS_IS(xpd->xbus, READY))
+               return -ENODEV;
+       switch (cmd) {
+       case DAHDI_TONEDETECT:
+               /*
+                * Asterisk call all span types with this (FXS specific)
+                * call. Silently ignore it.
+                */
+               LINE_DBG(SIGNAL, xpd, pos, "BRI: Starting a call\n");
+               return -ENOTTY;
+       default:
+               report_bad_ioctl(THIS_MODULE->name, xpd, pos, cmd);
+               return -ENOTTY;
+       }
+       return 0;
+}
+
+static int BRI_card_open(xpd_t *xpd, lineno_t pos)
+{
+       struct BRI_priv_data *priv;
+
+       BUG_ON(!xpd);
+       priv = xpd->priv;
+       if (pos == 2) {
+               priv->dchan_is_open = 1;
+               LINE_DBG(SIGNAL, xpd, pos, "OFFHOOK the whole span\n");
+               BIT_SET(PHONEDEV(xpd).offhook_state, 0);
+               BIT_SET(PHONEDEV(xpd).offhook_state, 1);
+               BIT_SET(PHONEDEV(xpd).offhook_state, 2);
+               CALL_PHONE_METHOD(card_pcm_recompute, xpd, 0);
+       }
+       return 0;
+}
+
+static int BRI_card_close(xpd_t *xpd, lineno_t pos)
+{
+       struct BRI_priv_data *priv;
+
+       priv = xpd->priv;
+       /* Clear D-Channel pending data */
+       if (pos == 2) {
+               LINE_DBG(SIGNAL, xpd, pos, "ONHOOK the whole span\n");
+               BIT_CLR(PHONEDEV(xpd).offhook_state, 0);
+               BIT_CLR(PHONEDEV(xpd).offhook_state, 1);
+               BIT_CLR(PHONEDEV(xpd).offhook_state, 2);
+               CALL_PHONE_METHOD(card_pcm_recompute, xpd, 0);
+               priv->dchan_is_open = 0;
+       } else if (!priv->dchan_is_open)
+               mark_offhook(xpd, pos, 0);      /* e.g: patgen/pattest */
+       return 0;
+}
+
+/*
+ * Called only for 'span' keyword in /etc/dahdi/system.conf
+ */
+static int bri_spanconfig(struct file *file, struct dahdi_span *span,
+                         struct dahdi_lineconfig *lc)
+{
+       struct phonedev *phonedev = container_of(span, struct phonedev, span);
+       xpd_t *xpd = container_of(phonedev, struct xpd, phonedev);
+       const char *framingstr = "";
+       const char *codingstr = "";
+       const char *crcstr = "";
+
+       /* framing first */
+       if (lc->lineconfig & DAHDI_CONFIG_B8ZS)
+               framingstr = "B8ZS";
+       else if (lc->lineconfig & DAHDI_CONFIG_AMI)
+               framingstr = "AMI";
+       else if (lc->lineconfig & DAHDI_CONFIG_HDB3)
+               framingstr = "HDB3";
+       /* then coding */
+       if (lc->lineconfig & DAHDI_CONFIG_ESF)
+               codingstr = "ESF";
+       else if (lc->lineconfig & DAHDI_CONFIG_D4)
+               codingstr = "D4";
+       else if (lc->lineconfig & DAHDI_CONFIG_CCS)
+               codingstr = "CCS";
+       /* E1's can enable CRC checking */
+       if (lc->lineconfig & DAHDI_CONFIG_CRC4)
+               crcstr = "CRC4";
+       XPD_DBG(GENERAL, xpd,
+               "[%s]: span=%d (%s) lbo=%d lineconfig=%s/%s/%s (0x%X) sync=%d\n",
+               IS_NT(xpd) ? "NT" : "TE", lc->span, lc->name, lc->lbo,
+               framingstr, codingstr, crcstr, lc->lineconfig, lc->sync);
+       PHONEDEV(xpd).timing_priority = lc->sync;
+       elect_syncer("BRI-spanconfig");
+       /*
+        * FIXME: validate
+        */
+       span->lineconfig = lc->lineconfig;
+       return 0;
+}
+
+/*
+ * Set signalling type (if appropriate)
+ * Called from dahdi with spinlock held on chan. Must not call back
+ * dahdi functions.
+ */
+static int bri_chanconfig(struct file *file, struct dahdi_chan *chan,
+                         int sigtype)
+{
+       DBG(GENERAL, "channel %d (%s) -> %s\n", chan->channo, chan->name,
+           sig2str(sigtype));
+       // FIXME: sanity checks:
+       // - should be supported (within the sigcap)
+       // - should not replace fxs <->fxo ??? (covered by previous?)
+       return 0;
+}
+
+/*
+ * Called only for 'span' keyword in /etc/dahdi/system.conf
+ */
+static int bri_startup(struct file *file, struct dahdi_span *span)
+{
+       struct phonedev *phonedev = container_of(span, struct phonedev, span);
+       xpd_t *xpd = container_of(phonedev, struct xpd, phonedev);
+       struct BRI_priv_data *priv;
+       struct dahdi_chan *dchan;
+
+       BUG_ON(!xpd);
+       priv = xpd->priv;
+       BUG_ON(!priv);
+       if (!XBUS_IS(xpd->xbus, READY)) {
+               XPD_DBG(GENERAL, xpd,
+                       "Startup called by dahdi. No Hardware. Ignored\n");
+               return -ENODEV;
+       }
+       XPD_DBG(GENERAL, xpd, "STARTUP\n");
+       // Turn on all channels
+       CALL_PHONE_METHOD(card_state, xpd, 1);
+       if (SPAN_REGISTERED(xpd)) {
+               dchan = XPD_CHAN(xpd, 2);
+               span->flags |= DAHDI_FLAG_RUNNING;
+               /*
+                * Dahdi (wrongly) assume that D-Channel need HDLC decoding
+                * and during dahdi registration override our flags.
+                *
+                * Don't Get Mad, Get Even:  Now we override dahdi :-)
+                */
+               clear_bit(DAHDI_FLAGBIT_HDLC, &dchan->flags);
+       }
+       return 0;
+}
+
+/*
+ * Called only for 'span' keyword in /etc/dahdi/system.conf
+ */
+static int bri_shutdown(struct dahdi_span *span)
+{
+       struct phonedev *phonedev = container_of(span, struct phonedev, span);
+       xpd_t *xpd = container_of(phonedev, struct xpd, phonedev);
+       struct BRI_priv_data *priv;
+
+       BUG_ON(!xpd);
+       priv = xpd->priv;
+       BUG_ON(!priv);
+       if (!XBUS_IS(xpd->xbus, READY)) {
+               XPD_DBG(GENERAL, xpd,
+                       "Shutdown called by dahdi. No Hardware. Ignored\n");
+               return -ENODEV;
+       }
+       XPD_DBG(GENERAL, xpd, "SHUTDOWN\n");
+       // Turn off all channels
+       CALL_PHONE_METHOD(card_state, xpd, 0);
+       return 0;
+}
+
+static void BRI_card_pcm_recompute(xpd_t *xpd, xpp_line_t dont_care)
+{
+       int i;
+       int line_count;
+       xpp_line_t pcm_mask;
+       uint pcm_len;
+       xpd_t *main_xpd;
+       unsigned long flags;
+
+       BUG_ON(!xpd);
+       main_xpd = xpd_byaddr(xpd->xbus, xpd->addr.unit, 0);
+       if (!main_xpd) {
+               XPD_DBG(DEVICES, xpd,
+                       "Unit 0 is already gone. Ignore request\n");
+               return;
+       }
+       /*
+        * We calculate all subunits, so use the main lock
+        * as a mutex for the whole operation.
+        */
+       spin_lock_irqsave(&PHONEDEV(main_xpd).lock_recompute_pcm, flags);
+       line_count = 0;
+       pcm_mask = 0;
+       for (i = 0; i < MAX_SUBUNIT; i++) {
+               xpd_t *sub_xpd = xpd_byaddr(xpd->xbus, main_xpd->addr.unit, i);
+
+               if (sub_xpd) {
+                       xpp_line_t lines =
+                           PHONEDEV(sub_xpd).
+                           offhook_state & ~(PHONEDEV(sub_xpd).
+                                             digital_signalling);
+
+                       if (lines) {
+                               pcm_mask |= PCM_SHIFT(lines, i);
+                               line_count += 2;
+                       }
+                       /* subunits have fake pcm_len and wanted_pcm_mask */
+                       if (i > 0)
+                               update_wanted_pcm_mask(sub_xpd, lines, 0);
+               }
+       }
+       /*
+        * FIXME: Workaround a bug in sync code of the Astribank.
+        *        Send dummy PCM for sync.
+        */
+       if (main_xpd->addr.unit == 0 && line_count == 0) {
+               pcm_mask = BIT(0);
+               line_count = 1;
+       }
+       /*
+        * The main unit account for all subunits (pcm_len and wanted_pcm_mask).
+        */
+       pcm_len = (line_count)
+           ? RPACKET_HEADERSIZE + sizeof(xpp_line_t) +
+           line_count * DAHDI_CHUNKSIZE : 0L;
+       update_wanted_pcm_mask(main_xpd, pcm_mask, pcm_len);
+       spin_unlock_irqrestore(&PHONEDEV(main_xpd).lock_recompute_pcm, flags);
+}
+
+static void BRI_card_pcm_fromspan(xpd_t *xpd, xpacket_t *pack)
+{
+       __u8 *pcm;
+       unsigned long flags;
+       int i;
+       int subunit;
+       xpp_line_t pcm_mask = 0;
+       xpp_line_t wanted_lines;
+
+       BUG_ON(!xpd);
+       BUG_ON(!pack);
+       pcm = RPACKET_FIELD(pack, GLOBAL, PCM_WRITE, pcm);
+       for (subunit = 0; subunit < MAX_SUBUNIT; subunit++) {
+               xpd_t *tmp_xpd;
+
+               tmp_xpd = xpd_byaddr(xpd->xbus, xpd->addr.unit, subunit);
+               if (!tmp_xpd || !tmp_xpd->card_present)
+                       continue;
+               spin_lock_irqsave(&tmp_xpd->lock, flags);
+               wanted_lines = PHONEDEV(tmp_xpd).wanted_pcm_mask;
+               for_each_line(tmp_xpd, i) {
+                       struct dahdi_chan *chan = XPD_CHAN(tmp_xpd, i);
+
+                       if (IS_SET(wanted_lines, i)) {
+                               if (SPAN_REGISTERED(tmp_xpd)) {
+#ifdef DEBUG_PCMTX
+                                       int channo = chan->channo;
+
+                                       if (pcmtx >= 0 && pcmtx_chan == channo)
+                                               memset((u_char *)pcm, pcmtx,
+                                                      DAHDI_CHUNKSIZE);
+                                       else
+#endif
+                                               memcpy((u_char *)pcm,
+                                                      chan->writechunk,
+                                                      DAHDI_CHUNKSIZE);
+                               } else
+                                       memset((u_char *)pcm, 0x7F,
+                                              DAHDI_CHUNKSIZE);
+                               pcm += DAHDI_CHUNKSIZE;
+                       }
+               }
+               pcm_mask |= PCM_SHIFT(wanted_lines, subunit);
+               XPD_COUNTER(tmp_xpd, PCM_WRITE)++;
+               spin_unlock_irqrestore(&tmp_xpd->lock, flags);
+       }
+       RPACKET_FIELD(pack, GLOBAL, PCM_WRITE, lines) = pcm_mask;
+}
+
+static void BRI_card_pcm_tospan(xpd_t *xpd, xpacket_t *pack)
+{
+       __u8 *pcm;
+       xpp_line_t pcm_mask;
+       unsigned long flags;
+       int subunit;
+       int i;
+
+       /*
+        * Subunit 0 handle all other subunits
+        */
+       if (xpd->addr.subunit != 0)
+               return;
+       if (!SPAN_REGISTERED(xpd))
+               return;
+       pcm = RPACKET_FIELD(pack, GLOBAL, PCM_READ, pcm);
+       pcm_mask = RPACKET_FIELD(pack, GLOBAL, PCM_WRITE, lines);
+       for (subunit = 0; subunit < MAX_SUBUNIT;
+            subunit++, pcm_mask >>= SUBUNIT_PCM_SHIFT) {
+               xpd_t *tmp_xpd;
+
+               if (!pcm_mask)
+                       break;  /* optimize */
+               tmp_xpd = xpd_byaddr(xpd->xbus, xpd->addr.unit, subunit);
+               if (!tmp_xpd || !tmp_xpd->card_present
+                   || !SPAN_REGISTERED(tmp_xpd))
+                       continue;
+               spin_lock_irqsave(&tmp_xpd->lock, flags);
+               for (i = 0; i < 2; i++) {
+                       xpp_line_t tmp_mask = pcm_mask & (BIT(0) | BIT(1));
+                       volatile u_char *r;
+
+                       if (IS_SET(tmp_mask, i)) {
+                               r = XPD_CHAN(tmp_xpd, i)->readchunk;
+#if 0
+                               /* DEBUG */
+                               memset((u_char *)r, 0x5A, DAHDI_CHUNKSIZE);
+#endif
+                               memcpy((u_char *)r, pcm, DAHDI_CHUNKSIZE);
+                               pcm += DAHDI_CHUNKSIZE;
+                       }
+               }
+               XPD_COUNTER(tmp_xpd, PCM_READ)++;
+               spin_unlock_irqrestore(&tmp_xpd->lock, flags);
+       }
+}
+
+static int BRI_timing_priority(xpd_t *xpd)
+{
+       struct BRI_priv_data *priv;
+
+       priv = xpd->priv;
+       BUG_ON(!priv);
+       if (priv->layer1_up)
+               return PHONEDEV(xpd).timing_priority;
+       XPD_DBG(SYNC, xpd, "No timing priority (no layer1)\n");
+       return -ENOENT;
+}
+
+static int BRI_echocancel_timeslot(xpd_t *xpd, int pos)
+{
+       return xpd->addr.subunit * 4 + pos;
+}
+
+static int BRI_echocancel_setmask(xpd_t *xpd, xpp_line_t ec_mask)
+{
+       struct BRI_priv_data *priv;
+       int i;
+
+       BUG_ON(!xpd);
+       priv = xpd->priv;
+       BUG_ON(!priv);
+       XPD_DBG(GENERAL, xpd, "0x%8X\n", ec_mask);
+       if (!ECHOOPS(xpd->xbus)) {
+               XPD_DBG(GENERAL, xpd,
+                       "No echo canceller in XBUS: Doing nothing.\n");
+               return -EINVAL;
+       }
+       for (i = 0; i < PHONEDEV(xpd).channels - 1; i++) {
+               int on = BIT(i) & ec_mask;
+
+               CALL_EC_METHOD(ec_set, xpd->xbus, xpd, i, on);
+       }
+       CALL_EC_METHOD(ec_update, xpd->xbus, xpd->xbus);
+       return 0;
+}
+
+/*---------------- BRI: HOST COMMANDS -------------------------------------*/
+
+static /* 0x33 */ HOSTCMD(BRI, SET_LED, enum bri_led_names which_led,
+                         enum led_state to_led_state)
+{
+       int ret = 0;
+       xframe_t *xframe;
+       xpacket_t *pack;
+       struct bri_leds *bri_leds;
+       struct BRI_priv_data *priv;
+
+       BUG_ON(!xbus);
+       priv = xpd->priv;
+       BUG_ON(!priv);
+       XPD_DBG(LEDS, xpd, "%s -> %d\n", (which_led) ? "RED" : "GREEN",
+               to_led_state);
+       XFRAME_NEW_CMD(xframe, pack, xbus, BRI, SET_LED, xpd->xbus_idx);
+       bri_leds = &RPACKET_FIELD(pack, BRI, SET_LED, bri_leds);
+       bri_leds->state = to_led_state;
+       bri_leds->led_sel = which_led;
+       XPACKET_LEN(pack) = RPACKET_SIZE(BRI, SET_LED);
+       ret = send_cmd_frame(xbus, xframe);
+       priv->ledstate[which_led] = to_led_state;
+       return ret;
+}
+
+static int write_state_register(xpd_t *xpd, __u8 value)
+{
+       int ret;
+
+       XPD_DBG(REGS, xpd, "value = 0x%02X\n", value);
+       ret = xpp_register_request(xpd->xbus, xpd,
+               BRI_PORT(xpd),  /* portno       */
+               1,              /* writing      */
+               A_SU_WR_STA,    /* regnum       */
+               0,              /* do_subreg    */
+               0,              /* subreg       */
+               value,          /* data_low     */
+               0,              /* do_datah     */
+               0,              /* data_high    */
+               0,              /* should_reply */
+               0               /* do_expander */
+           );
+       return ret;
+}
+
+/*---------------- BRI: Astribank Reply Handlers --------------------------*/
+static void su_new_state(xpd_t *xpd, __u8 reg_x30)
+{
+       struct BRI_priv_data *priv;
+       su_rd_sta_t new_state;
+
+       BUG_ON(!xpd);
+       priv = xpd->priv;
+       BUG_ON(!priv);
+       if (!priv->initialized) {
+               XPD_ERR(xpd, "%s called on uninitialized AB\n", __func__);
+               return;
+       }
+       new_state.reg = reg_x30;
+       if (new_state.bits.v_su_t2_exp)
+               XPD_NOTICE(xpd, "T2 Expired\n");
+       priv->reg30_ticks = 0;
+       priv->reg30_good = 1;
+       if (priv->state_register.bits.v_su_sta == new_state.bits.v_su_sta)
+               return;         /* same same */
+       XPD_DBG(SIGNAL, xpd, "%02X ---> %02X (info0=%d) (%s%i)\n",
+               priv->state_register.reg, reg_x30, new_state.bits.v_su_info0,
+               IS_NT(xpd) ? "G" : "F", new_state.bits.v_su_sta);
+       if (!IS_NT(xpd)) {
+               switch (new_state.bits.v_su_sta) {
+               case ST_TE_DEACTIVATED: /* F3 */
+                       XPD_DBG(SIGNAL, xpd, "State ST_TE_DEACTIVATED (F3)\n");
+                       clear_bit(HFC_L1_ACTIVATED, &priv->l1_flags);
+                       layer1_state(xpd, 0);
+                       break;
+               case ST_TE_SIGWAIT:     /* F4   */
+                       XPD_DBG(SIGNAL, xpd, "State ST_TE_SIGWAIT (F4)\n");
+                       layer1_state(xpd, 0);
+                       break;
+               case ST_TE_IDENT:       /* F5   */
+                       XPD_DBG(SIGNAL, xpd, "State ST_TE_IDENT (F5)\n");
+                       layer1_state(xpd, 0);
+                       break;
+               case ST_TE_SYNCED:      /* F6   */
+                       XPD_DBG(SIGNAL, xpd, "State ST_TE_SYNCED (F6)\n");
+                       layer1_state(xpd, 0);
+                       break;
+               case ST_TE_ACTIVATED:   /* F7 */
+                       XPD_DBG(SIGNAL, xpd, "State ST_TE_ACTIVATED (F7)\n");
+                       set_bri_timer(xpd, "T3", &priv->t3, HFC_TIMER_OFF);
+                       clear_bit(HFC_L1_ACTIVATING, &priv->l1_flags);
+                       set_bit(HFC_L1_ACTIVATED, &priv->l1_flags);
+                       layer1_state(xpd, 1);
+                       update_xpd_status(xpd, DAHDI_ALARM_NONE);
+                       break;
+               case ST_TE_LOST_FRAMING:        /* F8 */
+                       XPD_DBG(SIGNAL, xpd, "State ST_TE_LOST_FRAMING (F8)\n");
+                       layer1_state(xpd, 0);
+                       break;
+               default:
+                       XPD_NOTICE(xpd, "Bad TE state: %d\n",
+                                  new_state.bits.v_su_sta);
+                       break;
+               }
+
+       } else {
+               switch (new_state.bits.v_su_sta) {
+               case ST_NT_DEACTIVATED: /* G1 */
+                       XPD_DBG(SIGNAL, xpd, "State ST_NT_DEACTIVATED (G1)\n");
+                       clear_bit(HFC_L1_ACTIVATED, &priv->l1_flags);
+                       set_bri_timer(xpd, "T1", &priv->t1, HFC_TIMER_OFF);
+                       layer1_state(xpd, 0);
+                       break;
+               case ST_NT_ACTIVATING:  /* G2 */
+                       XPD_DBG(SIGNAL, xpd, "State ST_NT_ACTIVATING (G2)\n");
+                       layer1_state(xpd, 0);
+                       if (!test_bit(HFC_L1_ACTIVATED, &priv->l1_flags))
+                               nt_activation(xpd, 1);
+                       break;
+               case ST_NT_ACTIVATED:   /* G3 */
+                       XPD_DBG(SIGNAL, xpd, "State ST_NT_ACTIVATED (G3)\n");
+                       clear_bit(HFC_L1_ACTIVATING, &priv->l1_flags);
+                       set_bit(HFC_L1_ACTIVATED, &priv->l1_flags);
+                       set_bri_timer(xpd, "T1", &priv->t1, HFC_TIMER_OFF);
+                       layer1_state(xpd, 1);
+                       update_xpd_status(xpd, DAHDI_ALARM_NONE);
+                       break;
+               case ST_NT_DEACTIVTING: /* G4 */
+                       XPD_DBG(SIGNAL, xpd, "State ST_NT_DEACTIVTING (G4)\n");
+                       set_bri_timer(xpd, "T1", &priv->t1, HFC_TIMER_OFF);
+                       layer1_state(xpd, 0);
+                       break;
+               default:
+                       XPD_NOTICE(xpd, "Bad NT state: %d\n",
+                                  new_state.bits.v_su_sta);
+                       break;
+               }
+       }
+       priv->state_register.reg = new_state.reg;
+}
+
+static int BRI_card_register_reply(xbus_t *xbus, xpd_t *xpd, reg_cmd_t *info)
+{
+       unsigned long flags;
+       struct BRI_priv_data *priv;
+       struct xpd_addr addr;
+       xpd_t *orig_xpd;
+       int ret;
+
+       /* Map UNIT + PORTNUM to XPD */
+       orig_xpd = xpd;
+       addr.unit = orig_xpd->addr.unit;
+       addr.subunit = info->h.portnum;
+       xpd = xpd_byaddr(xbus, addr.unit, addr.subunit);
+       if (!xpd) {
+               static int rate_limit;
+
+               if ((rate_limit++ % 1003) < 5)
+                       notify_bad_xpd(__func__, xbus, addr, orig_xpd->xpdname);
+               return -EPROTO;
+       }
+       spin_lock_irqsave(&xpd->lock, flags);
+       priv = xpd->priv;
+       BUG_ON(!priv);
+       if (REG_FIELD(info, do_subreg)) {
+               XPD_DBG(REGS, xpd, "RI %02X %02X %02X\n",
+                       REG_FIELD(info, regnum), REG_FIELD(info, subreg),
+                       REG_FIELD(info, data_low));
+       } else {
+               if (REG_FIELD(info, regnum) != A_SU_RD_STA)
+                       XPD_DBG(REGS, xpd, "RD %02X %02X\n",
+                               REG_FIELD(info, regnum), REG_FIELD(info,
+                                                                  data_low));
+               else
+                       XPD_DBG(REGS, xpd, "Got SU_RD_STA=%02X\n",
+                               REG_FIELD(info, data_low));
+       }
+       if (info->h.is_multibyte) {
+               XPD_DBG(REGS, xpd, "Got Multibyte: %d bytes, eoframe: %d\n",
+                       info->h.bytes, info->h.eoframe);
+               ret = rx_dchan(xpd, info);
+               if (ret < 0) {
+                       priv->dchan_rx_drops++;
+                       if (atomic_read(&PHONEDEV(xpd).open_counter) > 0)
+                               XPD_NOTICE(xpd, "Multibyte Drop: errno=%d\n",
+                                          ret);
+               }
+               goto end;
+       }
+       if (REG_FIELD(info, regnum) == A_SU_RD_STA)
+               su_new_state(xpd, REG_FIELD(info, data_low));
+
+       /* Update /proc info only if reply relate to the last slic read request */
+       if (REG_FIELD(&xpd->requested_reply, regnum) ==
+                       REG_FIELD(info, regnum)
+               && REG_FIELD(&xpd->requested_reply, do_subreg) ==
+                       REG_FIELD(info, do_subreg)
+               && REG_FIELD(&xpd->requested_reply, subreg) ==
+                       REG_FIELD(info, subreg)) {
+               xpd->last_reply = *info;
+       }
+
+end:
+       spin_unlock_irqrestore(&xpd->lock, flags);
+       return 0;
+}
+
+static int BRI_card_state(xpd_t *xpd, bool on)
+{
+       struct BRI_priv_data *priv;
+
+       BUG_ON(!xpd);
+       priv = xpd->priv;
+       XPD_DBG(GENERAL, xpd, "%s\n", (on) ? "ON" : "OFF");
+       if (on) {
+               if (!test_bit(HFC_L1_ACTIVATED, &priv->l1_flags)) {
+                       if (!IS_NT(xpd))
+                               te_activation(xpd, 1);
+                       else
+                               nt_activation(xpd, 1);
+               }
+       } else if (IS_NT(xpd))
+               nt_activation(xpd, 0);
+       return 0;
+}
+
+static const struct xops bri_xops = {
+       .card_new = BRI_card_new,
+       .card_init = BRI_card_init,
+       .card_remove = BRI_card_remove,
+       .card_tick = BRI_card_tick,
+       .card_register_reply = BRI_card_register_reply,
+};
+
+static const struct phoneops bri_phoneops = {
+       .card_dahdi_preregistration = BRI_card_dahdi_preregistration,
+       .card_dahdi_postregistration = BRI_card_dahdi_postregistration,
+       .card_hooksig = BRI_card_hooksig,
+       .card_pcm_recompute = BRI_card_pcm_recompute,
+       .card_pcm_fromspan = BRI_card_pcm_fromspan,
+       .card_pcm_tospan = BRI_card_pcm_tospan,
+       .card_timing_priority = BRI_timing_priority,
+       .echocancel_timeslot = BRI_echocancel_timeslot,
+       .echocancel_setmask = BRI_echocancel_setmask,
+       .card_ioctl = BRI_card_ioctl,
+       .card_open = BRI_card_open,
+       .card_close = BRI_card_close,
+       .card_state = BRI_card_state,
+};
+
+static xproto_table_t PROTO_TABLE(BRI) = {
+       .owner = THIS_MODULE,
+       .entries = {
+               /*      Table   Card    Opcode          */
+       },
+       .name = "BRI",  /* protocol name */
+       .ports_per_subunit = 1,
+       .type = XPD_TYPE_BRI,
+       .xops = &bri_xops,
+       .phoneops = &bri_phoneops,
+       .packet_is_valid = bri_packet_is_valid,
+       .packet_dump = bri_packet_dump,
+};
+
+static bool bri_packet_is_valid(xpacket_t *pack)
+{
+       const xproto_entry_t *xe = NULL;
+       // DBG(GENERAL, "\n");
+       xe = xproto_card_entry(&PROTO_TABLE(BRI), XPACKET_OP(pack));
+       return xe != NULL;
+}
+
+static void bri_packet_dump(const char *msg, xpacket_t *pack)
+{
+       DBG(GENERAL, "%s\n", msg);
+}
+
+/*------------------------- REGISTER Handling --------------------------*/
+
+#ifdef CONFIG_PROC_FS
+static int proc_bri_info_show(struct seq_file *sfile, void *not_used)
+{
+       unsigned long flags;
+       xpd_t *xpd = sfile->private;
+       struct BRI_priv_data *priv;
+
+       DBG(PROC, "\n");
+       if (!xpd)
+               return -ENODEV;
+       spin_lock_irqsave(&xpd->lock, flags);
+       priv = xpd->priv;
+       BUG_ON(!priv);
+       seq_printf(sfile, "%05d Layer 1: ", priv->poll_counter);
+       if (priv->reg30_good) {
+               seq_printf(sfile, "%-5s ", (priv->layer1_up) ? "UP" : "DOWN");
+               seq_printf(sfile,
+                          "%c%d %-15s -- fr_sync=%d t2_exp=%d info0=%d g2_g3=%d\n",
+                          IS_NT(xpd) ? 'G' : 'F',
+                          priv->state_register.bits.v_su_sta,
+                          xhfc_state_name(IS_NT(xpd),
+                                   priv->state_register.bits.v_su_sta),
+                          priv->state_register.bits.v_su_fr_sync,
+                          priv->state_register.bits.v_su_t2_exp,
+                          priv->state_register.bits.v_su_info0,
+                          priv->state_register.bits.v_g2_g3);
+       } else {
+               seq_printf(sfile, "Unknown\n");
+       }
+       if (IS_NT(xpd))
+               seq_printf(sfile, "T1 Timer: %d\n", priv->t1);
+       else
+               seq_printf(sfile, "T3 Timer: %d\n", priv->t3);
+       seq_printf(sfile, "Tick Counter: %d\n", priv->tick_counter);
+       seq_printf(sfile, "Last Poll Reply: %d ticks ago\n",
+                   priv->reg30_ticks);
+       seq_printf(sfile, "reg30_good=%d\n", priv->reg30_good);
+       seq_printf(sfile, "D-Channel: TX=[%5d]    RX=[%5d]    BAD=[%5d] ",
+                   priv->dchan_tx_counter, priv->dchan_rx_counter,
+                   priv->dchan_rx_drops);
+       if (priv->dchan_alive) {
+               seq_printf(sfile, "(alive %d K-ticks)\n",
+                           priv->dchan_alive_ticks / 1000);
+       } else {
+               seq_printf(sfile, "(dead)\n");
+       }
+       seq_printf(sfile, "dchan_notx_ticks: %d\n",
+                   priv->dchan_notx_ticks);
+       seq_printf(sfile, "dchan_norx_ticks: %d\n",
+                   priv->dchan_norx_ticks);
+       seq_printf(sfile, "LED: %-10s = %d\n", "GREEN",
+                   priv->ledstate[GREEN_LED]);
+       seq_printf(sfile, "LED: %-10s = %d\n", "RED",
+                   priv->ledstate[RED_LED]);
+       seq_printf(sfile, "\nDCHAN:\n");
+       seq_printf(sfile, "\n");
+       spin_unlock_irqrestore(&xpd->lock, flags);
+       return 0;
+}
+
+static int proc_bri_info_open(struct inode *inode, struct file *file)
+{
+       return single_open(file, proc_bri_info_show, PDE_DATA(inode));
+}
+
+static const struct file_operations proc_bri_info_ops = {
+       .owner          = THIS_MODULE,
+       .open           = proc_bri_info_open,
+       .read           = seq_read,
+       .llseek         = seq_lseek,
+       .release        = single_release,
+};
+#endif
+
+static int bri_xpd_probe(struct device *dev)
+{
+       xpd_t *xpd;
+
+       xpd = dev_to_xpd(dev);
+       /* Is it our device? */
+       if (xpd->xpd_type != XPD_TYPE_BRI) {
+               XPD_ERR(xpd, "drop suggestion for %s (%d)\n", dev_name(dev),
+                       xpd->xpd_type);
+               return -EINVAL;
+       }
+       XPD_DBG(DEVICES, xpd, "SYSFS\n");
+       return 0;
+}
+
+static int bri_xpd_remove(struct device *dev)
+{
+       xpd_t *xpd;
+
+       xpd = dev_to_xpd(dev);
+       XPD_DBG(DEVICES, xpd, "SYSFS\n");
+       return 0;
+}
+
+static struct xpd_driver bri_driver = {
+       .xpd_type = XPD_TYPE_BRI,
+       .driver = {
+                  .name = "bri",
+                  .owner = THIS_MODULE,
+                  .probe = bri_xpd_probe,
+                  .remove = bri_xpd_remove}
+};
+
+static int __init card_bri_startup(void)
+{
+       int ret;
+
+       if ((ret = xpd_driver_register(&bri_driver.driver)) < 0)
+               return ret;
+       xproto_register(&PROTO_TABLE(BRI));
+       return 0;
+}
+
+static void __exit card_bri_cleanup(void)
+{
+       DBG(GENERAL, "\n");
+       xproto_unregister(&PROTO_TABLE(BRI));
+       xpd_driver_unregister(&bri_driver.driver);
+}
+
+MODULE_DESCRIPTION("XPP BRI Card Driver");
+MODULE_AUTHOR("Oron Peled <oron@actcom.co.il>");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS_XPD(XPD_TYPE_BRI);
+
+module_init(card_bri_startup);
+module_exit(card_bri_cleanup);
diff --git a/drivers/dahdi/xpp/card_bri.h b/drivers/dahdi/xpp/card_bri.h
new file mode 100644 (file)
index 0000000..8c49021
--- /dev/null
@@ -0,0 +1,31 @@
+#ifndef        CARD_BRI_H
+#define        CARD_BRI_H
+/*
+ * Written by Oron Peled <oron@actcom.co.il>
+ * Copyright (C) 2004-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.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ *
+ */
+
+#include "xpd.h"
+
+enum bri_opcodes {
+       XPROTO_NAME(BRI, SET_LED) = 0x33,
+};
+
+#endif /* CARD_BRI_H */
diff --git a/drivers/dahdi/xpp/card_echo.c b/drivers/dahdi/xpp/card_echo.c
new file mode 100644 (file)
index 0000000..4ddc06b
--- /dev/null
@@ -0,0 +1,363 @@
+/*
+ * Written by Oron Peled <oron@actcom.co.il>
+ * Copyright (C) 2011, 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.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ *
+ */
+
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/fs.h>
+#include <linux/delay.h>
+#include "xpd.h"
+#include "xproto.h"
+#include "card_echo.h"
+#include "xpp_dahdi.h"
+#include "dahdi_debug.h"
+#include "xpd.h"
+#include "xbus-core.h"
+
+static const char rcsid[] = "$Id$";
+
+/* must be before dahdi_debug.h: */
+static DEF_PARM(int, debug, 0, 0644, "Print DBG statements");
+
+/*---------------- ECHO Protocol Commands ----------------------------------*/
+
+static bool echo_packet_is_valid(xpacket_t *pack);
+static void echo_packet_dump(const char *msg, xpacket_t *pack);
+
+DEF_RPACKET_DATA(ECHO, SET, __u8 timeslots[ECHO_TIMESLOTS];);
+
+DEF_RPACKET_DATA(ECHO, SET_REPLY, __u8 status; __u8 reserved;);
+
+struct ECHO_priv_data {
+};
+
+static xproto_table_t PROTO_TABLE(ECHO);
+
+/*---------------- ECHO: Methods -------------------------------------------*/
+
+static xpd_t *ECHO_card_new(xbus_t *xbus, int unit, int subunit,
+                           const xproto_table_t *proto_table,
+                           const struct unit_descriptor *unit_descriptor,
+                           bool to_phone)
+{
+       xpd_t *xpd = NULL;
+       int channels = 0;
+
+       if (unit_descriptor->ports_per_chip != 1) {
+               XBUS_ERR(xbus, "Bad subunit_ports=%d\n", unit_descriptor->ports_per_chip);
+               return NULL;
+       }
+       XBUS_DBG(GENERAL, xbus, "\n");
+       xpd =
+           xpd_alloc(xbus, unit, subunit,
+                     sizeof(struct ECHO_priv_data), proto_table, unit_descriptor, channels);
+       if (!xpd)
+               return NULL;
+       xpd->type_name = "ECHO";
+       return xpd;
+}
+
+static int ECHO_card_init(xbus_t *xbus, xpd_t *xpd)
+{
+       int ret = 0;
+
+       BUG_ON(!xpd);
+       XPD_DBG(GENERAL, xpd, "\n");
+       xpd->xpd_type = XPD_TYPE_ECHO;
+       XPD_DBG(DEVICES, xpd, "%s\n", xpd->type_name);
+       ret = CALL_EC_METHOD(ec_update, xbus, xbus);
+       return ret;
+}
+
+static int ECHO_card_remove(xbus_t *xbus, xpd_t *xpd)
+{
+       BUG_ON(!xpd);
+       XPD_DBG(GENERAL, xpd, "\n");
+       return 0;
+}
+
+static int ECHO_card_tick(xbus_t *xbus, xpd_t *xpd)
+{
+       struct ECHO_priv_data *priv;
+
+       BUG_ON(!xpd);
+       priv = xpd->priv;
+       BUG_ON(!priv);
+       return 0;
+}
+
+static int ECHO_card_register_reply(xbus_t *xbus, xpd_t *xpd, reg_cmd_t *info)
+{
+       unsigned long flags;
+       struct xpd_addr addr;
+       xpd_t *orig_xpd;
+
+       /* Map UNIT + PORTNUM to XPD */
+       orig_xpd = xpd;
+       addr.unit = orig_xpd->addr.unit;
+       addr.subunit = info->h.portnum;
+       xpd = xpd_byaddr(xbus, addr.unit, addr.subunit);
+       if (!xpd) {
+               static int rate_limit;
+
+               if ((rate_limit++ % 1003) < 5)
+                       notify_bad_xpd(__func__, xbus, addr, orig_xpd->xpdname);
+               return -EPROTO;
+       }
+       spin_lock_irqsave(&xpd->lock, flags);
+       /* Update /proc info only if reply related to last reg read request */
+       if (REG_FIELD(&xpd->requested_reply, regnum) ==
+                       REG_FIELD(info, regnum)
+               && REG_FIELD(&xpd->requested_reply, do_subreg) ==
+                       REG_FIELD(info, do_subreg)
+               && REG_FIELD(&xpd->requested_reply, subreg) ==
+                       REG_FIELD(info, subreg)) {
+               xpd->last_reply = *info;
+       }
+       spin_unlock_irqrestore(&xpd->lock, flags);
+       return 0;
+}
+
+/*---------------- ECHO: HOST COMMANDS -------------------------------------*/
+
+static /* 0x39 */ HOSTCMD(ECHO, SET)
+{
+       struct xbus_echo_state *es;
+       __u8 *ts;
+       xframe_t *xframe;
+       xpacket_t *pack;
+       int ret;
+       uint16_t frm_len;
+       int xpd_idx;
+
+       BUG_ON(!xbus);
+       /*
+        * Find echo canceller XPD address
+        */
+       es = &xbus->echo_state;
+       xpd_idx = es->xpd_idx;
+       XFRAME_NEW_CMD(xframe, pack, xbus, ECHO, SET, xpd_idx);
+       ts = RPACKET_FIELD(pack, ECHO, SET, timeslots);
+       memcpy(ts, es->timeslots, ECHO_TIMESLOTS);
+       frm_len = XFRAME_LEN(xframe);
+       XBUS_DBG(GENERAL, xbus, "ECHO SET: (len = %d)\n", frm_len);
+       ret = send_cmd_frame(xbus, xframe);
+       return ret;
+}
+
+static int ECHO_ec_set(xpd_t *xpd, int pos, bool on)
+{
+       int ts_number;
+       int ts_mask;
+       __u8 *ts;
+
+       ts = xpd->xbus->echo_state.timeslots;
+       /*
+        * ts_number = PCM time slot ("channel number" in the PCM XPP packet)
+        *
+        * Bit 0 is for UNIT=0
+        * PRI: ts_number * 4 + SUBUNIT
+        * BRI: ts_number
+        * FXS/FXO(all units): UNIT * 32 + ts_number
+        *
+        * Bit 1 is for UNIT=1-3: FXS/FXO
+        *
+        */
+       ts_mask = (xpd->addr.unit == 0) ? 0x1 : 0x2;    /* Which bit? */
+       ts_number = CALL_PHONE_METHOD(echocancel_timeslot, xpd, pos);
+       if (ts_number >= ECHO_TIMESLOTS || ts_number < 0) {
+               XPD_ERR(xpd, "Bad ts_number=%d\n", ts_number);
+               return -EINVAL;
+       } else {
+               if (on)
+                       ts[ts_number] |= ts_mask;
+               else
+                       ts[ts_number] &= ~ts_mask;
+       }
+       LINE_DBG(GENERAL, xpd, pos, "%s = %d -- ts_number=%d ts_mask=0x%X\n",
+                __func__, on, ts_number, ts_mask);
+       return 0;
+}
+
+static int ECHO_ec_get(xpd_t *xpd, int pos)
+{
+       int ts_number;
+       int ts_mask;
+       int is_on;
+       __u8 *ts;
+
+       ts = xpd->xbus->echo_state.timeslots;
+       ts_mask = (xpd->addr.unit == 0) ? 0x1 : 0x2;    /* Which bit? */
+       ts_number = CALL_PHONE_METHOD(echocancel_timeslot, xpd, pos);
+       if (ts_number >= ECHO_TIMESLOTS || ts_number < 0) {
+               XPD_ERR(xpd, "Bad ts_number=%d\n", ts_number);
+               return -EINVAL;
+       } else {
+               is_on = ts[ts_number] & ts_mask;
+       }
+#if 0
+       LINE_DBG(GENERAL, xpd, pos, "ec_get=%d -- ts_number=%d ts_mask=0x%X\n",
+                is_on, ts_number, ts_mask);
+#endif
+       return is_on;
+}
+
+static void ECHO_ec_dump(xbus_t *xbus)
+{
+       __u8 *ts;
+       int i;
+
+       ts = xbus->echo_state.timeslots;
+       for (i = 0; i + 15 < ECHO_TIMESLOTS; i += 16) {
+               XBUS_DBG(GENERAL, xbus,
+                       "EC-DUMP[%03d]: "
+                       "0x%02X 0x%02X 0x%02X 0x%02X 0x%02X 0x%02X 0x%02X 0x%02X "
+                       "0x%02X 0x%02X 0x%02X 0x%02X 0x%02X 0x%02X 0x%02X 0x%02X\n",
+                       i, ts[i + 0], ts[i + 1], ts[i + 2], ts[i + 3],
+                       ts[i + 4], ts[i + 5], ts[i + 6], ts[i + 7], ts[i + 8],
+                       ts[i + 9], ts[i + 10], ts[i + 11], ts[i + 12],
+                       ts[i + 13], ts[i + 14], ts[i + 15]
+                   );
+       }
+}
+
+static int ECHO_ec_update(xbus_t *xbus)
+{
+       XBUS_DBG(GENERAL, xbus, "%s\n", __func__);
+       //ECHO_ec_dump(xbus);
+       return CALL_PROTO(ECHO, SET, xbus, NULL);
+}
+
+/*---------------- ECHO: Astribank Reply Handlers --------------------------*/
+HANDLER_DEF(ECHO, SET_REPLY)
+{
+       __u8 status;
+
+       BUG_ON(!xpd);
+       status = RPACKET_FIELD(pack, ECHO, SET_REPLY, status);
+       XPD_DBG(GENERAL, xpd, "status=0x%X\n", status);
+       return 0;
+}
+
+static const struct xops echo_xops = {
+       .card_new = ECHO_card_new,
+       .card_init = ECHO_card_init,
+       .card_remove = ECHO_card_remove,
+       .card_tick = ECHO_card_tick,
+       .card_register_reply = ECHO_card_register_reply,
+};
+
+static const struct echoops echoops = {
+       .ec_set = ECHO_ec_set,
+       .ec_get = ECHO_ec_get,
+       .ec_update = ECHO_ec_update,
+       .ec_dump = ECHO_ec_dump,
+};
+
+static xproto_table_t PROTO_TABLE(ECHO) = {
+       .owner = THIS_MODULE,
+       .entries = {
+               /*      Table   Card    Opcode          */
+               XENTRY( ECHO,   ECHO,   SET_REPLY       ),
+       },
+       .name = "ECHO",
+       .ports_per_subunit = 1,
+       .type = XPD_TYPE_ECHO,
+       .xops = &echo_xops,
+       .echoops = &echoops,
+       .packet_is_valid = echo_packet_is_valid,
+       .packet_dump = echo_packet_dump,
+};
+
+static bool echo_packet_is_valid(xpacket_t *pack)
+{
+       const xproto_entry_t *xe = NULL;
+       // DBG(GENERAL, "\n");
+       xe = xproto_card_entry(&PROTO_TABLE(ECHO), XPACKET_OP(pack));
+       return xe != NULL;
+}
+
+static void echo_packet_dump(const char *msg, xpacket_t *pack)
+{
+       DBG(GENERAL, "%s\n", msg);
+}
+
+/*------------------------- sysfs stuff --------------------------------*/
+static int echo_xpd_probe(struct device *dev)
+{
+       xpd_t *ec_xpd;
+       int ret = 0;
+
+       ec_xpd = dev_to_xpd(dev);
+       /* Is it our device? */
+       if (ec_xpd->xpd_type != XPD_TYPE_ECHO) {
+               XPD_ERR(ec_xpd, "drop suggestion for %s (%d)\n", dev_name(dev),
+                       ec_xpd->xpd_type);
+               return -EINVAL;
+       }
+       XPD_DBG(DEVICES, ec_xpd, "SYSFS\n");
+       return ret;
+}
+
+static int echo_xpd_remove(struct device *dev)
+{
+       xpd_t *ec_xpd;
+
+       ec_xpd = dev_to_xpd(dev);
+       XPD_DBG(DEVICES, ec_xpd, "SYSFS\n");
+       return 0;
+}
+
+static struct xpd_driver echo_driver = {
+       .xpd_type = XPD_TYPE_ECHO,
+       .driver = {
+                  .name = "echo",
+                  .owner = THIS_MODULE,
+                  .probe = echo_xpd_probe,
+                  .remove = echo_xpd_remove}
+};
+
+static int __init card_echo_startup(void)
+{
+       int ret;
+
+       ret = xpd_driver_register(&echo_driver.driver);
+       if (ret < 0)
+               return ret;
+       INFO("FEATURE: WITH Octasic echo canceller\n");
+       xproto_register(&PROTO_TABLE(ECHO));
+       return 0;
+}
+
+static void __exit card_echo_cleanup(void)
+{
+       DBG(GENERAL, "\n");
+       xproto_unregister(&PROTO_TABLE(ECHO));
+       xpd_driver_unregister(&echo_driver.driver);
+}
+
+MODULE_DESCRIPTION("XPP ECHO Card Driver");
+MODULE_AUTHOR("Oron Peled <oron@actcom.co.il>");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS_XPD(XPD_TYPE_ECHO);
+
+module_init(card_echo_startup);
+module_exit(card_echo_cleanup);
diff --git a/drivers/dahdi/xpp/card_echo.h b/drivers/dahdi/xpp/card_echo.h
new file mode 100644 (file)
index 0000000..55c1d73
--- /dev/null
@@ -0,0 +1,31 @@
+#ifndef        CARD_ECHO_H
+#define        CARD_ECHO_H
+/*
+ * Written by Oron Peled <oron@actcom.co.il>
+ * Copyright (C) 2011, 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.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ *
+ */
+#include "xpd.h"
+
+enum echo_opcodes {
+       XPROTO_NAME(ECHO, SET) = 0x39,
+       XPROTO_NAME(ECHO, SET_REPLY) = 0x3A,
+};
+
+#endif /* CARD_ECHO_H */
diff --git a/drivers/dahdi/xpp/card_fxo.c b/drivers/dahdi/xpp/card_fxo.c
new file mode 100644 (file)
index 0000000..1dbd299
--- /dev/null
@@ -0,0 +1,1643 @@
+/*
+ * Written by Oron Peled <oron@actcom.co.il>
+ * Copyright (C) 2004-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.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ *
+ */
+
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/fs.h>
+#include <linux/delay.h>
+#include <linux/proc_fs.h>
+#include <linux/seq_file.h>
+#include "xpd.h"
+#include "xproto.h"
+#include "xpp_dahdi.h"
+#include "card_fxo.h"
+#include "dahdi_debug.h"
+#include "xbus-core.h"
+
+static const char rcsid[] = "$Id$";
+
+static DEF_PARM(int, debug, 0, 0644, "Print DBG statements");
+static DEF_PARM(uint, poll_battery_interval, 500, 0644,
+               "Poll battery interval in milliseconds (0 - disable)");
+static DEF_PARM_BOOL(use_polrev_firmware, 1, 0444,
+               "Use firmware reports of polarity reversal");
+static DEF_PARM_BOOL(squelch_polrev, 0, 0644,
+               "Never report polarity reversal");
+#ifdef WITH_METERING
+static DEF_PARM(uint, poll_metering_interval, 500, 0644,
+               "Poll metering interval in milliseconds (0 - disable)");
+#endif
+static DEF_PARM(int, ring_debounce, 50, 0644,
+               "Number of ticks to debounce a false RING indication");
+static DEF_PARM(int, caller_id_style, 0, 0444,
+               "Caller-Id detection style: "
+               "0 - [BELL], "
+               "1 - [ETSI_FSK], "
+               "2 - [ETSI_DTMF], "
+               "3 - [PASSTHROUGH]");
+static DEF_PARM(int, power_denial_safezone, 650, 0644,
+               "msec after offhook to ignore power-denial ( (0 - disable power-denial)");
+static DEF_PARM(int, power_denial_minlen, 80, 0644,
+               "Minimal detected power-denial length (msec) (0 - disable power-denial)");
+static DEF_PARM(uint, battery_threshold, 3, 0644,
+               "Minimum voltage that shows there is battery");
+static DEF_PARM(uint, battery_debounce, 1000, 0644,
+               "Minimum interval (msec) for detection of battery off");
+
+enum cid_style {
+       CID_STYLE_BELL = 0,     /* E.g: US (Bellcore) */
+       CID_STYLE_ETSI_FSK = 1, /* E.g: UK (British Telecom) */
+       CID_STYLE_ETSI_DTMF = 2,        /* E.g: DK, Russia */
+       CID_STYLE_PASSTHROUGH = 3,      /* No change: Let asterisk  */
+                                       /* (>= 1.8) DSP handle this */
+};
+
+/* Signaling is opposite (fxs signalling for fxo card) */
+#if 1
+#define        FXO_DEFAULT_SIGCAP      (DAHDI_SIG_FXSKS | DAHDI_SIG_FXSLS)
+#else
+#define        FXO_DEFAULT_SIGCAP      (DAHDI_SIG_SF)
+#endif
+
+enum fxo_leds {
+       LED_GREEN,
+       LED_RED,
+};
+
+#define        NUM_LEDS                2
+#define        DELAY_UNTIL_DIALTONE    3000
+
+/*
+ * Minimum duration for polarity reversal detection (in ticks)
+ * Should be longer than the time to detect a ring, so voltage
+ * fluctuation during ring won't trigger false detection.
+ */
+#define        POLREV_THRESHOLD        200
+#define        POWER_DENIAL_CURRENT    3
+#define        POWER_DENIAL_DELAY      2500    /* ticks */
+
+/* Shortcuts */
+#define        DAA_WRITE       1
+#define        DAA_READ        0
+#define        DAA_DIRECT_REQUEST(xbus, xpd, port, writing, reg, dL)   \
+               xpp_register_request((xbus), (xpd), (port), \
+               (writing), (reg), 0, 0, (dL), 0, 0, 0, 0)
+
+/*---------------- FXO Protocol Commands ----------------------------------*/
+
+static bool fxo_packet_is_valid(xpacket_t *pack);
+static void fxo_packet_dump(const char *msg, xpacket_t *pack);
+#ifdef CONFIG_PROC_FS
+static const struct file_operations proc_fxo_info_ops;
+#ifdef WITH_METERING
+static const struct file_operations proc_xpd_metering_ops;
+#endif
+#endif
+static void dahdi_report_battery(xpd_t *xpd, lineno_t chan);
+static void report_polarity_reversal(xpd_t *xpd, xportno_t portno, char *msg);
+
+#define        PROC_FXO_INFO_FNAME     "fxo_info"
+#ifdef WITH_METERING
+#define        PROC_METERING_FNAME     "metering_read"
+#endif
+
+#define        REG_INTERRUPT_SRC       0x04    /*  4 -  Interrupt Source  */
+#define        REG_INTERRUPT_SRC_POLI  BIT(0)  /*  Polarity Reversal Detect Interrupt*/
+#define        REG_INTERRUPT_SRC_RING  BIT(7)  /*  Ring Detect Interrupt */
+
+#define        REG_DAA_CONTROL1        0x05    /*  5 -  DAA Control 1  */
+#define        REG_DAA_CONTROL1_OH     BIT(0)  /* Off-Hook.            */
+#define        REG_DAA_CONTROL1_ONHM   BIT(3)  /* On-Hook Line Monitor */
+
+#define        DAA_REG_METERING        0x11    /* 17 */
+#define        DAA_REG_CURRENT         0x1C    /* 28 */
+#define        DAA_REG_VBAT            0x1D    /* 29 */
+
+enum battery_state {
+       BATTERY_UNKNOWN = 0,
+       BATTERY_ON = 1,
+       BATTERY_OFF = -1
+};
+
+enum polarity_state {
+       POL_UNKNOWN = 0,
+       POL_POSITIVE = 1,
+       POL_NEGATIVE = -1
+};
+
+enum power_state {
+       POWER_UNKNOWN = 0,
+       POWER_ON = 1,
+       POWER_OFF = -1
+};
+
+struct FXO_priv_data {
+#ifdef WITH_METERING
+       struct proc_dir_entry *meteringfile;
+#endif
+       struct proc_dir_entry *fxo_info;
+       uint poll_counter;
+       signed char battery_voltage[CHANNELS_PERXPD];
+       signed char battery_current[CHANNELS_PERXPD];
+       enum battery_state battery[CHANNELS_PERXPD];
+       ushort nobattery_debounce[CHANNELS_PERXPD];
+       enum polarity_state polarity[CHANNELS_PERXPD];
+       ushort polarity_debounce[CHANNELS_PERXPD];
+       int  polarity_last_interval[CHANNELS_PERXPD];
+#define        POLARITY_LAST_INTERVAL_NONE     (-1)
+#define        POLARITY_LAST_INTERVAL_MAX      40
+       enum power_state power[CHANNELS_PERXPD];
+       ushort power_denial_delay[CHANNELS_PERXPD];
+       ushort power_denial_length[CHANNELS_PERXPD];
+       ushort power_denial_safezone[CHANNELS_PERXPD];
+       xpp_line_t cidfound;    /* 0 - OFF, 1 - ON */
+       unsigned int cidtimer[CHANNELS_PERXPD];
+       xpp_line_t ledstate[NUM_LEDS];  /* 0 - OFF, 1 - ON */
+       xpp_line_t ledcontrol[NUM_LEDS];        /* 0 - OFF, 1 - ON */
+       int led_counter[NUM_LEDS][CHANNELS_PERXPD];
+       atomic_t ring_debounce[CHANNELS_PERXPD];
+#ifdef WITH_METERING
+       uint metering_count[CHANNELS_PERXPD];
+       xpp_line_t metering_tone_state;
+#endif
+};
+
+/*
+ * LED counter values:
+ *     n>1     : BLINK every n'th tick
+ */
+#define        LED_COUNTER(priv, pos, color)   ((priv)->led_counter[color][pos])
+#define        IS_BLINKING(priv, pos, color)   (LED_COUNTER(priv, pos, color) > 0)
+#define        MARK_BLINK(priv, pos, color, t) \
+               ((priv)->led_counter[color][pos] = (t))
+#define        MARK_OFF(priv, pos, color) \
+               do { \
+                       BIT_CLR((priv)->ledcontrol[color], (pos)); \
+                       MARK_BLINK((priv), (pos), (color), 0); \
+               } while (0)
+#define        MARK_ON(priv, pos, color) \
+               do { \
+                       BIT_SET((priv)->ledcontrol[color], (pos)); \
+                       MARK_BLINK((priv), (pos), (color), 0); \
+               } while (0)
+
+#define        LED_BLINK_RING                  (1000/8)        /* in ticks */
+
+/*---------------- FXO: Static functions ----------------------------------*/
+
+static const char *power2str(enum power_state pw)
+{
+       switch (pw) {
+       case POWER_UNKNOWN:
+               return "UNKNOWN";
+       case POWER_OFF:
+               return "OFF";
+       case POWER_ON:
+               return "ON";
+       }
+       return NULL;
+}
+
+static void power_change(xpd_t *xpd, int portno, enum power_state pw)
+{
+       struct FXO_priv_data *priv;
+
+       priv = xpd->priv;
+       LINE_DBG(SIGNAL, xpd, portno, "power: %s -> %s\n",
+                power2str(priv->power[portno]), power2str(pw));
+       priv->power[portno] = pw;
+}
+
+static void reset_battery_readings(xpd_t *xpd, lineno_t pos)
+{
+       struct FXO_priv_data *priv = xpd->priv;
+
+       priv->nobattery_debounce[pos] = 0;
+       priv->power_denial_delay[pos] = 0;
+       power_change(xpd, pos, POWER_UNKNOWN);
+}
+
+static const int led_register_mask[] = { BIT(7), BIT(6), BIT(5) };
+
+/*
+ * LED control is done via DAA register 0x20
+ */
+static int do_led(xpd_t *xpd, lineno_t chan, __u8 which, bool on)
+{
+       int ret = 0;
+       struct FXO_priv_data *priv;
+       xbus_t *xbus;
+       __u8 value;
+
+       BUG_ON(!xpd);
+       xbus = xpd->xbus;
+       priv = xpd->priv;
+       which = which % NUM_LEDS;
+       if (IS_SET(PHONEDEV(xpd).digital_outputs, chan)
+           || IS_SET(PHONEDEV(xpd).digital_inputs, chan))
+               goto out;
+       if (chan == PORT_BROADCAST) {
+               priv->ledstate[which] = (on) ? ~0 : 0;
+       } else {
+               if (on)
+                       BIT_SET(priv->ledstate[which], chan);
+               else
+                       BIT_CLR(priv->ledstate[which], chan);
+       }
+       value = 0;
+       value |= ((BIT(5) | BIT(6) | BIT(7)) & ~led_register_mask[which]);
+       value |= (on) ? BIT(0) : 0;
+       value |= (on) ? BIT(1) : 0;
+       LINE_DBG(LEDS, xpd, chan, "LED: which=%d -- %s\n", which,
+                (on) ? "on" : "off");
+       ret = DAA_DIRECT_REQUEST(xbus, xpd, chan, DAA_WRITE, 0x20, value);
+out:
+       return ret;
+}
+
+static void handle_fxo_leds(xpd_t *xpd)
+{
+       int i;
+       unsigned long flags;
+       const enum fxo_leds colors[] = { LED_GREEN, LED_RED };
+       enum fxo_leds color;
+       unsigned int timer_count;
+       struct FXO_priv_data *priv;
+
+       BUG_ON(!xpd);
+       spin_lock_irqsave(&xpd->lock, flags);
+       priv = xpd->priv;
+       timer_count = xpd->timer_count;
+       for (color = 0; color < ARRAY_SIZE(colors); color++) {
+               for_each_line(xpd, i) {
+                       if (IS_SET(PHONEDEV(xpd).digital_outputs, i)
+                           || IS_SET(PHONEDEV(xpd).digital_inputs, i))
+                               continue;
+                       /* Blinking? */
+                       if ((xpd->blink_mode & BIT(i)) || IS_BLINKING(priv, i, color)) {
+                               int mod_value = LED_COUNTER(priv, i, color);
+
+                               if (!mod_value)
+                                       /* safety value */
+                                       mod_value = DEFAULT_LED_PERIOD;
+                               // led state is toggled
+                               if ((timer_count % mod_value) == 0) {
+                                       LINE_DBG(LEDS, xpd, i, "ledstate=%s\n",
+                                                (IS_SET
+                                                 (priv->ledstate[color],
+                                                  i)) ? "ON" : "OFF");
+                                       if (!IS_SET(priv->ledstate[color], i))
+                                               do_led(xpd, i, color, 1);
+                                       else
+                                               do_led(xpd, i, color, 0);
+                               }
+                       } else if (IS_SET(priv->ledcontrol[color], i)
+                                  && !IS_SET(priv->ledstate[color], i)) {
+                               do_led(xpd, i, color, 1);
+                       } else if (!IS_SET(priv->ledcontrol[color], i)
+                                  && IS_SET(priv->ledstate[color], i)) {
+                               do_led(xpd, i, color, 0);
+                       }
+               }
+       }
+       spin_unlock_irqrestore(&xpd->lock, flags);
+}
+
+static void update_dahdi_ring(xpd_t *xpd, int pos, bool on)
+{
+       BUG_ON(!xpd);
+       if (caller_id_style == CID_STYLE_BELL)
+               oht_pcm(xpd, pos, !on);
+       /*
+        * We should not spinlock before calling dahdi_hooksig() as
+        * it may call back into our xpp_hooksig() and cause
+        * a nested spinlock scenario
+        */
+       notify_rxsig(xpd, pos, (on) ? DAHDI_RXSIG_RING : DAHDI_RXSIG_OFFHOOK);
+}
+
+static void mark_ring(xpd_t *xpd, lineno_t pos, bool on, bool update_dahdi)
+{
+       struct FXO_priv_data *priv;
+
+       priv = xpd->priv;
+       BUG_ON(!priv);
+       atomic_set(&priv->ring_debounce[pos], 0);       /* Stop debouncing */
+       /*
+        * We don't want to check battery during ringing
+        * due to voltage fluctuations.
+        */
+       reset_battery_readings(xpd, pos);
+       if (on && !PHONEDEV(xpd).ringing[pos]) {
+               LINE_DBG(SIGNAL, xpd, pos, "START\n");
+               PHONEDEV(xpd).ringing[pos] = 1;
+               priv->cidtimer[pos] = xpd->timer_count;
+               MARK_BLINK(priv, pos, LED_GREEN, LED_BLINK_RING);
+               if (update_dahdi)
+                       update_dahdi_ring(xpd, pos, on);
+       } else if (!on && PHONEDEV(xpd).ringing[pos]) {
+               LINE_DBG(SIGNAL, xpd, pos, "STOP\n");
+               PHONEDEV(xpd).ringing[pos] = 0;
+               priv->cidtimer[pos] = xpd->timer_count;
+               if (IS_BLINKING(priv, pos, LED_GREEN))
+                       MARK_BLINK(priv, pos, LED_GREEN, 0);
+               if (update_dahdi)
+                       update_dahdi_ring(xpd, pos, on);
+               priv->polarity_last_interval[pos] = POLARITY_LAST_INTERVAL_NONE;
+       }
+}
+
+static int do_sethook(xpd_t *xpd, int pos, bool to_offhook)
+{
+       unsigned long flags;
+       xbus_t *xbus;
+       struct FXO_priv_data *priv;
+       int ret = 0;
+       __u8 value;
+
+       BUG_ON(!xpd);
+       /* We can SETHOOK state only on PSTN */
+       BUG_ON(PHONEDEV(xpd).direction == TO_PHONE);
+       xbus = xpd->xbus;
+       priv = xpd->priv;
+       BUG_ON(!priv);
+       if (priv->battery[pos] != BATTERY_ON && to_offhook) {
+               LINE_NOTICE(xpd, pos,
+                           "Cannot take offhook while battery is off!\n");
+               return -EINVAL;
+       }
+       spin_lock_irqsave(&xpd->lock, flags);
+       mark_ring(xpd, pos, 0, 0);      // No more rings
+       value = REG_DAA_CONTROL1_ONHM;  /* Bit 3 is for CID */
+       if (to_offhook)
+               value |= REG_DAA_CONTROL1_OH;
+       LINE_DBG(SIGNAL, xpd, pos, "SETHOOK: value=0x%02X %s\n", value,
+                (to_offhook) ? "OFFHOOK" : "ONHOOK");
+       if (to_offhook)
+               MARK_ON(priv, pos, LED_GREEN);
+       else
+               MARK_OFF(priv, pos, LED_GREEN);
+       ret =
+           DAA_DIRECT_REQUEST(xbus, xpd, pos, DAA_WRITE, REG_DAA_CONTROL1,
+                              value);
+       mark_offhook(xpd, pos, to_offhook);
+       switch (caller_id_style) {
+       case CID_STYLE_ETSI_DTMF:
+       case CID_STYLE_PASSTHROUGH:
+               break;
+       default:
+               oht_pcm(xpd, pos, 0);
+               break;
+       }
+#ifdef WITH_METERING
+       priv->metering_count[pos] = 0;
+       priv->metering_tone_state = 0L;
+       DAA_DIRECT_REQUEST(xbus, xpd, pos, DAA_WRITE, DAA_REG_METERING, 0x2D);
+#endif
+       /* unstable during hook changes */
+       reset_battery_readings(xpd, pos);
+       if (to_offhook) {
+               priv->power_denial_safezone[pos] = power_denial_safezone;
+       } else {
+               priv->power_denial_length[pos] = 0;
+               priv->power_denial_safezone[pos] = 0;
+       }
+       priv->cidtimer[pos] = xpd->timer_count;
+       spin_unlock_irqrestore(&xpd->lock, flags);
+       return ret;
+}
+
+/*---------------- FXO: Methods -------------------------------------------*/
+
+static void fxo_proc_remove(xbus_t *xbus, xpd_t *xpd)
+{
+       struct FXO_priv_data *priv;
+
+       BUG_ON(!xpd);
+       priv = xpd->priv;
+       XPD_DBG(PROC, xpd, "\n");
+#ifdef CONFIG_PROC_FS
+#ifdef WITH_METERING
+       if (priv->meteringfile) {
+               XPD_DBG(PROC, xpd, "Removing xpd metering tone file\n");
+               remove_proc_entry(PROC_METERING_FNAME, xpd->proc_xpd_dir);
+               priv->meteringfile = NULL;
+       }
+#endif
+       if (priv->fxo_info) {
+               XPD_DBG(PROC, xpd, "Removing xpd FXO_INFO file\n");
+               remove_proc_entry(PROC_FXO_INFO_FNAME, xpd->proc_xpd_dir);
+               priv->fxo_info = NULL;
+       }
+#endif
+}
+
+static int fxo_proc_create(xbus_t *xbus, xpd_t *xpd)
+{
+       struct FXO_priv_data *priv;
+
+       BUG_ON(!xpd);
+       priv = xpd->priv;
+#ifdef CONFIG_PROC_FS
+       XPD_DBG(PROC, xpd, "Creating FXO_INFO file\n");
+       priv->fxo_info = proc_create_data(PROC_FXO_INFO_FNAME, 0444,
+                                         xpd->proc_xpd_dir,
+                                         &proc_fxo_info_ops, xpd);
+       if (!priv->fxo_info) {
+               XPD_ERR(xpd, "Failed to create proc file '%s'\n",
+                       PROC_FXO_INFO_FNAME);
+               fxo_proc_remove(xbus, xpd);
+               return -EINVAL;
+       }
+       SET_PROC_DIRENTRY_OWNER(priv->fxo_info);
+#ifdef WITH_METERING
+       XPD_DBG(PROC, xpd, "Creating Metering tone file\n");
+       priv->meteringfile = proc_create_data(PROC_METERING_FNAME, 0444,
+                                             xpd->proc_xpd_dir,
+                                             &proc_xpd_metering_ops, xpd);
+       if (!priv->meteringfile) {
+               XPD_ERR(xpd, "Failed to create proc file '%s'\n",
+                       PROC_METERING_FNAME);
+               fxo_proc_remove(xbus, xpd);
+               return -EINVAL;
+       }
+       SET_PROC_DIRENTRY_OWNER(priv->meteringfile);
+#endif
+#endif
+       return 0;
+}
+
+static xpd_t *FXO_card_new(xbus_t *xbus, int unit, int subunit,
+                          const xproto_table_t *proto_table,
+                          const struct unit_descriptor *unit_descriptor,
+                          bool to_phone)
+{
+       xpd_t *xpd = NULL;
+       int channels;
+       int subunit_ports;
+
+       if (to_phone) {
+               XBUS_NOTICE(xbus,
+                       "XPD=%d%d: try to instanciate FXO with "
+                       "reverse direction\n",
+                       unit, subunit);
+               return NULL;
+       }
+       subunit_ports = unit_descriptor->numchips * unit_descriptor->ports_per_chip;
+       if (unit_descriptor->subtype == 2)
+               channels = min(2, subunit_ports);
+       else
+               channels = min(8, subunit_ports);
+       xpd =
+           xpd_alloc(xbus, unit, subunit,
+                     sizeof(struct FXO_priv_data), proto_table, unit_descriptor, channels);
+       if (!xpd)
+               return NULL;
+       PHONEDEV(xpd).direction = TO_PSTN;
+       xpd->type_name = "FXO";
+       if (fxo_proc_create(xbus, xpd) < 0)
+               goto err;
+       return xpd;
+err:
+       xpd_free(xpd);
+       return NULL;
+}
+
+static int FXO_card_init(xbus_t *xbus, xpd_t *xpd)
+{
+       struct FXO_priv_data *priv;
+       int i;
+
+       BUG_ON(!xpd);
+       priv = xpd->priv;
+       // Hanghup all lines
+       for_each_line(xpd, i) {
+               do_sethook(xpd, i, 0);
+               /* will be updated on next battery sample */
+               priv->polarity[i] = POL_UNKNOWN;
+               priv->polarity_debounce[i] = 0;
+               /* will be updated on next battery sample */
+               priv->battery[i] = BATTERY_UNKNOWN;
+               /* will be updated on next battery sample */
+               priv->power[i] = POWER_UNKNOWN;
+               switch (caller_id_style) {
+               case CID_STYLE_ETSI_DTMF:
+               case CID_STYLE_PASSTHROUGH:
+                       oht_pcm(xpd, i, 1);
+                       break;
+               }
+               priv->polarity_last_interval[i] = POLARITY_LAST_INTERVAL_NONE;
+       }
+       XPD_DBG(GENERAL, xpd, "done\n");
+       for_each_line(xpd, i) {
+               do_led(xpd, i, LED_GREEN, 0);
+       }
+       for_each_line(xpd, i) {
+               do_led(xpd, i, LED_GREEN, 1);
+               msleep(50);
+       }
+       for_each_line(xpd, i) {
+               do_led(xpd, i, LED_GREEN, 0);
+               msleep(50);
+       }
+       CALL_PHONE_METHOD(card_pcm_recompute, xpd, 0);
+       return 0;
+}
+
+static int FXO_card_remove(xbus_t *xbus, xpd_t *xpd)
+{
+       BUG_ON(!xpd);
+       XPD_DBG(GENERAL, xpd, "\n");
+       fxo_proc_remove(xbus, xpd);
+       return 0;
+}
+
+static int FXO_card_dahdi_preregistration(xpd_t *xpd, bool on)
+{
+       xbus_t *xbus;
+       struct FXO_priv_data *priv;
+       int i;
+       unsigned int timer_count;
+
+       BUG_ON(!xpd);
+       xbus = xpd->xbus;
+       BUG_ON(!xbus);
+       priv = xpd->priv;
+       BUG_ON(!priv);
+       timer_count = xpd->timer_count;
+       XPD_DBG(GENERAL, xpd, "%s\n", (on) ? "ON" : "OFF");
+       PHONEDEV(xpd).span.spantype = SPANTYPE_ANALOG_FXO;
+       for_each_line(xpd, i) {
+               struct dahdi_chan *cur_chan = XPD_CHAN(xpd, i);
+
+               XPD_DBG(GENERAL, xpd, "setting FXO channel %d\n", i);
+               snprintf(cur_chan->name, MAX_CHANNAME, "XPP_FXO/%02d/%1d%1d/%d",
+                        xbus->num, xpd->addr.unit, xpd->addr.subunit, i);
+               cur_chan->chanpos = i + 1;
+               cur_chan->pvt = xpd;
+               cur_chan->sigcap = FXO_DEFAULT_SIGCAP;
+       }
+       for_each_line(xpd, i) {
+               MARK_ON(priv, i, LED_GREEN);
+               msleep(4);
+               MARK_ON(priv, i, LED_RED);
+       }
+       for_each_line(xpd, i) {
+               priv->cidtimer[i] = timer_count;
+       }
+       return 0;
+}
+
+static int FXO_card_dahdi_postregistration(xpd_t *xpd, bool on)
+{
+       xbus_t *xbus;
+       struct FXO_priv_data *priv;
+       int i;
+
+       BUG_ON(!xpd);
+       xbus = xpd->xbus;
+       BUG_ON(!xbus);
+       priv = xpd->priv;
+       BUG_ON(!priv);
+       XPD_DBG(GENERAL, xpd, "%s\n", (on) ? "ON" : "OFF");
+       for_each_line(xpd, i) {
+               MARK_OFF(priv, i, LED_GREEN);
+               msleep(2);
+               MARK_OFF(priv, i, LED_RED);
+               msleep(2);
+       }
+       return 0;
+}
+
+static int FXO_span_assigned(xpd_t *xpd)
+{
+       xbus_t *xbus;
+       struct FXO_priv_data *priv;
+       int i;
+
+       BUG_ON(!xpd);
+       xbus = xpd->xbus;
+       BUG_ON(!xbus);
+       priv = xpd->priv;
+       BUG_ON(!priv);
+       XPD_DBG(GENERAL, xpd, "\n");
+       for_each_line(xpd, i)
+               dahdi_report_battery(xpd, i);
+       return 0;
+}
+
+static int FXO_card_hooksig(xpd_t *xpd, int pos, enum dahdi_txsig txsig)
+{
+       struct FXO_priv_data *priv;
+       int ret = 0;
+
+       priv = xpd->priv;
+       BUG_ON(!priv);
+       LINE_DBG(SIGNAL, xpd, pos, "%s\n", txsig2str(txsig));
+       BUG_ON(PHONEDEV(xpd).direction != TO_PSTN);
+       /* XXX Enable hooksig for FXO XXX */
+       switch (txsig) {
+       case DAHDI_TXSIG_START:
+       case DAHDI_TXSIG_OFFHOOK:
+               ret = do_sethook(xpd, pos, 1);
+               break;
+       case DAHDI_TXSIG_ONHOOK:
+               ret = do_sethook(xpd, pos, 0);
+               break;
+       default:
+               XPD_NOTICE(xpd, "Can't set tx state to %s (%d)\n",
+                          txsig2str(txsig), txsig);
+               return -EINVAL;
+       }
+       return ret;
+}
+
+static void dahdi_report_battery(xpd_t *xpd, lineno_t chan)
+{
+       struct FXO_priv_data *priv;
+
+       BUG_ON(!xpd);
+       priv = xpd->priv;
+       if (SPAN_REGISTERED(xpd)) {
+               switch (priv->battery[chan]) {
+               case BATTERY_UNKNOWN:
+                       /* no-op */
+                       break;
+               case BATTERY_OFF:
+                       LINE_DBG(SIGNAL, xpd, chan, "Send DAHDI_ALARM_RED\n");
+                       dahdi_alarm_channel(XPD_CHAN(xpd, chan),
+                                           DAHDI_ALARM_RED);
+                       break;
+               case BATTERY_ON:
+                       LINE_DBG(SIGNAL, xpd, chan, "Send DAHDI_ALARM_NONE\n");
+                       dahdi_alarm_channel(XPD_CHAN(xpd, chan),
+                                           DAHDI_ALARM_NONE);
+                       break;
+               }
+       }
+}
+
+static int FXO_card_open(xpd_t *xpd, lineno_t chan)
+{
+       BUG_ON(!xpd);
+       return 0;
+}
+
+static void poll_battery(xbus_t *xbus, xpd_t *xpd)
+{
+       int i;
+
+       for_each_line(xpd, i) {
+               DAA_DIRECT_REQUEST(xbus, xpd, i, DAA_READ, DAA_REG_VBAT, 0);
+       }
+}
+
+#ifdef WITH_METERING
+static void poll_metering(xbus_t *xbus, xpd_t *xpd)
+{
+       int i;
+
+       for_each_line(xpd, i) {
+               if (IS_OFFHOOK(xpd, i))
+                       DAA_DIRECT_REQUEST(xbus, xpd, i, DAA_READ,
+                                          DAA_REG_METERING, 0);
+       }
+}
+#endif
+
+static void handle_fxo_ring(xpd_t *xpd)
+{
+       struct FXO_priv_data *priv;
+       int i;
+
+       priv = xpd->priv;
+       for_each_line(xpd, i) {
+               if (likely(use_polrev_firmware)) {
+                       int *t = &priv->polarity_last_interval[i];
+                       if (*t != POLARITY_LAST_INTERVAL_NONE) {
+                               (*t)++;
+                               if (*t > POLARITY_LAST_INTERVAL_MAX) {
+                                       LINE_DBG(SIGNAL, xpd, i,
+                                               "polrev(GOOD): %d msec\n", *t);
+                                       *t = POLARITY_LAST_INTERVAL_NONE;
+                                       report_polarity_reversal(xpd,
+                                                               i, "firmware");
+                               }
+                       }
+               }
+               if (atomic_read(&priv->ring_debounce[i]) > 0) {
+                       /* Maybe start ring */
+                       if (atomic_dec_and_test(&priv->ring_debounce[i]))
+                               mark_ring(xpd, i, 1, 1);
+               } else if (atomic_read(&priv->ring_debounce[i]) < 0) {
+                       /* Maybe stop ring */
+                       if (atomic_inc_and_test(&priv->ring_debounce[i]))
+                               mark_ring(xpd, i, 0, 1);
+               }
+       }
+}
+
+static void handle_fxo_power_denial(xpd_t *xpd)
+{
+       struct FXO_priv_data *priv;
+       int i;
+
+       if (!power_denial_safezone)
+               return;         /* Ignore power denials */
+       priv = xpd->priv;
+       for_each_line(xpd, i) {
+               if (PHONEDEV(xpd).ringing[i] || !IS_OFFHOOK(xpd, i)) {
+                       priv->power_denial_delay[i] = 0;
+                       continue;
+               }
+               if (priv->power_denial_safezone[i] > 0) {
+                       if (--priv->power_denial_safezone[i] == 0) {
+                               /*
+                                * Poll current, prev answers are meaningless
+                                */
+                               DAA_DIRECT_REQUEST(xpd->xbus, xpd, i, DAA_READ,
+                                                  DAA_REG_CURRENT, 0);
+                       }
+                       continue;
+               }
+               if (priv->power_denial_length[i] > 0) {
+                       priv->power_denial_length[i]--;
+                       if (priv->power_denial_length[i] <= 0) {
+                               /*
+                                * But maybe the FXS started to ring (and
+                                * the firmware haven't detected it yet).
+                                * This would cause false power denials so
+                                * we just flag it and schedule more ticks
+                                * to wait.
+                                */
+                               LINE_DBG(SIGNAL, xpd, i,
+                                        "Possible Power Denial Hangup\n");
+                               priv->power_denial_delay[i] =
+                                   POWER_DENIAL_DELAY;
+                       }
+                       continue;
+               }
+               if (priv->power_denial_delay[i] > 0) {
+                       /*
+                        * Ring detection by the firmware takes some time.
+                        * Therefore we delay our decision until we are
+                        * sure that no ring has started during this time.
+                        */
+                       priv->power_denial_delay[i]--;
+                       if (priv->power_denial_delay[i] <= 0) {
+                               LINE_DBG(SIGNAL, xpd, i,
+                                        "Power Denial Hangup\n");
+                               priv->power_denial_delay[i] = 0;
+                               /*
+                                * Let Asterisk decide what to do
+                                */
+                               notify_rxsig(xpd, i, DAHDI_RXSIG_ONHOOK);
+                       }
+               }
+       }
+}
+
+/*
+ * For caller-id CID_STYLE_ETSI_DTMF:
+ *   - No indication is passed before the CID
+ *   - We try to detect it and send "fake" polarity reversal.
+ *   - The chan_dahdi.conf should have cidstart=polarity
+ *   - Based on an idea in http://bugs.digium.com/view.php?id=9096
+ */
+static void check_etsi_dtmf(xpd_t *xpd)
+{
+       struct FXO_priv_data *priv;
+       int portno;
+       unsigned int timer_count;
+
+       if (!SPAN_REGISTERED(xpd))
+               return;
+       priv = xpd->priv;
+       BUG_ON(!priv);
+       timer_count = xpd->timer_count;
+       for_each_line(xpd, portno) {
+               /* Skip offhook and ringing ports */
+               if (IS_OFFHOOK(xpd, portno) || PHONEDEV(xpd).ringing[portno])
+                       continue;
+               if (IS_SET(priv->cidfound, portno)) {
+                       if (timer_count > priv->cidtimer[portno] + 4000) {
+                               /* reset flags if it's been a while */
+                               priv->cidtimer[portno] = timer_count;
+                               BIT_CLR(priv->cidfound, portno);
+                               LINE_DBG(SIGNAL, xpd, portno,
+                                        "Reset CID flag\n");
+                       }
+                       continue;
+               }
+               if (timer_count > priv->cidtimer[portno] + 400) {
+                       struct dahdi_chan *chan = XPD_CHAN(xpd, portno);
+                       int sample;
+                       int i;
+
+                       for (i = 0; i < DAHDI_CHUNKSIZE; i++) {
+                               sample = DAHDI_XLAW(chan->readchunk[i], chan);
+                               if (sample > 16000 || sample < -16000) {
+                                       priv->cidtimer[portno] = timer_count;
+                                       BIT_SET(priv->cidfound, portno);
+                                       LINE_DBG(SIGNAL, xpd, portno,
+                                               "Found DTMF CLIP (%d)\n", i);
+                                       report_polarity_reversal(xpd, portno,
+                                                       "fake");
+                                       break;
+                               }
+                       }
+               }
+       }
+}
+
+static int FXO_card_tick(xbus_t *xbus, xpd_t *xpd)
+{
+       struct FXO_priv_data *priv;
+
+       BUG_ON(!xpd);
+       priv = xpd->priv;
+       BUG_ON(!priv);
+       if (poll_battery_interval != 0
+           && (priv->poll_counter % poll_battery_interval) == 0)
+               poll_battery(xbus, xpd);
+#ifdef WITH_METERING
+       if (poll_metering_interval != 0
+           && (priv->poll_counter % poll_metering_interval) == 0)
+               poll_metering(xbus, xpd);
+#endif
+       handle_fxo_leds(xpd);
+       handle_fxo_ring(xpd);
+       handle_fxo_power_denial(xpd);
+       if (caller_id_style == CID_STYLE_ETSI_DTMF && likely(xpd->card_present))
+               check_etsi_dtmf(xpd);
+       priv->poll_counter++;
+       return 0;
+}
+
+#include <dahdi/wctdm_user.h>
+/*
+ * The first register is the ACIM, the other are coefficient registers.
+ * We define the array size explicitly to track possible inconsistencies
+ * if the struct is modified.
+ */
+static const char echotune_regs[sizeof(struct wctdm_echo_coefs)] =
+    { 30, 45, 46, 47, 48, 49, 50, 51, 52 };
+
+static int FXO_card_ioctl(xpd_t *xpd, int pos, unsigned int cmd,
+                         unsigned long arg)
+{
+       int i, ret;
+       unsigned char echotune_data[ARRAY_SIZE(echotune_regs)];
+
+       BUG_ON(!xpd);
+       if (!XBUS_IS(xpd->xbus, READY))
+               return -ENODEV;
+       switch (cmd) {
+       case WCTDM_SET_ECHOTUNE:
+               XPD_DBG(GENERAL, xpd, "-- Setting echo registers: \n");
+               /* first off: check if this span is fxs. If not: -EINVALID */
+               if (copy_from_user
+                   (&echotune_data, (void __user *)arg, sizeof(echotune_data)))
+                       return -EFAULT;
+
+               for (i = 0; i < ARRAY_SIZE(echotune_regs); i++) {
+                       XPD_DBG(REGS, xpd, "Reg=0x%02X, data=0x%02X\n",
+                               echotune_regs[i], echotune_data[i]);
+                       ret =
+                           DAA_DIRECT_REQUEST(xpd->xbus, xpd, pos, DAA_WRITE,
+                                              echotune_regs[i],
+                                              echotune_data[i]);
+                       if (ret < 0) {
+                               LINE_NOTICE(xpd, pos,
+                                       "Couldn't write %0x02X to "
+                                       "register %0x02X\n",
+                                       echotune_data[i], echotune_regs[i]);
+                               return ret;
+                       }
+                       msleep(1);
+               }
+
+               XPD_DBG(GENERAL, xpd, "-- Set echo registers successfully\n");
+               break;
+       case DAHDI_TONEDETECT:
+               /*
+                * Asterisk call all span types with this (FXS specific)
+                * call. Silently ignore it.
+                */
+               LINE_DBG(GENERAL, xpd, pos,
+                        "DAHDI_TONEDETECT (FXO: NOTIMPLEMENTED)\n");
+               return -ENOTTY;
+       default:
+               report_bad_ioctl(THIS_MODULE->name, xpd, pos, cmd);
+               return -ENOTTY;
+       }
+       return 0;
+}
+
+/*---------------- FXO: HOST COMMANDS -------------------------------------*/
+
+/*---------------- FXO: Astribank Reply Handlers --------------------------*/
+
+HANDLER_DEF(FXO, SIG_CHANGED)
+{
+       xpp_line_t sig_status =
+           RPACKET_FIELD(pack, FXO, SIG_CHANGED, sig_status);
+       xpp_line_t sig_toggles =
+           RPACKET_FIELD(pack, FXO, SIG_CHANGED, sig_toggles);
+       unsigned long flags;
+       int i;
+       struct FXO_priv_data *priv;
+
+       if (!xpd) {
+               notify_bad_xpd(__func__, xbus, XPACKET_ADDR(pack), cmd->name);
+               return -EPROTO;
+       }
+       priv = xpd->priv;
+       BUG_ON(!priv);
+       XPD_DBG(SIGNAL, xpd, "(PSTN) sig_toggles=0x%04X sig_status=0x%04X\n",
+               sig_toggles, sig_status);
+       spin_lock_irqsave(&xpd->lock, flags);
+       for_each_line(xpd, i) {
+               int debounce;
+
+               if (IS_SET(sig_toggles, i)) {
+                       if (priv->battery[i] == BATTERY_OFF) {
+                               /*
+                                * With poll_battery_interval==0 we cannot
+                                * have BATTERY_OFF so we won't get here
+                                */
+                               LINE_NOTICE(xpd, i,
+                                       "SIG_CHANGED while battery is off. "
+                                       "Ignored.\n");
+                               continue;
+                       }
+                       /* First report false ring alarms */
+                       debounce = atomic_read(&priv->ring_debounce[i]);
+                       if (debounce)
+                               LINE_NOTICE(xpd, i,
+                                       "Ignored a false short ring "
+                                       "(lasted only %dms)\n",
+                                       ring_debounce - debounce);
+                       /*
+                        * Now set a new ring alarm.
+                        * It will be checked in handle_fxo_ring()
+                        */
+                       debounce =
+                           (IS_SET(sig_status, i)) ? ring_debounce :
+                           -ring_debounce;
+                       atomic_set(&priv->ring_debounce[i], debounce);
+               }
+       }
+       spin_unlock_irqrestore(&xpd->lock, flags);
+       return 0;
+}
+
+static void report_polarity_reversal(xpd_t *xpd, xportno_t portno, char *msg)
+{
+       /*
+        * Inform dahdi/Asterisk:
+        * 1. Maybe used for hangup detection during offhook
+        * 2. In some countries used to report caller-id
+        *    during onhook but before first ring.
+        */
+       if (caller_id_style == CID_STYLE_ETSI_FSK)
+               /* will be cleared on ring/offhook */
+               oht_pcm(xpd, portno, 1);
+       if (SPAN_REGISTERED(xpd)) {
+               LINE_DBG(SIGNAL, xpd, portno,
+                       "%s DAHDI_EVENT_POLARITY (%s)\n",
+                       (squelch_polrev) ? "Squelch" : "Send",
+                       msg);
+               if (!squelch_polrev)
+                       dahdi_qevent_lock(XPD_CHAN(xpd, portno),
+                               DAHDI_EVENT_POLARITY);
+       }
+}
+
+static void update_battery_voltage(xpd_t *xpd, __u8 data_low,
+       xportno_t portno)
+{
+       struct FXO_priv_data *priv;
+       enum polarity_state pol;
+       int msec;
+       signed char volts = (signed char)data_low;
+
+       priv = xpd->priv;
+       BUG_ON(!priv);
+       priv->battery_voltage[portno] = volts;
+       if (PHONEDEV(xpd).ringing[portno])
+               goto ignore_reading;    /* ring voltage create false alarms */
+       if (abs(volts) < battery_threshold) {
+               /*
+                * Check for battery voltage fluctuations
+                */
+               if (priv->battery[portno] != BATTERY_OFF) {
+                       int milliseconds;
+
+                       milliseconds =
+                           priv->nobattery_debounce[portno]++ *
+                           poll_battery_interval;
+                       if (milliseconds > battery_debounce) {
+                               LINE_DBG(SIGNAL, xpd, portno,
+                                        "BATTERY OFF voltage=%d\n", volts);
+                               priv->battery[portno] = BATTERY_OFF;
+                               dahdi_report_battery(xpd, portno);
+                               /* What's the polarity ? */
+                               priv->polarity[portno] = POL_UNKNOWN;
+                               priv->polarity_debounce[portno] = 0;
+                               /* What's the current ? */
+                               power_change(xpd, portno, POWER_UNKNOWN);
+                               /*
+                                * Stop further processing for now
+                                */
+                               goto ignore_reading;
+                       }
+
+               }
+       } else {
+               priv->nobattery_debounce[portno] = 0;
+               if (priv->battery[portno] != BATTERY_ON) {
+                       LINE_DBG(SIGNAL, xpd, portno, "BATTERY ON voltage=%d\n",
+                                volts);
+                       priv->battery[portno] = BATTERY_ON;
+                       dahdi_report_battery(xpd, portno);
+               }
+       }
+#if 0
+       /*
+        * Mark FXO ports without battery!
+        */
+       if (priv->battery[portno] != BATTERY_ON)
+               MARK_ON(priv, portno, LED_RED);
+       else
+               MARK_OFF(priv, portno, LED_RED);
+#endif
+       if (priv->battery[portno] != BATTERY_ON) {
+               /* What's the polarity ? */
+               priv->polarity[portno] = POL_UNKNOWN;
+               return;
+       }
+       /*
+        * Handle reverse polarity
+        */
+       if (volts == 0)
+               pol = POL_UNKNOWN;
+       else if (volts < 0)
+               pol = POL_NEGATIVE;
+       else
+               pol = POL_POSITIVE;
+       if (priv->polarity[portno] == pol) {
+               /*
+                * Same polarity, reset debounce counter
+                */
+               priv->polarity_debounce[portno] = 0;
+               return;
+       }
+       /*
+        * Track polarity reversals and debounce spikes.
+        * Only reversals with long duration count.
+        */
+       msec = priv->polarity_debounce[portno]++ * poll_battery_interval;
+       if (msec >= POLREV_THRESHOLD) {
+               priv->polarity_debounce[portno] = 0;
+               if (pol != POL_UNKNOWN && priv->polarity[portno] != POL_UNKNOWN) {
+                       char *polname = NULL;
+
+                       if (pol == POL_POSITIVE)
+                               polname = "Positive";
+                       else if (pol == POL_NEGATIVE)
+                               polname = "Negative";
+                       else
+                               BUG();
+                       LINE_DBG(SIGNAL, xpd, portno,
+                                "Polarity changed to %s\n", polname);
+                       if (!use_polrev_firmware)
+                               report_polarity_reversal(xpd, portno, polname);
+               }
+               priv->polarity[portno] = pol;
+       }
+       return;
+ignore_reading:
+       /*
+        * Reset debounce counters to prevent false alarms
+        */
+       /* unstable during hook changes */
+       reset_battery_readings(xpd, portno);
+}
+
+static void update_battery_current(xpd_t *xpd, __u8 data_low,
+       xportno_t portno)
+{
+       struct FXO_priv_data *priv;
+
+       priv = xpd->priv;
+       BUG_ON(!priv);
+       priv->battery_current[portno] = data_low;
+       /*
+        * During ringing, current is not stable.
+        * During onhook there should not be current anyway.
+        */
+       if (PHONEDEV(xpd).ringing[portno] || !IS_OFFHOOK(xpd, portno))
+               goto ignore_it;
+       /*
+        * Power denial with no battery voltage is meaningless
+        */
+       if (priv->battery[portno] != BATTERY_ON)
+               goto ignore_it;
+       /* Safe zone after offhook */
+       if (priv->power_denial_safezone[portno] > 0)
+               goto ignore_it;
+       if (data_low < POWER_DENIAL_CURRENT) {
+               if (priv->power[portno] == POWER_ON) {
+                       power_change(xpd, portno, POWER_OFF);
+                       priv->power_denial_length[portno] = power_denial_minlen;
+               }
+       } else {
+               if (priv->power[portno] != POWER_ON) {
+                       power_change(xpd, portno, POWER_ON);
+                       priv->power_denial_length[portno] = 0;
+                       /* We are now OFFHOOK */
+                       hookstate_changed(xpd, portno, 1);
+               }
+       }
+       return;
+ignore_it:
+       priv->power_denial_delay[portno] = 0;
+}
+
+#ifdef WITH_METERING
+#define        BTD_BIT BIT(0)
+
+static void update_metering_state(xpd_t *xpd, __u8 data_low, lineno_t portno)
+{
+       struct FXO_priv_data *priv;
+       bool metering_tone = data_low & BTD_BIT;
+       bool old_metering_tone;
+
+       priv = xpd->priv;
+       BUG_ON(!priv);
+       old_metering_tone = IS_SET(priv->metering_tone_state, portno);
+       LINE_DBG(SIGNAL, xpd, portno, "METERING: %s [dL=0x%X] (%d)\n",
+                (metering_tone) ? "ON" : "OFF", data_low,
+                priv->metering_count[portno]);
+       if (metering_tone && !old_metering_tone) {
+               /* Rising edge */
+               priv->metering_count[portno]++;
+               BIT_SET(priv->metering_tone_state, portno);
+       } else if (!metering_tone && old_metering_tone)
+               BIT_CLR(priv->metering_tone_state, portno);
+       if (metering_tone) {
+               /* Clear the BTD bit */
+               data_low &= ~BTD_BIT;
+               DAA_DIRECT_REQUEST(xpd->xbus, xpd, portno, DAA_WRITE,
+                                  DAA_REG_METERING, data_low);
+       }
+}
+#endif
+
+static void got_chip_interrupt(xpd_t *xpd, __u8 data_low,
+       xportno_t portno)
+{
+       struct FXO_priv_data *priv;
+       int t;
+
+       if (!use_polrev_firmware)
+               return;
+       priv = xpd->priv;
+       LINE_DBG(SIGNAL, xpd, portno, "mask=0x%X\n", data_low);
+       if (!(data_low & REG_INTERRUPT_SRC_POLI))
+               return;
+       t = priv->polarity_last_interval[portno];
+       if (PHONEDEV(xpd).ringing[portno]) {
+               priv->polarity_last_interval[portno] =
+                       POLARITY_LAST_INTERVAL_NONE;
+               LINE_DBG(SIGNAL, xpd, portno,
+                       "polrev(false): %d msec (while ringing)\n", t);
+       } else if (data_low & REG_INTERRUPT_SRC_RING) {
+               priv->polarity_last_interval[portno] =
+                       POLARITY_LAST_INTERVAL_NONE;
+               LINE_DBG(SIGNAL, xpd, portno,
+                       "polrev(false): %d msec (with chip-interrupt ring)\n",
+                       t);
+       } else if (t == POLARITY_LAST_INTERVAL_NONE) {
+               priv->polarity_last_interval[portno] = 0;
+               LINE_DBG(SIGNAL, xpd, portno,
+                       "polrev(start)\n");
+       } else if (t < POLARITY_LAST_INTERVAL_MAX) {
+               /*
+                * Start counting upward from -POLARITY_LAST_INTERVAL_MAX
+                * Until we reach POLARITY_LAST_INTERVAL_NONE.
+                * This way we filter bursts of false reports we get
+                * during ringing.
+                */
+               priv->polarity_last_interval[portno] =
+                       POLARITY_LAST_INTERVAL_NONE -
+                       POLARITY_LAST_INTERVAL_MAX;
+               LINE_DBG(SIGNAL, xpd, portno,
+                       "polrev(false): %d msec (interval shorter than %d)\n",
+                       t, POLARITY_LAST_INTERVAL_MAX);
+       }
+}
+
+static int FXO_card_register_reply(xbus_t *xbus, xpd_t *xpd, reg_cmd_t *info)
+{
+       struct FXO_priv_data *priv;
+       lineno_t portno;
+
+       priv = xpd->priv;
+       BUG_ON(!priv);
+       portno = info->h.portnum;
+       switch (REG_FIELD(info, regnum)) {
+       case REG_INTERRUPT_SRC:
+               got_chip_interrupt(xpd, REG_FIELD(info, data_low), portno);
+               break;
+       case DAA_REG_VBAT:
+               update_battery_voltage(xpd, REG_FIELD(info, data_low), portno);
+               break;
+       case DAA_REG_CURRENT:
+               update_battery_current(xpd, REG_FIELD(info, data_low), portno);
+               break;
+#ifdef WITH_METERING
+       case DAA_REG_METERING:
+               update_metering_state(xpd, REG_FIELD(info, data_low), portno);
+               break;
+#endif
+       }
+       LINE_DBG(REGS, xpd, portno, "%c reg_num=0x%X, dataL=0x%X dataH=0x%X\n",
+                ((info->h.bytes == 3) ? 'I' : 'D'), REG_FIELD(info, regnum),
+                REG_FIELD(info, data_low), REG_FIELD(info, data_high));
+       /* Update /proc info only if reply relate to the last slic read request */
+       if (REG_FIELD(&xpd->requested_reply, regnum) ==
+                       REG_FIELD(info, regnum)
+               && REG_FIELD(&xpd->requested_reply, do_subreg) ==
+                       REG_FIELD(info, do_subreg)
+               && REG_FIELD(&xpd->requested_reply, subreg) ==
+                       REG_FIELD(info, subreg)) {
+               xpd->last_reply = *info;
+       }
+       return 0;
+}
+
+static int FXO_card_state(xpd_t *xpd, bool on)
+{
+       int ret = 0;
+       struct FXO_priv_data *priv;
+
+       BUG_ON(!xpd);
+       priv = xpd->priv;
+       BUG_ON(!priv);
+       XPD_DBG(GENERAL, xpd, "%s\n", (on) ? "on" : "off");
+       return ret;
+}
+
+static const struct xops fxo_xops = {
+       .card_new = FXO_card_new,
+       .card_init = FXO_card_init,
+       .card_remove = FXO_card_remove,
+       .card_tick = FXO_card_tick,
+       .card_register_reply = FXO_card_register_reply,
+};
+
+static const struct phoneops fxo_phoneops = {
+       .card_dahdi_preregistration = FXO_card_dahdi_preregistration,
+       .card_dahdi_postregistration = FXO_card_dahdi_postregistration,
+       .card_hooksig = FXO_card_hooksig,
+       .card_pcm_recompute = generic_card_pcm_recompute,
+       .card_pcm_fromspan = generic_card_pcm_fromspan,
+       .card_pcm_tospan = generic_card_pcm_tospan,
+       .card_timing_priority = generic_timing_priority,
+       .echocancel_timeslot = generic_echocancel_timeslot,
+       .echocancel_setmask = generic_echocancel_setmask,
+       .card_ioctl = FXO_card_ioctl,
+       .card_open = FXO_card_open,
+       .card_state = FXO_card_state,
+       .span_assigned = FXO_span_assigned,
+};
+
+static xproto_table_t PROTO_TABLE(FXO) = {
+       .owner = THIS_MODULE,
+       .entries = {
+               /*      Prototable      Card    Opcode          */
+               XENTRY( FXO,            FXO,    SIG_CHANGED     ),
+       },
+       .name = "FXO",  /* protocol name */
+       .ports_per_subunit = 8,
+       .type = XPD_TYPE_FXO,
+       .xops = &fxo_xops,
+       .phoneops = &fxo_phoneops,
+       .packet_is_valid = fxo_packet_is_valid,
+       .packet_dump = fxo_packet_dump,
+};
+
+static bool fxo_packet_is_valid(xpacket_t *pack)
+{
+       const xproto_entry_t *xe;
+
+       //DBG(GENERAL, "\n");
+       xe = xproto_card_entry(&PROTO_TABLE(FXO), XPACKET_OP(pack));
+       return xe != NULL;
+}
+
+static void fxo_packet_dump(const char *msg, xpacket_t *pack)
+{
+       DBG(GENERAL, "%s\n", msg);
+}
+
+/*------------------------- DAA Handling --------------------------*/
+
+#ifdef CONFIG_PROC_FS
+static int proc_fxo_info_show(struct seq_file *sfile, void *not_used)
+{
+       unsigned long flags;
+       xpd_t *xpd = sfile->private;
+       struct FXO_priv_data *priv;
+       int i;
+
+       if (!xpd)
+               return -ENODEV;
+       spin_lock_irqsave(&xpd->lock, flags);
+       priv = xpd->priv;
+       BUG_ON(!priv);
+       seq_printf(sfile, "\t%-17s: ", "Channel");
+       for_each_line(xpd, i) {
+               if (!IS_SET(PHONEDEV(xpd).digital_outputs, i)
+                   && !IS_SET(PHONEDEV(xpd).digital_inputs, i)) {
+                       seq_printf(sfile, "%4d ", i % 10);
+               }
+       }
+       seq_printf(sfile, "\nLeds:");
+       seq_printf(sfile, "\n\t%-17s: ", "state");
+       for_each_line(xpd, i) {
+               if (!IS_SET(PHONEDEV(xpd).digital_outputs, i)
+                   && !IS_SET(PHONEDEV(xpd).digital_inputs, i)) {
+                       seq_printf(sfile, "  %d%d ",
+                                  IS_SET(priv->ledstate[LED_GREEN], i),
+                                  IS_SET(priv->ledstate[LED_RED], i));
+               }
+       }
+       seq_printf(sfile, "\n\t%-17s: ", "blinking");
+       for_each_line(xpd, i) {
+               if (!IS_SET(PHONEDEV(xpd).digital_outputs, i)
+                   && !IS_SET(PHONEDEV(xpd).digital_inputs, i)) {
+                       seq_printf(sfile, "  %d%d ",
+                                  IS_BLINKING(priv, i, LED_GREEN),
+                                  IS_BLINKING(priv, i, LED_RED));
+               }
+       }
+       seq_printf(sfile, "\nBattery-Data:");
+       seq_printf(sfile, "\n\t%-17s: ", "voltage");
+       for_each_line(xpd, i) {
+               seq_printf(sfile, "%4d ", priv->battery_voltage[i]);
+       }
+       seq_printf(sfile, "\n\t%-17s: ", "current");
+       for_each_line(xpd, i) {
+               seq_printf(sfile, "%4d ", priv->battery_current[i]);
+       }
+       seq_printf(sfile, "\nBattery:");
+       seq_printf(sfile, "\n\t%-17s: ", "on");
+       for_each_line(xpd, i) {
+               char *bat;
+
+               if (priv->battery[i] == BATTERY_ON)
+                       bat = "+";
+               else if (priv->battery[i] == BATTERY_OFF)
+                       bat = "-";
+               else
+                       bat = ".";
+               seq_printf(sfile, "%4s ", bat);
+       }
+       seq_printf(sfile, "\n\t%-17s: ", "debounce");
+       for_each_line(xpd, i) {
+               seq_printf(sfile, "%4d ", priv->nobattery_debounce[i]);
+       }
+       seq_printf(sfile, "\nPolarity-Reverse:");
+       seq_printf(sfile, "\n\t%-17s: ", "polarity");
+       for_each_line(xpd, i) {
+               char *polname;
+
+               if (priv->polarity[i] == POL_POSITIVE)
+                       polname = "+";
+               else if (priv->polarity[i] == POL_NEGATIVE)
+                       polname = "-";
+               else
+                       polname = ".";
+               seq_printf(sfile, "%4s ", polname);
+       }
+       seq_printf(sfile, "\n\t%-17s: ", "debounce");
+       for_each_line(xpd, i) {
+               seq_printf(sfile, "%4d ", priv->polarity_debounce[i]);
+       }
+       seq_printf(sfile, "\nPower-Denial:");
+       seq_printf(sfile, "\n\t%-17s: ", "power");
+       for_each_line(xpd, i) {
+               char *curr;
+
+               if (priv->power[i] == POWER_ON)
+                       curr = "+";
+               else if (priv->power[i] == POWER_OFF)
+                       curr = "-";
+               else
+                       curr = ".";
+               seq_printf(sfile, "%4s ", curr);
+       }
+       seq_printf(sfile, "\n\t%-17s: ", "safezone");
+       for_each_line(xpd, i) {
+               seq_printf(sfile, "%4d ", priv->power_denial_safezone[i]);
+       }
+       seq_printf(sfile, "\n\t%-17s: ", "delay");
+       for_each_line(xpd, i) {
+               seq_printf(sfile, "%4d ", priv->power_denial_delay[i]);
+       }
+#ifdef WITH_METERING
+       seq_printf(sfile, "\nMetering:");
+       seq_printf(sfile, "\n\t%-17s: ", "count");
+       for_each_line(xpd, i) {
+               seq_printf(sfile, "%4d ", priv->metering_count[i]);
+       }
+#endif
+       seq_printf(sfile, "\n");
+       spin_unlock_irqrestore(&xpd->lock, flags);
+       return 0;
+}
+
+static int proc_fxo_info_open(struct inode *inode, struct file *file)
+{
+       return single_open(file, proc_fxo_info_show, PDE_DATA(inode));
+}
+
+static const struct file_operations proc_fxo_info_ops = {
+       .owner          = THIS_MODULE,
+       .open           = proc_fxo_info_open,
+       .read           = seq_read,
+       .llseek         = seq_lseek,
+       .release        = single_release,
+};
+
+#ifdef WITH_METERING
+static int proc_xpd_metering_show(struct seq_file *sfile, void *not_used)
+{
+       unsigned long flags;
+       xpd_t *xpd = sfile->private;
+       struct FXO_priv_data *priv;
+       int i;
+
+       if (!xpd)
+               return -ENODEV;
+       priv = xpd->priv;
+       BUG_ON(!priv);
+       spin_lock_irqsave(&xpd->lock, flags);
+       seq_printf(sfile, "# Chan\tMeter (since last read)\n");
+       for_each_line(xpd, i) {
+               seq_printf(sfile, "%d\t%d\n", i, priv->metering_count[i]);
+       }
+       spin_unlock_irqrestore(&xpd->lock, flags);
+       /* Zero meters */
+       for_each_line(xpd, i)
+           priv->metering_count[i] = 0;
+       return 0;
+}
+
+static int proc_xpd_metering_open(struct inode *inode, struct file *file)
+{
+       return single_open(file, proc_xpd_metering_show, PDE_DATA(inode));
+}
+
+static const struct file_operations proc_xpd_metering_ops = {
+       .owner          = THIS_MODULE,
+       .open           = proc_xpd_metering_open,
+       .read           = seq_read,
+       .llseek         = seq_lseek,
+       .release        = single_release,
+};
+
+#endif
+#endif
+
+static DEVICE_ATTR_READER(fxo_battery_show, dev, buf)
+{
+       xpd_t *xpd;
+       struct FXO_priv_data *priv;
+       unsigned long flags;
+       int len = 0;
+       int i;
+
+       BUG_ON(!dev);
+       xpd = dev_to_xpd(dev);
+       if (!xpd)
+               return -ENODEV;
+       priv = xpd->priv;
+       BUG_ON(!priv);
+       spin_lock_irqsave(&xpd->lock, flags);
+       for_each_line(xpd, i) {
+               char bat;
+
+               if (priv->battery[i] == BATTERY_ON)
+                       bat = '+';
+               else if (priv->battery[i] == BATTERY_OFF)
+                       bat = '-';
+               else
+                       bat = '.';
+               len += sprintf(buf + len, "%c ", bat);
+       }
+       len += sprintf(buf + len, "\n");
+       spin_unlock_irqrestore(&xpd->lock, flags);
+       return len;
+}
+
+static DEVICE_ATTR(fxo_battery, S_IRUGO, fxo_battery_show, NULL);
+
+static int fxo_xpd_probe(struct device *dev)
+{
+       xpd_t *xpd;
+       int ret;
+
+       xpd = dev_to_xpd(dev);
+       /* Is it our device? */
+       if (xpd->xpd_type != XPD_TYPE_FXO) {
+               XPD_ERR(xpd, "drop suggestion for %s (%d)\n", dev_name(dev),
+                       xpd->xpd_type);
+               return -EINVAL;
+       }
+       XPD_DBG(DEVICES, xpd, "SYSFS\n");
+       ret = device_create_file(dev, &dev_attr_fxo_battery);
+       if (ret) {
+               XPD_ERR(xpd, "%s: device_create_file(fxo_battery) failed: %d\n",
+                       __func__, ret);
+               goto fail_fxo_battery;
+       }
+       return 0;
+fail_fxo_battery:
+       return ret;
+}
+
+static int fxo_xpd_remove(struct device *dev)
+{
+       xpd_t *xpd;
+
+       xpd = dev_to_xpd(dev);
+       XPD_DBG(DEVICES, xpd, "SYSFS\n");
+       device_remove_file(dev, &dev_attr_fxo_battery);
+       return 0;
+}
+
+static struct xpd_driver fxo_driver = {
+       .xpd_type = XPD_TYPE_FXO,
+       .driver = {
+                  .name = "fxo",
+                  .owner = THIS_MODULE,
+                  .probe = fxo_xpd_probe,
+                  .remove = fxo_xpd_remove}
+};
+
+static int __init card_fxo_startup(void)
+{
+       int ret;
+
+       if (ring_debounce <= 0) {
+               ERR("ring_debounce=%d. Must be positive number of ticks\n",
+                   ring_debounce);
+               return -EINVAL;
+       }
+       if ((ret = xpd_driver_register(&fxo_driver.driver)) < 0)
+               return ret;
+#ifdef WITH_METERING
+       INFO("FEATURE: WITH METERING Detection\n");
+#else
+       INFO("FEATURE: NO METERING Detection\n");
+#endif
+       xproto_register(&PROTO_TABLE(FXO));
+       return 0;
+}
+
+static void __exit card_fxo_cleanup(void)
+{
+       xproto_unregister(&PROTO_TABLE(FXO));
+       xpd_driver_unregister(&fxo_driver.driver);
+}
+
+MODULE_DESCRIPTION("XPP FXO Card Driver");
+MODULE_AUTHOR("Oron Peled <oron@actcom.co.il>");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS_XPD(XPD_TYPE_FXO);
+
+module_init(card_fxo_startup);
+module_exit(card_fxo_cleanup);
diff --git a/drivers/dahdi/xpp/card_fxo.h b/drivers/dahdi/xpp/card_fxo.h
new file mode 100644 (file)
index 0000000..00cf20f
--- /dev/null
@@ -0,0 +1,40 @@
+#ifndef        CARD_FXO_H
+#define        CARD_FXO_H
+/*
+ * Written by Oron Peled <oron@actcom.co.il>
+ * Copyright (C) 2004-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.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ *
+ */
+
+#include "xpd.h"
+
+enum fxo_opcodes {
+       XPROTO_NAME(FXO, SIG_CHANGED) = 0x06,           /**/
+       XPROTO_NAME(FXO, DAA_WRITE) = 0x0F,             /* Write to DAA */
+       XPROTO_NAME(FXO, CHAN_CID) = 0x0F,              /* Write to DAA */
+       XPROTO_NAME(FXO, LED) = 0x0F,                   /* Write to DAA */
+};
+
+
+DEF_RPACKET_DATA(FXO, SIG_CHANGED,
+               xpp_line_t sig_status;  /* channels: lsb=1, msb=8 */
+               xpp_line_t sig_toggles; /* channels: lsb=1, msb=8 */
+               );
+
+#endif /* CARD_FXO_H */
diff --git a/drivers/dahdi/xpp/card_fxs.c b/drivers/dahdi/xpp/card_fxs.c
new file mode 100644 (file)
index 0000000..ab5f5a8
--- /dev/null
@@ -0,0 +1,2379 @@
+/*
+ * Written by Oron Peled <oron@actcom.co.il>
+ * Copyright (C) 2004-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.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ *
+ */
+
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/fs.h>
+#include <linux/delay.h>
+#include <linux/seq_file.h>
+#include "xpd.h"
+#include "xproto.h"
+#include "xpp_dahdi.h"
+#include "card_fxs.h"
+#include "dahdi_debug.h"
+#include "xbus-core.h"
+
+static const char rcsid[] = "$Id$";
+
+/* must be before dahdi_debug.h */
+static DEF_PARM(int, debug, 0, 0644, "Print DBG statements");
+static DEF_PARM_BOOL(reversepolarity, 0, 0644, "Reverse Line Polarity");
+static DEF_PARM_BOOL(dtmf_detection, 1, 0644, "Do DTMF detection in hardware");
+#ifdef POLL_DIGITAL_INPUTS
+static DEF_PARM(uint, poll_digital_inputs, 1000, 0644, "Poll Digital Inputs");
+#endif
+static DEF_PARM(uint, poll_chan_linefeed, 30000, 0644, "Poll Channel Linefeed");
+
+static DEF_PARM_BOOL(vmwi_ioctl, 1, 0644,
+                    "Asterisk support VMWI notification via ioctl");
+static DEF_PARM_BOOL(ring_trapez, 0, 0664, "Use trapezoid ring type");
+static DEF_PARM_BOOL(lower_ringing_noise, 0, 0664,
+               "Lower ringing noise (may loose CallerID)");
+
+/* Signaling is opposite (fxo signalling for fxs card) */
+#if 1
+#define        FXS_DEFAULT_SIGCAP \
+               (DAHDI_SIG_FXOKS | DAHDI_SIG_FXOLS | DAHDI_SIG_FXOGS)
+#else
+#define        FXS_DEFAULT_SIGCAP \
+               (DAHDI_SIG_SF | DAHDI_SIG_EM)
+#endif
+
+#define        VMWI_TYPE(priv, pos, type)      \
+       ((priv)->vmwisetting[pos].vmwi_type & DAHDI_VMWI_ ## type)
+#define        VMWI_NEON(priv, pos)            VMWI_TYPE(priv, pos, HVAC)
+
+#define        LINES_DIGI_OUT  2
+#define        LINES_DIGI_INP  4
+
+enum fxs_leds {
+       LED_GREEN,
+       LED_RED,
+       OUTPUT_RELAY,
+};
+
+#define        NUM_LEDS        2
+
+/* Shortcuts */
+#define        SLIC_WRITE      1
+#define        SLIC_READ       0
+#define        SLIC_DIRECT_REQUEST(xbus, xpd, port, writing, reg, dL)  \
+       xpp_register_request((xbus), (xpd), (port), \
+       (writing), (reg), 0, 0, (dL), 0, 0, 0, 0)
+#define        SLIC_INDIRECT_REQUEST(xbus, xpd, port, writing, reg, dL, dH)    \
+       xpp_register_request((xbus), (xpd), (port), \
+       (writing), 0x1E, 1, (reg), (dL), 1, (dH), 0, 0)
+#define        EXP_REQUEST(xbus, xpd, writing, reg, dL, dH)    \
+       xpp_register_request((xbus), (xpd), 0, \
+       (writing), (reg), 1, 0, (dL), 1, (dH), 0, 1)
+#define        RAM_REQUEST(xbus, xpd, port, writing, addr, data)       \
+       xpp_ram_request((xbus), (xpd), (port), \
+       (writing), (__u8)(addr), (__u8)((addr) >> 8), (__u8)(data), (__u8)((data) >> 8), (__u8)((data) >> 16), (__u8)((data) >> 24), 0)
+
+#define        VALID_PORT(port) \
+               (((port) >= 0 && (port) <= 7) || (port) == PORT_BROADCAST)
+
+#define        REG_TYPE1_DIGITAL_IOCTRL        0x06    /* LED and RELAY control */
+
+/* Values of SLIC linefeed control register (0x40) */
+enum fxs_state {
+       FXS_LINE_OPEN = 0x00,   /* Open */
+       FXS_LINE_ACTIVE = 0x01, /* Forward active */
+       FXS_LINE_OHTRANS = 0x02,        /* Forward on-hook transmission */
+       FXS_LINE_TIPOPEN = 0x03,        /* TIP open */
+       FXS_LINE_RING = 0x04,   /* Ringing */
+       FXS_LINE_REV_ACTIVE = 0x05,     /* Reverse active */
+       FXS_LINE_REV_OHTRANS = 0x06,    /* Reverse on-hook transmission */
+       FXS_LINE_RING_OPEN = 0x07       /* RING open */
+};
+
+enum neon_state {
+       END_NEON = 0,
+       INIT_NEON = 1,
+};
+
+#define        FXS_LINE_POL_ACTIVE \
+               ((reversepolarity) ? FXS_LINE_REV_ACTIVE : FXS_LINE_ACTIVE)
+#define        FXS_LINE_POL_OHTRANS \
+               ((reversepolarity) ? FXS_LINE_REV_OHTRANS : FXS_LINE_OHTRANS)
+
+/* FXS type 1 registers */
+#define REG_TYPE1_RINGCON              0x22    /* 34 - Ringing Oscillator Control */
+
+/*
+ * DTMF detection
+ */
+#define REG_TYPE1_DTMF_DECODE          0x18    /* 24 - DTMF Decode Status */
+#define REG_TYPE1_BATTERY              0x42    /* 66 - Battery Feed Control */
+#define        REG_TYPE1_BATTERY_BATSL         BIT(1)  /* Battery Feed Select */
+
+/* 68 -  Loop Closure/Ring Trip Detect Status */
+#define        REG_TYPE1_LOOPCLOSURE           0x44
+#define        REG_TYPE1_LOOPCLOSURE_ZERO      0xF8    /* Loop Closure zero bits. */
+#define        REG_TYPE1_LOOPCLOSURE_LCR       BIT(0)  /* Loop Closure Detect Indicator. */
+
+/* FXS type 6 registers */
+#define REG_TYPE6_RINGCON              0x26    /* 38 - Ringing Oscillator Control */
+
+/* 34 -  Loop Closure/Ring Trip Detect Status */
+#define        REG_TYPE6_LCRRTP                0x22
+#define        REG_TYPE6_LCRRTP_ZERO           0xF0    /* Loop Closure zero bits. */
+#define        REG_TYPE6_LCRRTP_LCR            BIT(1)  /* Loop Closure Detect Indicator. */
+
+#define        REG_TYPE6_TONEN                 0x3E    /* 62 - Hardware DTMF detection */
+#define        REG_TYPE6_TONEN_DTMF_DIS        BIT(2)  /*      DTMF Disable */
+#define REG_TYPE6_LINEFEED             0x1E    /* 30 - Linefeed */
+#define REG_TYPE6_TONDTMF              0x3C    /* 60 - DTMF Decode Status */
+#define        REG_TYPE6_EXP_GPIOA             0x12    /* I/O Expander GPIOA */
+#define        REG_TYPE6_EXP_GPIOB             0x13    /* I/O Expander GPIOB */
+#define        REG_TYPE6_ENHANCE               0x2F    /* 47 - Enhance */
+#define        REG_TYPE6_USERSTAT              0x42    /* 66 - Userstat */
+#define        REG_TYPE6_DIAG1                 0x47    /* 71 - Diag1 */
+#define RAM_TYPE6_SLOPE_VLIM           634
+#define SLOPE_VLIM_DFLT                        0x1E655196L
+#define SLOPE_VLIM_MWI                 0x8000000L
+#define RAM_TYPE6_VBATH_EXPECT         767
+#define VBATH_EXPECT_DFLT              0x2B10A20L
+#define VBATH_EXPECT_MWI               0x6147AB2L
+
+/*---------------- FXS Protocol Commands ----------------------------------*/
+
+static bool fxs_packet_is_valid(xpacket_t *pack);
+static void fxs_packet_dump(const char *msg, xpacket_t *pack);
+#ifdef CONFIG_PROC_FS
+static const struct file_operations proc_fxs_info_ops;
+#ifdef WITH_METERING
+static const struct file_operations proc_xpd_metering_ops;
+#endif
+#endif
+static void start_stop_vm_led(xbus_t *xbus, xpd_t *xpd, lineno_t pos);
+
+#define        PROC_FXS_INFO_FNAME     "fxs_info"
+#ifdef WITH_METERING
+#define        PROC_METERING_FNAME     "metering_gen"
+#endif
+
+struct FXS_priv_data {
+#ifdef WITH_METERING
+       struct proc_dir_entry *meteringfile;
+#endif
+       struct proc_dir_entry *fxs_info;
+       xpp_line_t ledstate[NUM_LEDS];  /* 0 - OFF, 1 - ON */
+       xpp_line_t ledcontrol[NUM_LEDS];        /* 0 - OFF, 1 - ON */
+       xpp_line_t search_fsk_pattern;
+       xpp_line_t found_fsk_pattern;
+       xpp_line_t update_offhook_state;
+       xpp_line_t want_dtmf_events;    /* what dahdi want */
+       xpp_line_t want_dtmf_mute;      /* what dahdi want */
+       xpp_line_t prev_key_down;       /* DTMF down sets the bit */
+       xpp_line_t neon_blinking;
+       xpp_line_t neonstate;
+       xpp_line_t vbat_h;              /* High voltage */
+       struct timeval prev_key_time[CHANNELS_PERXPD];
+       int led_counter[NUM_LEDS][CHANNELS_PERXPD];
+       int overheat_reset_counter[CHANNELS_PERXPD];
+       int ohttimer[CHANNELS_PERXPD];
+#define OHT_TIMER              6000    /* How long after RING to retain OHT */
+       /* IDLE changing hook state */
+       enum fxs_state idletxhookstate[CHANNELS_PERXPD];
+       enum fxs_state lasttxhook[CHANNELS_PERXPD];
+       enum fxs_state polledhook[CHANNELS_PERXPD];
+       struct dahdi_vmwi_info vmwisetting[CHANNELS_PERXPD];
+};
+
+/*
+ * LED counter values:
+ *     n>1     : BLINK every n'th tick
+ */
+#define        LED_COUNTER(priv, pos, color)   ((priv)->led_counter[color][pos])
+#define        IS_BLINKING(priv, pos, color)   (LED_COUNTER(priv, pos, color) > 0)
+#define        MARK_BLINK(priv, pos, color, t) \
+               ((priv)->led_counter[color][pos] = (t))
+#define        MARK_OFF(priv, pos, color) \
+       do { \
+               BIT_CLR((priv)->ledcontrol[color], (pos)); \
+               MARK_BLINK((priv), (pos), (color), 0); \
+       } while (0)
+#define        MARK_ON(priv, pos, color) \
+       do { \
+               BIT_SET((priv)->ledcontrol[color], (pos)); \
+               MARK_BLINK((priv), (pos), (color), 0); \
+       } while (0)
+
+#define        LED_BLINK_RING                  (1000/8)        /* in ticks */
+
+/*---------------- FXS: Static functions ----------------------------------*/
+static int set_vm_led_mode(xbus_t *xbus, xpd_t *xpd, int pos,
+                          unsigned int msg_waiting);
+
+static int do_chan_power(xbus_t *xbus, xpd_t *xpd, lineno_t chan, bool on)
+{
+       struct FXS_priv_data *priv;
+       unsigned long *p;
+       int was;
+
+       BUG_ON(!xbus);
+       BUG_ON(!xpd);
+       if (XPD_HW(xpd).type == 6) {
+               LINE_DBG(SIGNAL, xpd, chan, "is ignored in Si32260\n");
+               return 0;
+       }
+       priv = xpd->priv;
+       p = (unsigned long *)&priv->vbat_h;
+       if (on)
+               was = test_and_set_bit(chan, p) != 0;
+       else
+               was = test_and_clear_bit(chan, p) != 0;
+       if (was == on) {
+               LINE_DBG(SIGNAL, xpd, chan,
+                       "%s (same, ignored)\n", (on) ? "up" : "down");
+               return 0;
+       }
+       LINE_DBG(SIGNAL, xpd, chan, "%s\n", (on) ? "up" : "down");
+       return SLIC_DIRECT_REQUEST(xbus, xpd, chan, SLIC_WRITE, REG_TYPE1_BATTERY,
+                       (on) ? (int)REG_TYPE1_BATTERY_BATSL : 0x00);
+}
+
+static int linefeed_control(xbus_t *xbus, xpd_t *xpd, lineno_t chan,
+                           enum fxs_state value)
+{
+       struct FXS_priv_data *priv;
+       bool want_vbat_h;
+
+       priv = xpd->priv;
+       /*
+        * Should we drop vbat_h only during actuall ring?
+        *   - It would lower the noise caused to other channels by
+        *     group ringing
+        *   - But it may also stop CallerID from passing through the SLIC
+        */
+       want_vbat_h = value == FXS_LINE_RING;
+       if (lower_ringing_noise || want_vbat_h)
+               do_chan_power(xbus, xpd, chan, want_vbat_h);
+       LINE_DBG(SIGNAL, xpd, chan, "value=0x%02X\n", value);
+       priv->lasttxhook[chan] = value;
+       if (XPD_HW(xpd).type == 6) {
+               int ret;
+
+               /* Make sure NEON state is off for */
+               if (value == FXS_LINE_POL_OHTRANS && IS_SET(priv->neon_blinking, chan))
+                       set_vm_led_mode(xpd->xbus, xpd, chan, 0);
+               ret = SLIC_DIRECT_REQUEST(xbus, xpd, chan, SLIC_WRITE, REG_TYPE6_LINEFEED, value);
+               if (value == FXS_LINE_POL_ACTIVE && PHONEDEV(xpd).msg_waiting[chan])
+                       set_vm_led_mode(xpd->xbus, xpd, chan, PHONEDEV(xpd).msg_waiting[chan]);
+               return ret;
+       } else {
+               return SLIC_DIRECT_REQUEST(xbus, xpd, chan, SLIC_WRITE, 0x40, value);
+       }
+       return 0;
+}
+
+static void vmwi_search(xpd_t *xpd, lineno_t pos, bool on)
+{
+       struct FXS_priv_data *priv;
+
+       priv = xpd->priv;
+       BUG_ON(!xpd);
+       if (VMWI_NEON(priv, pos) && on) {
+               LINE_DBG(SIGNAL, xpd, pos, "START\n");
+               BIT_SET(priv->search_fsk_pattern, pos);
+       } else {
+               LINE_DBG(SIGNAL, xpd, pos, "STOP\n");
+               BIT_CLR(priv->search_fsk_pattern, pos);
+       }
+}
+
+/*
+ * LED and RELAY control is done via SLIC register 0x06:
+ *         7     6     5     4     3     2     1     0
+ *  +-----+-----+-----+-----+-----+-----+-----+-----+
+ *  | M2  | M1  | M3  | C2  | O1  | O3  | C1  | C3  |
+ *  +-----+-----+-----+-----+-----+-----+-----+-----+
+ *
+ *  Cn - Control bit (control one digital line)
+ *  On - Output bit (program a digital line for output)
+ *  Mn - Mask bit (only the matching output control bit is affected)
+ *
+ *  C3 - OUTPUT RELAY (0 - OFF, 1 - ON)
+ *  C1 - GREEN LED (0 - OFF, 1 - ON)
+ *  O3 - Output RELAY (this line is output)
+ *  O1 - Output GREEN (this line is output)
+ *  C2 - RED LED (0 - OFF, 1 - ON)
+ *  M3 - Mask RELAY. (1 - C3 effect the OUTPUT RELAY)
+ *  M2 - Mask RED. (1 - C2 effect the RED LED)
+ *  M1 - Mask GREEN. (1 - C1 effect the GREEN LED)
+ *
+ *  The OUTPUT RELAY (actually a relay out) is connected to line 0 and 4 only.
+ */
+
+//                                              GREEN   RED     OUTPUT RELAY
+static const int led_register_mask[] = { BIT(7), BIT(6), BIT(5) };
+static const int led_register_vals[] = { BIT(4), BIT(1), BIT(0) };
+
+/*
+ * pos can be:
+ *     - A line number
+ *     - ALL_LINES. This is not valid anymore since 8-Jan-2007.
+ */
+static int do_led(xpd_t *xpd, lineno_t chan, __u8 which, bool on)
+{
+       int ret = 0;
+       struct FXS_priv_data *priv;
+       int value;
+       xbus_t *xbus;
+
+       BUG_ON(!xpd);
+       BUG_ON(chan == ALL_LINES);
+       xbus = xpd->xbus;
+       priv = xpd->priv;
+       which = which % NUM_LEDS;
+       if (IS_SET(PHONEDEV(xpd).digital_outputs, chan)
+           || IS_SET(PHONEDEV(xpd).digital_inputs, chan))
+               goto out;
+       if (chan == PORT_BROADCAST) {
+               priv->ledstate[which] = (on) ? ~0 : 0;
+       } else {
+               if (on)
+                       BIT_SET(priv->ledstate[which], chan);
+               else
+                       BIT_CLR(priv->ledstate[which], chan);
+       }
+       LINE_DBG(LEDS, xpd, chan, "LED: (type=%d) which=%d -- %s\n", XPD_HW(xpd).type, which,
+                (on) ? "on" : "off");
+       if (XPD_HW(xpd).type == 6) {
+               int mask = 1 << chan;
+               value = (on) << chan;
+               XPD_DBG(LEDS, xpd, "LED(%d): 0x%0X (mask: 0x%0X)\n", chan,
+                               value, mask);
+               if (which == LED_GREEN) { /* other leds ignored */
+                       ret = EXP_REQUEST(xbus, xpd, SLIC_WRITE,
+                               REG_TYPE6_EXP_GPIOA, value, mask);
+               }
+       } else {
+               value = BIT(2) | BIT(3);
+               value |= ((BIT(5) | BIT(6) | BIT(7)) & ~led_register_mask[which]);
+               if (on)
+                       value |= led_register_vals[which];
+               ret = SLIC_DIRECT_REQUEST(xbus, xpd, chan, SLIC_WRITE,
+                               REG_TYPE1_DIGITAL_IOCTRL, value);
+       }
+       return 0;
+out:
+       return ret;
+}
+
+static inline void set_mwi_led(xpd_t *xpd, int pos, int on)
+{
+       struct FXS_priv_data *priv;
+       BUG_ON(!xpd);
+       priv = xpd->priv;
+
+       if (XPD_HW(xpd).type != 6)
+               return;
+       if (on) {
+               if (! IS_SET(priv->neonstate, pos)) {
+                       SLIC_DIRECT_REQUEST(xpd->xbus, xpd, pos, SLIC_WRITE, REG_TYPE6_ENHANCE, 0x00);
+                       SLIC_DIRECT_REQUEST(xpd->xbus, xpd, pos, SLIC_WRITE, REG_TYPE6_USERSTAT, 0x04);
+                       SLIC_DIRECT_REQUEST(xpd->xbus, xpd, pos, SLIC_WRITE, REG_TYPE6_DIAG1, 0x0F);
+                       BIT_SET(priv->neonstate, pos);
+               }
+       } else {
+               if (IS_SET(priv->neonstate, pos)) {
+                       SLIC_DIRECT_REQUEST(xpd->xbus, xpd, pos, SLIC_WRITE, REG_TYPE6_DIAG1, 0x00);
+                       BIT_CLR(priv->neonstate, pos);
+               }
+       }
+}
+
+static void blink_mwi(xpd_t *xpd)
+{
+       struct FXS_priv_data *priv;
+       unsigned int timer_count;
+       int i;
+
+       BUG_ON(!xpd);
+       priv = xpd->priv;
+       timer_count = xpd->timer_count;
+       for_each_line(xpd, i) {
+               unsigned int msgs = PHONEDEV(xpd).msg_waiting[i];
+               /* LED duty cycle: 300ms on, 700ms off */
+               unsigned int in_range = (timer_count % 1000) >= 0 && (timer_count % 1000) <= 300;
+
+               if (!IS_OFFHOOK(xpd, i) && msgs && in_range && 
+                       IS_SET(priv->neon_blinking,i) && priv->ohttimer[i] == 0)
+                       set_mwi_led(xpd, i, 1);
+               else
+                       set_mwi_led(xpd, i, 0);
+       }
+}
+
+static void handle_fxs_leds(xpd_t *xpd)
+{
+       int i;
+       const enum fxs_leds colors[] = { LED_GREEN, LED_RED };
+       enum fxs_leds color;
+       unsigned int timer_count;
+       struct FXS_priv_data *priv;
+
+       BUG_ON(!xpd);
+       priv = xpd->priv;
+       timer_count = xpd->timer_count;
+       for (color = 0; color < ARRAY_SIZE(colors); color++) {
+               for_each_line(xpd, i) {
+                       if (IS_SET
+                           (PHONEDEV(xpd).digital_outputs | PHONEDEV(xpd).
+                            digital_inputs, i))
+                               continue;
+                       /* Blinking? */
+                       if ((xpd->blink_mode & BIT(i)) || IS_BLINKING(priv, i, color)) {
+                               int mod_value = LED_COUNTER(priv, i, color);
+
+                               if (!mod_value)
+                                       /* safety value */
+                                       mod_value = DEFAULT_LED_PERIOD;
+                               /* led state is toggled */
+                               if ((timer_count % mod_value) == 0) {
+                                       LINE_DBG(LEDS, xpd, i, "ledstate=%s\n",
+                                                (IS_SET
+                                                 (priv->ledstate[color],
+                                                  i)) ? "ON" : "OFF");
+                                       if (!IS_SET(priv->ledstate[color], i))
+                                               do_led(xpd, i, color, 1);
+                                       else
+                                               do_led(xpd, i, color, 0);
+                               }
+                       } else
+                           if (IS_SET
+                               (priv->ledcontrol[color] & ~priv->
+                                ledstate[color], i)) {
+                               do_led(xpd, i, color, 1);
+                       } else
+                           if (IS_SET
+                               (~priv->ledcontrol[color] & priv->
+                                ledstate[color], i)) {
+                               do_led(xpd, i, color, 0);
+                       }
+
+               }
+       }
+}
+
+static void restore_leds(xpd_t *xpd)
+{
+       struct FXS_priv_data *priv;
+       int i;
+
+       priv = xpd->priv;
+       for_each_line(xpd, i) {
+               if (IS_OFFHOOK(xpd, i))
+                       MARK_ON(priv, i, LED_GREEN);
+               else
+                       MARK_OFF(priv, i, LED_GREEN);
+       }
+}
+
+#ifdef WITH_METERING
+static int metering_gen(xpd_t *xpd, lineno_t chan, bool on)
+{
+       __u8 value = (on) ? 0x94 : 0x00;
+
+       if (XPD_HW(xpd).type == 6) {
+               XBUS_NOTICE("Metering not supported with FXS type 6");
+               return 0;
+       }
+       LINE_DBG(SIGNAL, xpd, chan, "METERING Generate: %s\n",
+                (on) ? "ON" : "OFF");
+       return SLIC_DIRECT_REQUEST(xpd->xbus, xpd, chan, SLIC_WRITE, 0x23,
+                                  value);
+}
+#endif
+
+/*---------------- FXS: Methods -------------------------------------------*/
+
+static void fxs_proc_remove(xbus_t *xbus, xpd_t *xpd)
+{
+       struct FXS_priv_data *priv;
+
+       BUG_ON(!xpd);
+       priv = xpd->priv;
+#ifdef CONFIG_PROC_FS
+#ifdef WITH_METERING
+       if (priv->meteringfile) {
+               XPD_DBG(PROC, xpd, "Removing xpd metering tone file\n");
+               remove_proc_entry(PROC_METERING_FNAME, xpd->proc_xpd_dir);
+               priv->meteringfile = NULL;
+       }
+#endif
+       if (priv->fxs_info) {
+               XPD_DBG(PROC, xpd, "Removing xpd FXS_INFO file\n");
+               remove_proc_entry(PROC_FXS_INFO_FNAME, xpd->proc_xpd_dir);
+               priv->fxs_info = NULL;
+       }
+#endif
+}
+
+static int fxs_proc_create(xbus_t *xbus, xpd_t *xpd)
+{
+       struct FXS_priv_data *priv;
+
+       BUG_ON(!xpd);
+       priv = xpd->priv;
+
+#ifdef CONFIG_PROC_FS
+       XPD_DBG(PROC, xpd, "Creating FXS_INFO file\n");
+       priv->fxs_info = proc_create_data(PROC_FXS_INFO_FNAME, 0444,
+                                         xpd->proc_xpd_dir,
+                                         &proc_fxs_info_ops, xpd);
+       if (!priv->fxs_info) {
+               XPD_ERR(xpd, "Failed to create proc file '%s'\n",
+                       PROC_FXS_INFO_FNAME);
+               fxs_proc_remove(xbus, xpd);
+               return -EINVAL;
+       }
+       SET_PROC_DIRENTRY_OWNER(priv->fxs_info);
+#ifdef WITH_METERING
+       XPD_DBG(PROC, xpd, "Creating Metering tone file\n");
+       priv->meteringfile = proc_create_data(PROC_METERING_FNAME, 0200,
+                                             xpd->proc_xpd_dir,
+                                             &proc_xpd_metering_ops, xpd);
+       if (!priv->meteringfile) {
+               XPD_ERR(xpd, "Failed to create proc file '%s'\n",
+                       PROC_METERING_FNAME);
+               fxs_proc_remove(xbus, xpd);
+               return -EINVAL;
+       }
+#endif
+#endif
+       return 0;
+}
+
+static xpd_t *FXS_card_new(xbus_t *xbus, int unit, int subunit,
+                          const xproto_table_t *proto_table,
+                          const struct unit_descriptor *unit_descriptor,
+                          bool to_phone)
+{
+       xpd_t *xpd = NULL;
+       int channels;
+       int subunit_ports;
+       int regular_channels;
+       struct FXS_priv_data *priv;
+       int i;
+       int d_inputs = 0;
+       int d_outputs = 0;
+
+       if (!to_phone) {
+               XBUS_NOTICE(xbus,
+                           "XPD=%d%d: try to instanciate FXS with reverse direction\n",
+                           unit, subunit);
+               return NULL;
+       }
+       subunit_ports = unit_descriptor->numchips * unit_descriptor->ports_per_chip;
+       if (unit_descriptor->subtype == 2)
+               regular_channels = min(6, subunit_ports);
+       else
+               regular_channels = min(8, subunit_ports);
+       channels = regular_channels;
+       /* Calculate digital inputs/outputs */
+       if (unit == 0 && unit_descriptor->subtype != 4 && unit_descriptor->numchips != 4) {
+               channels += 6;  /* 2 DIGITAL OUTPUTS, 4 DIGITAL INPUTS */
+               d_inputs = LINES_DIGI_INP;
+               d_outputs = LINES_DIGI_OUT;
+       }
+       xpd =
+           xpd_alloc(xbus, unit, subunit,
+                     sizeof(struct FXS_priv_data), proto_table, unit_descriptor, channels);
+       if (!xpd)
+               return NULL;
+       /* Initialize digital inputs/outputs */
+       if (d_inputs) {
+               XBUS_DBG(GENERAL, xbus, "Initialize %d digital inputs\n",
+                        d_inputs);
+               PHONEDEV(xpd).digital_inputs =
+                   BITMASK(d_inputs) << (regular_channels + d_outputs);
+       } else
+               XBUS_DBG(GENERAL, xbus, "No digital inputs\n");
+       if (d_outputs) {
+               XBUS_DBG(GENERAL, xbus, "Initialize %d digital outputs\n",
+                        d_outputs);
+               PHONEDEV(xpd).digital_outputs =
+                   BITMASK(d_outputs) << regular_channels;
+       } else
+               XBUS_DBG(GENERAL, xbus, "No digital outputs\n");
+       PHONEDEV(xpd).direction = TO_PHONE;
+       xpd->type_name = "FXS";
+       if (fxs_proc_create(xbus, xpd) < 0)
+               goto err;
+       priv = xpd->priv;
+       for_each_line(xpd, i) {
+               priv->idletxhookstate[i] = FXS_LINE_POL_ACTIVE;
+       }
+       return xpd;
+err:
+       xpd_free(xpd);
+       return NULL;
+}
+
+static int FXS_card_init(xbus_t *xbus, xpd_t *xpd)
+{
+       struct FXS_priv_data *priv;
+       int ret = 0;
+       int i;
+
+       BUG_ON(!xpd);
+       priv = xpd->priv;
+       /*
+        * Setup ring timers
+        */
+       /* Software controled ringing (for CID) */
+       /* Ringing Oscilator Control */
+       if (XPD_HW(xpd).type == 6) {
+               ret = SLIC_DIRECT_REQUEST(xbus, xpd, PORT_BROADCAST, SLIC_WRITE,
+                       REG_TYPE6_RINGCON, 0x00);
+       } else {
+               ret = SLIC_DIRECT_REQUEST(xbus, xpd, PORT_BROADCAST, SLIC_WRITE,
+                       REG_TYPE1_RINGCON, 0x00);
+       }
+       if (ret < 0)
+               goto err;
+       for_each_line(xpd, i) {
+               if (XPD_HW(xpd).type == 6)
+                       /* An arbitrary value that is not FXS_LINE_OPEN */
+                       priv->polledhook[i] = FXS_LINE_ACTIVE;
+               linefeed_control(xbus, xpd, i, FXS_LINE_POL_ACTIVE);
+       }
+       XPD_DBG(GENERAL, xpd, "done\n");
+       for_each_line(xpd, i) {
+               do_led(xpd, i, LED_GREEN, 0);
+               do_led(xpd, i, LED_RED, 0);
+       }
+       for_each_line(xpd, i) {
+               do_led(xpd, i, LED_GREEN, 1);
+               msleep(50);
+       }
+       for_each_line(xpd, i) {
+               do_led(xpd, i, LED_GREEN, 0);
+               msleep(50);
+       }
+       restore_leds(xpd);
+       CALL_PHONE_METHOD(card_pcm_recompute, xpd, 0);
+       /*
+        * We should query our offhook state long enough time after we
+        * set the linefeed_control()
+        * So we do this after the LEDs
+        */
+       for_each_line(xpd, i) {
+               if (IS_SET
+                   (PHONEDEV(xpd).digital_outputs | PHONEDEV(xpd).
+                    digital_inputs, i))
+                       continue;
+               if (XPD_HW(xpd).type == 6) {
+                       SLIC_DIRECT_REQUEST(xbus, xpd, i, SLIC_READ, REG_TYPE6_LCRRTP,
+                                           0);
+               } else {
+                       SLIC_DIRECT_REQUEST(xbus, xpd, i, SLIC_READ, REG_TYPE1_LOOPCLOSURE,
+                                           0);
+               }
+       }
+       return 0;
+err:
+       fxs_proc_remove(xbus, xpd);
+       XPD_ERR(xpd, "Failed initializing registers (%d)\n", ret);
+       return ret;
+}
+
+static int FXS_card_remove(xbus_t *xbus, xpd_t *xpd)
+{
+       BUG_ON(!xpd);
+       XPD_DBG(GENERAL, xpd, "\n");
+       fxs_proc_remove(xbus, xpd);
+       return 0;
+}
+
+static int FXS_card_dahdi_preregistration(xpd_t *xpd, bool on)
+{
+       xbus_t *xbus;
+       struct FXS_priv_data *priv;
+       int i;
+
+       BUG_ON(!xpd);
+       xbus = xpd->xbus;
+       BUG_ON(!xbus);
+       priv = xpd->priv;
+       BUG_ON(!priv);
+       XPD_DBG(GENERAL, xpd, "%s\n", (on) ? "on" : "off");
+       PHONEDEV(xpd).span.spantype = SPANTYPE_ANALOG_FXS;
+       for_each_line(xpd, i) {
+               struct dahdi_chan *cur_chan = XPD_CHAN(xpd, i);
+
+               XPD_DBG(GENERAL, xpd, "setting FXS channel %d\n", i);
+               if (IS_SET(PHONEDEV(xpd).digital_outputs, i)) {
+                       snprintf(cur_chan->name, MAX_CHANNAME,
+                                "XPP_OUT/%02d/%1d%1d/%d", xbus->num,
+                                xpd->addr.unit, xpd->addr.subunit, i);
+               } else if (IS_SET(PHONEDEV(xpd).digital_inputs, i)) {
+                       snprintf(cur_chan->name, MAX_CHANNAME,
+                                "XPP_IN/%02d/%1d%1d/%d", xbus->num,
+                                xpd->addr.unit, xpd->addr.subunit, i);
+               } else {
+                       snprintf(cur_chan->name, MAX_CHANNAME,
+                                "XPP_FXS/%02d/%1d%1d/%d", xbus->num,
+                                xpd->addr.unit, xpd->addr.subunit, i);
+               }
+               cur_chan->chanpos = i + 1;
+               cur_chan->pvt = xpd;
+               cur_chan->sigcap = FXS_DEFAULT_SIGCAP;
+               if (!vmwi_ioctl) {
+                       /* Old asterisk, assume default VMWI type */
+                       priv->vmwisetting[i].vmwi_type = DAHDI_VMWI_HVAC;
+               }
+       }
+       for_each_line(xpd, i) {
+               MARK_ON(priv, i, LED_GREEN);
+               msleep(4);
+               MARK_ON(priv, i, LED_RED);
+       }
+       return 0;
+}
+
+static int FXS_card_dahdi_postregistration(xpd_t *xpd, bool on)
+{
+       xbus_t *xbus;
+       struct FXS_priv_data *priv;
+       int i;
+
+       BUG_ON(!xpd);
+       xbus = xpd->xbus;
+       BUG_ON(!xbus);
+       priv = xpd->priv;
+       BUG_ON(!priv);
+       XPD_DBG(GENERAL, xpd, "%s\n", (on) ? "on" : "off");
+       for_each_line(xpd, i) {
+               MARK_OFF(priv, i, LED_GREEN);
+               msleep(2);
+               MARK_OFF(priv, i, LED_RED);
+               msleep(2);
+       }
+       restore_leds(xpd);
+       return 0;
+}
+
+/*
+ * Called with XPD spinlocked
+ */
+static void __do_mute_dtmf(xpd_t *xpd, int pos, bool muteit)
+{
+       struct FXS_priv_data *priv;
+
+       priv = xpd->priv;
+       LINE_DBG(SIGNAL, xpd, pos, "%s\n", (muteit) ? "MUTE" : "UNMUTE");
+       if (muteit)
+               BIT_SET(PHONEDEV(xpd).mute_dtmf, pos);
+       else
+               BIT_CLR(PHONEDEV(xpd).mute_dtmf, pos);
+       /* already spinlocked */
+       CALL_PHONE_METHOD(card_pcm_recompute, xpd, priv->search_fsk_pattern);
+}
+
+struct ring_reg_param {
+       int is_indirect;
+       int regno;
+       uint8_t h_val;
+       uint8_t l_val;
+};
+
+enum ring_types {
+       RING_TYPE_NEON = 0,
+       RING_TYPE_TRAPEZ,
+       RING_TYPE_NORMAL,
+};
+
+struct byte_pair {
+       uint8_t h_val;
+       uint8_t l_val;
+};
+
+struct ring_reg_params {
+       const int is_indirect;
+       const int regno;
+       struct byte_pair values[1 + RING_TYPE_NORMAL - RING_TYPE_NEON];
+};
+
+#define        REG_ENTRY(di, reg, vh1, vl1, vh2, vl2, vh3, vl3) \
+       { (di), (reg), .values = { \
+               [RING_TYPE_NEON]        = { .h_val = (vh1), .l_val = (vl1) }, \
+               [RING_TYPE_TRAPEZ]      = { .h_val = (vh2), .l_val = (vl2) }, \
+               [RING_TYPE_NORMAL]      = { .h_val = (vh3), .l_val = (vl3) }, \
+               }, \
+       }
+
+static struct ring_reg_params ring_parameters[] = {
+       /*        INDIR REG     NEON            TRAPEZ          NORMAL */
+       REG_ENTRY(1,    0x16,   0xE8, 0x03,     0xC8, 0x00,     0x00, 0x00),
+       REG_ENTRY(1,    0x15,   0xEF, 0x7B,     0xAB, 0x5E,     0x77, 0x01),
+       REG_ENTRY(1,    0x14,   0x9F, 0x00,     0x8C, 0x01,     0xFD, 0x7E),
+
+       REG_ENTRY(0,    0x22,   0x00, 0x19,     0x00, 0x01,     0x00, 0x00),
+
+       REG_ENTRY(0,    0x30,   0x00, 0xE0,     0x00, 0x00,     0x00, 0x00),
+       REG_ENTRY(0,    0x31,   0x00, 0x01,     0x00, 0x00,     0x00, 0x00),
+       REG_ENTRY(0,    0x32,   0x00, 0xF0,     0x00, 0x00,     0x00, 0x00),
+       REG_ENTRY(0,    0x33,   0x00, 0x05,     0x00, 0x00,     0x00, 0x00),
+
+       REG_ENTRY(1,    0x1D,   0x00, 0x46,     0x00, 0x36,     0x00, 0x36),
+};
+
+static void set_neon_state(xbus_t *xbus, xpd_t *xpd, int pos,
+               enum neon_state ns)
+{
+       struct FXS_priv_data *priv;
+
+       LINE_DBG(SIGNAL, xpd, pos, "set NEON -> %d\n", ns);
+       priv = xpd->priv;
+       if (ns == INIT_NEON)
+               BIT_SET(priv->neon_blinking, pos);
+       else
+               BIT_CLR(priv->neon_blinking, pos);
+       if (XPD_HW(xpd).type == 6) {
+               switch (ns) {
+               case INIT_NEON:
+                       RAM_REQUEST(xbus, xpd, pos, SLIC_WRITE, RAM_TYPE6_VBATH_EXPECT, VBATH_EXPECT_MWI << 3);
+                       //RAM_REQUEST(xbus, xpd, pos, SLIC_WRITE, RAM_TYPE6_SLOPE_VLIM, SLOPE_VLIM_MWI << 3);
+                       break;
+               default:
+                       LINE_DBG(REGS, xpd, pos, "0x%04X: R 0x\n", RAM_TYPE6_SLOPE_VLIM);
+                       set_mwi_led(xpd, pos, 0);       /* Cannot have NEON LED during OHT (type == 6) */
+                       SLIC_DIRECT_REQUEST(xbus, xpd, pos, SLIC_WRITE, REG_TYPE6_USERSTAT, 0x00);
+                       SLIC_DIRECT_REQUEST(xbus, xpd, pos, SLIC_WRITE, REG_TYPE6_ENHANCE, 0x10);
+                       RAM_REQUEST(xbus, xpd, pos, SLIC_WRITE, RAM_TYPE6_VBATH_EXPECT, VBATH_EXPECT_DFLT << 3);
+                       RAM_REQUEST(xbus, xpd, pos, SLIC_WRITE, RAM_TYPE6_SLOPE_VLIM, SLOPE_VLIM_DFLT << 3);
+                       break;
+               }
+       }
+}
+static int send_ring_parameters(xbus_t *xbus, xpd_t *xpd, int pos,
+               enum ring_types rtype)
+{
+       const struct ring_reg_params *p;
+       const struct byte_pair *v;
+       int ret = 0;
+       int i;
+
+       if (XPD_HW(xpd).type == 6)
+               return 0;
+       if (rtype < RING_TYPE_NEON || rtype > RING_TYPE_NORMAL)
+               return -EINVAL;
+       for (i = 0; i < ARRAY_SIZE(ring_parameters); i++) {
+               p = &ring_parameters[i];
+               v = &(p->values[rtype]);
+               if (p->is_indirect) {
+                       LINE_DBG(REGS, xpd, pos,
+                                       "[%d] 0x%02X: I 0x%02X 0x%02X\n",
+                                       i, p->regno, v->h_val, v->l_val);
+                       ret = SLIC_INDIRECT_REQUEST(xbus, xpd, pos, SLIC_WRITE,
+                               p->regno, v->h_val, v->l_val);
+                       if (ret < 0) {
+                               LINE_ERR(xpd, pos,
+                                       "Failed: 0x%02X: I 0x%02X, 0x%02X\n",
+                                       p->regno, v->h_val, v->l_val);
+                               break;
+                       }
+               } else {
+                       LINE_DBG(REGS, xpd, pos, "[%d] 0x%02X: D 0x%02X\n",
+                               i, p->regno, v->l_val);
+                       ret = SLIC_DIRECT_REQUEST(xbus, xpd, pos, SLIC_WRITE,
+                               p->regno, v->l_val);
+                       if (ret < 0) {
+                               LINE_ERR(xpd, pos,
+                                       "Failed: 0x%02X: D 0x%02X\n",
+                                       p->regno, v->l_val);
+                               break;
+                       }
+               }
+       }
+       return ret;
+}
+
+static int set_vm_led_mode(xbus_t *xbus, xpd_t *xpd, int pos,
+                          unsigned int msg_waiting)
+{
+       int ret = 0;
+       struct FXS_priv_data *priv;
+       BUG_ON(!xbus);
+       BUG_ON(!xpd);
+
+       priv = xpd->priv;
+       if (VMWI_NEON(priv, pos) && msg_waiting) {
+               /* A write to register 0x40 will now turn on/off the VM led */
+               LINE_DBG(SIGNAL, xpd, pos, "NEON\n");
+               set_neon_state(xbus, xpd, pos, INIT_NEON);
+               ret = send_ring_parameters(xbus, xpd, pos, RING_TYPE_NEON);
+       } else if (ring_trapez) {
+               LINE_DBG(SIGNAL, xpd, pos, "RINGER: Trapez ring\n");
+               set_neon_state(xbus, xpd, pos, END_NEON);
+               ret = send_ring_parameters(xbus, xpd, pos, RING_TYPE_TRAPEZ);
+       } else {
+               /* A write to register 0x40 will now turn on/off the ringer */
+               LINE_DBG(SIGNAL, xpd, pos, "RINGER\n");
+               set_neon_state(xbus, xpd, pos, END_NEON);
+               ret = send_ring_parameters(xbus, xpd, pos, RING_TYPE_NORMAL);
+       }
+       return (ret ? -EPROTO : 0);
+}
+
+static void start_stop_vm_led(xbus_t *xbus, xpd_t *xpd, lineno_t pos)
+{
+       struct FXS_priv_data *priv;
+       unsigned int msgs;
+
+       BUG_ON(!xpd);
+       if (IS_SET
+           (PHONEDEV(xpd).digital_outputs | PHONEDEV(xpd).digital_inputs, pos))
+               return;
+       priv = xpd->priv;
+       msgs = PHONEDEV(xpd).msg_waiting[pos];
+       LINE_DBG(SIGNAL, xpd, pos, "%s\n", (msgs) ? "ON" : "OFF");
+       set_vm_led_mode(xbus, xpd, pos, msgs);
+       if (XPD_HW(xpd).type == 1) {
+               do_chan_power(xbus, xpd, pos, msgs > 0);
+               linefeed_control(xbus, xpd, pos,
+                       (msgs > 0) ? FXS_LINE_RING : priv->idletxhookstate[pos]);
+       }
+}
+
+static int relay_out(xpd_t *xpd, int pos, bool on)
+{
+       int ret = 0;
+       int value = 0;
+       int which = pos;
+
+       BUG_ON(!xpd);
+       /* map logical position to output port number (0/1) */
+       which -= (XPD_HW(xpd).subtype == 2) ? 6 : 8;
+       LINE_DBG(SIGNAL, xpd, pos, "which=%d -- %s\n", which,
+                (on) ? "on" : "off");
+       if (XPD_HW(xpd).type == 6) {
+               int relay_values_type6[] = { 0x01, 0x40 };
+               which = which % ARRAY_SIZE(relay_values_type6);
+               if (on)
+                       value |= relay_values_type6[which];
+               ret = EXP_REQUEST(xpd->xbus, xpd, SLIC_WRITE,
+                       REG_TYPE6_EXP_GPIOB, value, relay_values_type6[which]);
+       } else {
+               int relay_channels_type1[] = { 0, 4 };
+               which = which % ARRAY_SIZE(relay_channels_type1);
+               value = BIT(2) | BIT(3);
+               value |=
+                   ((BIT(5) | BIT(6) | BIT(7)) & ~led_register_mask[OUTPUT_RELAY]);
+               if (on)
+                       value |= led_register_vals[OUTPUT_RELAY];
+               ret = SLIC_DIRECT_REQUEST(xpd->xbus, xpd, relay_channels_type1[which],
+                                  SLIC_WRITE, REG_TYPE1_DIGITAL_IOCTRL, value);
+       }
+       return ret;
+}
+
+static int send_ring(xpd_t *xpd, lineno_t chan, bool on)
+{
+       int ret = 0;
+       xbus_t *xbus;
+       struct FXS_priv_data *priv;
+       enum fxs_state value = (on) ? FXS_LINE_RING : FXS_LINE_POL_ACTIVE;
+
+       BUG_ON(!xpd);
+       xbus = xpd->xbus;
+       BUG_ON(!xbus);
+       LINE_DBG(SIGNAL, xpd, chan, "%s\n", (on) ? "on" : "off");
+       priv = xpd->priv;
+       set_vm_led_mode(xbus, xpd, chan, 0);
+       do_chan_power(xbus, xpd, chan, on);     /* Power up (for ring) */
+       ret = linefeed_control(xbus, xpd, chan, value);
+       if (on) {
+               MARK_BLINK(priv, chan, LED_GREEN, LED_BLINK_RING);
+       } else {
+               if (IS_BLINKING(priv, chan, LED_GREEN))
+                       MARK_BLINK(priv, chan, LED_GREEN, 0);
+       }
+       return ret;
+}
+
+static int FXS_card_hooksig(xpd_t *xpd, int pos, enum dahdi_txsig txsig)
+{
+       struct FXS_priv_data *priv;
+       int ret = 0;
+       struct dahdi_chan *chan = NULL;
+       enum fxs_state txhook;
+       unsigned long flags;
+
+       LINE_DBG(SIGNAL, xpd, pos, "%s\n", txsig2str(txsig));
+       priv = xpd->priv;
+       BUG_ON(PHONEDEV(xpd).direction != TO_PHONE);
+       if (IS_SET(PHONEDEV(xpd).digital_inputs, pos)) {
+               LINE_DBG(SIGNAL, xpd, pos,
+                        "Ignoring signal sent to digital input line\n");
+               return 0;
+       }
+       if (SPAN_REGISTERED(xpd))
+               chan = XPD_CHAN(xpd, pos);
+       switch (txsig) {
+       case DAHDI_TXSIG_ONHOOK:
+               spin_lock_irqsave(&xpd->lock, flags);
+               PHONEDEV(xpd).ringing[pos] = 0;
+               oht_pcm(xpd, pos, 0);
+               vmwi_search(xpd, pos, 0);
+               BIT_CLR(priv->want_dtmf_events, pos);
+               BIT_CLR(priv->want_dtmf_mute, pos);
+               __do_mute_dtmf(xpd, pos, 0);
+               spin_unlock_irqrestore(&xpd->lock, flags);
+               if (IS_SET(PHONEDEV(xpd).digital_outputs, pos)) {
+                       LINE_DBG(SIGNAL, xpd, pos, "%s -> digital output OFF\n",
+                                txsig2str(txsig));
+                       ret = relay_out(xpd, pos, 0);
+                       return ret;
+               }
+               if (priv->lasttxhook[pos] == FXS_LINE_OPEN) {
+                       /*
+                        * Restore state after KEWL hangup.
+                        */
+                       LINE_DBG(SIGNAL, xpd, pos, "KEWL STOP\n");
+                       linefeed_control(xpd->xbus, xpd, pos,
+                                        FXS_LINE_POL_ACTIVE);
+                       if (IS_OFFHOOK(xpd, pos))
+                               MARK_ON(priv, pos, LED_GREEN);
+               }
+               ret = send_ring(xpd, pos, 0);   // RING off
+               if (!IS_OFFHOOK(xpd, pos))
+                       start_stop_vm_led(xpd->xbus, xpd, pos);
+               txhook = priv->lasttxhook[pos];
+               if (chan) {
+                       switch (chan->sig) {
+                       case DAHDI_SIG_EM:
+                       case DAHDI_SIG_FXOKS:
+                       case DAHDI_SIG_FXOLS:
+                               txhook = priv->idletxhookstate[pos];
+                               break;
+                       case DAHDI_SIG_FXOGS:
+                               txhook = FXS_LINE_TIPOPEN;
+                               break;
+                       }
+               }
+               ret = linefeed_control(xpd->xbus, xpd, pos, txhook);
+               break;
+       case DAHDI_TXSIG_OFFHOOK:
+               if (IS_SET(PHONEDEV(xpd).digital_outputs, pos)) {
+                       LINE_NOTICE(xpd, pos,
+                                   "%s -> Is digital output. Ignored\n",
+                                   txsig2str(txsig));
+                       return -EINVAL;
+               }
+               txhook = priv->lasttxhook[pos];
+               if (PHONEDEV(xpd).ringing[pos]) {
+                       oht_pcm(xpd, pos, 1);
+                       txhook = FXS_LINE_OHTRANS;
+               }
+               PHONEDEV(xpd).ringing[pos] = 0;
+               if (chan) {
+                       switch (chan->sig) {
+                       case DAHDI_SIG_EM:
+                               txhook = FXS_LINE_POL_ACTIVE;
+                               break;
+                       default:
+                               txhook = priv->idletxhookstate[pos];
+                               break;
+                       }
+               }
+               ret = linefeed_control(xpd->xbus, xpd, pos, txhook);
+               break;
+       case DAHDI_TXSIG_START:
+               PHONEDEV(xpd).ringing[pos] = 1;
+               oht_pcm(xpd, pos, 0);
+               vmwi_search(xpd, pos, 0);
+               if (IS_SET(PHONEDEV(xpd).digital_outputs, pos)) {
+                       LINE_DBG(SIGNAL, xpd, pos, "%s -> digital output ON\n",
+                                txsig2str(txsig));
+                       ret = relay_out(xpd, pos, 1);
+                       return ret;
+               }
+               ret = send_ring(xpd, pos, 1);   // RING on
+               break;
+       case DAHDI_TXSIG_KEWL:
+               if (IS_SET(PHONEDEV(xpd).digital_outputs, pos)) {
+                       LINE_DBG(SIGNAL, xpd, pos,
+                                "%s -> Is digital output. Ignored\n",
+                                txsig2str(txsig));
+                       return -EINVAL;
+               }
+               linefeed_control(xpd->xbus, xpd, pos, FXS_LINE_OPEN);
+               MARK_OFF(priv, pos, LED_GREEN);
+               break;
+       default:
+               XPD_NOTICE(xpd, "%s: Can't set tx state to %s (%d)\n", __func__,
+                          txsig2str(txsig), txsig);
+               ret = -EINVAL;
+       }
+       return ret;
+}
+
+static int set_vmwi(xpd_t *xpd, int pos, unsigned long arg)
+{
+       struct FXS_priv_data *priv;
+       struct dahdi_vmwi_info vmwisetting;
+       const int vmwi_flags =
+           DAHDI_VMWI_LREV | DAHDI_VMWI_HVDC | DAHDI_VMWI_HVAC;
+
+       priv = xpd->priv;
+       BUG_ON(!priv);
+       if (copy_from_user
+           (&vmwisetting, (__user void *)arg, sizeof(vmwisetting)))
+               return -EFAULT;
+       if ((vmwisetting.vmwi_type & ~vmwi_flags) != 0) {
+               LINE_NOTICE(xpd, pos, "Bad DAHDI_VMWI_CONFIG: 0x%X\n",
+                           vmwisetting.vmwi_type);
+               return -EINVAL;
+       }
+       LINE_DBG(SIGNAL, xpd, pos, "DAHDI_VMWI_CONFIG: 0x%X\n",
+                vmwisetting.vmwi_type);
+       if (VMWI_TYPE(priv, pos, LREV)) {
+               LINE_NOTICE(xpd, pos,
+                           "%s: VMWI(lrev) is not implemented yet. Ignored.\n",
+                           __func__);
+       }
+       if (VMWI_TYPE(priv, pos, HVDC)) {
+               LINE_NOTICE(xpd, pos,
+                           "%s: VMWI(hvdc) is not implemented yet. Ignored.\n",
+                           __func__);
+       }
+       if (VMWI_TYPE(priv, pos, HVAC))
+               ;               /* VMWI_NEON */
+       if (priv->vmwisetting[pos].vmwi_type == 0)
+               ;               /* Disable VMWI */
+       priv->vmwisetting[pos] = vmwisetting;
+       set_vm_led_mode(xpd->xbus, xpd, pos, PHONEDEV(xpd).msg_waiting[pos]);
+       return 0;
+}
+
+static int hardware_dtmf_control(xpd_t *xpd, int pos, bool on)
+{
+       int ret = 0;
+
+       LINE_DBG(SIGNAL, xpd, pos, "%s: %s\n", __func__, (on) ? "on" : "off");
+       if (XPD_HW(xpd).type == 6) {
+               int value = (on) ? 0xE0 : REG_TYPE6_TONEN_DTMF_DIS;
+               ret = SLIC_DIRECT_REQUEST(xpd->xbus, xpd, pos, SLIC_WRITE,
+                       REG_TYPE6_TONEN, value);
+       } else {
+               ret = SLIC_DIRECT_REQUEST(xpd->xbus, xpd, pos, SLIC_WRITE, 0x17, on);
+       }
+       return ret;
+}
+
+/*
+ * Private ioctl()
+ * We don't need it now, since we detect vmwi via FSK patterns
+ */
+static int FXS_card_ioctl(xpd_t *xpd, int pos, unsigned int cmd,
+                         unsigned long arg)
+{
+       struct FXS_priv_data *priv;
+       xbus_t *xbus;
+       int val;
+       unsigned long flags;
+
+       BUG_ON(!xpd);
+       priv = xpd->priv;
+       BUG_ON(!priv);
+       xbus = xpd->xbus;
+       BUG_ON(!xbus);
+       if (!XBUS_IS(xbus, READY))
+               return -ENODEV;
+       if (pos < 0 || pos >= PHONEDEV(xpd).channels) {
+               XPD_NOTICE(xpd, "Bad channel number %d in %s(), cmd=%u\n", pos,
+                          __func__, cmd);
+               return -EINVAL;
+       }
+
+       switch (cmd) {
+       case DAHDI_ONHOOKTRANSFER:
+               if (get_user(val, (int __user *)arg))
+                       return -EFAULT;
+               LINE_DBG(SIGNAL, xpd, pos, "DAHDI_ONHOOKTRANSFER (%d millis)\n",
+                        val);
+               if (IS_SET
+                   (PHONEDEV(xpd).digital_inputs | PHONEDEV(xpd).
+                    digital_outputs, pos))
+                       return 0;       /* Nothing to do */
+               oht_pcm(xpd, pos, 1);   /* Get ready of VMWI FSK tones */
+               if (priv->lasttxhook[pos] == FXS_LINE_POL_ACTIVE
+                   || IS_SET(priv->neon_blinking, pos)) {
+                       priv->ohttimer[pos] = val;
+                       priv->idletxhookstate[pos] = FXS_LINE_POL_OHTRANS;
+                       vmwi_search(xpd, pos, 1);
+                       CALL_PHONE_METHOD(card_pcm_recompute, xpd,
+                                         priv->search_fsk_pattern);
+                       LINE_DBG(SIGNAL, xpd, pos,
+                                "Start OHT_TIMER. wanted_pcm_mask=0x%X\n",
+                                PHONEDEV(xpd).wanted_pcm_mask);
+               }
+               if (VMWI_NEON(priv, pos) && !IS_OFFHOOK(xpd, pos))
+                       start_stop_vm_led(xbus, xpd, pos);
+               return 0;
+       case DAHDI_TONEDETECT:
+               if (get_user(val, (int __user *)arg))
+                       return -EFAULT;
+               LINE_DBG(SIGNAL, xpd, pos,
+                        "DAHDI_TONEDETECT: %s %s (dtmf_detection=%s)\n",
+                        (val & DAHDI_TONEDETECT_ON) ? "ON" : "OFF",
+                        (val & DAHDI_TONEDETECT_MUTE) ? "MUTE" : "NO-MUTE",
+                        (dtmf_detection ? "YES" : "NO"));
+               if (!dtmf_detection) {
+                       spin_lock_irqsave(&xpd->lock, flags);
+                       if (IS_SET(priv->want_dtmf_events, pos)) {
+                               /*
+                                * Detection mode changed:
+                                * Disable DTMF interrupts
+                                */
+                       }
+                       hardware_dtmf_control(xpd, pos, 0);
+                       BIT_CLR(priv->want_dtmf_events, pos);
+                       BIT_CLR(priv->want_dtmf_mute, pos);
+                       __do_mute_dtmf(xpd, pos, 0);
+                       spin_unlock_irqrestore(&xpd->lock, flags);
+                       return -ENOTTY;
+               }
+               /*
+                * During natively bridged calls, Asterisk
+                * will request one of the sides to stop sending
+                * dtmf events. Check the requested state.
+                */
+               spin_lock_irqsave(&xpd->lock, flags);
+               if (val & DAHDI_TONEDETECT_ON) {
+                       if (!IS_SET(priv->want_dtmf_events, pos)) {
+                               /*
+                                * Detection mode changed:
+                                * Enable DTMF interrupts
+                                */
+                               LINE_DBG(SIGNAL, xpd, pos,
+                                       "DAHDI_TONEDETECT: "
+                                       "Enable Hardware DTMF\n");
+                               hardware_dtmf_control(xpd, pos, 1);
+                       }
+                       BIT_SET(priv->want_dtmf_events, pos);
+               } else {
+                       if (IS_SET(priv->want_dtmf_events, pos)) {
+                               /*
+                                * Detection mode changed:
+                                * Disable DTMF interrupts
+                                */
+                               LINE_DBG(SIGNAL, xpd, pos,
+                                       "DAHDI_TONEDETECT: "
+                                       "Disable Hardware DTMF\n");
+                               hardware_dtmf_control(xpd, pos, 0);
+                       }
+                       BIT_CLR(priv->want_dtmf_events, pos);
+               }
+               if (val & DAHDI_TONEDETECT_MUTE) {
+                       BIT_SET(priv->want_dtmf_mute, pos);
+               } else {
+                       BIT_CLR(priv->want_dtmf_mute, pos);
+                       __do_mute_dtmf(xpd, pos, 0);
+               }
+               spin_unlock_irqrestore(&xpd->lock, flags);
+               return 0;
+       case DAHDI_SETPOLARITY:
+               if (get_user(val, (int __user *)arg))
+                       return -EFAULT;
+               /*
+                * Asterisk may send us this if chan_dahdi config
+                * has "hanguponpolarityswitch=yes" to notify
+                * that the other side has hanged up.
+                *
+                * This has no effect on normal phone (but we may
+                * be connected to another FXO equipment).
+                * note that this chan_dahdi settings has different
+                * meaning for FXO, where it signals polarity
+                * reversal *detection* logic.
+                *
+                * It seems that sometimes we get this from
+                * asterisk in wrong state (e.g: while ringing).
+                * In these cases, silently ignore it.
+                */
+               if (priv->lasttxhook[pos] == FXS_LINE_RING
+                   || priv->lasttxhook[pos] == FXS_LINE_OPEN) {
+                       LINE_DBG(SIGNAL, xpd, pos,
+                               "DAHDI_SETPOLARITY: %s Cannot change "
+                               "when lasttxhook=0x%X\n",
+                               (val) ? "ON" : "OFF", priv->lasttxhook[pos]);
+                       return -EINVAL;
+               }
+               LINE_DBG(SIGNAL, xpd, pos, "DAHDI_SETPOLARITY: %s\n",
+                        (val) ? "ON" : "OFF");
+               if ((val && !reversepolarity) || (!val && reversepolarity))
+                       priv->lasttxhook[pos] |= FXS_LINE_RING;
+               else
+                       priv->lasttxhook[pos] &= ~FXS_LINE_RING;
+               linefeed_control(xbus, xpd, pos, priv->lasttxhook[pos]);
+               return 0;
+       case DAHDI_VMWI_CONFIG:
+               if (set_vmwi(xpd, pos, arg) < 0)
+                       return -EINVAL;
+               return 0;
+       case DAHDI_VMWI:        /* message-waiting led control */
+               if (get_user(val, (int __user *)arg))
+                       return -EFAULT;
+               if (!vmwi_ioctl) {
+                       static bool notified;
+
+                       if (!notified) {
+                               notified = true;
+                               LINE_NOTICE(xpd, pos,
+                                       "Got DAHDI_VMWI notification "
+                                       "but vmwi_ioctl parameter is off. "
+                                       "Ignoring.\n");
+                       }
+                       return 0;
+               }
+               /* Digital inputs/outputs don't have VM leds */
+               if (IS_SET
+                   (PHONEDEV(xpd).digital_inputs | PHONEDEV(xpd).
+                    digital_outputs, pos))
+                       return 0;
+               PHONEDEV(xpd).msg_waiting[pos] = val;
+               LINE_DBG(SIGNAL, xpd, pos, "DAHDI_VMWI: %s\n",
+                        (val) ? "yes" : "no");
+               return 0;
+       default:
+               report_bad_ioctl(THIS_MODULE->name, xpd, pos, cmd);
+       }
+       return -ENOTTY;
+}
+
+static int FXS_card_open(xpd_t *xpd, lineno_t chan)
+{
+       struct FXS_priv_data *priv;
+
+       BUG_ON(!xpd);
+       priv = xpd->priv;
+       if (IS_OFFHOOK(xpd, chan))
+               LINE_NOTICE(xpd, chan, "Already offhook during open. OK.\n");
+       else
+               LINE_DBG(SIGNAL, xpd, chan, "is onhook\n");
+       /*
+        * Delegate updating dahdi to FXS_card_tick():
+        *   The problem is that dahdi_hooksig() is spinlocking the channel and
+        *   we are called by dahdi with the spinlock already held on the
+        *   same channel.
+        */
+       BIT_SET(priv->update_offhook_state, chan);
+       return 0;
+}
+
+static int FXS_card_close(xpd_t *xpd, lineno_t chan)
+{
+       struct FXS_priv_data *priv;
+
+       BUG_ON(!xpd);
+       LINE_DBG(GENERAL, xpd, chan, "\n");
+       priv = xpd->priv;
+       priv->idletxhookstate[chan] = FXS_LINE_POL_ACTIVE;
+       return 0;
+}
+
+#ifdef POLL_DIGITAL_INPUTS
+/*
+ * INPUT polling is done via SLIC register 0x06 (same as LEDS):
+ *         7     6     5     4     3     2     1     0
+ *     +-----+-----+-----+-----+-----+-----+-----+-----+
+ *     | I1  | I3  |     |     | I2  | I4  |     |     |
+ *     +-----+-----+-----+-----+-----+-----+-----+-----+
+ *
+ */
+static int input_ports_type1[] = {
+       /* slic = input_port */
+       [0]     = -1,
+       [1]     = -1,
+       [2]     = 2,
+       [3]     = 3,
+       [4]     = -1,
+       [5]     = -1,
+       [6]     = 0,
+       [7]     = 1,
+       };
+
+static void poll_inputs(xpd_t *xpd)
+{
+       int i;
+
+       BUG_ON(xpd->xbus_idx != 0);     // Only unit #0 has digital inputs
+       if (XPD_HW(xpd).type == 6) {
+               EXP_REQUEST(xpd->xbus, xpd, SLIC_READ,
+                       REG_TYPE6_EXP_GPIOB, 0, 0);
+       } else {
+               for (i = 0; i < ARRAY_SIZE(input_ports_type1); i++) {
+                       int pos = input_ports_type1[i];
+                       if (pos >= 0) {
+                               SLIC_DIRECT_REQUEST(xpd->xbus, xpd, i, SLIC_READ, 0x06, 0);
+                       }
+               }
+       }
+}
+#endif
+
+static void poll_linefeed(xpd_t *xpd)
+{
+       struct FXS_priv_data *priv;
+       int i;
+
+       if (XPD_HW(xpd).type != 6)
+               return;
+       if (xpd->xpd_state != XPD_STATE_READY)
+               return;
+       priv = xpd->priv;
+       BUG_ON(!priv);
+       BUG_ON(!xpd->xbus);
+
+       XPD_DBG(GENERAL, xpd, "periodic poll");
+       for_each_line(xpd, i) {
+               if (IS_SET(PHONEDEV(xpd).digital_outputs, i)
+                   || IS_SET(PHONEDEV(xpd).digital_inputs, i))
+                       continue;
+               if (priv->polledhook[i] == FXS_LINE_OPEN &&
+                               priv->lasttxhook[i] != FXS_LINE_OPEN) {
+                       LINE_NOTICE(xpd, i, "Overheat detected, resetting.");
+                       priv->overheat_reset_counter[i]++;
+                       linefeed_control(xpd->xbus, xpd, i,
+                                       priv->lasttxhook[i]);
+               }
+               SLIC_DIRECT_REQUEST(xpd->xbus, xpd, i, SLIC_READ,
+                               REG_TYPE6_LINEFEED, 0);
+       }
+}
+
+static void handle_linefeed(xpd_t *xpd)
+{
+       struct FXS_priv_data *priv;
+       int i;
+
+       BUG_ON(!xpd);
+       priv = xpd->priv;
+       BUG_ON(!priv);
+       for_each_line(xpd, i) {
+               if (priv->lasttxhook[i] == FXS_LINE_RING
+                   && !IS_SET(priv->neon_blinking, i)) {
+                       /* RINGing, prepare for OHT */
+                       priv->ohttimer[i] = OHT_TIMER;
+                       priv->idletxhookstate[i] = FXS_LINE_POL_OHTRANS;
+               } else {
+                       if (priv->ohttimer[i]) {
+                               priv->ohttimer[i]--;
+                               if (!priv->ohttimer[i]) {
+                                       LINE_DBG(SIGNAL, xpd, i,
+                                                "ohttimer expired\n");
+                                       priv->idletxhookstate[i] =
+                                           FXS_LINE_POL_ACTIVE;
+                                       oht_pcm(xpd, i, 0);
+                                       vmwi_search(xpd, i, 0);
+                                       if (priv->lasttxhook[i] ==
+                                           FXS_LINE_POL_OHTRANS) {
+                                               /* Apply the change if appropriate */
+                                               linefeed_control(xpd->xbus, xpd,
+                                                                i,
+                                                                FXS_LINE_POL_ACTIVE);
+                                       }
+                               }
+                       }
+               }
+       }
+}
+
+/*
+ * Optimized memcmp() like function. Only test for equality (true/false).
+ * This optimization reduced the detect_vmwi() runtime by a factor of 3.
+ */
+static inline bool mem_equal(const char a[], const char b[], size_t len)
+{
+       int i;
+
+       for (i = 0; i < len; i++)
+               if (a[i] != b[i])
+                       return 0;
+       return 1;
+}
+
+/*
+ * Detect Voice Mail Waiting Indication
+ */
+static void detect_vmwi(xpd_t *xpd)
+{
+       struct FXS_priv_data *priv;
+       xbus_t *xbus;
+       static const __u8 FSK_COMMON_PATTERN[] =
+           { 0xA8, 0x49, 0x22, 0x3B, 0x9F, 0xFF, 0x1F, 0xBB };
+       static const __u8 FSK_ON_PATTERN[] =
+           { 0xA2, 0x2C, 0x1F, 0x2C, 0xBB, 0xA1, 0xA5, 0xFF };
+       static const __u8 FSK_OFF_PATTERN[] =
+           { 0xA2, 0x2C, 0x28, 0xA5, 0xB1, 0x21, 0x49, 0x9F };
+       int i;
+       xpp_line_t ignore_mask;
+
+       BUG_ON(!xpd);
+       xbus = xpd->xbus;
+       priv = xpd->priv;
+       BUG_ON(!priv);
+       ignore_mask =
+               PHONEDEV(xpd).offhook_state |
+               ~(PHONEDEV(xpd).oht_pcm_pass) |
+               ~(priv->search_fsk_pattern) |
+               PHONEDEV(xpd).digital_inputs |
+               PHONEDEV(xpd).digital_outputs;
+       for_each_line(xpd, i) {
+               struct dahdi_chan *chan = XPD_CHAN(xpd, i);
+               __u8 *writechunk = chan->writechunk;
+
+               if (IS_SET(ignore_mask, i))
+                       continue;
+#if 0
+               if (writechunk[0] != 0x7F && writechunk[0] != 0) {
+                       int j;
+
+                       LINE_DBG(GENERAL, xpd, i, "MSG:");
+                       for (j = 0; j < DAHDI_CHUNKSIZE; j++) {
+                               if (debug)
+                                       printk(" %02X", writechunk[j]);
+                       }
+                       if (debug)
+                               printk("\n");
+               }
+#endif
+               if (unlikely
+                   (mem_equal
+                    (writechunk, FSK_COMMON_PATTERN, DAHDI_CHUNKSIZE))) {
+                       LINE_DBG(SIGNAL, xpd, i,
+                               "Found common FSK pattern. "
+                               "Start looking for ON/OFF patterns.\n");
+                       BIT_SET(priv->found_fsk_pattern, i);
+               } else if (unlikely(IS_SET(priv->found_fsk_pattern, i))) {
+                       BIT_CLR(priv->found_fsk_pattern, i);
+                       oht_pcm(xpd, i, 0);
+                       if (unlikely
+                           (mem_equal
+                            (writechunk, FSK_ON_PATTERN, DAHDI_CHUNKSIZE))) {
+                               LINE_DBG(SIGNAL, xpd, i, "MSG WAITING ON\n");
+                               PHONEDEV(xpd).msg_waiting[i] = 1;
+                               start_stop_vm_led(xbus, xpd, i);
+                       } else
+                           if (unlikely
+                               (mem_equal
+                                (writechunk, FSK_OFF_PATTERN,
+                                 DAHDI_CHUNKSIZE))) {
+                               LINE_DBG(SIGNAL, xpd, i, "MSG WAITING OFF\n");
+                               PHONEDEV(xpd).msg_waiting[i] = 0;
+                               start_stop_vm_led(xbus, xpd, i);
+                       } else {
+                               int j;
+
+                               LINE_NOTICE(xpd, i, "MSG WAITING Unexpected:");
+                               for (j = 0; j < DAHDI_CHUNKSIZE; j++)
+                                       printk(" %02X", writechunk[j]);
+                               printk("\n");
+                       }
+               }
+       }
+}
+
+static int FXS_card_tick(xbus_t *xbus, xpd_t *xpd)
+{
+       struct FXS_priv_data *priv;
+
+       BUG_ON(!xpd);
+       priv = xpd->priv;
+       BUG_ON(!priv);
+#ifdef POLL_DIGITAL_INPUTS
+       if (poll_digital_inputs && PHONEDEV(xpd).digital_inputs) {
+               if ((xpd->timer_count % poll_digital_inputs) == 0)
+                       poll_inputs(xpd);
+       }
+#endif
+       if ((xpd->timer_count % poll_chan_linefeed) == 0)
+               poll_linefeed(xpd);
+       handle_fxs_leds(xpd);
+       handle_linefeed(xpd);
+       if (XPD_HW(xpd).type == 6)
+               blink_mwi(xpd);
+       /*
+        * Hack alert (FIXME):
+        *   Asterisk did FXS_card_open() and we wanted to report
+        *   offhook state. However, the channel is spinlocked by dahdi
+        *   so we marked it in the priv->update_offhook_state mask and
+        *   now we take care of notification to dahdi and Asterisk
+        */
+       if (priv->update_offhook_state) {
+               enum dahdi_rxsig rxsig;
+               int i;
+
+               for_each_line(xpd, i) {
+                       if (!IS_SET(priv->update_offhook_state, i))
+                               continue;
+                       rxsig =
+                           IS_OFFHOOK(xpd,
+                                      i) ? DAHDI_RXSIG_OFFHOOK :
+                           DAHDI_RXSIG_ONHOOK;
+                       /* Notify after open() */
+                       notify_rxsig(xpd, i, rxsig);
+                       BIT_CLR(priv->update_offhook_state, i);
+               }
+       }
+       if (SPAN_REGISTERED(xpd)) {
+               if (!vmwi_ioctl && priv->search_fsk_pattern)
+                       detect_vmwi(xpd);       /* Detect via FSK modulation */
+       }
+       return 0;
+}
+
+/*---------------- FXS: HOST COMMANDS -------------------------------------*/
+
+/*---------------- FXS: Astribank Reply Handlers --------------------------*/
+
+/*
+ * Should be called with spinlocked XPD
+ */
+static void process_hookstate(xpd_t *xpd, xpp_line_t offhook,
+                             xpp_line_t change_mask)
+{
+       xbus_t *xbus;
+       struct FXS_priv_data *priv;
+       int i;
+
+       BUG_ON(!xpd);
+       BUG_ON(PHONEDEV(xpd).direction != TO_PHONE);
+       xbus = xpd->xbus;
+       priv = xpd->priv;
+       XPD_DBG(SIGNAL, xpd, "offhook=0x%X change_mask=0x%X\n", offhook,
+               change_mask);
+       for_each_line(xpd, i) {
+               if (IS_SET(PHONEDEV(xpd).digital_outputs, i)
+                   || IS_SET(PHONEDEV(xpd).digital_inputs, i))
+                       continue;
+               if (IS_SET(change_mask, i)) {
+                       PHONEDEV(xpd).ringing[i] = 0;   /* No more ringing... */
+#ifdef WITH_METERING
+                       metering_gen(xpd, i, 0);        /* Stop metering... */
+#endif
+                       MARK_BLINK(priv, i, LED_GREEN, 0);
+                       /*
+                        * Reset our previous DTMF memories...
+                        */
+                       BIT_CLR(priv->prev_key_down, i);
+                       priv->prev_key_time[i].tv_sec =
+                           priv->prev_key_time[i].tv_usec = 0L;
+                       if (IS_SET(offhook, i)) {
+                               LINE_DBG(SIGNAL, xpd, i, "OFFHOOK\n");
+                               MARK_ON(priv, i, LED_GREEN);
+                               hookstate_changed(xpd, i, 1);
+                       } else {
+                               LINE_DBG(SIGNAL, xpd, i, "ONHOOK\n");
+                               MARK_OFF(priv, i, LED_GREEN);
+                               hookstate_changed(xpd, i, 0);
+                       }
+                       /*
+                        * Must switch to low power. In high power, an ONHOOK
+                        * won't be detected.
+                        */
+                       do_chan_power(xbus, xpd, i, 0);
+               }
+       }
+}
+
+HANDLER_DEF(FXS, SIG_CHANGED)
+{
+       xpp_line_t sig_status =
+           RPACKET_FIELD(pack, FXS, SIG_CHANGED, sig_status);
+       xpp_line_t sig_toggles =
+           RPACKET_FIELD(pack, FXS, SIG_CHANGED, sig_toggles);
+       unsigned long flags;
+
+       BUG_ON(!xpd);
+       BUG_ON(PHONEDEV(xpd).direction != TO_PHONE);
+       XPD_DBG(SIGNAL, xpd, "(PHONE) sig_toggles=0x%04X sig_status=0x%04X\n",
+               sig_toggles, sig_status);
+#if 0
+       Is this needed ? for_each_line(xpd, i) {
+               // Power down (prevent overheating!!!)
+               if (IS_SET(sig_toggles, i))
+                       do_chan_power(xpd->xbus, xpd, BIT(i), 0);
+       }
+#endif
+       spin_lock_irqsave(&xpd->lock, flags);
+       process_hookstate(xpd, sig_status, sig_toggles);
+       spin_unlock_irqrestore(&xpd->lock, flags);
+       return 0;
+}
+
+#ifdef POLL_DIGITAL_INPUTS
+static inline void notify_digital_input(xpd_t *xpd, int input_port, int offhook)
+{
+       int channo = PHONEDEV(xpd).channels - LINES_DIGI_INP + input_port;
+
+       /* Stop ringing. No leds for digital inputs. */
+       PHONEDEV(xpd).ringing[channo] = 0;
+       if (offhook && !IS_OFFHOOK(xpd, channo)) {
+               LINE_DBG(SIGNAL, xpd, channo, "OFFHOOK\n");
+               hookstate_changed(xpd, channo, 1);
+       } else if (!offhook && IS_OFFHOOK(xpd, channo)) {
+               LINE_DBG(SIGNAL, xpd, channo, "ONHOOK\n");
+               hookstate_changed(xpd, channo, 0);
+       }
+}
+
+static void process_digital_inputs(xpd_t *xpd, const reg_cmd_t *info)
+{
+       bool offhook;
+       /* Sanity check */
+       if (!PHONEDEV(xpd).digital_inputs) {
+               XPD_NOTICE(xpd, "%s called without digital inputs. Ignored\n",
+                          __func__);
+               return;
+       }
+       if (XPD_HW(xpd).type == 6) {
+               static int input_values_type6[] = { 0x80, 0x20, 0x08, 0x02 };   /* I/O Expander values of input relays */
+               int i;
+
+               /* Map I/O Expander GPIO into line number */
+               for (i = 0; i < ARRAY_SIZE(input_values_type6); i++) {
+                       int chanmask = input_values_type6[i];
+
+                       offhook = (REG_FIELD(info, data_low) & chanmask) == 0;
+                       notify_digital_input(xpd, i, offhook);
+               }
+       } else {
+               int channo = info->h.portnum;
+               int input_port;
+               offhook = (REG_FIELD(info, data_low) & 0x1) == 0;
+               if (channo < 0 || channo >= ARRAY_SIZE(input_ports_type1)) {
+                       XPD_ERR(xpd, "%s: got bad portnum=%d\n", __func__, channo);
+                       return;
+               }
+               input_port = input_ports_type1[channo];
+               if (input_port < 0) {
+                       XPD_ERR(xpd, "%s: portnum=%d is not input port\n", __func__, channo);
+                       return;
+               }
+               notify_digital_input(xpd, input_port, offhook);
+       }
+}
+#endif
+
+static const char dtmf_digits[] = {
+       'D', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '*', '#',
+       'A', 'B', 'C'
+};
+
+/*
+ * This function is called with spinlocked XPD
+ */
+static void process_dtmf(xpd_t *xpd, uint portnum, __u8 val)
+{
+       __u8 digit;
+       bool key_down = val & 0x10;
+       bool want_mute;
+       bool want_event;
+       struct FXS_priv_data *priv;
+       struct timeval now;
+       int msec = 0;
+
+       if (!dtmf_detection)
+               return;
+       if (!SPAN_REGISTERED(xpd))
+               return;
+       priv = xpd->priv;
+       val &= 0xF;
+       digit = dtmf_digits[val];
+       want_mute = IS_SET(priv->want_dtmf_mute, portnum);
+       want_event = IS_SET(priv->want_dtmf_events, portnum);
+       if (!IS_SET(priv->prev_key_down, portnum) && !key_down)
+               LINE_NOTICE(xpd, portnum, "DTMF: duplicate UP (%c)\n", digit);
+       if (key_down)
+               BIT_SET(priv->prev_key_down, portnum);
+       else
+               BIT_CLR(priv->prev_key_down, portnum);
+       do_gettimeofday(&now);
+       if (priv->prev_key_time[portnum].tv_sec != 0)
+               msec = usec_diff(&now, &priv->prev_key_time[portnum]) / 1000;
+       priv->prev_key_time[portnum] = now;
+       LINE_DBG(SIGNAL, xpd, portnum,
+               "[%lu.%06lu] DTMF digit %-4s '%c' "
+               "(val=%d, want_mute=%s want_event=%s, delta=%d msec)\n",
+               now.tv_sec, now.tv_usec, (key_down) ? "DOWN" : "UP", digit,
+               val, (want_mute) ? "yes" : "no", (want_event) ? "yes" : "no",
+               msec);
+       /*
+        * FIXME: we currently don't use the want_dtmf_mute until
+        * we are sure about the logic in Asterisk native bridging.
+        * Meanwhile, simply mute it on button press.
+        */
+       if (key_down && want_mute)
+               __do_mute_dtmf(xpd, portnum, 1);
+       else
+               __do_mute_dtmf(xpd, portnum, 0);
+       if (want_event) {
+               int event =
+                   (key_down) ? DAHDI_EVENT_DTMFDOWN : DAHDI_EVENT_DTMFUP;
+
+               dahdi_qevent_lock(XPD_CHAN(xpd, portnum), event | digit);
+       }
+}
+
+static int FXS_card_register_reply(xbus_t *xbus, xpd_t *xpd, reg_cmd_t *info)
+{
+       unsigned long flags;
+       struct FXS_priv_data *priv;
+       __u8 regnum = 0;
+       bool indirect = 0;
+
+       spin_lock_irqsave(&xpd->lock, flags);
+       priv = xpd->priv;
+       BUG_ON(!priv);
+       if (info->h.bytes == REG_CMD_SIZE(REG)) {
+               if ((XPD_HW(xpd).type == 1) && (REG_FIELD(info, regnum) == 0x1E))
+                       indirect = 1;
+               regnum = (indirect) ? REG_FIELD(info, subreg) : REG_FIELD(info, regnum);
+               XPD_DBG(REGS, xpd, "%s reg_num=0x%X, dataL=0x%X dataH=0x%X\n",
+                       (indirect) ? "I" : "D", regnum, REG_FIELD(info, data_low),
+                       REG_FIELD(info, data_high));
+       }
+       if (info->h.bytes == REG_CMD_SIZE(RAM)) {
+               uint addr;
+               unsigned long val;
+               XPD_DBG(REGS, xpd, "port=%d, addr_low=0x%X, addr_high=0x%X, data_0=0x%X data_1=0x%X data_2=0x%X data_3=0x%X\n",
+                       info->h.portnum,
+                       REG_FIELD_RAM(info, addr_low),
+                       REG_FIELD_RAM(info, addr_high),
+                       REG_FIELD_RAM(info, data_0),
+                       REG_FIELD_RAM(info, data_1),
+                       REG_FIELD_RAM(info, data_2),
+                       REG_FIELD_RAM(info, data_3));
+               addr = (REG_FIELD_RAM(info, addr_high) << 8) | REG_FIELD_RAM(info, addr_low);
+               val = (REG_FIELD_RAM(info, data_3) << 24) |
+                     (REG_FIELD_RAM(info, data_2) << 16) |
+                     (REG_FIELD_RAM(info, data_1) << 8) |
+                      REG_FIELD_RAM(info, data_0);
+       } else if ((XPD_HW(xpd).type == 1 && !indirect && regnum == REG_TYPE1_DTMF_DECODE) ||
+           (XPD_HW(xpd).type == 6 && !indirect && regnum == REG_TYPE6_TONDTMF)) {
+               __u8 val = REG_FIELD(info, data_low);
+
+               process_dtmf(xpd, info->h.portnum, val);
+       } else if ((XPD_HW(xpd).type == 6 && !indirect && regnum == REG_TYPE6_LINEFEED)) {
+               __u8 val = REG_FIELD(info, data_low);
+
+               LINE_DBG(SIGNAL, xpd, info->h.portnum,
+                       "REG_TYPE6_LINEFEED: dataL=0x%X \n", val);
+               priv->polledhook[info->h.portnum] = val;
+       }
+#ifdef POLL_DIGITAL_INPUTS
+       /*
+        * Process digital inputs polling results
+        */
+       else if ( (XPD_HW(xpd).type == 1 && !indirect && regnum == REG_TYPE1_DIGITAL_IOCTRL) ||
+                 (XPD_HW(xpd).type == 6 && !indirect && regnum == REG_TYPE6_EXP_GPIOB && 
+                  REG_FIELD(info, do_expander)))
+               process_digital_inputs(xpd, info);
+#endif
+       else if (XPD_HW(xpd).type == 1 && !indirect && regnum == REG_TYPE1_LOOPCLOSURE) { /* OFFHOOK ? */
+               __u8 val = REG_FIELD(info, data_low);
+               xpp_line_t mask = BIT(info->h.portnum);
+               xpp_line_t offhook;
+
+               /*
+                * Validate reply. Non-existing/disabled ports
+                * will reply with 0xFF. Ignore these.
+                */
+               if ((val & REG_TYPE1_LOOPCLOSURE_ZERO) == 0) {
+                       offhook = (val & REG_TYPE1_LOOPCLOSURE_LCR) ? mask : 0;
+                       LINE_DBG(SIGNAL, xpd, info->h.portnum,
+                               "REG_TYPE1_LOOPCLOSURE: dataL=0x%X "
+                               "(offhook=0x%X mask=0x%X)\n",
+                               val, offhook, mask);
+                       process_hookstate(xpd, offhook, mask);
+               }
+       } else if (XPD_HW(xpd).type == 6 && !indirect && regnum == REG_TYPE6_LCRRTP) { /* OFFHOOK ? */
+               __u8 val = REG_FIELD(info, data_low);
+               xpp_line_t mask = BIT(info->h.portnum);
+               xpp_line_t offhook;
+
+               /*
+                * Validate reply. Non-existing/disabled ports
+                * will reply with 0xFF. Ignore these.
+                */
+               if ((val & REG_TYPE6_LCRRTP_ZERO) == 0) {
+                       offhook = (val & REG_TYPE6_LCRRTP_LCR) ? mask : 0;
+                       LINE_DBG(SIGNAL, xpd, info->h.portnum,
+                               "REG_TYPE6_LCRRTP: dataL=0x%X "
+                               "(offhook=0x%X mask=0x%X)\n",
+                               val, offhook, mask);
+                       process_hookstate(xpd, offhook, mask);
+               }
+       } else {
+#if 0
+               XPD_NOTICE(xpd,
+                       "Spurious register reply(ignored): "
+                       "%s reg_num=0x%X, dataL=0x%X dataH=0x%X\n",
+                       (indirect) ? "I" : "D",
+                       regnum, REG_FIELD(info, data_low),
+                       REG_FIELD(info, data_high));
+#endif
+       }
+       /*
+        * Update /proc info only if reply relate to the last slic
+        * read request
+        */
+       if (REG_FIELD(&xpd->requested_reply, regnum) ==
+                       REG_FIELD(info, regnum)
+               && REG_FIELD(&xpd->requested_reply, do_subreg) ==
+                       REG_FIELD(info, do_subreg)
+               && REG_FIELD(&xpd->requested_reply, subreg) ==
+                       REG_FIELD(info, subreg)) {
+               xpd->last_reply = *info;
+       }
+       spin_unlock_irqrestore(&xpd->lock, flags);
+       return 0;
+}
+
+static int FXS_card_state(xpd_t *xpd, bool on)
+{
+       BUG_ON(!xpd);
+       XPD_DBG(GENERAL, xpd, "%s\n", (on) ? "on" : "off");
+       return 0;
+}
+
+static const struct xops fxs_xops = {
+       .card_new = FXS_card_new,
+       .card_init = FXS_card_init,
+       .card_remove = FXS_card_remove,
+       .card_tick = FXS_card_tick,
+       .card_register_reply = FXS_card_register_reply,
+};
+
+static const struct phoneops fxs_phoneops = {
+       .card_dahdi_preregistration = FXS_card_dahdi_preregistration,
+       .card_dahdi_postregistration = FXS_card_dahdi_postregistration,
+       .card_hooksig = FXS_card_hooksig,
+       .card_pcm_recompute = generic_card_pcm_recompute,
+       .card_pcm_fromspan = generic_card_pcm_fromspan,
+       .card_pcm_tospan = generic_card_pcm_tospan,
+       .card_timing_priority = generic_timing_priority,
+       .echocancel_timeslot = generic_echocancel_timeslot,
+       .echocancel_setmask = generic_echocancel_setmask,
+       .card_open = FXS_card_open,
+       .card_close = FXS_card_close,
+       .card_ioctl = FXS_card_ioctl,
+       .card_state = FXS_card_state,
+};
+
+static xproto_table_t PROTO_TABLE(FXS) = {
+       .owner = THIS_MODULE,
+       .entries = {
+               /*      Prototable      Card    Opcode          */
+               XENTRY( FXS,            FXS,    SIG_CHANGED     ),
+       },
+       .name = "FXS",  /* protocol name */
+       .ports_per_subunit = 8,
+       .type = XPD_TYPE_FXS,
+       .xops = &fxs_xops,
+       .phoneops = &fxs_phoneops,
+       .packet_is_valid = fxs_packet_is_valid,
+       .packet_dump = fxs_packet_dump,
+};
+
+static bool fxs_packet_is_valid(xpacket_t *pack)
+{
+       const xproto_entry_t *xe;
+
+       // DBG(GENERAL, "\n");
+       xe = xproto_card_entry(&PROTO_TABLE(FXS), XPACKET_OP(pack));
+       return xe != NULL;
+}
+
+static void fxs_packet_dump(const char *msg, xpacket_t *pack)
+{
+       DBG(GENERAL, "%s\n", msg);
+}
+
+/*------------------------- SLIC Handling --------------------------*/
+
+#ifdef CONFIG_PROC_FS
+static int proc_fxs_info_show(struct seq_file *sfile, void *not_used)
+{
+       unsigned long flags;
+       xpd_t *xpd = sfile->private;
+       struct FXS_priv_data *priv;
+       int i;
+       int led;
+
+       if (!xpd)
+               return -ENODEV;
+       spin_lock_irqsave(&xpd->lock, flags);
+       priv = xpd->priv;
+       BUG_ON(!priv);
+       seq_printf(sfile, "%-12s", "Channel:");
+       for_each_line(xpd, i) {
+               seq_printf(sfile, "%4d", i);
+       }
+       seq_printf(sfile, "\n%-12s", "");
+       for_each_line(xpd, i) {
+               char *chan_type;
+
+               if (IS_SET(PHONEDEV(xpd).digital_outputs, i))
+                       chan_type = "out";
+               else if (IS_SET(PHONEDEV(xpd).digital_inputs, i))
+                       chan_type = "in";
+               else
+                       chan_type = "";
+               seq_printf(sfile, "%4s", chan_type);
+       }
+       seq_printf(sfile, "\n%-12s", "idletxhook:");
+       for_each_line(xpd, i) {
+               seq_printf(sfile, "%4d", priv->idletxhookstate[i]);
+       }
+       seq_printf(sfile, "\n%-12s", "lasttxhook:");
+       for_each_line(xpd, i) {
+               seq_printf(sfile, "%4d", priv->lasttxhook[i]);
+       }
+       seq_printf(sfile, "\n%-12s", "ohttimer:");
+       for_each_line(xpd, i) {
+               seq_printf(sfile, "%4d", priv->ohttimer[i]);
+       }
+       seq_printf(sfile, "\n%-12s", "neon_blink:");
+       for_each_line(xpd, i) {
+               seq_printf(sfile, "%4d",
+                           IS_SET(priv->neon_blinking, i));
+       }
+       seq_printf(sfile, "\n%-12s", "search_fsk:");
+       for_each_line(xpd, i) {
+               seq_printf(sfile, "%4d",
+                           IS_SET(priv->search_fsk_pattern, i));
+       }
+       seq_printf(sfile, "\n%-12s", "vbat_h:");
+       for_each_line(xpd, i) {
+               seq_printf(sfile, "%4d",
+                       test_bit(i, (unsigned long *)&priv->vbat_h));
+       }
+       seq_printf(sfile, "\n");
+       for (led = 0; led < NUM_LEDS; led++) {
+               seq_printf(sfile, "\nLED #%d\t%-12s: ",
+                       led, "ledstate");
+               for_each_line(xpd, i) {
+                       if (!IS_SET(PHONEDEV(xpd).digital_outputs, i)
+                           && !IS_SET(PHONEDEV(xpd).digital_inputs, i))
+                               seq_printf(sfile, "%d ",
+                                           IS_SET(priv->ledstate[led], i));
+               }
+               seq_printf(sfile, "\nLED #%d\t%-12s: ",
+                       led, "ledcontrol");
+               for_each_line(xpd, i) {
+                       if (!IS_SET(PHONEDEV(xpd).digital_outputs, i)
+                           && !IS_SET(PHONEDEV(xpd).digital_inputs, i))
+                               seq_printf(sfile, "%d ",
+                                           IS_SET(priv->ledcontrol[led], i));
+               }
+               seq_printf(sfile, "\nLED #%d\t%-12s: ",
+                       led, "led_counter");
+               for_each_line(xpd, i) {
+                       if (!IS_SET(PHONEDEV(xpd).digital_outputs, i)
+                           && !IS_SET(PHONEDEV(xpd).digital_inputs, i))
+                               seq_printf(sfile, "%d ",
+                                           LED_COUNTER(priv, i, led));
+               }
+       }
+       seq_printf(sfile, "\n%-12s", "overheats:");
+       for_each_line(xpd, i) {
+               seq_printf(sfile, "%4d", priv->overheat_reset_counter[i]);
+       }
+       seq_printf(sfile, "\n");
+       spin_unlock_irqrestore(&xpd->lock, flags);
+       return 0;
+}
+
+static int proc_fxs_info_open(struct inode *inode, struct file *file)
+{
+       return single_open(file, proc_fxs_info_show, PDE_DATA(inode));
+}
+
+static const struct file_operations proc_fxs_info_ops = {
+       .owner          = THIS_MODULE,
+       .open           = proc_fxs_info_open,
+       .read           = seq_read,
+       .llseek         = seq_lseek,
+       .release        = single_release,
+};
+
+#ifdef WITH_METERING
+static ssize_t proc_xpd_metering_write(struct file *file,
+               const char __user *buffer, size_t count, loff_t *offset)
+{
+       xpd_t *xpd = file->private_data;
+       char buf[MAX_PROC_WRITE];
+       lineno_t chan;
+       int num;
+       int ret;
+
+       if (!xpd)
+               return -ENODEV;
+       if (count >= MAX_PROC_WRITE - 1) {
+               XPD_ERR(xpd, "Metering string too long (%zu)\n", count);
+               return -EINVAL;
+       }
+       if (copy_from_user(&buf, buffer, count))
+               return -EFAULT;
+       buf[count] = '\0';
+       ret = sscanf(buf, "%d", &num);
+       if (ret != 1) {
+               XPD_ERR(xpd, "Metering value should be number. Got '%s'\n",
+                       buf);
+               return -EINVAL;
+       }
+       chan = num;
+       if (chan != PORT_BROADCAST && chan > xpd->channels) {
+               XPD_ERR(xpd, "Metering tone: bad channel number %d\n", chan);
+               return -EINVAL;
+       }
+       if ((ret = metering_gen(xpd, chan, 1)) < 0) {
+               XPD_ERR(xpd, "Failed sending metering tone\n");
+               return ret;
+       }
+       return count;
+}
+
+static int proc_xpd_metering_open(struct inode *inode, struct file *file)
+{
+       file->private_data = PDE_DATA(inode);
+}
+
+static const struct file_operations proc_xpd_metering_ops = {
+       .owner          = THIS_MODULE,
+       .open           = proc_xpd_metering_open,
+       .write          = proc_xpd_metering_write,
+       .release        = single_release,
+};
+#endif
+#endif
+
+static DEVICE_ATTR_READER(fxs_ring_registers_show, dev, buf)
+{
+       xpd_t *xpd;
+       struct FXS_priv_data *priv;
+       unsigned long flags;
+       const struct ring_reg_params *p;
+       const struct byte_pair *v;
+       enum ring_types rtype;
+       int len = 0;
+       int i;
+
+       BUG_ON(!dev);
+       xpd = dev_to_xpd(dev);
+       if (!xpd)
+               return -ENODEV;
+       priv = xpd->priv;
+       BUG_ON(!priv);
+       spin_lock_irqsave(&xpd->lock, flags);
+       len += sprintf(buf + len, "#   Reg#: D/I\tNEON     \tTRAPEZ   \tNORMAL   \n");
+       for (i = 0; i < ARRAY_SIZE(ring_parameters); i++) {
+               p = &ring_parameters[i];
+               len += sprintf(buf + len, "[%d] 0x%02X: %c",
+                       i, p->regno, (p->is_indirect) ? 'I' : 'D');
+               for (rtype = RING_TYPE_NEON; rtype <= RING_TYPE_NORMAL; rtype++) {
+                       v = &(p->values[rtype]);
+                       if (p->is_indirect)
+                               len += sprintf(buf + len, "\t0x%02X 0x%02X",
+                                       v->h_val, v->l_val);
+                       else
+                               len += sprintf(buf + len, "\t0x%02X ----",
+                                       v->l_val);
+               }
+               len += sprintf(buf + len, "\n");
+       }
+       spin_unlock_irqrestore(&xpd->lock, flags);
+       return len;
+}
+
+static DEVICE_ATTR_WRITER(fxs_ring_registers_store, dev, buf, count)
+{
+       xpd_t *xpd;
+       struct FXS_priv_data *priv;
+       unsigned long flags;
+       char rtype_name[MAX_PROC_WRITE];
+       enum ring_types rtype;
+       struct ring_reg_params *params;
+       struct byte_pair *v;
+       int regno;
+       int h_val;
+       int l_val;
+       int ret;
+       int i;
+
+       BUG_ON(!dev);
+       xpd = dev_to_xpd(dev);
+       if (!xpd)
+               return -ENODEV;
+       priv = xpd->priv;
+       BUG_ON(!priv);
+       ret = sscanf(buf, "%10s %X %X %X\n",
+               rtype_name, &regno, &h_val, &l_val);
+       if (ret < 3 || ret > 4) {
+               XPD_ERR(xpd, "Bad input: '%s'\n", buf);
+               XPD_ERR(xpd, "# Correct input\n");
+               XPD_ERR(xpd, "{NEON|TRAPEZ|NORMAL} <regno> <byte> [<byte>]\n");
+               goto invalid_input;
+       }
+       if (strcasecmp("NEON", rtype_name) == 0)
+               rtype = RING_TYPE_NEON;
+       else if (strcasecmp("TRAPEZ", rtype_name) == 0)
+               rtype = RING_TYPE_TRAPEZ;
+       else if (strcasecmp("NORMAL", rtype_name) == 0)
+               rtype = RING_TYPE_NORMAL;
+       else {
+               XPD_ERR(xpd, "Unknown ring type '%s' (NEON/TRAPEZ/NORMAL)\n",
+                       rtype_name);
+               goto invalid_input;
+       }
+       params = NULL;
+       for (i = 0; i < ARRAY_SIZE(ring_parameters); i++) {
+               if (ring_parameters[i].regno == regno) {
+                       params = &ring_parameters[i];
+                       break;
+               }
+       }
+       if (!params) {
+               XPD_ERR(xpd, "Bad register 0x%X\n", regno);
+               goto invalid_input;
+       }
+       if (params->is_indirect) {
+               if (ret != 4) {
+                       XPD_ERR(xpd,
+                               "Missing low-byte (0x%X is indirect register)\n",
+                               regno);
+                       goto invalid_input;
+               }
+               XPD_DBG(SIGNAL, xpd, "%s Indirect 0x%X <=== 0x%X 0x%X\n",
+                       rtype_name, regno, h_val, l_val);
+       } else {
+               if (ret != 3) {
+                       XPD_ERR(xpd,
+                               "Should give exactly one value (0x%X is direct register)\n",
+                               regno);
+                       goto invalid_input;
+               }
+               l_val = h_val;
+               h_val = 0;
+               XPD_DBG(SIGNAL, xpd, "%s Direct 0x%X <=== 0x%X\n",
+                       rtype_name, regno, h_val);
+       }
+       spin_lock_irqsave(&xpd->lock, flags);
+       v = &(params->values[rtype]);
+       v->h_val = h_val;
+       v->l_val = l_val;
+       spin_unlock_irqrestore(&xpd->lock, flags);
+       return count;
+invalid_input:
+       return -EINVAL;
+}
+
+static DEVICE_ATTR(fxs_ring_registers, S_IRUGO | S_IWUSR,
+       fxs_ring_registers_show,
+       fxs_ring_registers_store);
+
+static int fxs_xpd_probe(struct device *dev)
+{
+       xpd_t *xpd;
+       int ret;
+
+       xpd = dev_to_xpd(dev);
+       /* Is it our device? */
+       if (xpd->xpd_type != XPD_TYPE_FXS) {
+               XPD_ERR(xpd, "drop suggestion for %s (%d)\n", dev_name(dev),
+                       xpd->xpd_type);
+               return -EINVAL;
+       }
+       XPD_DBG(DEVICES, xpd, "SYSFS\n");
+       ret = device_create_file(dev, &dev_attr_fxs_ring_registers);
+       if (ret) {
+               XPD_ERR(xpd, "%s: device_create_file(fxs_ring_registers) failed: %d\n",
+                       __func__, ret);
+               goto fail_fxs_ring_registers;
+       }
+       return 0;
+fail_fxs_ring_registers:
+       return ret;
+}
+
+static int fxs_xpd_remove(struct device *dev)
+{
+       xpd_t *xpd;
+
+       xpd = dev_to_xpd(dev);
+       XPD_DBG(DEVICES, xpd, "SYSFS\n");
+       device_remove_file(dev, &dev_attr_fxs_ring_registers);
+       return 0;
+}
+
+static struct xpd_driver fxs_driver = {
+       .xpd_type = XPD_TYPE_FXS,
+       .driver = {
+                  .name = "fxs",
+                  .owner = THIS_MODULE,
+                  .probe = fxs_xpd_probe,
+                  .remove = fxs_xpd_remove}
+};
+
+static int __init card_fxs_startup(void)
+{
+       int ret;
+
+       if ((ret = xpd_driver_register(&fxs_driver.driver)) < 0)
+               return ret;
+
+#ifdef POLL_DIGITAL_INPUTS
+       INFO("FEATURE: with DIGITAL INPUTS support (polled every %d msec)\n",
+            poll_digital_inputs);
+#else
+       INFO("FEATURE: without DIGITAL INPUTS support\n");
+#endif
+       INFO("FEATURE: DAHDI_VMWI (HVAC only)\n");
+#ifdef WITH_METERING
+       INFO("FEATURE: WITH METERING Generation\n");
+#else
+       INFO("FEATURE: NO METERING Generation\n");
+#endif
+       xproto_register(&PROTO_TABLE(FXS));
+       return 0;
+}
+
+static void __exit card_fxs_cleanup(void)
+{
+       xproto_unregister(&PROTO_TABLE(FXS));
+       xpd_driver_unregister(&fxs_driver.driver);
+}
+
+MODULE_DESCRIPTION("XPP FXS Card Driver");
+MODULE_AUTHOR("Oron Peled <oron@actcom.co.il>");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS_XPD(XPD_TYPE_FXS);
+
+module_init(card_fxs_startup);
+module_exit(card_fxs_cleanup);
diff --git a/drivers/dahdi/xpp/card_fxs.h b/drivers/dahdi/xpp/card_fxs.h
new file mode 100644 (file)
index 0000000..5be4833
--- /dev/null
@@ -0,0 +1,39 @@
+#ifndef        CARD_FXS_H
+#define        CARD_FXS_H
+/*
+ * Written by Oron Peled <oron@actcom.co.il>
+ * Copyright (C) 2004-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.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ *
+ */
+
+#include "xpd.h"
+
+enum fxs_opcodes {
+       XPROTO_NAME(FXS, SIG_CHANGED) = 0x06,
+        /**/ XPROTO_NAME(FXS, CHAN_POWER) = 0x0F,      /* Write to SLIC */
+       XPROTO_NAME(FXS, CHAN_CID) = 0x0F,      /* Write to SLIC */
+       XPROTO_NAME(FXS, LED) = 0x0F,   /* Write to SLIC */
+};
+
+DEF_RPACKET_DATA(FXS, SIG_CHANGED,
+               xpp_line_t sig_status;  /* channels: lsb=1, msb=8 */
+               xpp_line_t sig_toggles; /* channels: lsb=1, msb=8 */
+               );
+
+#endif /* CARD_FXS_H */
diff --git a/drivers/dahdi/xpp/card_global.c b/drivers/dahdi/xpp/card_global.c
new file mode 100644 (file)
index 0000000..da1393e
--- /dev/null
@@ -0,0 +1,907 @@
+/*
+ * Written by Oron Peled <oron@actcom.co.il>
+ * Copyright (C) 2004-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.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/delay.h>
+#include <linux/kmod.h>
+#include "xdefs.h"
+#include "xpd.h"
+#include "xpp_dahdi.h"
+#include "xproto.h"
+#include "dahdi_debug.h"
+#include "xbus-core.h"
+#include "parport_debug.h"
+
+static const char rcsid[] = "$Id$";
+
+DEF_PARM(charp, initdir, "/usr/share/dahdi", 0644,
+        "The directory of card initialization scripts");
+
+#define        CHIP_REGISTERS  "chipregs"
+
+extern int debug;
+
+/*---------------- GLOBAL PROC handling -----------------------------------*/
+
+static int send_magic_request(xbus_t *xbus, unsigned unit, xportno_t portno,
+                             bool eoftx)
+{
+       xframe_t *xframe;
+       xpacket_t *pack;
+       reg_cmd_t *reg_cmd;
+       int ret;
+
+       /*
+        * Zero length multibyte is legal and has special meaning for the
+        * firmware:
+        *   eoftx==1: Start sending us D-channel packets.
+        *   eoftx==0: Stop sending us D-channel packets.
+        */
+       XFRAME_NEW_REG_CMD(xframe, pack, xbus, GLOBAL, REG, unit);
+       reg_cmd = &RPACKET_FIELD(pack, GLOBAL, REGISTER_REQUEST, reg_cmd);
+       reg_cmd->h.bytes = 0;
+       reg_cmd->h.is_multibyte = 1;
+       reg_cmd->h.portnum = portno;
+       reg_cmd->h.eoframe = eoftx;
+       PORT_DBG(REGS, xbus, unit, portno, "Magic Packet (eoftx=%d)\n", eoftx);
+       if (debug & DBG_REGS)
+               dump_xframe(__func__, xbus, xframe, debug);
+       ret = send_cmd_frame(xbus, xframe);
+       if (ret < 0)
+               PORT_ERR(xbus, unit, portno, "%s: failed sending xframe\n",
+                        __func__);
+       return ret;
+}
+
+static int parse_hexbyte(const char *buf)
+{
+       char *endp;
+       unsigned val;
+
+       val = simple_strtoul(buf, &endp, 16);
+       if (*endp != '\0' || val > 0xFF)
+               return -EBADR;
+       return (__u8)val;
+}
+
+static int execute_chip_command(xpd_t *xpd, const int argc, char *argv[])
+{
+       int argno;
+       char num_args;
+       int portno;
+       bool writing;
+       int op;                 /* [W]rite, [R]ead */
+       int addr_mode;          /* [D]irect, [I]ndirect, [Mm]ulti, [R]AM */
+       bool do_subreg = 0;
+       int regnum;
+       int subreg;
+       int data_low;
+       bool do_datah;
+       int data_high;
+       bool do_expander = 0;
+       int ret = -EBADR;
+
+       num_args = 2;           /* port + operation */
+       if (argc < num_args) {
+               XPD_ERR(xpd, "Not enough arguments (%d)\n", argc);
+               XPD_ERR(xpd,
+                       "Any Command is composed of at least %d words "
+                       "(got only %d)\n",
+                       num_args, argc);
+               goto out;
+       }
+       /* Process the arguments */
+       argno = 0;
+       if (strcmp(argv[argno], "*") == 0) {
+               portno = PORT_BROADCAST;
+               //XPD_DBG(REGS, xpd, "Port broadcast\n");
+       } else {
+               portno = parse_hexbyte(argv[argno]);
+               if (portno < 0 || portno >= 8) {
+                       XPD_ERR(xpd, "Illegal port number '%s'\n", argv[argno]);
+                       goto out;
+               }
+               //XPD_DBG(REGS, xpd, "Port is %d\n", portno);
+       }
+       argno++;
+       if (strlen(argv[argno]) != 2) {
+               XPD_ERR(xpd, "Wrong operation codes '%s'\n", argv[argno]);
+               goto out;
+       }
+       op = argv[argno][0];
+       switch (op) {
+       case 'W':
+               writing = 1;
+               num_args++;     /* data low */
+               //XPD_DBG(REGS, xpd, "WRITING\n");
+               break;
+       case 'R':
+               writing = 0;
+               //XPD_DBG(REGS, xpd, "READING\n");
+               break;
+       default:
+               XPD_ERR(xpd, "Unknown operation type '%c'\n", op);
+               goto out;
+       }
+       addr_mode = argv[argno][1];
+       switch (addr_mode) {
+       case 'I':
+               XPD_NOTICE(xpd,
+                       "'I' is deprecated in register commands. "
+                       "Use 'S' instead.\n");
+               /* fall through */
+       case 'S':
+               do_subreg = 1;
+               num_args += 2;  /* register + subreg */
+               //XPD_DBG(REGS, xpd, "SUBREG\n");
+               break;
+       case 'D':
+               do_subreg = 0;
+               num_args++;     /* register */
+               //XPD_DBG(REGS, xpd, "DIRECT\n");
+               break;
+       case 'X':
+               do_subreg = 0;
+               do_expander = 1;
+               num_args++;     /* register */
+               //XPD_DBG(REGS, xpd, "EXPANDER\n");
+               break;
+       case 'M':
+       case 'm':
+               if (op != 'W') {
+                       XPD_ERR(xpd,
+                               "Can use Multibyte (%c) only with op 'W'\n",
+                               addr_mode);
+                       goto out;
+               }
+               num_args--;     /* No data low */
+               //XPD_DBG(REGS, xpd, "Multibyte (%c)\n", addr_mode);
+               break;
+       case 'R':
+               switch (op) {
+               case 'W':
+                       num_args += 5;  /* add: addr_high, data_[0-3] */
+                       break;
+               case 'R':
+                       num_args += 2;  /* add: addr_low, addr_high */
+                       break;
+               }
+               break;
+       default:
+               XPD_ERR(xpd, "Unknown addressing type '%c'\n", addr_mode);
+               goto out;
+       }
+       if (argv[argno][2] != '\0') {
+               XPD_ERR(xpd, "Bad operation field '%s'\n", argv[argno]);
+               goto out;
+       }
+       if (argc < num_args) {
+               XPD_ERR(xpd,
+                       "Command \"%s\" is composed of at least %d words "
+                       "(got only %d)\n",
+                       argv[argno], num_args, argc);
+               goto out;
+       }
+       argno++;
+       if (addr_mode == 'M' || addr_mode == 'm') {
+               if (argno < argc) {
+                       XPD_ERR(xpd,
+                               "Magic-Multibyte(%c) with %d extra arguments\n",
+                               addr_mode, argc - argno);
+                       goto out;
+               }
+               ret =
+                   send_magic_request(xpd->xbus, xpd->addr.unit, portno,
+                                      addr_mode == 'm');
+               goto out;
+       }
+       if (addr_mode == 'R') {
+               __u8 input[6];
+               int i;
+
+               if (num_args - 2 > 6) {
+                       XPD_ERR(xpd, "Too many args (%d) -- should be less than 6\n", num_args - 2);
+                       goto out;
+               }
+               for (i = 0; i < num_args - 2; i++, argno++) {
+                       int hexbyte= parse_hexbyte(argv[argno]);
+                       if (hexbyte < 0) {
+                               XPD_ERR(xpd, "Illegal input[%d] number '%s'\n", i, argv[argno]);
+                               goto out;
+                       }
+                       input[i] = hexbyte;
+               }
+               ret = xpp_ram_request(xpd->xbus, xpd, portno, writing,
+                       input[0],
+                       input[1],
+                       input[2],
+                       input[3],
+                       input[4],
+                       input[5],
+                       1);
+               goto out;
+       }
+       /* Normal (non-Magic) register commands */
+       do_datah = 0;
+       if (argno >= argc) {
+               XPD_ERR(xpd, "Missing register number\n");
+               goto out;
+       }
+       regnum = parse_hexbyte(argv[argno]);
+       if (regnum < 0) {
+               XPD_ERR(xpd, "Illegal register number '%s'\n", argv[argno]);
+               goto out;
+       }
+       //XPD_DBG(REGS, xpd, "Register is %X\n", regnum);
+       argno++;
+       if (do_subreg) {
+               if (argno >= argc) {
+                       XPD_ERR(xpd, "Missing subregister number\n");
+                       goto out;
+               }
+               subreg = parse_hexbyte(argv[argno]);
+               if (subreg < 0) {
+                       XPD_ERR(xpd, "Illegal subregister number '%s'\n",
+                               argv[argno]);
+                       goto out;
+               }
+               //XPD_DBG(REGS, xpd, "Subreg is %X\n", subreg);
+               argno++;
+       } else
+               subreg = 0;
+       if (writing) {
+               if (argno >= argc) {
+                       XPD_ERR(xpd, "Missing data low number\n");
+                       goto out;
+               }
+               data_low = parse_hexbyte(argv[argno]);
+               if (data_low < 0) {
+                       XPD_ERR(xpd, "Illegal data_low number '%s'\n",
+                               argv[argno]);
+                       goto out;
+               }
+               //XPD_DBG(REGS, xpd, "Data Low is %X\n", data_low);
+               argno++;
+       } else
+               data_low = 0;
+       if (argno < argc) {
+               do_datah = 1;
+               if (!argv[argno]) {
+                       XPD_ERR(xpd, "Missing data high number\n");
+                       goto out;
+               }
+               data_high = parse_hexbyte(argv[argno]);
+               if (data_high < 0) {
+                       XPD_ERR(xpd, "Illegal data_high number '%s'\n",
+                               argv[argno]);
+                       goto out;
+               }
+               //XPD_DBG(REGS, xpd, "Data High is %X\n", data_high);
+               argno++;
+       } else
+               data_high = 0;
+       if (argno < argc) {
+               XPD_ERR(xpd, "Command contains an extra %d argument\n",
+                       argc - argno);
+               goto out;
+       }
+#if 0
+       XPD_DBG(REGS, xpd,
+               "portno=%d writing=%d regnum=%d do_subreg=%d subreg=%d "
+               "dataL=%d do_datah=%d dataH=%d do_expander=%d\n",
+               portno,                 /* portno       */
+               writing,                /* writing      */
+               regnum, do_subreg,      /* use subreg   */
+               subreg,                 /* subreg       */
+               data_low, do_datah,     /* use data_high */
+               data_high, do_expander);
+#endif
+       ret = xpp_register_request(xpd->xbus, xpd, portno,
+               writing, regnum, do_subreg, subreg,
+               data_low, do_datah, data_high, 1, do_expander);
+out:
+       return ret;
+}
+
+#define        MAX_ARGS        10
+
+int parse_chip_command(xpd_t *xpd, char *cmdline)
+{
+       xbus_t *xbus;
+       int ret = -EBADR;
+       __u8 buf[MAX_PROC_WRITE];
+       char *str;
+       char *p;
+       char *argv[MAX_ARGS + 1];
+       int argc;
+       int i;
+
+       BUG_ON(!xpd);
+       xbus = xpd->xbus;
+       if (!XBUS_FLAGS(xbus, CONNECTED)) {
+               XBUS_DBG(GENERAL, xbus, "Dropped packet. Disconnected.\n");
+               return -EBUSY;
+       }
+       strlcpy(buf, cmdline, MAX_PROC_WRITE);  /* Save a copy */
+       if (buf[0] == '#' || buf[0] == ';')
+               XPD_DBG(REGS, xpd, "Note: '%s'\n", buf);
+       if ((p = strchr(buf, '#')) != NULL)     /* Truncate comments */
+               *p = '\0';
+       if ((p = strchr(buf, ';')) != NULL)     /* Truncate comments */
+               *p = '\0';
+       /* Trim leading whitespace */
+       for (p = buf; *p && (*p == ' ' || *p == '\t'); p++)
+               ;
+       str = p;
+       for (i = 0; (p = strsep(&str, " \t")) != NULL && i < MAX_ARGS;) {
+               if (*p != '\0') {
+                       argv[i] = p;
+                       // XPD_DBG(REGS, xpd, "ARG %d = '%s'\n", i, p);
+                       i++;
+               }
+       }
+       argv[i] = NULL;
+       argc = i;
+       if (p) {
+               XPD_ERR(xpd, "Too many words (%d) to process. Last was '%s'\n",
+                       i, p);
+               goto out;
+       }
+       if (argc)
+               ret = execute_chip_command(xpd, argc, argv);
+       else
+               ret = 0;        /* empty command - no op */
+out:
+       return ret;
+}
+
+/*---------------- GLOBAL Protocol Commands -------------------------------*/
+
+static bool global_packet_is_valid(xpacket_t *pack);
+static void global_packet_dump(const char *msg, xpacket_t *pack);
+
+/*---------------- GLOBAL: HOST COMMANDS ----------------------------------*/
+
+/* 0x07 */ HOSTCMD(GLOBAL, AB_REQUEST)
+{
+       int ret = -ENODEV;
+       xframe_t *xframe;
+       xpacket_t *pack;
+
+       if (!xbus) {
+               DBG(DEVICES, "NO XBUS\n");
+               return -EINVAL;
+       }
+       if (xbus_check_unique(xbus))
+               return -EBUSY;
+       XFRAME_NEW_CMD(xframe, pack, xbus, GLOBAL, AB_REQUEST, 0);
+       RPACKET_FIELD(pack, GLOBAL, AB_REQUEST, rev) = XPP_PROTOCOL_VERSION;
+       RPACKET_FIELD(pack, GLOBAL, AB_REQUEST, reserved) = 0;
+       XBUS_DBG(DEVICES, xbus, "Protocol Version %d\n", XPP_PROTOCOL_VERSION);
+       if (xbus_setstate(xbus, XBUS_STATE_SENT_REQUEST))
+               ret = send_cmd_frame(xbus, xframe);
+       return ret;
+}
+
+int xpp_register_request(xbus_t *xbus, xpd_t *xpd, xportno_t portno,
+                        bool writing, __u8 regnum, bool do_subreg, __u8 subreg,
+                        __u8 data_low, bool do_datah, __u8 data_high,
+                        bool should_reply, bool do_expander)
+{
+       int ret = 0;
+       xframe_t *xframe;
+       xpacket_t *pack;
+       reg_cmd_t *reg_cmd;
+
+       if (!xbus) {
+               DBG(REGS, "NO XBUS\n");
+               return -EINVAL;
+       }
+       XFRAME_NEW_REG_CMD(xframe, pack, xbus, GLOBAL, REG, xpd->xbus_idx);
+       LINE_DBG(REGS, xpd, portno, "%c%c %02X %02X %02X %02X\n",
+                (writing) ? 'W' : 'R', (do_subreg) ? 'S' : 'D', regnum, subreg,
+                data_low, data_high);
+       reg_cmd = &RPACKET_FIELD(pack, GLOBAL, REGISTER_REQUEST, reg_cmd);
+       /* do not count the 'bytes' field */
+       reg_cmd->h.bytes = REG_CMD_SIZE(REG);
+       reg_cmd->h.is_multibyte = 0;
+       if (portno == PORT_BROADCAST) {
+               reg_cmd->h.portnum = 0;
+               REG_FIELD(reg_cmd, all_ports_broadcast) = 1;
+       } else {
+               reg_cmd->h.portnum = portno;
+               REG_FIELD(reg_cmd, all_ports_broadcast) = 0;
+       }
+       reg_cmd->h.eoframe = 0;
+       REG_FIELD(reg_cmd, reserved) = 0;       /* force reserved bits to 0 */
+       REG_FIELD(reg_cmd, read_request) = (writing) ? 0 : 1;
+       REG_FIELD(reg_cmd, do_subreg) = do_subreg;
+       REG_FIELD(reg_cmd, regnum) = regnum;
+       REG_FIELD(reg_cmd, subreg) = subreg;
+       REG_FIELD(reg_cmd, do_datah) = do_datah;
+       REG_FIELD(reg_cmd, data_low) = data_low;
+       REG_FIELD(reg_cmd, data_high) = data_high;
+       REG_FIELD(reg_cmd, do_expander) = do_expander;
+       if (should_reply)
+               xpd->requested_reply = *reg_cmd;
+       if (debug & DBG_REGS) {
+               dump_reg_cmd("REG_REQ", 1, xbus, xpd->addr.unit,
+                            reg_cmd->h.portnum, reg_cmd);
+               dump_packet("REG_REQ", pack, 1);
+       }
+       if (!xframe->usec_towait) {     /* default processing time of SPI */
+               if (subreg)
+                       xframe->usec_towait = 2000;
+               else
+                       xframe->usec_towait = 1000;
+       }
+       ret = send_cmd_frame(xbus, xframe);
+       return ret;
+}
+EXPORT_SYMBOL(xpp_register_request);
+
+int xpp_ram_request(xbus_t *xbus, xpd_t *xpd, xportno_t portno,
+                        bool writing,
+                       __u8 addr_low,
+                       __u8 addr_high,
+                       __u8 data_0,
+                       __u8 data_1,
+                       __u8 data_2,
+                       __u8 data_3,
+                        bool should_reply)
+{
+       int ret = 0;
+       xframe_t *xframe;
+       xpacket_t *pack;
+       reg_cmd_t *reg_cmd;
+
+       if (!xbus) {
+               DBG(REGS, "NO XBUS\n");
+               return -EINVAL;
+       }
+       XFRAME_NEW_REG_CMD(xframe, pack, xbus, GLOBAL, RAM, xpd->xbus_idx);
+       LINE_DBG(REGS, xpd, portno, "%cR %02X %02X %02X %02X %02X %02X\n",
+               (writing) ? 'W' : 'R',
+               addr_low, addr_high,
+               data_0, data_1, data_2, data_3);
+       reg_cmd = &RPACKET_FIELD(pack, GLOBAL, REGISTER_REQUEST, reg_cmd);
+       /* do not count the 'bytes' field */
+       reg_cmd->h.bytes = REG_CMD_SIZE(RAM);
+       reg_cmd->h.is_multibyte = 0;
+       if (portno == PORT_BROADCAST) {
+               reg_cmd->h.portnum = 0;
+               REG_FIELD_RAM(reg_cmd, all_ports_broadcast) = 1;
+       } else {
+               reg_cmd->h.portnum = portno;
+               REG_FIELD_RAM(reg_cmd, all_ports_broadcast) = 0;
+       }
+       reg_cmd->h.eoframe = 0;
+       REG_FIELD_RAM(reg_cmd, reserved) = 0;   /* force reserved bits to 0 */
+       REG_FIELD_RAM(reg_cmd, read_request) = (writing) ? 0 : 1;
+       REG_FIELD_RAM(reg_cmd, do_datah) = 1;
+       REG_FIELD_RAM(reg_cmd, do_subreg) = 1;
+       REG_FIELD_RAM(reg_cmd, addr_low) = addr_low;
+       REG_FIELD_RAM(reg_cmd, addr_high) = addr_high;
+       REG_FIELD_RAM(reg_cmd, data_0) = data_0;
+       REG_FIELD_RAM(reg_cmd, data_1) = data_1;
+       REG_FIELD_RAM(reg_cmd, data_2) = data_2;
+       REG_FIELD_RAM(reg_cmd, data_3) = data_3;
+       if (should_reply)
+               xpd->requested_reply = *reg_cmd;
+       if (debug & DBG_REGS) {
+               dump_reg_cmd("REG_RAM", 1, xbus, xpd->addr.unit,
+                            reg_cmd->h.portnum, reg_cmd);
+               dump_packet("REG_RAM", pack, 1);
+       }
+       if (!xframe->usec_towait) {     /* default processing time of SPI */
+               xframe->usec_towait = 1000;
+       }
+       ret = send_cmd_frame(xbus, xframe);
+       return ret;
+}
+EXPORT_SYMBOL(xpp_ram_request);
+
+/*
+ * The XPD parameter is totaly ignored by the driver and firmware as well.
+ */
+/* 0x19 */ HOSTCMD(GLOBAL, SYNC_SOURCE, enum sync_mode mode, int drift)
+{
+       xframe_t *xframe;
+       xpacket_t *pack;
+       const char *mode_name;
+
+       BUG_ON(!xbus);
+       if ((mode_name = sync_mode_name(mode)) == NULL) {
+               XBUS_ERR(xbus, "SYNC_SOURCE: bad sync_mode=0x%X\n", mode);
+               return -EINVAL;
+       }
+       XBUS_DBG(SYNC, xbus, "%s (0x%X), drift=%d\n", mode_name, mode, drift);
+       XFRAME_NEW_CMD(xframe, pack, xbus, GLOBAL, SYNC_SOURCE, 0);
+       RPACKET_FIELD(pack, GLOBAL, SYNC_SOURCE, sync_mode) = mode;
+       RPACKET_FIELD(pack, GLOBAL, SYNC_SOURCE, drift) = drift;
+       send_cmd_frame(xbus, xframe);
+       return 0;
+}
+
+/*
+ * Wrapper for different types of xbus reset
+ */
+static int send_xbus_reset(xbus_t *xbus, uint8_t reset_mask)
+{
+       xframe_t *xframe;
+       xpacket_t *pack;
+
+       BUG_ON(!xbus);
+       XFRAME_NEW_CMD(xframe, pack, xbus, GLOBAL, XBUS_RESET, 0);
+       RPACKET_FIELD(pack, GLOBAL, XBUS_RESET, mask) = reset_mask;
+       send_cmd_frame(xbus, xframe);
+       return 0;
+}
+
+/* 0x23 */ HOSTCMD(GLOBAL, RESET_SPI)
+{
+       XBUS_DBG(DEVICES, xbus, "Sending SPI reset\n");
+       /* toggle reset line */
+       send_xbus_reset(xbus, 0x04);
+       send_xbus_reset(xbus, 0x00);
+       return 0;
+}
+
+
+/* 0x23 */ HOSTCMD(GLOBAL, RESET_SYNC_COUNTERS)
+{
+       //XBUS_DBG(SYNC, xbus, "\n");
+       return send_xbus_reset(xbus, 0x10);
+}
+
+/*---------------- GLOBAL: Astribank Reply Handlers -----------------------*/
+
+HANDLER_DEF(GLOBAL, NULL_REPLY)
+{
+       XBUS_DBG(GENERAL, xbus, "got len=%d\n", XPACKET_LEN(pack));
+       return 0;
+}
+
+HANDLER_DEF(GLOBAL, AB_DESCRIPTION)
+{                              /* 0x08 */
+       struct xbus_workqueue *worker;
+       __u8 rev;
+       struct unit_descriptor *units;
+       int count_units;
+       int i;
+       int ret = 0;
+
+       if (!xbus) {
+               NOTICE("%s: xbus is gone!!!\n", __func__);
+               goto out;
+       }
+       rev = RPACKET_FIELD(pack, GLOBAL, AB_DESCRIPTION, rev);
+       units = RPACKET_FIELD(pack, GLOBAL, AB_DESCRIPTION, unit_descriptor);
+       count_units = XPACKET_LEN(pack) - ((__u8 *)units - (__u8 *)pack);
+       count_units /= sizeof(*units);
+       if (rev != XPP_PROTOCOL_VERSION) {
+               XBUS_NOTICE(xbus, "Bad protocol version %d (should be %d)\n",
+                           rev, XPP_PROTOCOL_VERSION);
+               ret = -EPROTO;
+               goto proto_err;
+       }
+       if (count_units > NUM_UNITS) {
+               XBUS_NOTICE(xbus, "Too many units %d (should be %d)\n",
+                           count_units, NUM_UNITS);
+               ret = -EPROTO;
+               goto proto_err;
+       }
+       if (count_units <= 0) {
+               XBUS_NOTICE(xbus, "Empty astribank? (%d units)\n", count_units);
+               ret = -EPROTO;
+               goto proto_err;
+       }
+       if (units[0].addr.unit != 0 || units[0].addr.subunit != 0) {
+               XBUS_NOTICE(xbus, "No first module. Astribank unusable.\n");
+               ret = -EPROTO;
+               goto proto_err;
+       }
+       if (!xbus_setstate(xbus, XBUS_STATE_RECVD_DESC)) {
+               ret = -EPROTO;
+               goto proto_err;
+       }
+       XBUS_INFO(xbus, "DESCRIPTOR: %d cards, protocol revision %d\n",
+                 count_units, rev);
+       if (xbus_check_unique(xbus))
+               return -EBUSY;
+       xbus->revision = rev;
+       worker = &xbus->worker;
+       if (!worker->wq) {
+               XBUS_ERR(xbus, "missing worker thread\n");
+               ret = -ENODEV;
+               goto out;
+       }
+       for (i = 0; i < count_units; i++) {
+               struct unit_descriptor *this_unit = &units[i];
+               struct card_desc_struct *card_desc;
+               unsigned long flags;
+
+               if ((card_desc =
+                    KZALLOC(sizeof(struct card_desc_struct),
+                            GFP_ATOMIC)) == NULL) {
+                       XBUS_ERR(xbus, "Card description allocation failed.\n");
+                       ret = -ENOMEM;
+                       goto out;
+               }
+               card_desc->magic = CARD_DESC_MAGIC;
+               INIT_LIST_HEAD(&card_desc->card_list);
+               card_desc->unit_descriptor = *this_unit;
+               XBUS_INFO(xbus,
+                       "    CARD %d type=%d.%d ports=(%dx%d), "
+                       "port-dir=0x%02X\n",
+                       this_unit->addr.unit, this_unit->type,
+                       this_unit->subtype,
+                       this_unit->numchips, this_unit->ports_per_chip,
+                       this_unit->port_dir);
+               spin_lock_irqsave(&worker->worker_lock, flags);
+               worker->num_units++;
+               XBUS_COUNTER(xbus, UNITS)++;
+               list_add_tail(&card_desc->card_list, &worker->card_list);
+               spin_unlock_irqrestore(&worker->worker_lock, flags);
+       }
+       CALL_PROTO(GLOBAL, RESET_SPI, xbus, NULL);
+       if (!xbus_process_worker(xbus)) {
+               ret = -ENODEV;
+               goto out;
+       }
+       goto out;
+proto_err:
+       xbus_setstate(xbus, XBUS_STATE_FAIL);
+       dump_packet("AB_DESCRIPTION", pack, DBG_ANY);
+out:
+       return ret;
+}
+
+HANDLER_DEF(GLOBAL, REGISTER_REPLY)
+{
+       reg_cmd_t *reg = &RPACKET_FIELD(pack, GLOBAL, REGISTER_REPLY, regcmd);
+
+       if (!xpd) {
+               static int rate_limit;
+
+               if ((rate_limit++ % 1003) < 5)
+                       notify_bad_xpd(__func__, xbus, XPACKET_ADDR(pack), "");
+               return -EPROTO;
+       }
+       if (debug & DBG_REGS) {
+               dump_reg_cmd("REG_REPLY", 0, xbus, xpd->addr.unit, reg->h.portnum,
+                            reg);
+               dump_packet("REG_REPLY", pack, 1);
+       }
+       if (!XMETHOD(card_register_reply, xpd)) {
+               XPD_ERR(xpd,
+                       "REGISTER_REPLY: missing card_register_reply()\n");
+               return -EINVAL;
+       }
+       return CALL_XMETHOD(card_register_reply, xpd, reg);
+}
+
+HANDLER_DEF(GLOBAL, SYNC_REPLY)
+{
+       __u8 mode = RPACKET_FIELD(pack, GLOBAL, SYNC_REPLY, sync_mode);
+       __u8 drift = RPACKET_FIELD(pack, GLOBAL, SYNC_REPLY, drift);
+       const char *mode_name;
+
+       BUG_ON(!xbus);
+       if ((mode_name = sync_mode_name(mode)) == NULL) {
+               XBUS_ERR(xbus, "SYNC_REPLY: bad sync_mode=0x%X\n", mode);
+               return -EINVAL;
+       }
+       XBUS_DBG(SYNC, xbus, "%s (0x%X), drift=%d\n", mode_name, mode, drift);
+       //dump_packet("SYNC_REPLY", pack, debug & DBG_SYNC);
+       got_new_syncer(xbus, mode, drift);
+       return 0;
+}
+
+#define        TMP_NAME_LEN    (XBUS_NAMELEN + XPD_NAMELEN + 5)
+
+HANDLER_DEF(GLOBAL, ERROR_CODE)
+{
+       char tmp_name[TMP_NAME_LEN];
+       static long rate_limit;
+       __u8 category_code;
+       __u8 errorbits;
+
+       BUG_ON(!xbus);
+       if ((rate_limit++ % 5003) > 200)
+               return 0;
+       category_code = RPACKET_FIELD(pack, GLOBAL, ERROR_CODE, category_code);
+       errorbits = RPACKET_FIELD(pack, GLOBAL, ERROR_CODE, errorbits);
+       if (!xpd) {
+               snprintf(tmp_name, TMP_NAME_LEN, "%s(%1d%1d)", xbus->busname,
+                        XPACKET_ADDR_UNIT(pack), XPACKET_ADDR_SUBUNIT(pack));
+       } else {
+               snprintf(tmp_name, TMP_NAME_LEN, "%s/%s", xbus->busname,
+                        xpd->xpdname);
+       }
+       NOTICE
+           ("%s: FIRMWARE %s: category=%d errorbits=0x%02X (rate_limit=%ld)\n",
+            tmp_name, cmd->name, category_code, errorbits, rate_limit);
+       dump_packet("FIRMWARE: ", pack, 1);
+       /*
+        * FIXME: Should implement an error recovery plan
+        */
+       return 0;
+}
+
+xproto_table_t PROTO_TABLE(GLOBAL) = {
+       .entries = {
+               /*      Prototable      Card    Opcode          */
+               XENTRY( GLOBAL,         GLOBAL, NULL_REPLY      ),
+               XENTRY( GLOBAL,         GLOBAL, AB_DESCRIPTION  ),
+               XENTRY( GLOBAL,         GLOBAL, SYNC_REPLY      ),
+               XENTRY( GLOBAL,         GLOBAL, ERROR_CODE      ),
+               XENTRY( GLOBAL,         GLOBAL, REGISTER_REPLY  ),
+       },
+       .name = "GLOBAL",
+       .packet_is_valid = global_packet_is_valid,
+       .packet_dump = global_packet_dump,
+};
+
+static bool global_packet_is_valid(xpacket_t *pack)
+{
+       const xproto_entry_t *xe;
+
+       //DBG(GENERAL, "\n");
+       xe = xproto_global_entry(XPACKET_OP(pack));
+       return xe != NULL;
+}
+
+static void global_packet_dump(const char *msg, xpacket_t *pack)
+{
+       DBG(GENERAL, "%s\n", msg);
+}
+
+#define        MAX_PATH_STR    128
+
+#ifndef        UMH_WAIT_PROC
+/*
+ * - UMH_WAIT_PROC was introduced as enum in 2.6.23
+ *   with a value of 1
+ * - It was changed to a macro (and it's value was modified) in 3.3.0
+ */
+#define        UMH_WAIT_PROC   1
+#endif
+
+int run_initialize_registers(xpd_t *xpd)
+{
+       int ret;
+       xbus_t *xbus;
+       char busstr[MAX_ENV_STR];
+       char busnumstr[MAX_ENV_STR];
+       char modelstr[MAX_ENV_STR];
+       char unitstr[MAX_ENV_STR];
+       char subunitsstr[MAX_ENV_STR];
+       char typestr[MAX_ENV_STR];
+       char directionstr[MAX_ENV_STR];
+       char revstr[MAX_ENV_STR];
+       char connectorstr[MAX_ENV_STR];
+       char xbuslabel[MAX_ENV_STR];
+       char init_card[MAX_PATH_STR];
+       __u8 direction_mask;
+       __u8 hw_type = XPD_HW(xpd).type;
+       int i;
+       char *argv[] = {
+               init_card,
+               NULL
+       };
+       char *envp[] = {
+               busstr,
+               busnumstr,
+               modelstr,
+               unitstr,
+               subunitsstr,
+               typestr,
+               directionstr,
+               revstr,
+               connectorstr,
+               xbuslabel,
+               NULL
+       };
+
+       BUG_ON(!xpd);
+       xbus = xpd->xbus;
+       if (!initdir || !initdir[0]) {
+               XPD_NOTICE(xpd, "Missing initdir parameter\n");
+               ret = -EINVAL;
+               goto err;
+       }
+       if (!xpd_setstate(xpd, XPD_STATE_INIT_REGS)) {
+               ret = -EINVAL;
+               goto err;
+       }
+       direction_mask = 0;
+       for (i = 0; i < xpd->subunits; i++) {
+               xpd_t *su = xpd_byaddr(xbus, xpd->addr.unit, i);
+
+               if (!su) {
+                       XPD_ERR(xpd, "Have %d subunits, but not subunit #%d\n",
+                               xpd->subunits, i);
+                       continue;
+               }
+               direction_mask |=
+                   (PHONEDEV(su).direction == TO_PHONE) ? BIT(i) : 0;
+       }
+       snprintf(busstr, MAX_ENV_STR, "XBUS_NAME=%s", xbus->busname);
+       snprintf(busnumstr, MAX_ENV_STR, "XBUS_NUMBER=%d", xbus->num);
+       snprintf(modelstr, MAX_ENV_STR, "XBUS_MODEL_STRING=%s",
+                xbus->transport.model_string);
+       snprintf(typestr, MAX_ENV_STR, "HW_TYPE=%d", hw_type);
+       snprintf(unitstr, MAX_ENV_STR, "UNIT_NUMBER=%d", xpd->addr.unit);
+       snprintf(typestr, MAX_ENV_STR, "UNIT_TYPE=%d", xpd->xpd_type);
+       snprintf(subunitsstr, MAX_ENV_STR, "UNIT_SUBUNITS=%d", xpd->subunits);
+       snprintf(directionstr, MAX_ENV_STR, "UNIT_SUBUNITS_DIR=%d",
+                direction_mask);
+       snprintf(revstr, MAX_ENV_STR, "XBUS_REVISION=%d", xbus->revision);
+       snprintf(connectorstr, MAX_ENV_STR, "XBUS_CONNECTOR=%s",
+                xbus->connector);
+       snprintf(xbuslabel, MAX_ENV_STR, "XBUS_LABEL=%s", xbus->label);
+       if (snprintf
+           (init_card, MAX_PATH_STR, "%s/init_card_%d_%d", initdir, hw_type,
+            xbus->revision) > MAX_PATH_STR) {
+               XPD_NOTICE(xpd,
+                       "Cannot initialize. pathname is longer "
+                       "than %d characters.\n",
+                       MAX_PATH_STR);
+               ret = -E2BIG;
+               goto err;
+       }
+       if (!XBUS_IS(xbus, RECVD_DESC)) {
+               XBUS_ERR(xbus,
+                        "Skipped register initialization. In state %s.\n",
+                        xbus_statename(XBUS_STATE(xbus)));
+               ret = -ENODEV;
+               goto err;
+       }
+       XPD_DBG(DEVICES, xpd, "running '%s' for type=%d revision=%d\n",
+               init_card, xpd->xpd_type, xbus->revision);
+       ret = call_usermodehelper(init_card, argv, envp, UMH_WAIT_PROC);
+       /*
+        * Carefully report results
+        */
+       if (ret == 0)
+               XPD_DBG(DEVICES, xpd, "'%s' finished OK\n", init_card);
+       else if (ret < 0) {
+               XPD_ERR(xpd, "Failed running '%s' (errno %d)\n", init_card,
+                       ret);
+       } else {
+               __u8 exitval = ((unsigned)ret >> 8) & 0xFF;
+               __u8 sigval = ret & 0xFF;
+
+               if (!exitval) {
+                       XPD_ERR(xpd, "'%s' killed by signal %d\n", init_card,
+                               sigval);
+               } else {
+                       XPD_ERR(xpd, "'%s' aborted with exitval %d\n",
+                               init_card, exitval);
+               }
+               ret = -EINVAL;
+       }
+err:
+       return ret;
+}
+EXPORT_SYMBOL(run_initialize_registers);
diff --git a/drivers/dahdi/xpp/card_global.h b/drivers/dahdi/xpp/card_global.h
new file mode 100644 (file)
index 0000000..bdd1b62
--- /dev/null
@@ -0,0 +1,94 @@
+#ifndef        CARD_GLOBAL_H
+#define        CARD_GLOBAL_H
+/*
+ * Written by Oron Peled <oron@actcom.co.il>
+ * Copyright (C) 2004-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.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ *
+ */
+
+#include "xdefs.h"
+#include "xbus-pcm.h"
+
+enum global_opcodes {
+       XPROTO_NAME(GLOBAL, AB_REQUEST) = 0x07,
+       XPROTO_NAME(GLOBAL, AB_DESCRIPTION) = 0x08,
+       XPROTO_NAME(GLOBAL, REGISTER_REQUEST) = 0x0F,
+       XPROTO_NAME(GLOBAL, REGISTER_REPLY) = 0x10,
+        /**/ XPROTO_NAME(GLOBAL, PCM_WRITE) = 0x11,
+       XPROTO_NAME(GLOBAL, PCM_READ) = 0x12,
+        /**/ XPROTO_NAME(GLOBAL, SYNC_SOURCE) = 0x19,
+       XPROTO_NAME(GLOBAL, SYNC_REPLY) = 0x1A,
+        /**/ XPROTO_NAME(GLOBAL, ERROR_CODE) = 0x22,
+       XPROTO_NAME(GLOBAL, XBUS_RESET) = 0x23,
+       XPROTO_NAME(GLOBAL, NULL_REPLY) = 0xFE,
+};
+
+struct unit_descriptor {
+       struct xpd_addr addr;
+       __u8 subtype:4;
+       __u8 type:4;
+       __u8 numchips;
+       __u8 ports_per_chip;
+       __u8 port_dir;          /* bitmask: 0 - PSTN, 1 - PHONE */
+       __u8 reserved[2];
+       struct xpd_addr ec_addr;
+};
+
+#define        NUM_UNITS       6
+
+DEF_RPACKET_DATA(GLOBAL, NULL_REPLY);
+DEF_RPACKET_DATA(GLOBAL, AB_REQUEST, __u8 rev; __u8 reserved;);
+DEF_RPACKET_DATA(GLOBAL, AB_DESCRIPTION, __u8 rev; __u8 reserved[3];
+                struct unit_descriptor unit_descriptor[NUM_UNITS];);
+DEF_RPACKET_DATA(GLOBAL, REGISTER_REQUEST, reg_cmd_t reg_cmd;);
+DEF_RPACKET_DATA(GLOBAL, PCM_WRITE, xpp_line_t lines; __u8 pcm[PCM_CHUNKSIZE];);
+DEF_RPACKET_DATA(GLOBAL, PCM_READ, xpp_line_t lines; __u8 pcm[PCM_CHUNKSIZE];);
+DEF_RPACKET_DATA(GLOBAL, SYNC_SOURCE, __u8 sync_mode; __u8 drift;);
+DEF_RPACKET_DATA(GLOBAL, SYNC_REPLY, __u8 sync_mode; __u8 drift;);
+DEF_RPACKET_DATA(GLOBAL, REGISTER_REPLY, reg_cmd_t regcmd;);
+DEF_RPACKET_DATA(GLOBAL, XBUS_RESET, __u8 mask;);
+DEF_RPACKET_DATA(GLOBAL, ERROR_CODE, __u8 category_code; __u8 errorbits;
+                __u8 bad_packet[0];);
+
+/* 0x07 */ DECLARE_CMD(GLOBAL, AB_REQUEST);
+/* 0x19 */ DECLARE_CMD(GLOBAL, SYNC_SOURCE, enum sync_mode mode, int drift);
+/* 0x23 */ DECLARE_CMD(GLOBAL, RESET_SPI);
+/* 0x23 */ DECLARE_CMD(GLOBAL, RESET_SYNC_COUNTERS);
+
+int xpp_register_request(xbus_t *xbus, xpd_t *xpd, xportno_t portno,
+                        bool writing, __u8 regnum, bool do_subreg, __u8 subreg,
+                        __u8 data_low, bool do_datah, __u8 data_high,
+                        bool should_reply, bool do_expander);
+int send_multibyte_request(xbus_t *xbus, unsigned unit, xportno_t portno,
+                          bool eoftx, __u8 *buf, unsigned len);
+int xpp_ram_request(xbus_t *xbus, xpd_t *xpd, xportno_t portno,
+                        bool writing,
+                       __u8 addr_low,
+                       __u8 addr_high,
+                       __u8 data_0,
+                       __u8 data_1,
+                       __u8 data_2,
+                       __u8 data_3,
+                        bool should_reply);
+extern xproto_table_t PROTO_TABLE(GLOBAL);
+int run_initialize_registers(xpd_t *xpd);
+int parse_chip_command(xpd_t *xpd, char *cmdline);
+extern charp initdir;
+
+#endif /* CARD_GLOBAL_H */
diff --git a/drivers/dahdi/xpp/card_pri.c b/drivers/dahdi/xpp/card_pri.c
new file mode 100644 (file)
index 0000000..c43bc73
--- /dev/null
@@ -0,0 +1,2770 @@
+/*
+ * Written by Oron Peled <oron@actcom.co.il>
+ * Copyright (C) 2004-2006, Xorcom
+ *
+ * Parts derived from Cologne demo driver for the chip.
+ *
+ * All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+&