/*
 * 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
#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 6, 0)
static const struct proc_ops proc_fxo_info_ops;
#else
static const struct file_operations proc_fxo_info_ops;
#endif
#ifdef	WITH_METERING
#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 6, 0)
static const struct proc_ops proc_xpd_metering_ops;
#else
static const struct file_operations proc_xpd_metering_ops;
#endif
#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));
}

#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 6, 0)
static const struct proc_ops proc_fxo_info_ops = {
	.proc_open	= proc_fxo_info_open,
	.proc_read	= seq_read,
	.proc_lseek	= seq_lseek,
	.proc_release	= single_release,
};
#else
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,
};
#endif

#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));
}

#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 6, 0)
static const struct proc_ops proc_xpd_metering_ops = {
	.proc_open	= proc_xpd_metering_open,
	.proc_read	= seq_read,
	.proc_lseek	= seq_lseek,
	.proc_release	= single_release,
};
#else
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
#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);
