/*
 * 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 <version.h>		/* For zaptel version */
#include "xpd.h"
#include "xproto.h"
#include "xpp_zap.h"
#include "card_fxo.h"
#include "zap_debug.h"

static const char rcsid[] = "$Id: card_fxo.c 1557 2006-11-06 18:12:16Z tzafrir $";

DEF_PARM(int, print_dbg, 0, "Print DBG statements");
DEF_PARM(uint, poll_battery_interval, 100, "Poll battery interval in milliseconds (0 - disable)");
DEF_PARM(bool, report_battery, 0, "Report battery status to zaptel");

/* Signaling is opposite (fxs signalling for fxo card) */
#if 1
#define	FXO_DEFAULT_SIGCAP	(ZT_SIG_FXSKS | ZT_SIG_FXSLS)
#else
#define	FXO_DEFAULT_SIGCAP	(ZT_SIG_SF)
#endif

enum fxo_leds {
	LED_GREEN,
};

#define	NUM_LEDS		1
#define	DELAY_UNTIL_DIALTONE	3000

#define	BAT_THRESHOLD		3
#define	BAT_DEBOUNCE		3	/* compensate for battery voltage fluctuation (in poll_battery_interval's) */

static /* 0x0F */ DECLARE_CMD(FXO, REGISTER_REQUEST, byte chipsel, bool writing, bool do_subreg, byte regnum, byte subreg, byte data_low, byte data_high);
/* Shortcuts */
#define	DAA_WRITE	1
#define	DAA_READ	0
#define	DAA_DIRECT_REQUEST(xbus,xpd,chipsel,writing,reg,dL)	\
	CALL_PROTO(FXO, REGISTER_REQUEST, (xbus), (xpd), (chipsel), (writing), 0, (reg), 0, (dL), 0)

#define	VALID_CHIPSEL(x)	(((chipsel) >= 0 && (chipsel) <= 7) || (chipsel) == ALL_CHANS)

/*---------------- FXO Protocol Commands ----------------------------------*/

static /* 0x0F */ DECLARE_CMD(FXO, XPD_STATE, bool on);
static /* 0x0F */ DECLARE_CMD(FXO, CHAN_CID, lineno_t chan);
static /* 0x0F */ DECLARE_CMD(FXO, RING, lineno_t chan, bool on);
static /* 0x0F */ DECLARE_CMD(FXO, RELAY_OUT, byte which, bool on);

static bool fxo_packet_is_valid(xpacket_t *pack);
static void fxo_packet_dump(const char *msg, xpacket_t *pack);
static int proc_fxo_info_read(char *page, char **start, off_t off, int count, int *eof, void *data);
static int proc_xpd_register_read(char *page, char **start, off_t off, int count, int *eof, void *data);
static int proc_xpd_register_write(struct file *file, const char __user *buffer, unsigned long count, void *data);
static int handle_register_command(xpd_t *xpd, char *cmdline);

#define	PROC_REGISTER_FNAME	"slics"
#define	PROC_FXO_INFO_FNAME	"fxo_info"

#ifdef SOFT_RING
#define	POLL_RING_INTERVAL	2
#define	RING_THRESHOLD		3
#define	NORING_THRESHOLD	10
#endif
#define	DAA_RING_REGISTER	0x05

struct FXO_priv_data {
	struct proc_dir_entry		*regfile;
	struct proc_dir_entry		*fxo_info;
	uint				poll_counter;
	xpp_line_t			battery;
	ushort				battery_debounce[CHANNELS_PERXPD];
	xpp_line_t			ledstate[NUM_LEDS];	/* 0 - OFF, 1 - ON */
	xpp_line_t			ledcontrol[NUM_LEDS];	/* 0 - OFF, 1 - ON */
	int				blinking[NUM_LEDS][CHANNELS_PERXPD];
#ifdef SOFT_RING
	ushort				ring_thresh[CHANNELS_PERXPD];
	ushort				noring_thresh[CHANNELS_PERXPD];
	/* ring_sig is set when Reg5, bit 2 (Ring Detect) is set. 
	 * While ring_sig=1 we check R5 bit 20H and 40H for ringing. 
	 * When it drops to 0 that's the end of the ring sequence and 
	 * we clear the ring detection variables */
	ushort				ring_sig[CHANNELS_PERXPD];
#endif
};

/*---------------- FXO: Static functions ----------------------------------*/

#define	IS_BLINKING(priv,pos,color)	((priv)->blinking[color][pos] != 0)
#define	MARK_BLINK(priv,pos,color,val)	((priv)->blinking[color][pos] = (val))

void MARK_LED(xpd_t *xpd, lineno_t pos, byte color, bool on)
{
	struct FXO_priv_data *priv = xpd->priv;

	if(on)
		BIT_SET(priv->ledcontrol[color], pos);
	else
		BIT_CLR(priv->ledcontrol[color], pos);
}

