/*
 * 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 "xdefs.h"
#include "xpd.h"
#include "xpp_zap.h"
#include "xproto.h"
#include "zap_debug.h"
#include <linux/module.h>

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

DEF_PARM(charp,initdir, "/usr/share/zaptel", "The directory of card initialization scripts");
/*
 * BRI: Temporary software workaround for firmware limitation:
 *      - The BRI firmware count PCM channel number globally across subunits.
 *      - The module parameter 'bri_pcmshift' enables us to cheat and shift
 *        each B-channel, 4 bits for each subsequent subunit.
 *      - Eventually, this should be fixed in the firmware, otherwise we won't
 *        handle PRI (all the space we have is 32bits).
 */
DEF_PARM(bool,bri_pcmshift, 1, "TESTING: shift bri PCM bits by subunit number");

extern	int print_dbg;
static bool pcm_valid(xpd_t *xpd, xpacket_t *pack);

/*---------------- GLOBAL Protocol Commands -------------------------------*/

static bool global_packet_is_valid(xpacket_t *pack);
static void global_packet_dump(const char *msg, xpacket_t *pack);

/*---------------- GLOBAL: HOST COMMANDS ----------------------------------*/

/* 0x04 */ HOSTCMD(GLOBAL, DESC_REQ, int xpd_num)
{
	int		ret = 0;
	xpacket_t	*pack;

	if(!xbus) {
		DBG("NO XBUS\n");
		return -EINVAL;
	}
	XPACKET_NEW(pack, xbus, GLOBAL, DESC_REQ, xpd_num);
	DBG("on %s #%d\n", xbus->busname, xpd_num);
	ret = packet_send(xbus, pack);
	XBUS_COUNTER(xbus, DESC_REQ)++;
	return ret;
}

/* 0x11 */ HOSTCMD(GLOBAL, PCM_WRITE, xpp_line_t lines, volatile byte *buf)
{
	int	ret = 0;
	xpacket_t	*pack;
	byte		*pcm;
	byte		*start_pcm;
	int i;

	BUG_ON(!xbus);
	BUG_ON(!xpd);
	lines &= ~xpd->no_pcm;
	/*
	 * FIXME: Workaround a bug in sync code of the Astribank.
	 *        Send dummy PCM for sync.
	 */
	if(lines == 0)
		lines = BIT(0);

	XPACKET_NEW(pack, xbus, GLOBAL, PCM_WRITE, xpd->id);
	RPACKET_FIELD(pack, GLOBAL, PCM_WRITE, lines) = lines;
	start_pcm = pcm = RPACKET_FIELD(pack, GLOBAL, PCM_WRITE, pcm);
	for_each_line(xpd, i) {
		if(IS_SET(lines, i)) {
			memcpy(pcm, (byte *)buf, ZT_CHUNKSIZE);
			pcm += ZT_CHUNKSIZE;
		}
		buf += ZT_CHUNKSIZE;
	}
	if(bri_pcmshift) {	/* workaround for pcm problem in BRI */
		lines = lines << (xpd->addr.subunit * 4);
		RPACKET_FIELD(pack, GLOBAL, PCM_WRITE, lines) = lines;
	}
	pack->datalen = sizeof(xpp_line_t) + (pcm - start_pcm);
	packet_send(xbus, pack);
	XPD_COUNTER(xpd, PCM_WRITE)++;
	XBUS_COUNTER(xbus, PCM_WRITE)++;
	return ret;
}

/* 0x19 */ HOSTCMD(GLOBAL, SYNC_SOURCE, bool setit, bool is_master)
{
	xpacket_t	*pack;
	byte		mask = 0;

	BUG_ON(!xbus);
	BUG_ON(!xpd);
	if(is_master)
		mask |= BIT(0);
	if(!setit)
		mask |= BIT(1);
	DBG("SYNC_SOURCE %s setit=%s is_master=%s (mask=0x%X)\n",
			xpd->xpdname, (setit)?"yes":"no", (is_master)?"yes":"no", mask);
	XPACKET_NEW(pack, xbus, GLOBAL, SYNC_SOURCE, xpd->id);
	RPACKET_FIELD(pack, GLOBAL, SYNC_SOURCE, mask) = mask;
	packet_send(xbus, pack);
	return 0;
}

