summaryrefslogtreecommitdiff
path: root/drivers/dahdi/xpp/card_fxo.c
diff options
context:
space:
mode:
authorKevin P. Fleming <kpfleming@digium.com>2008-05-21 15:11:48 +0000
committerKevin P. Fleming <kpfleming@digium.com>2008-05-21 15:11:48 +0000
commit802b567e6c7ba7803a950324cbed13f7d57944cb (patch)
tree6b90ca3119aaa2e4073d3b651ac965dea5d3430e /drivers/dahdi/xpp/card_fxo.c
parentec5ce88e015b41c2f46f6c9b783339b945f9502a (diff)
start copying kernel bits
git-svn-id: http://svn.asterisk.org/svn/dahdi/linux/trunk@4315 a0bf4364-ded3-4de4-8d8a-66a801d63aff
Diffstat (limited to 'drivers/dahdi/xpp/card_fxo.c')
-rw-r--r--drivers/dahdi/xpp/card_fxo.c1289
1 files changed, 1289 insertions, 0 deletions
diff --git a/drivers/dahdi/xpp/card_fxo.c b/drivers/dahdi/xpp/card_fxo.c
new file mode 100644
index 0000000..2e48dca
--- /dev/null
+++ b/drivers/dahdi/xpp/card_fxo.c
@@ -0,0 +1,1289 @@
+/*
+ * 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 "xpd.h"
+#include "xproto.h"
+#include "xpp_zap.h"
+#include "card_fxo.h"
+#include "zap_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)");
+#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 - [BT], 2 - [PASS]");
+
+/* Backward compatibility plug */
+#ifndef ZT_GET_PARAMS_V1
+#define zt_alarm_channel(a,b) zt_qevent_lock(a,( (b)==ZT_ALARM_NONE )? \
+ ZT_EVENT_NOALARM : ZT_EVENT_ALARM)
+#endif
+
+enum cid_style {
+ CID_STYLE_BELL = 0, /* E.g: US (Bellcore) */
+ CID_STYLE_ETSI_POLREV = 1, /* E.g: UK (British Telecom) */
+ CID_STYLE_PASS_ALWAYS = 2, /* E.g: DK */
+};
+
+/* Signaling is opposite (fxs signalling for fxo card) */
+#if 1
+#define FXO_DEFAULT_SIGCAP (ZT_SIG_FXSKS | ZT_SIG_FXSLS)
+#else
+#define FXO_DEFAULT_SIGCAP (ZT_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 BAT_THRESHOLD 3
+#define BAT_DEBOUNCE 1000 /* compensate for battery voltage fluctuation (in ticks) */
+#define POWER_DENIAL_CURRENT 3
+#define POWER_DENIAL_TIME 80 /* ticks */
+#define POWER_DENIAL_SAFEZONE 100 /* ticks */
+#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)
+
+/*---------------- FXO Protocol Commands ----------------------------------*/
+
+static /* 0x0F */ DECLARE_CMD(FXO, XPD_STATE, bool on);
+
+static bool fxo_packet_is_valid(xpacket_t *pack);
+static void fxo_packet_dump(const char *msg, xpacket_t *pack);
+static int proc_fxo_info_read(char *page, char **start, off_t off, int count, int *eof, void *data);
+#ifdef WITH_METERING
+static int proc_xpd_metering_read(char *page, char **start, off_t off, int count, int *eof, void *data);
+#endif
+static void zap_report_battery(xpd_t *xpd, lineno_t chan);
+
+#define PROC_REGISTER_FNAME "slics"
+#define PROC_FXO_INFO_FNAME "fxo_info"
+#ifdef WITH_METERING
+#define PROC_METERING_FNAME "metering_read"
+#endif
+
+#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];
+ enum power_state power[CHANNELS_PERXPD];
+ xpp_line_t maybe_power_denial;
+ ushort power_denial_debounce[CHANNELS_PERXPD];
+ ushort power_denial_delay[CHANNELS_PERXPD];
+ ushort power_denial_minimum[CHANNELS_PERXPD];
+ ushort power_denial_safezone[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 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_debounce[pos] = 0;
+ priv->power_denial_delay[pos] = 0;
+ BIT_CLR(priv->maybe_power_denial, pos);
+}
+
+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, byte which, bool on)
+{
+ int ret = 0;
+ struct FXO_priv_data *priv;
+ xbus_t *xbus;
+ byte value;
+
+ BUG_ON(!xpd);
+ xbus = xpd->xbus;
+ priv = xpd->priv;
+ which = which % NUM_LEDS;
+ if(IS_SET(xpd->digital_outputs, chan) || IS_SET(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(xpd->digital_outputs, i) || IS_SET(xpd->digital_inputs, i))
+ continue;
+ if((xpd->blink_mode & BIT(i)) || IS_BLINKING(priv, i, color)) { // Blinking
+ int mod_value = LED_COUNTER(priv, i, color);
+
+ if(!mod_value)
+ mod_value = DEFAULT_LED_PERIOD; /* safety value */
+ // 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_zap_ring(xpd_t *xpd, int pos, bool on)
+{
+ zt_rxsig_t rxsig;
+
+ BUG_ON(!xpd);
+ if(on) {
+ if(caller_id_style == CID_STYLE_BELL) {
+ LINE_DBG(SIGNAL, xpd, pos, "Caller-ID PCM: off\n");
+ BIT_CLR(xpd->cid_on, pos);
+ }
+ rxsig = ZT_RXSIG_RING;
+ } else {
+ if(caller_id_style == CID_STYLE_BELL) {
+ LINE_DBG(SIGNAL, xpd, pos, "Caller-ID PCM: on\n");
+ BIT_SET(xpd->cid_on, pos);
+ }
+ rxsig = ZT_RXSIG_OFFHOOK;
+ }
+ pcm_recompute(xpd, 0);
+ /*
+ * We should not spinlock before calling zt_hooksig() as
+ * it may call back into our xpp_hooksig() and cause
+ * a nested spinlock scenario
+ */
+ if(SPAN_REGISTERED(xpd))
+ zt_hooksig(&xpd->chans[pos], rxsig);
+}
+
+static void mark_ring(xpd_t *xpd, lineno_t pos, bool on, bool update_zap)
+{
+ 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 && !xpd->ringing[pos]) {
+ LINE_DBG(SIGNAL, xpd, pos, "START\n");
+ xpd->ringing[pos] = 1;
+ MARK_BLINK(priv, pos, LED_GREEN, LED_BLINK_RING);
+ if(update_zap)
+ update_zap_ring(xpd, pos, on);
+ } else if(!on && xpd->ringing[pos]) {
+ LINE_DBG(SIGNAL, xpd, pos, "STOP\n");
+ xpd->ringing[pos] = 0;
+ if(IS_BLINKING(priv, pos, LED_GREEN))
+ MARK_BLINK(priv, pos, LED_GREEN, 0);
+ if(update_zap)
+ update_zap_ring(xpd, pos, on);
+ }
+}
+
+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;
+ byte value;
+
+ BUG_ON(!xpd);
+ BUG_ON(xpd->direction == TO_PHONE); // We can SETHOOK state only on PSTN
+ 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);
+ if(to_offhook) {
+ BIT_SET(xpd->offhook, pos);
+ } else {
+ BIT_CLR(xpd->offhook, pos);
+ }
+ if(caller_id_style != CID_STYLE_PASS_ALWAYS) {
+ LINE_DBG(SIGNAL, xpd, pos, "Caller-ID PCM: off\n");
+ BIT_CLR(xpd->cid_on, pos);
+ }
+#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
+ reset_battery_readings(xpd, pos); /* unstable during hook changes */
+ priv->power_denial_safezone[pos] = (to_offhook) ? POWER_DENIAL_SAFEZONE : 0;
+ if(!to_offhook)
+ priv->power[pos] = POWER_UNKNOWN;
+ 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");
+ priv->meteringfile->data = NULL;
+ 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 = create_proc_read_entry(PROC_FXO_INFO_FNAME, 0444, xpd->proc_xpd_dir, proc_fxo_info_read, xpd);
+ if(!priv->fxo_info) {
+ XPD_ERR(xpd, "Failed to create proc file '%s'\n", PROC_FXO_INFO_FNAME);
+ goto err;
+ }
+ priv->fxo_info->owner = THIS_MODULE;
+#ifdef WITH_METERING
+ XPD_DBG(PROC, xpd, "Creating Metering tone file\n");
+ priv->meteringfile = create_proc_read_entry(PROC_METERING_FNAME, 0444, xpd->proc_xpd_dir,
+ proc_xpd_metering_read, xpd);
+ if(!priv->meteringfile) {
+ XPD_ERR(xpd, "Failed to create proc file '%s'\n", PROC_METERING_FNAME);
+ goto err;
+ }
+ priv->meteringfile->owner = THIS_MODULE;
+#endif
+#endif
+ return 0;
+err:
+ return -EINVAL;
+}
+
+static xpd_t *FXO_card_new(xbus_t *xbus, int unit, int subunit, const xproto_table_t *proto_table, byte subtype, int subunits, bool to_phone)
+{
+ xpd_t *xpd = NULL;
+ int channels;
+
+ if(to_phone) {
+ XBUS_NOTICE(xbus,
+ "XPD=%d%d: try to instanciate FXO with reverse direction\n",
+ unit, subunit);
+ return NULL;
+ }
+ if(subtype == 2)
+ channels = min(2, CHANNELS_PERXPD);
+ else
+ channels = min(8, CHANNELS_PERXPD);
+ xpd = xpd_alloc(sizeof(struct FXO_priv_data), proto_table, channels);
+ if(!xpd)
+ return NULL;
+ xpd->direction = TO_PSTN;
+ xpd->type_name = "FXO";
+ if(xpd_common_init(xbus, xpd, unit, subunit, subtype, subunits) < 0)
+ goto err;
+ 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);
+ priv->polarity[i] = POL_UNKNOWN; /* will be updated on next battery sample */
+ priv->battery[i] = BATTERY_UNKNOWN; /* will be updated on next battery sample */
+ priv->power[i] = POWER_UNKNOWN; /* will be updated on next battery sample */
+ }
+ 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);
+ }
+ pcm_recompute(xpd, 0);
+ return 0;
+}
+
+static int FXO_card_remove(xbus_t *xbus, xpd_t *xpd)
+{
+ struct FXO_priv_data *priv;
+
+ BUG_ON(!xpd);
+ priv = xpd->priv;
+ XPD_DBG(GENERAL, xpd, "\n");
+ fxo_proc_remove(xbus, xpd);
+ return 0;
+}
+
+static int FXO_card_zaptel_preregistration(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");
+#ifdef ZT_SPANSTAT_V2
+ xpd->span.spantype = "FXO";
+#endif
+ for_each_line(xpd, i) {
+ struct zt_chan *cur_chan = &xpd->chans[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);
+ }
+ return 0;
+}
+
+static int FXO_card_zaptel_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) {
+ zap_report_battery(xpd, i);
+ MARK_OFF(priv, i, LED_GREEN);
+ msleep(2);
+ MARK_OFF(priv, i, LED_RED);
+ msleep(2);
+ }
+ return 0;
+}
+
+static int FXO_card_hooksig(xbus_t *xbus, xpd_t *xpd, int pos, zt_txsig_t 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(xpd->direction != TO_PSTN);
+ /* XXX Enable hooksig for FXO XXX */
+ switch(txsig) {
+ case ZT_TXSIG_START:
+ break;
+ case ZT_TXSIG_OFFHOOK:
+ ret = do_sethook(xpd, pos, 1);
+ break;
+ case ZT_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;
+ }
+ pcm_recompute(xpd, 0);
+ return ret;
+}
+
+static void zap_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 ZT_ALARM_RED\n");
+ zt_alarm_channel(&xpd->chans[chan], ZT_ALARM_RED);
+ break;
+ case BATTERY_ON:
+ LINE_DBG(SIGNAL, xpd, chan, "Send ZT_ALARM_NONE\n");
+ zt_alarm_channel(&xpd->chans[chan], ZT_ALARM_NONE);
+ break;
+ }
+ }
+}
+
+static int FXO_card_open(xpd_t *xpd, lineno_t chan)
+{
+ struct FXO_priv_data *priv;
+
+ BUG_ON(!xpd);
+ priv = xpd->priv;
+ 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_SET(xpd->offhook, 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(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;
+
+ priv = xpd->priv;
+ for_each_line(xpd, i) {
+ if(priv->power_denial_minimum[i] > 0) {
+ priv->power_denial_minimum[i]--;
+ if(priv->power_denial_minimum[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_debounce[i] = 0;
+ BIT_SET(priv->maybe_power_denial, i);
+ }
+ }
+ if(priv->power_denial_safezone[i] > 0) {
+ if(--priv->power_denial_safezone[i]) {
+ /*
+ * Poll current, previous answers are meaningless
+ */
+ DAA_DIRECT_REQUEST(xpd->xbus, xpd, i, DAA_READ, DAA_REG_CURRENT, 0);
+ }
+ }
+ if(IS_SET(priv->maybe_power_denial, i) && !xpd->ringing[i] && IS_SET(xpd->offhook, i)) {
+ /*
+ * 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] >= POWER_DENIAL_DELAY) {
+ LINE_DBG(SIGNAL, xpd, i, "Power Denial Hangup\n");
+ priv->power_denial_delay[i] = 0;
+ BIT_CLR(priv->maybe_power_denial, i);
+ do_sethook(xpd, i, 0);
+ update_line_status(xpd, i, 0);
+ pcm_recompute(xpd, 0);
+ }
+ } else {
+ priv->power_denial_delay[i] = 0;
+ BIT_CLR(priv->maybe_power_denial, i);
+ }
+ }
+}
+
+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);
+ priv->poll_counter++;
+ return 0;
+}
+
+/* FIXME: based on data from from wctdm.h */
+#include <wctdm.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(!TRANSPORT_RUNNING(xpd->xbus))
+ 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 ZT_TONEDETECT:
+ /*
+ * Asterisk call all span types with this (FXS specific)
+ * call. Silently ignore it.
+ */
+ LINE_DBG(GENERAL, xpd, pos,
+ "ZT_TONEDETECT (FXO: NOTIMPLEMENTED)\n");
+ return -ENOTTY;
+ default:
+ report_bad_ioctl(THIS_MODULE->name, xpd, pos, cmd);
+ return -ENOTTY;
+ }
+ return 0;
+}
+
+/*---------------- FXO: HOST COMMANDS -------------------------------------*/
+
+static /* 0x0F */ HOSTCMD(FXO, XPD_STATE, bool on)
+{
+ int ret = 0;
+ struct FXO_priv_data *priv;
+
+ BUG_ON(!xbus);
+ BUG_ON(!xpd);
+ priv = xpd->priv;
+ BUG_ON(!priv);
+ XPD_DBG(GENERAL, xpd, "%s\n", (on) ? "on" : "off");
+ return ret;
+}
+
+/*---------------- 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(__FUNCTION__, 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, "debounced false ring (only %d ticks)\n", 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 update_battery_voltage(xpd_t *xpd, byte data_low, xportno_t portno)
+{
+ struct FXO_priv_data *priv;
+ enum polarity_state pol;
+ int msec;
+
+ priv = xpd->priv;
+ BUG_ON(!priv);
+ priv->battery_voltage[portno] = data_low;
+ if(xpd->ringing[portno])
+ goto ignore_reading; /* ring voltage create false alarms */
+ if(abs((signed char)data_low) < BAT_THRESHOLD) {
+ /*
+ * Check for battery voltage fluctuations
+ */
+ if(priv->battery[portno] != BATTERY_OFF) {
+ int milliseconds;
+
+ milliseconds = priv->nobattery_debounce[portno]++ *
+ poll_battery_interval;
+ if(milliseconds > BAT_DEBOUNCE) {
+ LINE_DBG(SIGNAL, xpd, portno, "BATTERY OFF voltage=%d\n", data_low);
+ priv->battery[portno] = BATTERY_OFF;
+ if(SPAN_REGISTERED(xpd))
+ zap_report_battery(xpd, portno);
+ priv->polarity[portno] = POL_UNKNOWN; /* What's the polarity ? */
+ priv->power[portno] = POWER_UNKNOWN; /* What's the current ? */
+ /*
+ * 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", data_low);
+ priv->battery[portno] = BATTERY_ON;
+ if(SPAN_REGISTERED(xpd))
+ zap_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) {
+ priv->polarity[portno] = POL_UNKNOWN; /* What's the polarity ? */
+ return;
+ }
+ /*
+ * Handle reverse polarity
+ */
+ if(data_low == 0)
+ pol = POL_UNKNOWN;
+ else if(IS_SET(data_low, 7))
+ 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) {
+ 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);
+ /*
+ * Inform zaptel/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_POLREV) {
+ LINE_DBG(SIGNAL, xpd, portno, "Caller-ID PCM: on\n");
+ BIT_SET(xpd->cid_on, portno); /* will be cleared on ring/offhook */
+ }
+ if(SPAN_REGISTERED(xpd)) {
+ LINE_DBG(SIGNAL, xpd, portno,
+ "Send ZT_EVENT_POLARITY: %s\n", polname);
+ zt_qevent_lock(&xpd->chans[portno], ZT_EVENT_POLARITY);
+ }
+ }
+ priv->polarity[portno] = pol;
+ }
+ return;
+ignore_reading:
+ /*
+ * Reset debounce counters to prevent false alarms
+ */
+ reset_battery_readings(xpd, portno); /* unstable during hook changes */
+}
+
+static void update_battery_current(xpd_t *xpd, byte 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(xpd->ringing[portno] || !IS_SET(xpd->offhook, 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) {
+ LINE_DBG(SIGNAL, xpd, portno, "power: ON -> OFF\n");
+ priv->power[portno] = POWER_OFF;
+ priv->power_denial_minimum[portno] = POWER_DENIAL_TIME;
+ }
+ } else {
+ LINE_DBG(SIGNAL, xpd, portno, "power: ON\n");
+ priv->power[portno] = POWER_ON;
+ priv->power_denial_minimum[portno] = 0;
+ update_line_status(xpd, portno, 1);
+ }
+ return;
+ignore_it:
+ BIT_CLR(priv->maybe_power_denial, portno);
+ priv->power_denial_debounce[portno] = 0;
+}
+
+#ifdef WITH_METERING
+#define BTD_BIT BIT(0)
+
+static void update_metering_state(xpd_t *xpd, byte 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 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->portnum;
+ switch(REG_FIELD(info, regnum)) {
+ 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->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 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 = {
+ .card_new = FXO_card_new,
+ .card_init = FXO_card_init,
+ .card_remove = FXO_card_remove,
+ .card_zaptel_preregistration = FXO_card_zaptel_preregistration,
+ .card_zaptel_postregistration = FXO_card_zaptel_postregistration,
+ .card_hooksig = FXO_card_hooksig,
+ .card_tick = FXO_card_tick,
+ .card_pcm_fromspan = generic_card_pcm_fromspan,
+ .card_pcm_tospan = generic_card_pcm_tospan,
+ .card_ioctl = FXO_card_ioctl,
+ .card_open = FXO_card_open,
+ .card_register_reply = FXO_card_register_reply,
+
+ .XPD_STATE = XPROTO_CALLER(FXO, XPD_STATE),
+ },
+ .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 --------------------------*/
+
+static int proc_fxo_info_read(char *page, char **start, off_t off, int count, int *eof, void *data)
+{
+ int len = 0;
+ unsigned long flags;
+ xpd_t *xpd = data;
+ struct FXO_priv_data *priv;
+ int i;
+
+ if(!xpd)
+ return -ENODEV;
+ spin_lock_irqsave(&xpd->lock, flags);
+ priv = xpd->priv;
+ BUG_ON(!priv);
+ len += sprintf(page + len, "\t%-17s: ", "Channel");
+ for_each_line(xpd, i) {
+ if(!IS_SET(xpd->digital_outputs, i) && !IS_SET(xpd->digital_inputs, i))
+ len += sprintf(page + len, "%4d ", i % 10);
+ }
+ len += sprintf(page + len, "\nLeds:");
+ len += sprintf(page + len, "\n\t%-17s: ", "state");
+ for_each_line(xpd, i) {
+ if(!IS_SET(xpd->digital_outputs, i) && !IS_SET(xpd->digital_inputs, i))
+ len += sprintf(page + len, " %d%d ",
+ IS_SET(priv->ledstate[LED_GREEN], i),
+ IS_SET(priv->ledstate[LED_RED], i));
+ }
+ len += sprintf(page + len, "\n\t%-17s: ", "blinking");
+ for_each_line(xpd, i) {
+ if(!IS_SET(xpd->digital_outputs, i) && !IS_SET(xpd->digital_inputs, i))
+ len += sprintf(page + len, " %d%d ",
+ IS_BLINKING(priv,i,LED_GREEN),
+ IS_BLINKING(priv,i,LED_RED));
+ }
+ len += sprintf(page + len, "\nBattery-Data:");
+ len += sprintf(page + len, "\n\t%-17s: ", "voltage");
+ for_each_line(xpd, i) {
+ len += sprintf(page + len, "%4d ", priv->battery_voltage[i]);
+ }
+ len += sprintf(page + len, "\n\t%-17s: ", "current");
+ for_each_line(xpd, i) {
+ len += sprintf(page + len, "%4d ", priv->battery_current[i]);
+ }
+ len += sprintf(page + len, "\nBattery:");
+ len += sprintf(page + len, "\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 = ".";
+ len += sprintf(page + len, "%4s ", bat);
+ }
+ len += sprintf(page + len, "\n\t%-17s: ", "debounce");
+ for_each_line(xpd, i) {
+ len += sprintf(page + len, "%4d ", priv->nobattery_debounce[i]);
+ }
+ len += sprintf(page + len, "\nPolarity-Reverse:");
+ len += sprintf(page + len, "\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 = ".";
+ len += sprintf(page + len, "%4s ", polname);
+ }
+ len += sprintf(page + len, "\n\t%-17s: ", "debounce");
+ for_each_line(xpd, i) {
+ len += sprintf(page + len, "%4d ", priv->polarity_debounce[i]);
+ }
+ len += sprintf(page + len, "\nPower-Denial:");
+ len += sprintf(page + len, "\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 = ".";
+ len += sprintf(page + len, "%4s ", curr);
+ }
+ len += sprintf(page + len, "\n\t%-17s: ", "maybe");
+ for_each_line(xpd, i) {
+ len += sprintf(page + len, "%4d ", IS_SET(priv->maybe_power_denial, i));
+ }
+ len += sprintf(page + len, "\n\t%-17s: ", "debounce");
+ for_each_line(xpd, i) {
+ len += sprintf(page + len, "%4d ", priv->power_denial_debounce[i]);
+ }
+ len += sprintf(page + len, "\n\t%-17s: ", "safezone");
+ for_each_line(xpd, i) {
+ len += sprintf(page + len, "%4d ", priv->power_denial_safezone[i]);
+ }
+ len += sprintf(page + len, "\n\t%-17s: ", "delay");
+ for_each_line(xpd, i) {
+ len += sprintf(page + len, "%4d ", priv->power_denial_delay[i]);
+ }
+#ifdef WITH_METERING
+ len += sprintf(page + len, "\nMetering:");
+ len += sprintf(page + len, "\n\t%-17s: ", "count");
+ for_each_line(xpd, i) {
+ len += sprintf(page + len, "%4d ", priv->metering_count[i]);
+ }
+#endif
+ len += sprintf(page + len, "\n");
+ spin_unlock_irqrestore(&xpd->lock, flags);
+ if (len <= off+count)
+ *eof = 1;
+ *start = page + off;
+ len -= off;
+ if (len > count)
+ len = count;
+ if (len < 0)
+ len = 0;
+ return len;
+}
+
+#ifdef WITH_METERING
+static int proc_xpd_metering_read(char *page, char **start, off_t off, int count, int *eof, void *data)
+{
+ int len = 0;
+ unsigned long flags;
+ xpd_t *xpd = data;
+ struct FXO_priv_data *priv;
+ int i;
+
+ if(!xpd)
+ return -ENODEV;
+ priv = xpd->priv;
+ BUG_ON(!priv);
+ spin_lock_irqsave(&xpd->lock, flags);
+ len += sprintf(page + len, "# Chan\tMeter (since last read)\n");
+ for_each_line(xpd, i) {
+ len += sprintf(page + len, "%d\t%d\n",
+ i, priv->metering_count[i]);
+ }
+ spin_unlock_irqrestore(&xpd->lock, flags);
+ if (len <= off+count)
+ *eof = 1;
+ *start = page + off;
+ len -= off;
+ if (len > count)
+ len = count;
+ if (len < 0)
+ len = 0;
+ /* Zero meters */
+ for_each_line(xpd, i)
+ priv->metering_count[i] = 0;
+ return len;
+}
+#endif
+
+static int __init card_fxo_startup(void)
+{
+ if(ring_debounce <= 0) {
+ ERR("ring_debounce=%d. Must be positive number of ticks\n", ring_debounce);
+ return -EINVAL;
+ }
+ INFO("revision %s\n", XPP_VERSION);
+#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));
+}
+
+MODULE_DESCRIPTION("XPP FXO Card Driver");
+MODULE_AUTHOR("Oron Peled <oron@actcom.co.il>");
+MODULE_LICENSE("GPL");
+MODULE_VERSION(XPP_VERSION);
+MODULE_ALIAS_XPD(XPD_TYPE_FXO);
+
+module_init(card_fxo_startup);
+module_exit(card_fxo_cleanup);