/*
 * LED control is done via DAA register 0x20
 */
static int do_led(xpd_t *xpd, lineno_t chan, byte which, bool on)
{
	int			ret = 0;
	struct FXO_priv_data	*priv;
	xbus_t			*xbus;

	BUG_ON(!xpd);
	xbus = xpd->xbus;
	priv = xpd->priv;
	which = which % NUM_LEDS;
	if(IS_SET(xpd->digital_outputs, chan) || IS_SET(xpd->digital_inputs, chan))
		goto out;
	if(chan == ALL_CHANS) {
		priv->ledstate[which] = (on) ? ~0 : 0;
	} else {
		if(on) {
			BIT_SET(priv->ledstate[which], chan);
		} else {
			BIT_CLR(priv->ledstate[which], chan);
		}
	}
	DBG("%s/%s/%d: LED: which=%d -- %s\n", xbus->busname, xpd->xpdname, chan, which, (on) ? "on" : "off");
	ret = DAA_DIRECT_REQUEST(xbus, xpd, chan, DAA_WRITE, 0x20, on);
out:
	return ret;
}

static void handle_fxo_leds(xpd_t *xpd)
{
	int			i;
	unsigned long		flags;
	const enum fxo_leds	color = LED_GREEN;
	unsigned int		timer_count;
	struct FXO_priv_data	*priv;

	BUG_ON(!xpd);
	spin_lock_irqsave(&xpd->lock, flags);
	priv = xpd->priv;
	timer_count = xpd->timer_count;
	for_each_line(xpd, i) {
		if(IS_SET(xpd->digital_outputs, i) || IS_SET(xpd->digital_inputs, i))
			continue;
		if(IS_BLINKING(priv,i,color)) {
			// led state is toggled
			if((timer_count % LED_BLINK_PERIOD) == 0) {
				DBG("%s/%s/%d: ledstate=%s\n", xpd->xbus->busname, xpd->xpdname, i,
						(IS_SET(priv->ledstate[color], i))?"ON":"OFF");
				if(!IS_SET(priv->ledstate[color], i)) {
					do_led(xpd, i, color, 1);
				} else {
					do_led(xpd, i, color, 0);
				}
			}
		} else if(IS_SET(priv->ledcontrol[color], i) && !IS_SET(priv->ledstate[color], i)) {
			do_led(xpd, i, color, 1);
		} else if(!IS_SET(priv->ledcontrol[color], i) && IS_SET(priv->ledstate[color], i)) {
			do_led(xpd, i, color, 0);
		}
	}
	spin_unlock_irqrestore(&xpd->lock, flags);
}

static void mark_ring(xpd_t *xpd, lineno_t pos, bool on)
{
	struct FXO_priv_data	*priv;

	priv = xpd->priv;
	BUG_ON(!priv);
	if(on && !xpd->ringing[pos]) {
		DBG("%s/%s/%d: START\n", xpd->xbus->busname, xpd->xpdname, pos);
		xpd->ringing[pos] = 1;
		MARK_BLINK(priv, pos, LED_GREEN, LED_BLINK);
		update_zap_ring(xpd, pos, 1);
	} else if(!on && xpd->ringing[pos]) {
		DBG("%s/%s/%d: STOP\n", xpd->xbus->busname, xpd->xpdname, pos);
		xpd->ringing[pos] = 0;
		if(IS_BLINKING(priv, pos, LED_GREEN))
			MARK_BLINK(priv, pos, LED_GREEN, 0);
		update_zap_ring(xpd, pos, 0);
	}
}

static int do_sethook(xpd_t *xpd, int pos, bool to_offhook)
{
	unsigned long		flags;
	xbus_t			*xbus;
	struct FXO_priv_data	*priv;
	int			ret = 0;
	bool			value;

	BUG_ON(!xpd);
	BUG_ON(xpd->direction == TO_PHONE);		// We can SETHOOK state only on PSTN
	xbus = xpd->xbus;
	priv = xpd->priv;
	BUG_ON(!priv);
	if(!IS_SET(priv->battery, pos)) {
		NOTICE("%s/%s/%d: WARNING: called while battery is off\n", xbus->busname, xpd->xpdname, pos);
	}
	spin_lock_irqsave(&xpd->lock, flags);
	mark_ring(xpd, pos, 0);				// No more rings
	value = (to_offhook) ? 0x09 : 0x08;	/* Bit 3 is for CID */
	DBG("%s/%s/%d: SETHOOK: value=0x%02X %s\n", xbus->busname, xpd->xpdname, pos, value, (to_offhook)?"OFFHOOK":"ONHOOK");
	MARK_LED(xpd, pos, LED_GREEN, (to_offhook)?LED_ON:LED_OFF);
	ret = DAA_DIRECT_REQUEST(xbus, xpd, pos, DAA_WRITE, DAA_RING_REGISTER, value);
#ifdef	SOFT_RING
	priv->ring_sig[pos] = 0;
#endif
	if(to_offhook) {
		BIT_SET(xpd->offhook, pos);
	} else {
		BIT_CLR(xpd->offhook, pos);
		BIT_CLR(xpd->cid_on, pos);
		xpd->delay_until_dialtone[pos] = 0;
	}
	spin_unlock_irqrestore(&xpd->lock, flags);
	if(to_offhook)
		wake_up_interruptible(&xpd->txstateq[pos]);
	return ret;
}