/*---------------- GLOBAL: Astribank Reply Handlers -----------------------*/

HANDLER_DEF(GLOBAL, NULL_REPLY)
{
	DBG("got len=%d\n", pack->datalen);
	return 0;
}

HANDLER_DEF(GLOBAL, DEV_DESC)
{
	byte			rev = RPACKET_FIELD(pack, GLOBAL, DEV_DESC, rev);
	byte			type = RPACKET_FIELD(pack, GLOBAL, DEV_DESC, type);
	xpp_line_t		line_status = RPACKET_FIELD(pack, GLOBAL, DEV_DESC, line_status);
	xpd_addr_t		xpd_addr = RPACKET_FIELD(pack, GLOBAL, DEV_DESC, addr);
	struct card_desc_struct	*card_desc;
	unsigned long		flags;

	BUG_ON(!xbus);
	if((card_desc = kmalloc(sizeof(struct card_desc_struct), GFP_ATOMIC)) == NULL) {
		ERR("%s: Card description allocation failed.\n", __FUNCTION__);
		return -ENOMEM;
	}
	memset(card_desc, 0, sizeof(struct card_desc_struct));
	card_desc->magic = CARD_DESC_MAGIC;
	INIT_LIST_HEAD(&card_desc->card_list);
	card_desc->xbus = xbus;
	card_desc->type = type;
	card_desc->rev = rev;
	card_desc->xpd_addr = xpd_addr;
	spin_lock_irqsave(&xbus->lock, flags);
	DBG("xpd=%d-%d type=%d rev=%d line_status=0x%04X\n",
			xpd_addr.unit, xpd_addr.subunit, type, rev, line_status);
	if(type == XPD_TYPE_NOMODULE)
		XBUS_COUNTER(xbus, DEV_DESC_EMPTY)++;
	else
		XBUS_COUNTER(xbus, DEV_DESC_FULL)++;
	atomic_inc(&xbus->count_poll_answers);
	wake_up(&xbus->wait_for_polls);
	list_add_tail(&card_desc->card_list, &xbus->poll_results);
	spin_unlock_irqrestore(&xbus->lock, flags);
	return 0;
}

HANDLER_DEF(GLOBAL, PCM_READ)
{
	/* FIXME: work around temporary hardware bug */
	xpp_line_t	lines = RPACKET_FIELD(pack, GLOBAL, PCM_READ, lines);
	const byte	*pcm = RPACKET_FIELD(pack, GLOBAL, PCM_READ, pcm);
	volatile u_char	*readchunk;
	volatile u_char	*r;
	unsigned long	flags;
	int		i;

	BUG_ON(!xbus);
	if(!xpd) {
#if 0
		int xpd_num = XPD_NUM(pack->content.addr);
		NOTICE("%s: received %s for non-existing xpd: %d\n",
				__FUNCTION__, cmd->name, xpd_num);
#endif
		return -EPROTO;
	}
	// DBG("lines=0x%04X\n", lines);

	if(bri_pcmshift) {	/* workaround for pcm problem in BRI */
		lines = (lines >> (xpd->addr.subunit * 4)) & 0x7;
		RPACKET_FIELD(pack, GLOBAL, PCM_WRITE, lines) = lines;
	}
	if(!pcm_valid(xpd, pack)) {
		return -EPROTO;
	}
	spin_lock_irqsave(&xpd->lock, flags);
	if (xpd->timer_count & 1) {
		/* First part */
		r = readchunk = xpd->readchunk;
	} else {
		r = readchunk = xpd->readchunk + ZT_CHUNKSIZE * CHANNELS_PERXPD;
	}

	/* Copy PCM and put each channel in its index */
	for_each_line(xpd, i) {
		if(IS_SET(lines, i)) {
			memcpy((u_char *)r, pcm, ZT_CHUNKSIZE);
			//memset((u_char *)r, 0x5A, ZT_CHUNKSIZE);	// DEBUG
			pcm += ZT_CHUNKSIZE;
		}
		r += ZT_CHUNKSIZE;
	}

	XPD_COUNTER(xpd, PCM_READ)++;
	XBUS_COUNTER(xpd->xbus, PCM_READ)++;
	spin_unlock_irqrestore(&xpd->lock, flags);
	xpp_tick((unsigned long)xpd);

	return 0;
}

