/*
 * Written by Oron Peled <oron@actcom.co.il>
 * Copyright (C) 2004-2005, 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 "xpd.h"
#include "xproto.h"
#include "xpp_zap.h"
#include <linux/delay.h>

static const char rcsid[] = "$Id: card_fxs.c,v 1.15 2006/04/11 09:05:16 svn-mirror Exp $";
static const char revision[] = "$Revision: 1.15 $";

DEF_PARM(int, print_dbg, 0, "Print DBG statements");	/* must be before zap_debug.h */

#define	LINES_REGULAR	8
#define	LINES_DIGI_OUT	2
#define	LINES_DIGI_INP	4

#define	MASK_BITS(b)		((1U << (b)) - 1)

#define	MASK_DIGI_OUT	(MASK_BITS(LINES_DIGI_OUT) << LINES_REGULAR)
#define	MASK_DIGI_INP	(MASK_BITS(LINES_DIGI_INP) << (LINES_REGULAR + LINES_DIGI_OUT))

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

/* 0x0F */ DECLARE_CMD(FXS, CHAN_ENABLE, xpp_line_t lines, bool on);
/* 0x0F */ DECLARE_CMD(FXS, CHAN_POWER, xpp_line_t lines, bool on);
/* 0x0F */ DECLARE_CMD(FXS, CHAN_CID, xpp_line_t lines);
/* 0x0F */ DECLARE_CMD(FXS, RING, int pos, bool on);
/* 0x0F */ DECLARE_CMD(FXS, SETHOOK, xpp_line_t hook_status);
/* 0x0F */ DECLARE_CMD(FXS, LED, xpp_line_t lines, byte which, bool on);
/* 0x0F */ DECLARE_CMD(FXS, RELAY_OUT, byte which, bool on);
/* 0x0F */ DECLARE_CMD(FXS, SLIC_INIT);
/* 0x0F */ DECLARE_CMD(FXS, SLIC_QUERY, int pos, byte reg_num);

static bool fxs_packet_is_valid(xpacket_t *pack);
static void fxs_packet_dump(xpacket_t *pack);
static int proc_xpd_slic_read(char *page, char **start, off_t off, int count, int *eof, void *data);
static int proc_xpd_slic_write(struct file *file, const char __user *buffer, unsigned long count, void *data);

#define	S_(s,l,...)					\
	{						\
		.lines = s,				\
		{					\
			.len = l,			\
			.data = { __VA_ARGS__ },	\
		}					\
	}
struct slic_init_data {
	xpp_line_t	lines;
	slic_data_t	slic_data;
} slic_init_data[] = {
#include "slic_init.inc"
};
#undef	S_

#define	PROC_SLIC_FNAME		"slics"

struct FXS_priv_data {
	struct proc_dir_entry	*xpd_slic;
	slic_reply_t		last_reply;
};

/*---------------- FXS: Methods -------------------------------------------*/

static xpd_t *FXS_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);

	if(xpd_num == 0)
		channels += 6;	/* 2 DIGITAL OUTPUTS, 4 DIGITAL INPUTS */
	xpd = xpd_alloc(sizeof(struct FXS_priv_data), xbus, xpd_num, proto_table, channels, revision);
	if(!xpd)
		return NULL;
	if(xpd_num == 0) {
		DBG("First XPD on %s detected. Initialize digital outputs/inputs\n", xbus->busname);
		xpd->digital_outputs = MASK_DIGI_OUT;
		xpd->digital_inputs = MASK_DIGI_INP;
	}
	xpd->direction = TO_PHONE;
	return xpd;
}

static int FXS_card_init(xbus_t *xbus, xpd_t *xpd)
{
	struct FXS_priv_data	*priv;

	BUG_ON(!xpd);
	priv = xpd->priv;
	CALL_PROTO(FXS, SLIC_INIT, xbus, xpd);
#ifdef	CONFIG_PROC_FS
	DBG("Creating SLICs file for %s/%s\n", xbus->busname, xpd->xpdname);
	priv->xpd_slic = create_proc_entry(PROC_SLIC_FNAME, 0644, xpd->proc_xpd_dir);
	if(!priv->xpd_slic) {
		ERR("Failed to create proc file for SLICs of %s/%s\n", xbus->busname, xpd->xpdname);
		goto out;
	}
	priv->xpd_slic->write_proc = proc_xpd_slic_write;
	priv->xpd_slic->read_proc = proc_xpd_slic_read;
	priv->xpd_slic->data = xpd;
out:
#endif
	return 0;
}

