/*
 * Written by Oron Peled <oron@actcom.co.il>
 * Copyright (C) 2004-2006, Xorcom
 *
 * All rights reserved.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 */

#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/delay.h>
#include "xpd.h"
#include "xproto.h"
#include "xpp_dahdi.h"
#include "card_fxo.h"
#include "dahdi_debug.h"
#include "xbus-core.h"

static const char rcsid[] = "$Id: card_fxs.c 8986 2010-07-26 00:30:43Z sruffell $";

static DEF_PARM(int, debug, 0, 0644, "Print DBG statements");	/* must be before dahdi_debug.h */
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_BOOL(vmwi_ioctl, 1, 0644, "Asterisk support VMWI notification via ioctl");

/* 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)
#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)

#define	VALID_PORT(port)	(((port) >= 0 && (port) <= 7) || (port) == PORT_BROADCAST)

#define	REG_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 */
};

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

/*
 * DTMF detection
 */
#define REG_DTMF_DECODE		0x18	/* 24 - DTMF Decode Status */
#define REG_BATTERY		0x42	/* 66 - Battery Feed Control */
#define	REG_BATTERY_BATSL	BIT(1)	/* Battery Feed Select */

#define	REG_LOOPCLOSURE		0x44	/* 68 -  Loop Closure/Ring Trip Detect Status */
#define	REG_LOOPCLOSURE_ZERO	0xF8	/* Loop Closure zero bits. */
#define	REG_LOOPCLOSURE_LCR	BIT(0)	/* Loop Closure Detect Indicator. */

/*---------------- FXS Protocol Commands ----------------------------------*/

static /* 0x0F */ DECLARE_CMD(FXS, XPD_STATE, bool on);

static bool fxs_packet_is_valid(xpacket_t *pack);
static void fxs_packet_dump(const char *msg, xpacket_t *pack);
#ifdef CONFIG_PROC_FS
static int proc_fxs_info_read(char *page, char **start, off_t off, int count, int *eof, void *data);
#ifdef	WITH_METERING
static int proc_xpd_metering_write(struct file *file, const char __user *buffer, unsigned long count, void *data);
#endif
#endif
static void start_stop_vm_led(xbus_t *xbus, xpd_t *xpd, lineno_t pos);

#define	PROC_REGISTER_FNAME	"slics"
#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 */
	struct timeval		prev_key_time[CHANNELS_PERXPD];
	int			led_counter[NUM_LEDS][CHANNELS_PERXPD];
	int			ohttimer[CHANNELS_PERXPD];
#define OHT_TIMER		6000	/* How long after RING to retain OHT */
	enum fxs_state		idletxhookstate[CHANNELS_PERXPD];	/* IDLE changing hook state */
	enum fxs_state		lasttxhook[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 linefeed_control(xbus_t *xbus, xpd_t *xpd, lineno_t chan, enum fxs_state value)
{
	struct FXS_priv_data	*priv;

	priv = xpd->priv;
	LINE_DBG(SIGNAL, xpd, chan, "value=0x%02X\n", value);
	priv->lasttxhook[chan] = value;
	return SLIC_DIRECT_REQUEST(xbus, xpd, chan, SLIC_WRITE, 0x40, value);
}

static int do_chan_power(xbus_t *xbus, xpd_t *xpd, lineno_t chan, bool on)
{
	int		value = (on) ? REG_BATTERY_BATSL : 0x00;

	BUG_ON(!xbus);
	BUG_ON(!xpd);
	LINE_DBG(SIGNAL, xpd, chan, "%s\n", (on) ? "up" : "down");
	return SLIC_DIRECT_REQUEST(xbus, xpd, chan, SLIC_WRITE, REG_BATTERY, value);
}

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, byte 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(xpd->digital_outputs, chan) || IS_SET(xpd->digital_inputs, chan))
		goto out;
	if(chan == PORT_BROADCAST) {
		priv->ledstate[which] = (on) ? ~0 : 0;
	} else {
		if(on) {
			BIT_SET(priv->ledstate[which], chan);
		} else {
			BIT_CLR(priv->ledstate[which], chan);
		}
	}
	LINE_DBG(LEDS, xpd, chan, "LED: which=%d -- %s\n", which, (on) ? "on" : "off");
	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_DIGITAL_IOCTRL, value);
