#include <linux/module.h>
#include "xpd.h"
#include "xpp_zap.h"
#include "xpp_proto.h"
#include "cards.h"

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

extern	int print_dbg;
#include "zap_debug.h"

#define	MAX_SLIC_REGISTERS	100

struct FXS_private_data {
	slic_reply_t	last_slic_reply;
};

/*------------------------- FXS Functions --------------------------*/
int	FXS_card_new(xpd_t *xpd)
{
	xpd->direction = TO_PHONE;
	xpd->channels = min(8, CHANNELS_PERXPD);
	if(xpd->id == 0) {
		DBG("First XPD detected. Initialize digital outputs\n");
		xpd->channels += 2;
		xpd->digital_outputs = BIT(8) | BIT(9);	// Two extra channels
	}
	return 0;
}

int	FXS_card_remove(xpd_t *xpd)
{
	return 0;
}

static int FXS_card_startup(struct zt_span *span)
{
	DBG("\n");
	return 0;
}

/*
 * Called only for 'span' keyword in /etc/zaptel.conf
 */
static int FXS_card_spanconfig(struct zt_span *span, struct zt_lineconfig *lc)
{
	xpd_t *xpd = span->pvt;

	DBG("%s\n", xpd->xpdname);
	return 0;
}

/* Set signalling type (if appropriate) */
static int FXS_card_chanconfig(struct zt_chan *chan, int sigtype)
{
	DBG("channel %d (%s), sigtype %d.\n", chan->channo, chan->name, sigtype);
	dump_sigtype(print_dbg, "  ", sigtype);
	// FIXME: sanity checks:
	// - should be supported (within the sigcap)
	// - should not replace fxs <->fxo ??? (covered by previous?)
	return 0;
}

/*
 * Called only for 'span' keyword in /etc/zaptel.conf
 */
static int FXS_card_shutdown(struct zt_span *span)
{
	xpd_t *xpd = span->pvt;

	DBG("%s\n", xpd->xpdname);
	return 0;
}

static int FXS_card_sethook(struct zt_chan *chan, int hookstate)
{
	int pos = chan->chanpos - 1;
	xpd_t	*xpd = chan->pvt;
	xbus_t	*xbus;
	int ret = 0;

	if(!xpd) {
		ERR("%s: channel=%d without an XPD!\n", __FUNCTION__, pos);
		return -EINVAL;
	}
	xbus = xpd->xbus;
	// DBG("%s (%d) (old=0x%04X, hook-command=%d)\n", chan->name, pos, xpd->hookstate, hookstate);
	switch(hookstate) {
		/* On-hook, off-hook: The PBX is playing a phone on an FXO line. 
		 * Can be ignored for an FXS line
		 */
		case ZT_ONHOOK:
			if(IS_SET(xpd->digital_outputs, pos)) {
				DBG("ZT_ONHOOK %s digital output OFF\n", chan->name);
				ret = CALL_PROTO(RELAY_OUT, xpd->xbus, xpd, pos-8, 0);
				return ret;
			}
			DBG("ZT_ONHOOK: %s hookstate=0x%04X (stop ringing pos=%d)\n", chan->name, xpd->hookstate, pos);
			xpd->ringing[pos] = 0;
#if 1	// FIXME: Not needed -- verify
			ret = CALL_PROTO(RING, xbus, xpd, pos, 0);			// RING off
#endif
			ret = CALL_PROTO(LED, xpd->xbus, xpd, BIT(pos), LED_GREEN, 0);
			ret = CALL_PROTO(CHAN_POWER, xbus, xpd, BIT(pos), 0);		// Power down (prevent overheating!!!)
			if(ret) {
				DBG("ZT_ONHOOK(stop ring) Failed: ret=0x%02X\n", ret);
				break;
			}
			break;
		case ZT_START:
			DBG("ZT_START: %s hookstate=0x%04X (fall through ZT_OFFHOOK)\n", chan->name, xpd->hookstate);
			// Fall through
		case ZT_OFFHOOK:
			DBG("ZT_OFFHOOK: %s hookstate=0x%04X -- ignoring (FXS)\n", chan->name, xpd->hookstate);
			break;
		case ZT_WINK:
			DBG("ZT_WINK %s\n", chan->name);
			break;
		case ZT_FLASH:
			DBG("ZT_FLASH %s\n", chan->name);
			break;
		case ZT_RING:
			DBG("ZT_RING %s pos=%d (ringing[pos]=%d)\n", chan->name, pos, xpd->ringing[pos]);
			if(IS_SET(xpd->digital_outputs, pos)) {
				DBG("ZT_ONHOOK %s digital output ON\n", chan->name);
				ret = CALL_PROTO(RELAY_OUT, xpd->xbus, xpd, pos-8, 1);
				return ret;
			}
			xpd->ringing[pos] = RINGS_NUM*2;
			ret = CALL_PROTO(CHAN_POWER, xbus, xpd, (1 << pos), 1);	// Power up (for ring)
			ret = CALL_PROTO(RING, xbus, xpd, pos, 1);			// RING on
			if(ret) {
				DBG("ZT_RING Failed: ret=0x%02X\n", ret);
			}
			break;
		case ZT_RINGOFF:
			DBG("ZT_RINGOFF %s\n", chan->name);
			break;
		default:
			DBG("UNKNOWN hookstate=0x%X\n", hookstate);
	}
	return ret;
}