HANDLER_DEF(GLOBAL, SYNC_REPLY)
{
	byte	mask = RPACKET_FIELD(pack, GLOBAL, SYNC_REPLY, mask);
	bool	setit = mask & 0x01;

	BUG_ON(!xbus);
	if(!xpd) {
		int xpd_num = XPD_NUM(pack->content.addr);
		NOTICE("%s: received %s for non-existing xpd: %d\n", __FUNCTION__, cmd->name, xpd_num);
		return -EPROTO;
	}
	DBG("%s/%s: SYNC_REPLY: 0x%X %s\n", xpd->xbus->busname, xpd->xpdname,
			mask, (setit) ? "SET SYNC MASTER" : "");
	if(setit)
		sync_master_is(xpd);
	return 0;
}

HANDLER_DEF(GLOBAL, ERROR_CODE)
{
	byte	errorcode = RPACKET_FIELD(pack, GLOBAL, ERROR_CODE, errorcode);
	reg_cmd_t *bad_cmd;
	char	xpdname[XPD_NAMELEN];

	BUG_ON(!xbus);
	if(!xpd) {
		int xpd_num = XPD_NUM(pack->content.addr);
		snprintf(xpdname, XPD_NAMELEN, "....#%d", xpd_num);
	} else {
		snprintf(xpdname, XPD_NAMELEN, "%s", xpd->xpdname);
	}
	NOTICE("%s/%s: %s CODE = 0x%X\n", xbus->busname, xpdname, cmd->name, errorcode);
	switch(errorcode) {
		case 1:
			bad_cmd = &RPACKET_FIELD(pack, GLOBAL, ERROR_CODE, info.bad_spi_cmd);
			dump_packet("BAD_SPI_CMD", pack, 1);
			break;
		default:
			NOTICE("%s/%s: %s UNKNOWN CODE = 0x%X\n", xbus->busname, xpdname, cmd->name, errorcode);
			dump_packet("PACKET", pack, 1);
	}
	/*
	 * FIXME: Should implement an error recovery plan
	 */
	return 0;
}


xproto_table_t PROTO_TABLE(GLOBAL) = {
	.entries = {
		/*	Prototable	Card	Opcode		*/
		XENTRY(	GLOBAL,		GLOBAL, NULL_REPLY	),
		XENTRY(	GLOBAL,		GLOBAL, DEV_DESC	),
		XENTRY(	GLOBAL,		GLOBAL,	PCM_READ	),
		XENTRY(	GLOBAL,		GLOBAL,	SYNC_REPLY	),
		XENTRY(	GLOBAL,		GLOBAL, ERROR_CODE	),
	},
	.name = "GLOBAL",
	.packet_is_valid = global_packet_is_valid,
	.packet_dump = global_packet_dump,
};

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

	//DBG("\n");
	xe = xproto_global_entry(pack->content.opcode);
	return xe != NULL;
}

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