static int FXS_card_remove(xbus_t *xbus, xpd_t *xpd)
{
	struct FXS_priv_data	*priv;

	BUG_ON(!xpd);
	priv = xpd->priv;
	DBG("%s/%s\n", xbus->busname, xpd->xpdname);
#ifdef	CONFIG_PROC_FS
	if(priv->xpd_slic) {
		DBG("Removing xpd SLIC file %s/%s\n", xbus->busname, xpd->xpdname);
		remove_proc_entry(PROC_SLIC_FNAME, xpd->proc_xpd_dir);
	}
#endif
	return 0;
}

/*
 * 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(xbus_t *xbus, xpd_t *xpd)
{
	int	i;

	for(i = 0; i < ARRAY_SIZE(input_channels); i++) {
		int	pos = input_channels[i];

		CALL_PROTO(FXS, SLIC_QUERY, xbus, xpd, pos, 0x06);
	}
}

static int FXS_card_tick(xbus_t *xbus, xpd_t *xpd)
{
	static	int	rate_limit = 0;

	if((rate_limit++ % 1000) == 0) {
		poll_inputs(xbus, xpd);
	}
	return 0;
}

/*---------------- FXS: HOST COMMANDS -------------------------------------*/

/* 0x0F */ HOSTCMD(FXS, CHAN_ENABLE, xpp_line_t lines, bool on)
{
	int		ret = 0;
	xpacket_t	*pack;
	slic_cmd_t	*sc;
	int		len;

	BUG_ON(!xbus);
	BUG_ON(!xpd);
	lines &= xpd->enabled_chans;	// Ignore disabled channels
	if(!lines) {
		return 0;
	}
	DBG("Channel Activation: 0x%4X %s\n", lines, (on) ? "on" : "off");
	XPACKET_NEW(pack, xbus, FXS, SLIC_WRITE, xpd->id);
	sc = &RPACKET_FIELD(pack, FXS, SLIC_WRITE, slic_cmd);
	len = slic_cmd_direct_write(sc, lines, 0x40, (on)?0x01:0x00);
	pack->datalen = len;

	packet_send(xbus, pack);
	return ret;
}

/* 0x0F */ HOSTCMD(FXS, CHAN_POWER, xpp_line_t lines, bool on)
{
	int		ret = 0;
	xpacket_t	*pack;
	slic_cmd_t	*sc;
	int		len;

	BUG_ON(!xbus);
	BUG_ON(!xpd);
	lines &= xpd->enabled_chans;	// Ignore disabled channels
	if(!lines) {
		return 0;
	}
	DBG("Channel Power: 0x%04X %s\n", lines, (on) ? "up" : "down");
	XPACKET_NEW(pack, xbus, FXS, SLIC_WRITE, xpd->id);
	sc = &RPACKET_FIELD(pack, FXS, SLIC_WRITE, slic_cmd);
	if(on) {
		// Power up
		len = slic_cmd_direct_write(sc, lines, 0x42, 0x06);
	} else {
		// Power down
		len = slic_cmd_direct_write(sc, lines, 0x42, 0x00);
	}
	pack->datalen = len;

	packet_send(xbus, pack);
	return ret;
}

/* 0x0F */ HOSTCMD(FXS, CHAN_CID, xpp_line_t lines)
{
	int		ret = 0;
	xpacket_t	*pack;
	slic_cmd_t	*sc;

	BUG_ON(!xbus);
	BUG_ON(!xpd);
	lines &= xpd->enabled_chans;	// Ignore disabled channels
	if(!lines) {
		return 0;
	}
	DBG("Channel CID: 0x%04X\n", lines);
	XPACKET_NEW(pack, xbus, FXS, SLIC_WRITE, xpd->id);
	sc = &RPACKET_FIELD(pack, FXS, SLIC_WRITE, slic_cmd);
	pack->datalen = slic_cmd_direct_write(sc, lines, 0x40, 0x02);
	packet_send(xbus, pack);
	return ret;
}