static int FXS_card_ioctl(struct zt_chan *chan, unsigned int cmd, unsigned long arg)
{
	switch (cmd) {
	default:
		return xpp_ioctl(chan, cmd, arg);
	}
	return 0;
}

#if 0
int FXS_zaptel_setup(xpd_t *xpd)
{
	struct zt_chan	*cur_chan;
	struct zt_span	*span;
	xbus_t		*xbus;
	int		i;
	int		cn;

	BUG_ON(!xpd);

	sigfxs = ! (xpd->direction == TO_PHONE);	/* signaling is opposite */
	cn = xpd->channels;
	DBG("Initializing span: xpd %d have %d channels.\n", xpd->id, cn);

	xpd->chans = kmalloc(sizeof(struct zt_chan)*cn, GFP_ATOMIC);
	if (xpd->chans == NULL) {
		ERR("xpd: Unable to allocate channels\n");
		return -ENOMEM;
	}
	memset(xpd->chans, 0, sizeof(struct zt_chan)*cn);
	memset(&xpd->span, 0, sizeof(struct zt_span));

	span = &xpd->span;
	xbus = xpd->xbus;
	snprintf(span->name, MAX_SPANNAME, "%s/%s",
			xbus->busname, xpd->xpdname);
	{
		char tmp[MAX_SPANNAME];
		struct xpd_sim	*sim = &xbus->sim[xpd->id];

		if(sim->simulated)
			snprintf(tmp, MAX_SPANNAME, " (sim to=%d)", sim->loopto);
		else
			tmp[0] = '\0';

		snprintf(span->desc, MAX_SPANDESC, "Xorcom XPD #%d/%d: %s%s",
				xbus->num, xpd->id,
				(xpd->direction == TO_PHONE) ? "FXS" : "FXO",
				tmp
				);
	}
	for(i = 0; i < cn; i++) {
		
		cur_chan = &xpd->chans[i];
		DBG("setting channel %d\n", i);
		snprintf(cur_chan->name, MAX_CHANNAME, "XPP_FXS/%d-%d", xpd->id, i);
		cur_chan->chanpos = i + 1;
		cur_chan->pvt = xpd;
		cur_chan->sigcap =
#if 1
				ZT_SIG_FXOKS	|
				ZT_SIG_FXOLS	|
				ZT_SIG_FXOGS	|
#else
				ZT_SIG_SF	|
				ZT_SIG_EM	|
#endif
				0;
	}
	span->deflaw = ZT_LAW_MULAW;
	init_waitqueue_head(&span->maintq);
	span->pvt = xpd;
	span->channels = cn;
	span->chans = xpd->chans;

	span->startup = FXS_xpp_startup;
	span->shutdown = FXS_xpp_shutdown;
	span->spanconfig = FXS_xpp_spanconfig;
	span->chanconfig = FXS_xpp_chanconfig;
	span->open = xpp_open;
	span->close = xpp_close;
#ifdef	WITH_RBS
	span->flags = ZT_FLAG_RBS;
	span->hooksig = xpp_hooksig;	/* Only with RBS bits */
#else
	span->sethook = FXS_xpp_sethook;
#endif
	span->ioctl = FXS_xpp_ioctl;
	span->maint = xpp_maint;
#ifdef	CONFIG_ZAPTEL_WATCHDOG
	span->watchdog = xpp_watchdog;
#endif

	return 0;
}
#endif

int FXS_zaptel_cleanup(xpd_t *xpd)
{
	return 0;
}

/*------------------------- FXO Functions --------------------------*/
static int FXO_card_new(xpd_t *xpd)
{
	xpd->direction = TO_TRUNK;
	xpd->channels = min(8, CHANNELS_PERXPD);
	return 0;
}

static int FXO_card_remove(xpd_t *xpd)
{
	return 0;
}

static int FXO_card_startup(struct zt_span *span)
{
	DBG("\n");
	return 0;
}

/*
 * Called only for 'span' keyword in /etc/zaptel.conf
 */