static bool pcm_valid(xpd_t *xpd, xpacket_t *pack)
{
	xpp_line_t	lines = RPACKET_FIELD(pack, GLOBAL, PCM_READ, lines);
	int		i;
	int		count = 0;

	BUG_ON(!pack);
	BUG_ON(pack->content.opcode != XPROTO_NAME(GLOBAL, PCM_READ));
	for_each_line(xpd, i)
		if(IS_SET(lines, i))
			count++;
	if(pack->datalen != (sizeof(xpp_line_t) + count * 8)) {
		static int rate_limit = 0;

		XPD_COUNTER(xpd, RECV_ERRORS)++;
		if((rate_limit++ % 1000) <= 10) {
			ERR("BAD PCM REPLY: pack->datalen=%d, count=%d\n", pack->datalen, count);
			dump_packet("BAD PCM REPLY", pack, 1);
		}
		return 0;
	}
	return 1;
}

#define	MAX_ENV_STR	20
#define	MAX_PATH_STR	60

int run_initialize_registers(xpd_t *xpd)
{
	int	ret;
	xbus_t	*xbus;
	char	busstr[MAX_ENV_STR];
	char	xpdstr[MAX_ENV_STR];
	char	unitstr[MAX_ENV_STR];
	char	subunitstr[MAX_ENV_STR];
	char	typestr[MAX_ENV_STR];
	char	revstr[MAX_ENV_STR];
	char	init_card[MAX_PATH_STR];
	char	*argv[] = {
		init_card,
		NULL
	};
	char	*envp[] = {
		busstr,
		xpdstr,
		unitstr,
		subunitstr,
		typestr,
		revstr,
		NULL
	};

	BUG_ON(!xpd);
	xbus = xpd->xbus;
	if(!initdir || !initdir[0]) {
		NOTICE("%s/%s: Missing initdir parameter\n", xbus->busname, xpd->xpdname);
		return -EINVAL;
	}
	snprintf(busstr, MAX_ENV_STR, "XPD_BUS=%s", xbus->busname);
	snprintf(xpdstr, MAX_ENV_STR, "XPD_NAME=%s", xpd->xpdname);
	snprintf(unitstr, MAX_ENV_STR, "XPD_UNIT=%d", xpd->addr.unit);
	snprintf(subunitstr, MAX_ENV_STR, "XPD_SUBUNIT=%d", xpd->addr.subunit);
	snprintf(typestr, MAX_ENV_STR, "XPD_TYPE=%d", xpd->type);
	snprintf(revstr, MAX_ENV_STR, "XPD_REVISION=%d", xpd->revision);
	if(snprintf(init_card, MAX_PATH_STR, "%s/init_card_%d_%d",
				initdir, xpd->type, xpd->revision) > MAX_PATH_STR) {
		NOTICE("%s/%s: Cannot initialize. pathname is longer than %d characters.\n",
				xbus->busname, xpd->xpdname, MAX_PATH_STR);
		return -E2BIG;
	}
	if(!down_read_trylock(&xbus->in_use)) {
		ERR("Skipped register initialization. %s is going down\n", xbus->busname);
		return -ENODEV;
	}
	DBG("%s/%s: running '%s' for type=%d revision=%d\n",
			xbus->busname, xpd->xpdname, init_card, xpd->type, xpd->revision);
	ret = call_usermodehelper(init_card, argv, envp, 1);
	/*
	 * Carefully report results
	 */
	if(ret == 0)
		DBG("%s/%s: '%s' finished OK\n", xbus->busname, xpd->xpdname, init_card);
	else if(ret < 0) {
		ERR("%s/%s: Failed running '%s' (errno %d)\n",
				xbus->busname, xpd->xpdname, init_card, ret);
	} else {
		byte	exitval = ((unsigned)ret >> 8) & 0xFF;
		byte	sigval = ret & 0xFF;

		if(!exitval) {
			ERR("%s/%s: '%s' killed by signal %d\n",
					xbus->busname, xpd->xpdname, init_card, sigval);
		} else {
			ERR("%s/%s: '%s' aborted with exitval %d\n",
					xbus->busname, xpd->xpdname, init_card, exitval);
		}
		ret = -EINVAL;
	}
	up_read(&xbus->in_use);
	return ret;
}

EXPORT_SYMBOL(run_initialize_registers);
