/*
 * 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
#ifdef DAHDI_HAVE_PROC_OPS
static const struct proc_ops proc_fxs_info_ops;
#else
static const struct file_operations proc_fxs_info_ops;
#endif
#ifdef	WITH_METERING
#ifdef DAHDI_HAVE_PROC_OPS
static const struct proc_ops proc_xpd_metering_ops;
#else
static const struct file_operations proc_xpd_metering_ops;
#endif
#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 */
	ktime_t 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] = ktime_set(0L, 0UL);
			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;
	ktime_t now;
	s64 msec = 0;
	struct timespec64 ts;

	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);
	now = ktime_get();
	if (!dahdi_ktime_equal(priv->prev_key_time[portnum], ktime_set(0, 0)))
		msec = ktime_ms_delta(now, priv->prev_key_time[portnum]);
	priv->prev_key_time[portnum] = now;
	ts = ktime_to_timespec64(now);
	LINE_DBG(SIGNAL, xpd, portnum,
		"[%lld.%06ld] DTMF digit %-4s '%c' (val=%d, want_mute=%s want_event=%s, delta=%lld msec)\n",
		(s64)ts.tv_sec, ts.tv_nsec * NSEC_PER_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));
}

#ifdef DAHDI_HAVE_PROC_OPS
static const struct proc_ops proc_fxs_info_ops = {
	.proc_open		= proc_fxs_info_open,
	.proc_read		= seq_read,
	.proc_lseek		= seq_lseek,
	.proc_release		= single_release,
};
#else
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,
};
#endif

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

#ifdef DAHDI_HAVE_PROC_OPS
static const struct proc_ops proc_xpd_metering_ops = {
	.proc_open	= proc_xpd_metering_open,
	.proc_write	= proc_xpd_metering_write,
	.proc_release	= single_release,
};
#else
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 /* DAHDI_HAVE_PROC_OPS */
#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);