/*---------------- FXO: Methods -------------------------------------------*/

static xpd_t *FXO_card_new(xbus_t *xbus, int xpd_num, const xproto_table_t *proto_table, byte revision)
{
	xpd_t		*xpd = NULL;
	int		channels = min(8, CHANNELS_PERXPD);

	xpd = xpd_alloc(sizeof(struct FXO_priv_data), xbus, xpd_num, proto_table, channels, revision);
	if(!xpd)
		return NULL;
	xpd->direction = TO_PSTN;
	xpd->revision = revision;
	return xpd;
}

static void clean_proc(xbus_t *xbus, xpd_t *xpd)
{
	struct FXO_priv_data	*priv;

	BUG_ON(!xpd);
	priv = xpd->priv;
	DBG("%s/%s\n", xbus->busname, xpd->xpdname);
#ifdef	CONFIG_PROC_FS
	if(priv->regfile) {
		DBG("Removing xpd DAA file %s/%s\n", xbus->busname, xpd->xpdname);
		remove_proc_entry(PROC_REGISTER_FNAME, xpd->proc_xpd_dir);
		priv->regfile->data = NULL;
	}
	if(priv->fxo_info) {
		DBG("Removing xpd FXO_INFO file %s/%s\n", xbus->busname, xpd->xpdname);
		remove_proc_entry(PROC_FXO_INFO_FNAME, xpd->proc_xpd_dir);
		priv->fxo_info = NULL;
	}
#endif
}

static int FXO_card_init(xbus_t *xbus, xpd_t *xpd)
{
	struct FXO_priv_data	*priv;
	int			ret = 0;
	int			i;

	BUG_ON(!xpd);
	priv = xpd->priv;
#ifdef	CONFIG_PROC_FS
	DBG("Creating FXO_INFO file for %s/%s\n", xbus->busname, xpd->xpdname);
	priv->fxo_info = create_proc_read_entry(PROC_FXO_INFO_FNAME, 0444, xpd->proc_xpd_dir, proc_fxo_info_read, xpd);
	if(!priv->fxo_info) {
		ERR("Failed to create proc '%s' for %s/%s\n", PROC_FXO_INFO_FNAME, xbus->busname, xpd->xpdname);
		ret = -ENOENT;
		goto err;
	}
	priv->fxo_info->owner = THIS_MODULE;
	DBG("Creating DAAs file for %s/%s\n", xbus->busname, xpd->xpdname);
	priv->regfile = create_proc_entry(PROC_REGISTER_FNAME, 0644, xpd->proc_xpd_dir);
	if(!priv->regfile) {
		ERR("Failed to create proc file for DAAs of %s/%s\n", xbus->busname, xpd->xpdname);
		ret = -ENOENT;
		goto err;
	}
	priv->regfile->owner = THIS_MODULE;
	priv->regfile->write_proc = proc_xpd_register_write;
	priv->regfile->read_proc = proc_xpd_register_read;
	priv->regfile->data = xpd;
#endif
	ret = run_initialize_registers(xpd);
	if(ret < 0)
		goto err;
	// Hanghup all lines
	for_each_line(xpd, i) {
		init_waitqueue_head(&xpd->txstateq[i]);
		do_sethook(xpd, i, 0);
	}
	DBG("done: %s/%s\n", xbus->busname, xpd->xpdname);
	return 0;
err:
	clean_proc(xbus, xpd);
	ERR("%s/%s: Failed initializing registers (%d)\n", xbus->busname, xpd->xpdname, ret);
	return ret;
}

static int FXO_card_remove(xbus_t *xbus, xpd_t *xpd)
{
	struct FXO_priv_data	*priv;

	BUG_ON(!xpd);
	priv = xpd->priv;
	DBG("%s/%s\n", xbus->busname, xpd->xpdname);
	clean_proc(xbus, xpd);
	return 0;
}

