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

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

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(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;
	extern ulong	pcm_gen;

	BUG_ON(!xbus);
	BUG_ON(!xpd);
	lines &= xpd->enabled_chans;
	if(pcm_gen != 0)
		return 0;
//	if(lines == 0)
//		return 0;

	/*
	 * 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(i = 0; i < CHANNELS_PERXPD; i++) {
		if(IS_SET(lines, i)) {
			memcpy(pcm, (byte *)buf, ZT_CHUNKSIZE);
			pcm += ZT_CHUNKSIZE;
		}
		buf += ZT_CHUNKSIZE;
	}
	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, 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);
	int			xpd_num = XPD_NUM(pack->content.addr);
	struct card_desc_struct	*card_desc;

	DBG("xpd=%d type=%d rev=%d line_status=0x%04X\n",
			xpd_num, type, rev, line_status);
	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;
	card_desc->xbus = xbus;
	card_desc->type = type;
	card_desc->rev = rev;
	card_desc->xpd_num = xpd_num;
	INIT_WORK(&card_desc->work, card_detected, card_desc);
	DBG("Queueing xpp_worker for xpd %d\n", xpd_num);
	if(!queue_work(xpp_worker, &card_desc->work)) {
		ERR("Failed to queue card description work\n");
		return -EINVAL;
	}
	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;

	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(!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 (i = 0; i < CHANNELS_PERXPD; 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)
{
	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("SYNC_REPLY: 0x%X\n", RPACKET_FIELD(pack, GLOBAL, SYNC_REPLY, mask));
	return 0;
}


xproto_table_t PROTO_TABLE(GLOBAL) = {
	.entries = {
		/*	Card	Opcode		*/
		XENTRY(	GLOBAL, DEV_DESC	),
		XENTRY(	GLOBAL,	PCM_READ	),
		XENTRY(	GLOBAL,	SYNC_REPLY	),
	},
	.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(xpacket_t *pack)
{
	DBG("\n");
}

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 (i = 0; i < CHANNELS_PERXPD; 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);
		}
		return 0;
	}
	return 1;
}