/* 0x0F */ HOSTCMD(FXS, RING, int pos, bool on)
{
	int		ret = 0;
	xpacket_t	*pack;
	slic_cmd_t	*sc;
	xpp_line_t	mask = (1 << pos);
	int		len;

	BUG_ON(!xbus);
	BUG_ON(!xpd);
	mask &= xpd->enabled_chans;	// Ignore disabled channels
	if(!mask) {
		return 0;
	}
	DBG("%s pos=%d %s\n", xpd->xpdname, pos, (on) ? "on" : "off");
	XPACKET_NEW(pack, xbus, FXS, SLIC_WRITE, xpd->id);
	sc = &RPACKET_FIELD(pack, FXS, SLIC_WRITE, slic_cmd);
	len = slic_cmd_direct_write(sc, mask, 0x40, (on)?0x04:0x01);
	pack->datalen = len;

	packet_send(xbus, pack);
	return ret;
}

/* 0x0F */ HOSTCMD(FXS, SETHOOK, xpp_line_t hook_status)
{
	DBG("\n");
	return 0;
}

/*
 * LED control is done via SLIC register 0x06:
 *         7     6     5     4     3     2     1     0
 * 	+-----+-----+-----+-----+-----+-----+-----+-----+
 * 	| MR  | MG  | MB  |  R  | OG  | OB  |  G  | B   |
 * 	+-----+-----+-----+-----+-----+-----+-----+-----+
 *
 * 	B	- BLUE LED (0 - OFF, 1 - ON)
 * 	G	- GREEN LED (0 - OFF, 1 - ON)
 * 	OB	- Output BLUE (this line is output)
 * 	OG	- Output GREEN (this line is output)
 * 	R	- RED LED (0 - OFF, 1 - ON)
 * 	MB	- Mask BLUE. (1 - B effect the BLUE LED)
 * 	MR	- Mask RED. (1 - R effect the RED LED)
 * 	MG	- Mask GREEN. (1 - G effect the GREEN LED)
 *
 * 	The BLUE LED (actually a relay out) is connected to line 0 and 4 only.
 */

//		                 		GREEN	RED	BLUE
static const int	led_mask[NUM_LEDS] = { 	BIT(7),	BIT(6),	BIT(5) };
static const int	led_vals[NUM_LEDS] = { 	BIT(4),	BIT(1),	BIT(0) };

/* 0x0F */ HOSTCMD(FXS, LED, xpp_line_t lines, byte which, bool on)
{
	int		ret = 0;
	xpacket_t	*pack;
	slic_cmd_t	*sc;
	int		len;
	int		value;
	int		i;

	BUG_ON(!xbus);
	BUG_ON(!xpd);
	lines &= xpd->enabled_chans;	// Ignore disabled channels
	if(!lines) {
		return 0;
	}
	DBG("LED: lines=0x%04X which=%d -- %s\n", lines, which, (on) ? "on" : "off");
	which = which % NUM_LEDS;
	value = BIT(2) | BIT(3);
	value |= ((BIT(5) | BIT(6) | BIT(7)) & ~led_mask[which]);
	if(on)
		value |= led_vals[which];
	for(i = 0; i < CHANNELS_PERXPD; i++) {
		if(!IS_SET(lines, i))
				continue;
		XPACKET_NEW(pack, xbus, FXS, SLIC_WRITE, xpd->id);
		sc = &RPACKET_FIELD(pack, FXS, SLIC_WRITE, slic_cmd);
		len = slic_cmd_direct_write(sc, lines, 0x06, value);
		DBG("LED pack: line=%d value=0x%04X\n", i, value);
		pack->datalen = len;
		packet_send(xbus, pack);
	}
	return ret;
}