static int FXO_card_zaptel_preregistration(xpd_t *xpd, bool on)
{
	xbus_t			*xbus;
	struct FXO_priv_data	*priv;
	int			i;

	BUG_ON(!xpd);
	xbus = xpd->xbus;
	BUG_ON(!xbus);
	priv = xpd->priv;
	BUG_ON(!priv);
	DBG("%s/%s (%d)\n", xbus->busname, xpd->xpdname, on);
	snprintf(xpd->span.desc, MAX_SPANDESC, "Xorcom XPD #%d/%d: FXO", xbus->num, xpd->id);
	for_each_line(xpd, i) {
		struct zt_chan	*cur_chan = &xpd->chans[i];

		DBG("setting FXO channel %d\n", i);
		snprintf(cur_chan->name, MAX_CHANNAME, "XPP_FXO/%d/%d/%d", xbus->num, xpd->id, i);
		cur_chan->chanpos = i + 1;
		cur_chan->pvt = xpd;
		cur_chan->sigcap = FXO_DEFAULT_SIGCAP;
	}
	MARK_LED(xpd, ALL_LINES, LED_GREEN, LED_OFF);
	for_each_line(xpd, i) {
		MARK_LED(xpd, i, LED_GREEN, LED_ON);
		mdelay(50);
	}
	return 0;
}

static int FXO_card_zaptel_postregistration(xpd_t *xpd, bool on)
{
	xbus_t			*xbus;
	struct FXO_priv_data	*priv;
	int			i;

	BUG_ON(!xpd);
	xbus = xpd->xbus;
	BUG_ON(!xbus);
	priv = xpd->priv;
	BUG_ON(!priv);
	DBG("%s/%s (%d)\n", xbus->busname, xpd->xpdname, on);
	for_each_line(xpd, i) {
		MARK_LED(xpd, i, LED_GREEN, LED_OFF);
		mdelay(50);
	}
	return 0;
}

int FXO_card_hooksig(xbus_t *xbus, xpd_t *xpd, int pos, zt_txsig_t txsig)
{
	struct FXO_priv_data	*priv;

	priv = xpd->priv;
	BUG_ON(!priv);
	DBG("%s/%s/%d: %s\n", xbus->busname, xpd->xpdname, pos, txsig2str(txsig));
	BUG_ON(xpd->direction != TO_PSTN);
	/* XXX Enable hooksig for FXO XXX */
	switch(txsig) {
		case ZT_TXSIG_START:
			break;
		case ZT_TXSIG_OFFHOOK:
			do_sethook(xpd, pos, 1);
			break;
		case ZT_TXSIG_ONHOOK:
			do_sethook(xpd, pos, 0);
			break;
		default:
			NOTICE("Can't set tx state to %s (%d)\n", txsig2str(txsig), txsig);
			return -EINVAL;
	}
	return 0;
}

static void poll_battery(xbus_t *xbus, xpd_t *xpd)
{
	int	i;

	for_each_line(xpd, i) {
		DAA_DIRECT_REQUEST(xbus, xpd, i, DAA_READ, DAA_VBAT_REGISTER, 0);
	}
}

#ifdef	SOFT_RING
static void poll_ring(xbus_t *xbus, xpd_t *xpd)
{
	int			i;
	struct FXO_priv_data	*priv;

	priv = xpd->priv;
	BUG_ON(!priv);
	for_each_line(xpd, i) {
		if(priv->ring_sig[i])
			DAA_DIRECT_REQUEST(xbus, xpd, i, DAA_READ, DAA_RING_REGISTER, 0);
	}
}
#endif

static int FXO_card_tick(xbus_t *xbus, xpd_t *xpd)
{
	struct FXO_priv_data	*priv;

	BUG_ON(!xpd);
	priv = xpd->priv;
	BUG_ON(!priv);
	if(poll_battery_interval != 0 && (priv->poll_counter % poll_battery_interval) == 0) {
		poll_battery(xbus, xpd);
	}
#ifdef	SOFT_RING
	if((priv->poll_counter % POLL_RING_INTERVAL) == 0)
		poll_ring(xbus, xpd);
#endif
	handle_fxo_leds(xpd);
	priv->poll_counter++;
	return 0;
}

/* FIXME: based on data from from wctdm.h */
#include <wctdm.h>
static const int echotune_reg[] = {30,45,46,47,58,49,50,51,52};
union echotune {
	/* "coeff 0" is acim */
	unsigned char		coeff[sizeof(echotune_reg)];
	struct wctdm_echo_coefs	wctdm_struct;
};

static int FXO_card_ioctl(xpd_t *xpd, int pos, unsigned int cmd, unsigned long arg)
{
	union echotune	echoregs;
	int 		i,ret;

	BUG_ON(!xpd);
	DBG("cmd: 0x%X, expecting: 0x%X, pos=%d.\n", cmd, WCTDM_SET_ECHOTUNE, pos);
	switch (cmd) {
		case WCTDM_SET_ECHOTUNE:
			DBG("-- Setting echo registers: \n");
			/* first off: check if this span is fxs. If not: -EINVALID */
			if (copy_from_user(&echoregs.wctdm_struct, 
				(struct wctdm_echo_coefs __user *)arg, sizeof(echoregs.wctdm_struct)))
				return -EFAULT;

			/* Set the ACIM register */
			/* quick and dirty registers writing: */
			for (i=0; i<sizeof(echotune_reg); i++) {
				char buf[22];
				sprintf(buf, "%d WD %2X %2X",
					pos,echotune_reg[i],echoregs.coeff[i]
				);
				/* FIXME: code duplicated from proc_xpd_register_write */
				ret = handle_register_command(xpd, buf);
				if(ret < 0)
					return ret;
				mdelay(1);
			}

			DBG("-- Set echo registers successfully\n");

			break;
		default:
			return -ENOTTY;
	}
	return 0;
}