static int FXO_card_spanconfig(struct zt_span *span, struct zt_lineconfig *lc)
{
	xpd_t *xpd = span->pvt;

	DBG("%s\n", xpd->xpdname);
	return 0;
}

/* Set signalling type (if appropriate) */
static int FXO_card_chanconfig(struct zt_chan *chan, int sigtype)
{
	DBG("channel %d (%s), sigtype %d.\n", chan->channo, chan->name, sigtype);
	dump_sigtype(print_dbg, "  ", sigtype);
	// FIXME: sanity checks:
	// - should be supported (within the sigcap)
	// - should not replace fxs <->fxo ??? (covered by previous?)
	return 0;
}

/*
 * Called only for 'span' keyword in /etc/zaptel.conf
 */
static int FXO_card_shutdown(struct zt_span *span)
{
	xpd_t *xpd = span->pvt;

	DBG("%s\n", xpd->xpdname);
	return 0;
}


static int FXO_card_sethook(struct zt_chan *chan, int hookstate)
{
	int pos = chan->chanpos - 1;
	xpd_t	*xpd = chan->pvt;
	xbus_t	*xbus;
	int ret = 0;

	BUG_ON(!xpd);
	xbus = xpd->xbus;
	// DBG("%s (%d) (old=0x%04X, hook-command=%d)\n", chan->name, pos, xpd->hookstate, hookstate);
	switch(hookstate) {
		/* On-hook, off-hook: The PBX is playing a phone on an FXO line. 
		 * Can be ignored for an FXS line
		 */
		case ZT_ONHOOK:
			if(IS_SET(xpd->digital_outputs, pos)) {
				DBG("ZT_ONHOOK %s digital output OFF\n", chan->name);
				ret = CALL_PROTO(RELAY_OUT, xpd->xbus, xpd, pos-8, 0);
				return ret;
			}
			DBG("ZT_ONHOOK: %s hookstate=0x%04X (pos=%d)\n", chan->name, xpd->hookstate, pos);
			xpd->ringing[pos] = 0;
			BIT_CLR(xpd->hookstate, pos);
			ret = CALL_PROTO(SETHOOK, xbus, xpd, xpd->hookstate);
			if(ret) {
				DBG("ZT_ONHOOK Failed: ret=0x%02X\n", ret);
				break;
			}
			break;
		case ZT_START:
			DBG("ZT_START: %s hookstate=0x%04X (fall through ZT_OFFHOOK)\n", chan->name, xpd->hookstate);
			// Fall through
		case ZT_OFFHOOK:
			DBG("ZT_OFFHOOK: %s hookstate=0x%04X (pos=%d)\n", chan->name, xpd->hookstate, pos);
			BIT_SET(xpd->hookstate, pos);
			xpd->ringing[pos] = 0;
			ret = CALL_PROTO(SETHOOK, xbus, xpd, xpd->hookstate);
			if(ret) {
				DBG("ZT_OFFHOOK Failed: ret=0x%02X\n", ret);
				break;
			}
			break;
		case ZT_WINK:
			DBG("ZT_WINK %s\n", chan->name);
			break;
		case ZT_FLASH:
			DBG("ZT_FLASH %s\n", chan->name);
			break;
		case ZT_RING:
			DBG("ZT_RING %s pos=%d (ringing[pos]=%d)\n", chan->name, pos, xpd->ringing[pos]);
			break;
		case ZT_RINGOFF:
			DBG("ZT_RINGOFF %s\n", chan->name);
			break;
		default:
			DBG("UNKNOWN hookstate=0x%X\n", hookstate);
	}
	return ret;
}

static int FXO_card_ioctl(struct zt_chan *chan, unsigned int cmd, unsigned long arg)
{
	switch (cmd) {
	default:
		return xpp_ioctl(chan, cmd, arg);
	}
	return 0;
}


/*------------------------- Function table -------------------------*/

#define	DEF_(t)	\
	[XPD_TYPE_ ## t] {					\
		.card_new = t ## _card_new,			\
		.card_remove = t ## _card_remove,		\
		.card_startup = t ## _card_startup,		\
		.card_shutdown = t ## _card_shutdown,		\
		.card_spanconfig = t ## _card_spanconfig,	\
		.card_chanconfig = t ## _card_chanconfig,	\
		.card_sethook = t ## _card_sethook,		\
		.card_ioctl = t ## _card_ioctl,			\
		}

xops_t	xpd_card_ops[XPD_TYPE_NOMODULE] = {
	DEF_(FXS),
	DEF_(FXO)
};

xops_t	*get_xops(xpd_type_t xpd_type)
{
	if(xpd_type >= XPD_TYPE_NOMODULE)
		return NULL;
	return &xpd_card_ops[xpd_type];
}