/* 0x0F */ HOSTCMD(FXS, RELAY_OUT, byte which, bool on)
{
	int		ret = 0;
	xpacket_t	*pack;
	slic_cmd_t	*sc;
	int		len;
	int		value;
	xpp_line_t	lines;
	int		relay_channels[] = { 0, 4 };

	BUG_ON(!xbus);
	BUG_ON(!xpd);

	DBG("RELAY_OUT: which=%d -- %s\n", which, (on) ? "on" : "off");
	which = which % ARRAY_SIZE(relay_channels);
	lines = BIT(relay_channels[which]);
	value = BIT(2) | BIT(3);
	value |= ((BIT(5) | BIT(6) | BIT(7)) & ~led_mask[LED_BLUE]);
	if(on)
		value |= led_vals[LED_BLUE];
	XPACKET_NEW(pack, xbus, FXS, SLIC_WRITE, xpd->id);
	sc = &RPACKET_FIELD(pack, FXS, SLIC_WRITE, slic_cmd);
	len = slic_cmd_direct_write(sc, lines, 0x06, value);

	DBG("RELAY_OUT pack: line=%d value=0x%04X\n", lines, value);
	pack->datalen = len;
	packet_send(xbus, pack);
	return ret;
}

/* 0x0F */ HOSTCMD(FXS, SLIC_INIT)
{
	int	ret = 0;
	xpacket_t		*pack;
	slic_data_t		*slic;
	struct slic_init_data	*source;
	int			i;

	BUG_ON(!xbus);
	BUG_ON(!xpd);
	DBG("INITIALIZING SLIC\n");
	for(i = 0; i < ARRAY_SIZE(slic_init_data); i++) {
		source = &slic_init_data[i];
		XPACKET_NEW(pack, xbus, FXS, SLIC_INIT, xpd->id);
		RPACKET_FIELD(pack, FXS, SLIC_INIT, lines) = source->lines;

		slic = &RPACKET_FIELD(pack, FXS, SLIC_INIT, slic_data);
		slic->len = source->slic_data.len;
		memcpy(slic->data, source->slic_data.data, source->slic_data.len);
		pack->datalen = sizeof(xpp_line_t) + slic->len + 1;
//		dump_packet("SLIC", pack, print_dbg);
		packet_send(xbus, pack);
		mdelay(10);	// FIXME: check with Dima
	}
	return ret;
}

/* 0x0F */ HOSTCMD(FXS, SLIC_QUERY, int pos, byte reg_num)
{
	int	ret = 0;
	xpacket_t	*pack;
	slic_cmd_t	*sc;
	int		len;

	BUG_ON(!xbus);
	BUG_ON(!xpd);
	DBG("\n");
	XPACKET_NEW(pack, xbus, FXS, SLIC_WRITE, xpd->id);
	sc = &RPACKET_FIELD(pack, FXS, SLIC_WRITE, slic_cmd);
	len = slic_cmd_direct_read(sc, BIT(pos), reg_num);

	pack->datalen = len;

	packet_send(xbus, pack);
	return ret;
}

/*---------------- FXS: Astribank Reply Handlers --------------------------*/

HANDLER_DEF(FXS, SIG_CHANGED)
{
	xpp_line_t	sig_status = RPACKET_FIELD(pack, FXS, SIG_CHANGED, sig_status);

	if(!xpd) {
		NOTICE("%s: received %s for non-existing xpd: %d\n",
				__FUNCTION__, cmd->name, XPD_NUM(pack->content.addr));
		return -EPROTO;
	}
	if(xpd->direction == TO_PHONE) {		/* Hook state changes */
		DBG("%s (PHONE) sig_status=0x%04X\n", xpd->xpdname, sig_status);
		xpp_check_hookstate(xpd, sig_status);
	} else {					/* TO_PSTN - line ring changes */
		unsigned long	flags;
		int		i;

		DBG("%s (PSTN) sig_status=0x%04X\n", xpd->xpdname, sig_status);
		spin_lock_irqsave(&xpd->lock, flags);
		for(i = 0; i < xpd->channels; i++) {
			if(IS_SET(sig_status, i)) {
				xpd->ringing[i] = RINGS_NUM*2;
				zt_hooksig(&xpd->chans[i], ZT_RXSIG_OFFHOOK);
			} else {
				zt_hooksig(&xpd->chans[i], ZT_RXSIG_ONHOOK);
				xpd->ringing[i] = 0;
			}
		}
		spin_unlock_irqrestore(&xpd->lock, flags);
	}
	return 0;
}