/*---------------- FXO: HOST COMMANDS -------------------------------------*/

/* 0x0F */ HOSTCMD(FXO, REGISTER_REQUEST, byte chipsel, bool writing, bool do_subreg, byte regnum, byte subreg, byte data_low, byte data_high)
{
	int		ret = 0;
	xpacket_t	*pack;
	reg_cmd_t	*reg_cmd;

	if(!xbus) {
		DBG("NO XBUS\n");
		return -EINVAL;
	}
	XPACKET_NEW(pack, xbus, GLOBAL, REGISTER_REQUEST, xpd->id);
#if 0
	DBG("%s/%s/%d: %c%c R%02X S%02X %02X %02X\n",
			xbus->busname, xpd->xpdname, chipsel,
			(writing)?'W':'R',
			(do_subreg)?'S':'D',
			regnum, subreg, data_low, data_high);
#endif
	reg_cmd = &RPACKET_FIELD(pack, GLOBAL, REGISTER_REQUEST, reg_cmd);
	pack->datalen = sizeof(*reg_cmd);
	reg_cmd->bytes = sizeof(*reg_cmd) - 1;	// do not count the 'bytes' field
	REG_FIELD(reg_cmd, chipsel) = chipsel;
	REG_FIELD(reg_cmd, read_request) = (writing) ? 0 : 1;
	REG_FIELD(reg_cmd, do_subreg) = do_subreg;
	REG_FIELD(reg_cmd, regnum) = regnum;
	REG_FIELD(reg_cmd, subreg) = subreg;
	REG_FIELD(reg_cmd, data_low) = data_low;
	REG_FIELD(reg_cmd, data_high) = data_high;
	ret = packet_send(xbus, pack);
	return ret;
}

static /* 0x0F */ HOSTCMD(FXO, XPD_STATE, bool on)
{
	int	ret = 0;
	int	i;

	BUG_ON(!xbus);
	BUG_ON(!xpd);
	DBG("%s/%s: %s\n", xbus->busname, xpd->xpdname, (on) ? "on" : "off");
	if(on) {
		for_each_line(xpd, i) {
			MARK_LED(xpd, i, LED_GREEN, LED_ON);
			mdelay(20);
		}
		for_each_line(xpd, i) {
			MARK_LED(xpd, i, LED_GREEN, LED_OFF);
			mdelay(20);
		}
	}
	return ret;
}

static /* 0x0F */ HOSTCMD(FXO, CHAN_CID, lineno_t chan)
{
	int		ret = 0;

	BUG_ON(!xbus);
	BUG_ON(!xpd);
	DBG("%s/%s/%d:\n", xbus->busname, xpd->xpdname, chan);
	return ret;
}


static /* 0x0F */ HOSTCMD(FXO, RING, lineno_t chan, bool on)
{
	BUG_ON(!xbus);
	BUG_ON(!xpd);
	DBG("%s/%s/%d: %s\n", xbus->busname, xpd->xpdname, chan, (on) ? "on" : "off");
	return DAA_DIRECT_REQUEST(xbus, xpd, chan, DAA_WRITE, 0x40, (on)?0x04:0x01);
}

static /* 0x0F */ HOSTCMD(FXO, RELAY_OUT, byte which, bool on)
{
	return -ENOSYS;
}

/*---------------- FXO: Astribank Reply Handlers --------------------------*/