out:
	return ret;
}

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(xpd->digital_outputs | xpd->digital_inputs, i))
				continue;
			if((xpd->blink_mode & BIT(i)) || IS_BLINKING(priv, i, color)) {		// Blinking
				int	mod_value = LED_COUNTER(priv, i, color);

				if(!mod_value)
					mod_value = DEFAULT_LED_PERIOD;		/* safety value */
				// led state is toggled
				if((timer_count % mod_value) == 0) {
					LINE_DBG(LEDS, xpd, i, "ledstate=%s\n", (IS_SET(priv->ledstate[color], i))?"ON":"OFF");
					if(!IS_SET(priv->ledstate[color], i)) {
						do_led(xpd, i, color, 1);
					} else {
						do_led(xpd, i, color, 0);
					}
				}
			} else if(IS_SET(priv->ledcontrol[color] & ~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)
{
	byte	value = (on) ? 0x94 : 0x00;

	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");
		priv->meteringfile->data = NULL;
		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 = create_proc_read_entry(PROC_FXS_INFO_FNAME, 0444, xpd->proc_xpd_dir, proc_fxs_info_read, 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 = create_proc_entry(PROC_METERING_FNAME, 0200, xpd->proc_xpd_dir);
	if(!priv->meteringfile) {
		XPD_ERR(xpd, "Failed to create proc file '%s'\n", PROC_METERING_FNAME);
		fxs_proc_remove(xbus, xpd);
		return -EINVAL;
	}
	SET_PROC_DIRENTRY_OWNER(priv->meteringfile);
	priv->meteringfile->write_proc = proc_xpd_metering_write;
	priv->meteringfile->read_proc = NULL;
	priv->meteringfile->data = xpd;
#endif
#endif
	return 0;
}

static xpd_t *FXS_card_new(xbus_t *xbus, int unit, int subunit, const xproto_table_t *proto_table,
	byte subtype, int subunits, int subunit_ports, bool to_phone)
{
	xpd_t			*xpd = NULL;
	int			channels;
	int			regular_channels;
	struct FXS_priv_data	*priv;
	int			i;

	if(!to_phone) {
		XBUS_NOTICE(xbus,
			"XPD=%d%d: try to instanciate FXS with reverse direction\n",
			unit, subunit);
		return NULL;
	}
	if(subtype == 2)
		regular_channels = min(6, subunit_ports);
	else
		regular_channels = min(8, subunit_ports);
	channels = regular_channels;
	if(unit == 0)
		channels += 6;	/* 2 DIGITAL OUTPUTS, 4 DIGITAL INPUTS */
	xpd = xpd_alloc(xbus, unit, subunit, subtype, subunits, sizeof(struct FXS_priv_data), proto_table, channels);
	if(!xpd)
		return NULL;
	if(unit == 0) {
		XBUS_DBG(GENERAL, xbus, "First XPD detected. Initialize digital outputs/inputs\n");
		xpd->digital_outputs = BITMASK(LINES_DIGI_OUT) << regular_channels;
		xpd->digital_inputs = BITMASK(LINES_DIGI_INP) << (regular_channels + LINES_DIGI_OUT);
	}
	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) */
	ret = SLIC_DIRECT_REQUEST(xbus, xpd, PORT_BROADCAST, SLIC_WRITE, 0x22, 0x00);	/* Ringing Oscilator Control */
	if(ret < 0)
		goto err;
	for_each_line(xpd, i) {
		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_XMETHOD(card_pcm_recompute, xbus, 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(xpd->digital_outputs | xpd->digital_inputs, i))
			continue;
		SLIC_DIRECT_REQUEST(xbus, xpd, i, SLIC_READ, REG_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)
{
	struct FXS_priv_data	*priv;

	BUG_ON(!xpd);
	priv = xpd->priv;
	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");
	xpd->span.spantype = "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(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(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)
{
	LINE_DBG(SIGNAL, xpd, pos, "%s\n", (muteit) ? "MUTE" : "UNMUTE");
	if(muteit)
		BIT_SET(xpd->mute_dtmf, pos);
	else
		BIT_CLR(xpd->mute_dtmf, pos);
	CALL_XMETHOD(card_pcm_recompute, xpd->xbus, xpd, 0);	/* already spinlocked */
}

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");
		ret += SLIC_INDIRECT_REQUEST(xbus, xpd, pos, SLIC_WRITE, 0x16, 0xE8, 0x03);
		ret += SLIC_INDIRECT_REQUEST(xbus, xpd, pos, SLIC_WRITE, 0x15, 0xEF, 0x7B);
		ret += SLIC_INDIRECT_REQUEST(xbus, xpd, pos, SLIC_WRITE, 0x14, 0x9F, 0x00);
		ret += SLIC_DIRECT_REQUEST(xbus, xpd, pos, SLIC_WRITE, 0x22, 0x19);
		ret += SLIC_DIRECT_REQUEST(xbus, xpd, pos, SLIC_WRITE, 0x30, 0xE0);
		ret += SLIC_DIRECT_REQUEST(xbus, xpd, pos, SLIC_WRITE, 0x31, 0x01);
		ret += SLIC_DIRECT_REQUEST(xbus, xpd, pos, SLIC_WRITE, 0x32, 0xF0);
		ret += SLIC_DIRECT_REQUEST(xbus, xpd, pos, SLIC_WRITE, 0x33, 0x05);
		ret += SLIC_INDIRECT_REQUEST(xbus, xpd, pos, SLIC_WRITE, 0x1D, 0x00, 0x46);
	} else {
		/* A write to register 0x40 will now turn on/off the ringer */
		LINE_DBG(SIGNAL, xpd, pos, "RINGER\n");

		ret += SLIC_INDIRECT_REQUEST(xbus, xpd, pos, SLIC_WRITE, 0x16, 0x00, 0x00);
		ret += SLIC_INDIRECT_REQUEST(xbus, xpd, pos, SLIC_WRITE, 0x15, 0x77, 0x01);
		ret += SLIC_INDIRECT_REQUEST(xbus, xpd, pos, SLIC_WRITE, 0x14, 0xFD, 0x7E);
 
		ret += SLIC_DIRECT_REQUEST(xbus, xpd, pos, SLIC_WRITE, 0x22, 0x00);
		ret += SLIC_DIRECT_REQUEST(xbus, xpd, pos, SLIC_WRITE, 0x30, 0x00);
		ret += SLIC_DIRECT_REQUEST(xbus, xpd, pos, SLIC_WRITE, 0x31, 0x00);
		ret += SLIC_DIRECT_REQUEST(xbus, xpd, pos, SLIC_WRITE, 0x32, 0x00);
		ret += SLIC_DIRECT_REQUEST(xbus, xpd, pos, SLIC_WRITE, 0x33, 0x00);
		ret += SLIC_INDIRECT_REQUEST(xbus, xpd, pos, SLIC_WRITE, 0x1D, 0x00, 0x36);
	}
	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(xpd->digital_outputs | xpd->digital_inputs, pos))
		return;
	priv = xpd->priv;
	msgs = xpd->msg_waiting[pos];
	LINE_DBG(SIGNAL, xpd, pos, "%s\n", (msgs) ? "ON" : "OFF");
	set_vm_led_mode(xbus, xpd, pos, msgs);
	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		value;
	int		which = pos;
	int		relay_channels[] = { 0, 4 };

	BUG_ON(!xpd);
	/* map logical position to output port number (0/1) */
	which -= (xpd->subtype == 2) ? 6 : 8;
	LINE_DBG(SIGNAL, xpd, pos, "which=%d -- %s\n", which, (on) ? "on" : "off");
	which = which % ARRAY_SIZE(relay_channels);
	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];
	return SLIC_DIRECT_REQUEST(xpd->xbus, xpd, relay_channels[which],
			SLIC_WRITE, REG_DIGITAL_IOCTRL, value);
}

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(xbus_t *xbus, 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(xpd->direction != TO_PHONE);
	if (IS_SET(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);
			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(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(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(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(xbus, xpd, pos, txhook);
			break;
		case DAHDI_TXSIG_OFFHOOK:
			if(IS_SET(xpd->digital_outputs, pos)) {
				LINE_NOTICE(xpd, pos, "%s -> Is digital output. Ignored\n", txsig2str(txsig));
				return -EINVAL;
			}
			txhook = priv->lasttxhook[pos];
			if(xpd->ringing[pos]) {
				oht_pcm(xpd, pos, 1);
				txhook = FXS_LINE_OHTRANS;
			}
			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(xbus, xpd, pos, txhook);
			break;
		case DAHDI_TXSIG_START:
			xpd->ringing[pos] = 1;
			oht_pcm(xpd, pos, 0);
			vmwi_search(xpd, pos, 0);
			if(IS_SET(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(xpd->digital_outputs, pos)) {
				LINE_DBG(SIGNAL, xpd, pos, "%s -> Is digital output. Ignored\n", txsig2str(txsig));
				return -EINVAL;
			}
			linefeed_control(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",
				__FUNCTION__, 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, xpd->msg_waiting[pos]);
	return 0;
}

/*
 * 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 >= xpd->channels) {
		XPD_NOTICE(xpd, "Bad channel number %d in %s(), cmd=%u\n",
			pos, __FUNCTION__, 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(xpd->digital_inputs | 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) {
				priv->ohttimer[pos] = val;
				priv->idletxhookstate[pos] = FXS_LINE_POL_OHTRANS;
				vmwi_search(xpd, pos, 1);
				CALL_XMETHOD(card_pcm_recompute, xbus, xpd, priv->search_fsk_pattern);
				LINE_DBG(SIGNAL, xpd, pos, "Start OHT_TIMER. wanted_pcm_mask=0x%X\n", 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 */
					SLIC_DIRECT_REQUEST(xbus, xpd, pos, SLIC_WRITE, 0x17, 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");
					SLIC_DIRECT_REQUEST(xbus, xpd, pos, SLIC_WRITE, 0x17, 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");
					SLIC_DIRECT_REQUEST(xbus, xpd, pos, SLIC_WRITE, 0x17, 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;
			/* Can't change polarity while ringing or when open */
			if (priv->lasttxhook[pos] == FXS_LINE_RING || priv->lasttxhook[pos] == FXS_LINE_OPEN) {
				LINE_ERR(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 = 0;

				if(!notified++)
					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(xpd->digital_inputs | xpd->digital_outputs, pos))
				return 0;
			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_channels[] = { 6, 7, 2, 3 };	// Slic numbers of input relays

static void poll_inputs(xpd_t *xpd)
{
	int	i;

	BUG_ON(xpd->xbus_idx != 0);	// Only unit #0 has digital inputs
	for(i = 0; i < ARRAY_SIZE(input_channels); i++) {
		byte	pos = input_channels[i];

		SLIC_DIRECT_REQUEST(xpd->xbus, xpd, pos, SLIC_READ, 0x06, 0);
	}
}
#endif

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) {
			/* 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 byte	FSK_COMMON_PATTERN[] = { 0xA8, 0x49, 0x22, 0x3B, 0x9F, 0xFF, 0x1F, 0xBB };
	static const byte	FSK_ON_PATTERN[] = { 0xA2, 0x2C, 0x1F, 0x2C, 0xBB, 0xA1, 0xA5, 0xFF };
	static const byte	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 =
		xpd->offhook_state |
		~xpd->oht_pcm_pass |
		~priv->search_fsk_pattern |
		xpd->digital_inputs |
		xpd->digital_outputs;
	for_each_line(xpd, i) {
		struct dahdi_chan	*chan = XPD_CHAN(xpd, i);
		byte		*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, pos, "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");
				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");
				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 && xpd->xbus_idx == 0) {
		if((xpd->timer_count % poll_digital_inputs) == 0)
			poll_inputs(xpd);
	}
#endif
	handle_fxs_leds(xpd);
	handle_linefeed(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_rxsig(xpd, i, rxsig);	/* Notify after open() */
			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 -------------------------------------*/

static /* 0x0F */ HOSTCMD(FXS, XPD_STATE, bool on)
{
	BUG_ON(!xbus);
	BUG_ON(!xpd);
	XPD_DBG(GENERAL, xpd, "%s\n", (on)?"on":"off");
	return 0;
}

/*---------------- 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(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(xpd->digital_outputs, i) || IS_SET(xpd->digital_inputs, i))
			continue;
		if(IS_SET(change_mask, i)) {
			xpd->ringing[i] = 0;		/* No more ringing... */
#ifdef	WITH_METERING
			metering_gen(xpd, i, 0);	/* Stop metering... */
#endif
			MARK_BLINK(priv, i, LED_GREEN, 0);
			/*
			 * Reset our previous DTMF memories...
			 */
			BIT_CLR(priv->prev_key_down, i);
			priv->prev_key_time[i].tv_sec = priv->prev_key_time[i].tv_usec = 0L;
			if(IS_SET(offhook, i)) {
				LINE_DBG(SIGNAL, xpd, i, "OFFHOOK\n");
				MARK_ON(priv, i, LED_GREEN);
				hookstate_changed(xpd, i, 1);
			} else {
				LINE_DBG(SIGNAL, xpd, i, "ONHOOK\n");
				MARK_OFF(priv, i, LED_GREEN);
				hookstate_changed(xpd, i, 0);
			}
			/*
			 * Must switch to low power. In high power, an ONHOOK
			 * won't be detected.
			 */
			do_chan_power(xbus, xpd, i, 0);
		}
	}
}

HANDLER_DEF(FXS, SIG_CHANGED)
{
	xpp_line_t		sig_status = RPACKET_FIELD(pack, FXS, SIG_CHANGED, sig_status);
	xpp_line_t		sig_toggles = RPACKET_FIELD(pack, FXS, SIG_CHANGED, sig_toggles);
	unsigned long		flags;

	BUG_ON(!xpd);
	BUG_ON(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) {
		if(IS_SET(sig_toggles, i))
			do_chan_power(xpd->xbus, xpd, BIT(i), 0);		// Power down (prevent overheating!!!)
	}
#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 void process_digital_inputs(xpd_t *xpd, const reg_cmd_t *info)
{
	int		i;
	bool		offhook = (REG_FIELD(info, data_low) & 0x1) == 0;
	xpp_line_t	lines = BIT(info->portnum);

	/* Map SLIC number into line number */
	for(i = 0; i < ARRAY_SIZE(input_channels); i++) {
		int		channo = input_channels[i];
		int		newchanno;

		if(IS_SET(lines, channo)) {
			newchanno = xpd->channels - LINES_DIGI_INP + i;
			BIT_CLR(lines, channo);
			BIT_SET(lines, newchanno);
			xpd->ringing[newchanno] = 0;			// Stop ringing. No leds for digital inputs.
			if(offhook && !IS_OFFHOOK(xpd, newchanno)) {		// OFFHOOK
				LINE_DBG(SIGNAL, xpd, newchanno, "OFFHOOK\n");
				hookstate_changed(xpd, newchanno, 1);
			} else if(!offhook && IS_OFFHOOK(xpd, newchanno)) {	// ONHOOK
				LINE_DBG(SIGNAL, xpd, newchanno, "ONHOOK\n");
				hookstate_changed(xpd, newchanno, 0);
			}
		}
	}
}
#endif

static const char dtmf_digits[] = {
	'1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '*', '#', 'A', 'B', 'C', 'D'
};

/*
 * This function is called with spinlocked XPD
 */
static void process_dtmf(xpd_t *xpd, uint portnum, byte val)
{
	byte			digit;
	bool			key_down = val & 0x10;
	bool			want_mute;
	bool			want_event;
	struct FXS_priv_data	*priv;
	struct timeval		now;
	int			msec = 0;

	if(!dtmf_detection)
		return;
	if(!SPAN_REGISTERED(xpd))
		return;
	priv = xpd->priv;
	val &= 0xF;
	if(val <= 0) {
		if(key_down)
			XPD_NOTICE(xpd, "Bad DTMF value %d. Ignored\n", val);
		return;
	}
	val--;
	digit = dtmf_digits[val];
	want_mute = IS_SET(priv->want_dtmf_mute, portnum);
	want_event = IS_SET(priv->want_dtmf_events, portnum);
	if(!IS_SET(priv->prev_key_down, portnum) && !key_down) {
		LINE_NOTICE(xpd, portnum, "DTMF: duplicate UP (%c)\n", digit);
	}
	if(key_down)
		BIT_SET(priv->prev_key_down, portnum);
	else
		BIT_CLR(priv->prev_key_down, portnum);
	do_gettimeofday(&now);
	if(priv->prev_key_time[portnum].tv_sec != 0)
		msec = usec_diff(&now, &priv->prev_key_time[portnum]) / 1000;
	priv->prev_key_time[portnum] = now;
	LINE_DBG(SIGNAL, xpd, portnum,
			"[%lu.%06lu] DTMF digit %-4s '%c' (val=%d, want_mute=%s want_event=%s, delta=%d msec)\n",
			now.tv_sec, now.tv_usec,
			(key_down)?"DOWN":"UP", digit, val,
			(want_mute) ? "yes" : "no",
			(want_event) ? "yes" : "no",
			msec);
	/*
	 * FIXME: we currently don't use the want_dtmf_mute until
	 * we are sure about the logic in Asterisk native bridging.
	 * Meanwhile, simply mute it on button press.
	 */
	if(key_down && want_mute)
		__do_mute_dtmf(xpd, portnum, 1);
	else
		__do_mute_dtmf(xpd, portnum, 0);
	if(want_event)  {
		int	event = (key_down) ? DAHDI_EVENT_DTMFDOWN : DAHDI_EVENT_DTMFUP;

		dahdi_qevent_lock(XPD_CHAN(xpd, portnum), event | digit);
	}
}

static int FXS_card_register_reply(xbus_t *xbus, xpd_t *xpd, reg_cmd_t *info)
{
	unsigned long		flags;
	struct FXS_priv_data	*priv;
	byte			regnum;
	bool			indirect;

	spin_lock_irqsave(&xpd->lock, flags);
	priv = xpd->priv;
	BUG_ON(!priv);
	indirect = (REG_FIELD(info, regnum) == 0x1E);
	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(!indirect && regnum == REG_DTMF_DECODE) {
		byte		val = REG_FIELD(info, data_low);

		process_dtmf(xpd, info->portnum, val);
	}
#ifdef	POLL_DIGITAL_INPUTS
	/*
	 * Process digital inputs polling results
	 */
	else if(xpd->xbus_idx == 0 && !indirect && regnum == REG_DIGITAL_IOCTRL) {
		process_digital_inputs(xpd, info);
	}
#endif
	else if(!indirect && regnum == REG_LOOPCLOSURE) {	/* OFFHOOK ? */
		byte		val = REG_FIELD(info, data_low);
		xpp_line_t	mask = BIT(info->portnum);
		xpp_line_t	offhook;

		/*
		 * Validate reply. Non-existing/disabled ports
		 * will reply with 0xFF. Ignore these.
		 */
		if((val & REG_LOOPCLOSURE_ZERO) == 0) {
			offhook = (val & REG_LOOPCLOSURE_LCR) ? mask : 0;
			LINE_DBG(SIGNAL, xpd, info->portnum,
				"REG_LOOPCLOSURE: 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 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 = {
		.card_new	= FXS_card_new,
		.card_init	= FXS_card_init,
		.card_remove	= FXS_card_remove,
		.card_dahdi_preregistration	= FXS_card_dahdi_preregistration,
		.card_dahdi_postregistration	= FXS_card_dahdi_postregistration,
		.card_hooksig	= FXS_card_hooksig,
		.card_tick	= FXS_card_tick,
		.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,
		.card_open	= FXS_card_open,
		.card_close	= FXS_card_close,
		.card_ioctl	= FXS_card_ioctl,
		.card_register_reply	= FXS_card_register_reply,

		.XPD_STATE	= XPROTO_CALLER(FXS, XPD_STATE),
	},
	.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_read(char *page, char **start, off_t off, int count, int *eof, void *data)
{
	int			len = 0;
	unsigned long		flags;
	xpd_t			*xpd = data;
	struct FXS_priv_data	*priv;
	int			i;
	int			led;

	if(!xpd)
		return -ENODEV;
	spin_lock_irqsave(&xpd->lock, flags);
	priv = xpd->priv;
	BUG_ON(!priv);
	len += sprintf(page + len, "%-8s %-10s %-10s %-10s\n",
			"Channel",
			"idletxhookstate",
			"lasttxhook",
			"ohttimer"
			);
	for_each_line(xpd, i) {
		char	pref;

		if(IS_SET(xpd->digital_outputs, i))
			pref = 'O';
		else if(IS_SET(xpd->digital_inputs, i))
			pref = 'I';
		else
			pref = ' ';
		len += sprintf(page + len, "%c%7d %10d %10d %10d\n",
				pref,
				i,
				priv->idletxhookstate[i],
				priv->lasttxhook[i],
				priv->ohttimer[i]
			      );
	}
	len += sprintf(page + len, "\n");
	for(led = 0; led < NUM_LEDS; led++) {
		len += sprintf(page + len, "LED #%d", led);
		len += sprintf(page + len, "\n\t%-17s: ", "ledstate");
		for_each_line(xpd, i) {
			if(!IS_SET(xpd->digital_outputs, i) && !IS_SET(xpd->digital_inputs, i))
				len += sprintf(page + len, "%d ", IS_SET(priv->ledstate[led], i));
		}
		len += sprintf(page + len, "\n\t%-17s: ", "ledcontrol");
		for_each_line(xpd, i) {
			if(!IS_SET(xpd->digital_outputs, i) && !IS_SET(xpd->digital_inputs, i))
				len += sprintf(page + len, "%d ", IS_SET(priv->ledcontrol[led], i));
		}
		len += sprintf(page + len, "\n\t%-17s: ", "led_counter");
		for_each_line(xpd, i) {
			if(!IS_SET(xpd->digital_outputs, i) && !IS_SET(xpd->digital_inputs, i))
				len += sprintf(page + len, "%d ", LED_COUNTER(priv,i,led));
		}
		len += sprintf(page + len, "\n");
	}
	spin_unlock_irqrestore(&xpd->lock, flags);
	if (len <= off+count)
		*eof = 1;
	*start = page + off;
	len -= off;
	if (len > count)
		len = count;
	if (len < 0)
		len = 0;
	return len;
}
#endif

#ifdef	WITH_METERING
static int proc_xpd_metering_write(struct file *file, const char __user *buffer, unsigned long count, void *data)
{
	xpd_t		*xpd = 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 (%lu)\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;
}
#endif

static int fxs_xpd_probe(struct device *dev)
{
	xpd_t	*xpd;

	xpd = dev_to_xpd(dev);
	/* Is it our device? */
	if(xpd->type != XPD_TYPE_FXS) {
		XPD_ERR(xpd, "drop suggestion for %s (%d)\n",
			dev_name(dev), xpd->type);
		return -EINVAL;
	}
	XPD_DBG(DEVICES, xpd, "SYSFS\n");
	return 0;
}

static int fxs_xpd_remove(struct device *dev)
{
	xpd_t	*xpd;

	xpd = dev_to_xpd(dev);
	XPD_DBG(DEVICES, xpd, "SYSFS\n");
	return 0;
}

static struct xpd_driver	fxs_driver = {
	.type		= XPD_TYPE_FXS,
	.driver		= {
		.name = "fxs",
#ifndef OLD_HOTPLUG_SUPPORT
		.owner = THIS_MODULE,
#endif
		.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;

	INFO("revision %s\n", XPP_VERSION);
#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_VERSION(XPP_VERSION);
MODULE_ALIAS_XPD(XPD_TYPE_FXS);

module_init(card_fxs_startup);
module_exit(card_fxs_cleanup);