HANDLER_DEF(FXS, SLIC_REPLY)
{
	slic_reply_t		*info = &RPACKET_FIELD(pack, FXS, SLIC_REPLY, info);
	xpp_line_t		lines = RPACKET_FIELD(pack, FXS, SLIC_REPLY, lines);
	unsigned long		flags;
	struct FXS_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;
	}
	spin_lock_irqsave(&xpd->lock, flags);
	priv = xpd->priv;
	BUG_ON(!priv);
	DBG("SLIC_REPLY: xpd #%d %s reg_num=0x%X, dataL=0x%X dataH=0x%X\n",
			xpd->id, (info->indirect)?"I":"D",
			info->reg_num, info->data_low, info->data_high);
	priv->last_reply = *info;
	if(xpd->id == 0 && info->indirect == 0 && info->reg_num == 0x06) {	/* Digital Inputs Poll Result */
		int	i;
		bool	offhook = (info->data_low & 0x1) == 0;

		/* 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 = LINES_REGULAR + LINES_DIGI_OUT + i;
				BIT_CLR(lines, channo);
				BIT_SET(lines, newchanno);
				phone_hook(xpd, newchanno, offhook);
			}
		}
	}
	spin_unlock_irqrestore(&xpd->lock, flags);
	return 0;
}


xproto_table_t PROTO_TABLE(FXS) = {
	.entries = {
		/*	Card	Opcode		*/
		XENTRY(	FXS,	SIG_CHANGED	),
		XENTRY(	FXS,	SLIC_REPLY	),
	},
	.name = "FXS",
	.type = XPD_TYPE(FXS),
	.xops = {
		.card_new	= FXS_card_new,
		.card_init	= FXS_card_init,
		.card_remove	= FXS_card_remove,
		.card_tick	= FXS_card_tick,

		.RING		= XPROTO_CALLER(FXS, RING),
		.SETHOOK	= XPROTO_CALLER(FXS, SETHOOK),
		.LED		= XPROTO_CALLER(FXS, LED),
		.RELAY_OUT	= XPROTO_CALLER(FXS, RELAY_OUT),
		.CHAN_ENABLE	= XPROTO_CALLER(FXS, CHAN_ENABLE),
		.CHAN_POWER	= XPROTO_CALLER(FXS, CHAN_POWER),
		.CHAN_CID	= XPROTO_CALLER(FXS, CHAN_CID),

		.SYNC_SOURCE	= XPROTO_CALLER(GLOBAL, SYNC_SOURCE),
		.PCM_WRITE	= XPROTO_CALLER(GLOBAL, PCM_WRITE),
	},
	.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("\n");
	xe = xproto_card_entry(&PROTO_TABLE(FXS), pack->content.opcode);
	return xe != NULL;
}

static void fxs_packet_dump(xpacket_t *pack)
{
	DBG("\n");
}

/*------------------------- SLIC Handling --------------------------*/

static int proc_xpd_slic_read(char *page, char **start, off_t off, int count, int *eof, void *data)
{
	int			len = 0;
	unsigned long		flags;
	xpd_t			*xpd = data;
	slic_reply_t		*info;
	struct FXS_priv_data	*priv;

	BUG_ON(!xpd);
	spin_lock_irqsave(&xpd->lock, flags);
	priv = xpd->priv;
	BUG_ON(!priv);
	info = &priv->last_reply;
	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, "SLIC_REPLY: %s reg_num=0x%X, dataH=0x%X dataL=0x%X\n",
			(info->indirect)?"I":"D",
			info->reg_num, info->data_high, 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;
}

/*
 *        Direct/Indirect
 *              v
 * FF FF FF FF WD 06 1
 * ^---------^ ^  Reg
 *      | Write/Read
 *      |
 *    SLIC #
 */