HANDLER_DEF(FXO, SIG_CHANGED)
{
	xpp_line_t	sig_status = RPACKET_FIELD(pack, FXO, SIG_CHANGED, sig_status);
	xpp_line_t	sig_toggles = RPACKET_FIELD(pack, FXO, SIG_CHANGED, sig_toggles);
	unsigned long	flags;
	int		i;
	struct FXO_priv_data	*priv;

	if(!xpd) {
		NOTICE("%s: received %s for non-existing xpd: %d\n",
				__FUNCTION__, cmd->name, XPD_NUM(pack->content.addr));
		return -EPROTO;
	}
	priv = xpd->priv;
	BUG_ON(!priv);
	DBG("%s/%s: (PSTN) sig_toggles=0x%04X sig_status=0x%04X\n", xpd->xbus->busname, xpd->xpdname, sig_toggles, sig_status);
	spin_lock_irqsave(&xpd->lock, flags);
	for_each_line(xpd, i) {
		if(IS_SET(sig_toggles, i)) {
			if(!IS_SET(priv->battery, i)) {
				DBG("%s/%s/%d: battery is off. ignore false alarm.\n",
					xbus->busname, xpd->xpdname, i);
				continue;
			}
			if(IS_SET(sig_status, i)) {
#ifdef	SOFT_RING
				priv->ring_sig[i]=1;		/* trigger register polling */
				/* reset ring check counters */
				priv->ring_thresh[i] = 0;
				priv->noring_thresh[i] = 0;
#else
				mark_ring(xpd, i, 1);
#endif
			} else {
#ifdef	SOFT_RING
				priv->ring_sig[i] = 0;
#endif
				mark_ring(xpd, i, 0);
			}
		}
	}
	spin_unlock_irqrestore(&xpd->lock, flags);
	return 0;
}

HANDLER_DEF(FXO, DAA_REPLY)
{
	reg_cmd_t		*info = &RPACKET_FIELD(pack, FXO, DAA_REPLY, regcmd);
	unsigned long		flags;
	struct FXO_priv_data	*priv;
	lineno_t		chipsel;

	if(!xpd) {
		NOTICE("%s: received %s for non-existing xpd: %d\n",
				__FUNCTION__, cmd->name, XPD_NUM(pack->content.addr));
		return -EPROTO;
	}
	spin_lock_irqsave(&xpd->lock, flags);
	priv = xpd->priv;
	BUG_ON(!priv);
	chipsel = REG_FIELD(info, chipsel);

	/*
	 * Update battery status
	 */
	if(REG_FIELD(info, regnum) == DAA_VBAT_REGISTER) {
		byte		bat = abs((signed char)REG_FIELD(info, data_low));

		if(bat < BAT_THRESHOLD) {
			/*
			 * Check for battery voltage fluctuations
			 */
			if(IS_SET(priv->battery, chipsel) && priv->battery_debounce[chipsel]++ > BAT_DEBOUNCE) {
				DBG("%s/%s/%d: BATTERY OFF voltage=%d\n", xpd->xbus->busname, xpd->xpdname, chipsel, bat);
				BIT_CLR(priv->battery, chipsel);
				update_line_status(xpd, chipsel, 0);
			}
		} else {
			priv->battery_debounce[chipsel] = 0;
			if(!IS_SET(priv->battery, chipsel)) {
				DBG("%s/%s/%d: BATTERY ON voltage=%d\n", xpd->xbus->busname, xpd->xpdname, chipsel, bat);
				BIT_SET(priv->battery, chipsel);
			}
		}
	}
#ifdef	SOFT_RING
	if(REG_FIELD(info, regnum) == DAA_RING_REGISTER && priv->ring_sig[chipsel]) {
		bool		ringit = (REG_FIELD(info, data_low) & (0x20 | 0x40)) ? 1 : 0;	/* Ring positive | Ring negative */

		if(ringit) {
			if(priv->ring_thresh[chipsel] > RING_THRESHOLD) {
				mark_ring(xpd, chipsel, 1);
				priv->noring_thresh[chipsel] = 0;
			} else
				priv->ring_thresh[chipsel]++;
		} else {
			if(priv->noring_thresh[chipsel] > NORING_THRESHOLD) {
				mark_ring(xpd, chipsel, 0);
				priv->ring_thresh[chipsel] = 0;
			} else
				priv->noring_thresh[chipsel]++;
		}
	}
#endif
#if 0
	DBG("DAA_REPLY: xpd #%d %s reg_num=0x%X, dataL=0x%X dataH=0x%X\n",
			xpd->id, (info->size == 3)?"I":"D",
			info->reg_num, info->data_low, 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;
}


xproto_table_t PROTO_TABLE(FXO) = {
	.owner = THIS_MODULE,
	.entries = {
		/*	Prototable	Card	Opcode		*/
		XENTRY(	FXO,		FXO,	SIG_CHANGED	),
		XENTRY(	FXO,		FXO,	DAA_REPLY	),
	},
	.name = "FXO",
	.type = XPD_TYPE_FXO,
	.xops = {
		.card_new	= FXO_card_new,
		.card_init	= FXO_card_init,
		.card_remove	= FXO_card_remove,
		.card_zaptel_preregistration	= FXO_card_zaptel_preregistration,
		.card_zaptel_postregistration	= FXO_card_zaptel_postregistration,
		.card_hooksig	= FXO_card_hooksig,
		.card_tick	= FXO_card_tick,
		.card_ioctl	= FXO_card_ioctl,

		.RING		= XPROTO_CALLER(FXO, RING),
		.RELAY_OUT	= XPROTO_CALLER(FXO, RELAY_OUT),
		.XPD_STATE	= XPROTO_CALLER(FXO, XPD_STATE),
		.CHAN_CID	= XPROTO_CALLER(FXO, CHAN_CID),

		.SYNC_SOURCE	= XPROTO_CALLER(GLOBAL, SYNC_SOURCE),
		.PCM_WRITE	= XPROTO_CALLER(GLOBAL, PCM_WRITE),
	},
	.packet_is_valid = fxo_packet_is_valid,
	.packet_dump = fxo_packet_dump,
};

static bool fxo_packet_is_valid(xpacket_t *pack)
{
	const xproto_entry_t	*xe;

	//DBG("\n");
	xe = xproto_card_entry(&PROTO_TABLE(FXO), pack->content.opcode);
	return xe != NULL;
}

static void fxo_packet_dump(const char *msg, xpacket_t *pack)
{
	DBG("%s\n", msg);
}

/*------------------------- DAA Handling --------------------------*/

static int proc_fxo_info_read(char *page, char **start, off_t off, int count, int *eof, void *data)
{
	int			len = 0;
	unsigned long		flags;
	xpd_t			*xpd = data;
	struct FXO_priv_data	*priv;
	int			i;

	if(!xpd)
		return -ENODEV;
	spin_lock_irqsave(&xpd->lock, flags);
	priv = xpd->priv;
	BUG_ON(!priv);
	len += sprintf(page + len, "\t%-17s: ", "Channel");
	for_each_line(xpd, i) {
		if(!IS_SET(xpd->digital_outputs, i) && !IS_SET(xpd->digital_inputs, i))
			len += sprintf(page + len, "%2d ", i % 10);
	}
	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, "%2d ", IS_SET(priv->ledstate[LED_GREEN], i));
	}
	len += sprintf(page + len, "\n\t%-17s: ", "blinking");
	for_each_line(xpd, i) {
		if(!IS_SET(xpd->digital_outputs, i) && !IS_SET(xpd->digital_inputs, i))
			len += sprintf(page + len, "%2d ", IS_BLINKING(priv,i,LED_GREEN));
	}
#ifdef	SOFT_RING
	len += sprintf(page + len, "\n\t%-17s: ", "ring_thresh");
	for_each_line(xpd, i) {
		if(!IS_SET(xpd->digital_outputs, i) && !IS_SET(xpd->digital_inputs, i))
			len += sprintf(page + len, "%2d ", priv->ring_thresh[i]);
	}
	len += sprintf(page + len, "\n\t%-17s: ", "noring_thresh");
	for_each_line(xpd, i) {
		if(!IS_SET(xpd->digital_outputs, i) && !IS_SET(xpd->digital_inputs, i))
			len += sprintf(page + len, "%2d ", priv->noring_thresh[i]);
	}
	len += sprintf(page + len, "\n\t%-17s: ", "ring_sig");
	for_each_line(xpd, i) {
		if(!IS_SET(xpd->digital_outputs, i) && !IS_SET(xpd->digital_inputs, i))
			len += sprintf(page + len, "%2d ", priv->ring_sig[i]);
	}
#endif
	len += sprintf(page + len, "\n\t%-17s: ", "battery");
	for_each_line(xpd, i) {
		len += sprintf(page + len, "%2d ", IS_SET(priv->battery, i));
	}
	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;
}

/*
 *
 * Direct/Indirect
 *     |
 *     | Reg#
 *     | |
 *     | |  Data (only in Write)
 *     | |    |
 *     | |  +-+-+
 *     v v  v   v
 * FF WD 06 01 05
 * ^  ^
 * |  |
 * |  Write/Read
 * |
 * Chan#
 *
 */