static int parse_slic_cmd(const char *buf, slic_cmd_t *sc)
{
	char		op;		/* [W]rite, [R]ead */
	char		reg_type;	/* [D]irect, [I]ndirect */
	int		s1, s2, s3, s4;
	int		reg_num;
	int		data_low, data_high;
	xpp_line_t	lines;
	int		ret;

	ret = sscanf(buf, "%x %x %x %x %c%c %x %x %x",
			&s1, &s2, &s3, &s4, &op, &reg_type, &reg_num, &data_high, &data_low);
	lines = (s4 << 24) | (s3 << 16) | (s2 << 8) | (s1);
	switch(op) {
		case 'R':
			if(reg_type == 'D' && ret == 7) {
				// DBG("0x%X 0x%X 0x%X 0x%X %c %x\n", s1, s2, s3, s4, reg_type, reg_num);
				ret = slic_cmd_direct_read(sc, lines, reg_num);
			} else if(reg_type == 'I' && ret == 7) {
				// DBG("0x%X 0x%X 0x%X 0x%X %c %x\n", s1, s2, s3, s4, reg_type, reg_num);
				ret = slic_cmd_indirect_read(sc, lines, reg_num);
			} else {
				NOTICE("%s: Bad read input: ret=%d buf='%s' reg_type=%c\n", __FUNCTION__, ret, buf, reg_type);
				goto err;
			}
			break;
		case 'W':
			if(reg_type == 'D' && ret == 8) {
				// DBG("0x%X 0x%X 0x%X 0x%X %c %x %X\n", s1, s2, s3, s4, reg_type, reg_num, data_high);
				ret = slic_cmd_direct_write(sc, lines, reg_num, data_high);
			} else if(reg_type == 'I' && ret == 9) {
				// DBG("0x%X 0x%X 0x%X 0x%X %c %x %X %X\n", s1, s2, s3, s4, reg_type, reg_num, data_high, data_low);
				ret = slic_cmd_indirect_write(sc, lines, reg_num, data_low, data_high);
			} else {
				NOTICE("%s: Bad write input: ret=%d buf='%s' reg_type=%c\n", __FUNCTION__, ret, buf, reg_type);
				goto err;
			}
			break;
		default:
			NOTICE("%s: Bad input: ret=%d buf='%s' op=%c\n", __FUNCTION__, ret, buf, op);
			goto err;
	}
	return ret;
err:
	return -EINVAL;
}

static int process_slic_cmdline(xpd_t *xpd, char *cmdline)
{
	xbus_t		*xbus;
	slic_cmd_t	sc;
	xpacket_t	*pack;
	char		*p;
	int		len = strlen(cmdline);

	BUG_ON(!xpd);
	xbus = xpd->xbus;
	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;
	len = parse_slic_cmd(p, &sc);
	if(len < 0)
		return len;
	sc.lines &= xpd->enabled_chans;	// Ignore disabled channels
	if(!sc.lines) {
		NOTICE("%s: no enabled channels are marked. Skip.\n", __FUNCTION__);
		return 0;
	}
	dump_slic_cmd("WRITE_SLIC", &sc);
	XPACKET_NEW(pack, xbus, FXS, SLIC_WRITE, xpd->id);
	RPACKET_FIELD(pack, FXS, SLIC_WRITE, slic_cmd) = sc;
	pack->datalen = len;
	packet_send(xbus, pack);
	return 0;
}

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

	BUG_ON(!xpd);
	for(i = 0; i < count; /* noop */) {
		for(p = buf; p < buf + LINE_LEN; 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 + LINE_LEN)
			return -E2BIG;
		*p = '\0';
		ret = process_slic_cmdline(xpd, buf);
		if(ret < 0)
			return ret;
	}
	return count;
}


int __init card_fxs_startup(void)
{
	INFO("%s revision %s\n", THIS_MODULE->name, revision);
	xproto_register(&PROTO_TABLE(FXS));
	return 0;
}

void __exit card_fxs_cleanup(void)
{
	xproto_unregister(&PROTO_TABLE(FXS));
}

MODULE_DESCRIPTION("XPP FXS Card Driver");
MODULE_AUTHOR("Oron Peled <oron@actcom.co.il>");
MODULE_LICENSE("GPL");
MODULE_VERSION("$Id: card_fxs.c,v 1.15 2006/04/11 09:05:16 svn-mirror Exp $");

module_init(card_fxs_startup);
module_exit(card_fxs_cleanup);