static int handle_register_command(xpd_t *xpd, char *cmdline)
{
	unsigned		chipsel;
	unsigned		data_low = 0;
	char			op;		/* [W]rite, [R]ead */
	char			reg_type;	/* [D]irect */
	int			reg_num;
	int			elements;
	bool			writing;
	char			*p;
	reg_cmd_t		regcmd;
	xbus_t			*xbus;
	int			ret;

	if((p = strchr(cmdline, '#')) != NULL)	/* Truncate comments */
		*p = '\0';
	if((p = strchr(cmdline, ';')) != NULL)	/* Truncate comments */
		*p = '\0';
	for(p = cmdline; *p && (*p == ' ' || *p == '\t'); p++) /* Trim leading whitespace */
		;
	if(*p == '\0')
		return 0;

	elements = sscanf(cmdline, "%d %c%c %x %x",
			&chipsel,
			&op, &reg_type, &reg_num,
			&data_low);
	// DBG("'%s': %d %c%c %02X %02X %02X\n", cmdline, chipsel, op, reg_type, reg_num, data_low);
	if(elements < 4) {	// At least: chipsel, op, reg_type, reg_num
		ERR("Not enough arguments: (%d args) '%s'\n", elements, cmdline);
		return -EINVAL;
	}
	if(!VALID_CHIPSEL(chipsel)) {
		ERR("Bad chipsel number: %d\n", chipsel);
		return -EINVAL;
	}
	REG_FIELD(&regcmd, chipsel) = chipsel;
	REG_FIELD(&regcmd, do_subreg) = 0;
	switch(op) {
		case 'W':
			writing = 1;
			break;
		case 'R':
			writing = 0;
			break;
		default:
			ERR("Unkown operation type '%c'\n", op);
			return -EINVAL;
	}
	switch(reg_type) {
		case 'D':
			REG_FIELD(&regcmd, regnum) = reg_num;
			REG_FIELD(&regcmd, subreg) = 0;
			break;
		default:
			ERR("Unkown register type '%c'\n", reg_type);
			return -EINVAL;
	}
	if(
			(op == 'W' && reg_type == 'D' && elements != 5) ||
			(op == 'R' && reg_type == 'D' && elements != 4)
	  ) {
		ERR("%s: '%s' (%d elements): %d %c%c %02X %02X\n", __FUNCTION__,
				cmdline, elements,
				chipsel, op, reg_type, reg_num, data_low);
		return -EINVAL;
	}
	regcmd.bytes = sizeof(regcmd) - 1;
	REG_FIELD(&regcmd, data_low) = data_low;
	REG_FIELD(&regcmd, data_high) = 0;
	REG_FIELD(&regcmd, read_request) = writing;
	BUG_ON(!xpd);
	xbus = xpd->xbus;
	if(!down_read_trylock(&xbus->in_use)) {
		DBG("Dropped packet. %s is in_use\n", xbus->busname);
		return -EBUSY;
	}
	xpd->requested_reply = regcmd;
	if(print_dbg)
		dump_reg_cmd("FXO", &regcmd);
	ret = DAA_DIRECT_REQUEST(xpd->xbus, xpd, REG_FIELD(&regcmd, chipsel), writing, REG_FIELD(&regcmd, regnum), REG_FIELD(&regcmd, data_low));
	up_read(&xbus->in_use);
	return ret;
}

static int proc_xpd_register_write(struct file *file, const char __user *buffer, unsigned long count, void *data)
{
	xpd_t		*xpd = data;
	char		buf[MAX_PROC_WRITE];
	char		*p;
	int		i;
	int		ret;

	if(!xpd)
		return -ENODEV;
	for(i = 0; i < count; /* noop */) {
		for(p = buf; p < buf + MAX_PROC_WRITE; p++) {	/* read a line */
			if(i >= count)
				break;
			if(get_user(*p, buffer + i))
				return -EFAULT;
			i++;
			if(*p == '\n' || *p == '\r')	/* whatever */
				break;
		}
		if(p >= buf + MAX_PROC_WRITE)
			return -E2BIG;
		*p = '\0';
		ret = handle_register_command(xpd, buf);
		if(ret < 0)
			return ret;
		mdelay(1);
	}
	return count;
}

static int proc_xpd_register_read(char *page, char **start, off_t off, int count, int *eof, void *data)
{
	int			len = 0;
	unsigned long		flags;
	xpd_t			*xpd = data;
	reg_cmd_t		*info;
	byte			regnum;

	if(!xpd)
		return -ENODEV;
	spin_lock_irqsave(&xpd->lock, flags);
	info = &xpd->last_reply;
	regnum = REG_FIELD(info, regnum);
	len += sprintf(page + len, "# Writing bad data into this file may damage your hardware!\n");
	len += sprintf(page + len, "# Consult firmware docs first\n");
	len += sprintf(page + len, "#\n");
	len += sprintf(page + len, "#CH\tD/I\tReg.\tDL\n");
	len += sprintf(page + len, "%2d\tRD\t%02X\t%02X\n",
			REG_FIELD(info, chipsel),
			regnum, REG_FIELD(info, data_low));
	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;
}

int __init card_fxo_startup(void)
{
	INFO("%s revision %s\n", THIS_MODULE->name, ZAPTEL_VERSION);
#ifdef	SOFT_RING
	INFO("FEATURE: %s with SOFT_RING\n", THIS_MODULE->name);
#else
	INFO("FEATURE: %s without SOFT_RING\n", THIS_MODULE->name);
#endif
	xproto_register(&PROTO_TABLE(FXO));
	return 0;
}

void __exit card_fxo_cleanup(void)
{
	xproto_unregister(&PROTO_TABLE(FXO));
}

MODULE_DESCRIPTION("XPP FXO Card Driver");
MODULE_AUTHOR("Oron Peled <oron@actcom.co.il>");
MODULE_LICENSE("GPL");
MODULE_VERSION(ZAPTEL_VERSION);
MODULE_ALIAS_XPD(XPD_TYPE_FXO);

module_init(card_fxo_startup);
module_exit(card_fxo_cleanup);
