/*****************************************************************************
* voicesync_pci.c	Amfeltec VoiceSync (PCI)- PCI Voice Sync Timer Interface
*
*
* Copyright:	(c) 2010 Amfeltec Corp.
*
* ============================================================================
* June 10, 2008		Initial version.
*****************************************************************************/

/***************************************************************************
****		I N C L U D E  		F I L E S			****
***************************************************************************/


#include "os/os_abstr_includes.h"
#include "os/os_abstr_defines.h"
#include "os/os_abstr_debug.h"
#include "os/os_abstr_basic.h"
#include "os/os_abstr_time.h"
#include "os/os_abstr_bus.h"
#include "os/os_abstr_pci.h"

#if defined(ENABLE_STATS)
# include "voicesync_stats.h"
#endif
#include "voicesync.h"

/***************************************************************************
****		   	  M A C R O S / D E F I N E S			****
***************************************************************************/
#define VSPCI_NAME		"vspci"
#define VSPCI_DESC		"Amfeltec VoiceSync (PCI)"

#define VSPCI_VENDOR_ID		0x10EE
#define VSPCI_DEVICE_ID		0x0300

#define VSPCI_REG_CTRL		0x40

#define VSPCI_DEF_CLOCK_EXT		/* External */
#define VSPCI_DEF_IRQ_FREQ	1	/* interrupt every ms */

#define VSPCI_CLOCK_SRC		"voicesync"

#define VSPCI_RXBYTES_SEC	(VS_RX_LEN * VSPCI_RXINT_IN_SEC)	// number of bytes per sec
#define VSPCI_RXBYTES_USEC  	(VS_RXBYTES_SEC/1000000)		// number of bytes per usec		

#define VSPCI_CTRL_BIT_IRQ	31
#define VSPCI_CTRL_BIT_LED	30
#define VSPCI_CTRL_BIT_REV	28
#define VSPCI_CTRL_BIT_DIV	0

#define VSPCI_CTRL_MASK_REV	0x00000003
	
#define VSPCI_CTRL_IRQ		(1 << VSPCI_CTRL_BIT_IRQ)
#define VSPCI_CTRL_LED		(1 << VSPCI_CTRL_BIT_LED)
#define VSPCI_CTRL_DIV_1ms	0x00000000
#define VSPCI_CTRL_DIV_2ms	0x00000001
#define VSPCI_CTRL_DIV_5ms	0x00000002
#define VSPCI_CTRL_DIV_10ms	0x00000003
#define VSPCI_CTRL_DIV_CLR	0x00000003
		
#define VSPCI_IRQ(ctrl)		((ctrl)&VSPCI_CTRL_IRQ)
#define VSPCI_LED(ctrl)		((ctrl)&VSPCI_CTRL_LED)
#define VSPCI_REV(ctrl)		((ctrl)>>VSPCI_CTRL_BIT_REV)&VSPCI_CTRL_MASK_REV

#define VSPCI_REV_FREQ_1MHZ	0x01
#define VSPCI_REV_FREQ_DECODE(rev)					\
		((rev) == VSPCI_REV_FREQ_1MHZ) ? "1MHZ" : "??Mhz"

#define VSPCI_FLAG_NONE		0x00000000
#define VSPCI_FLAG_MEMORY	0x00000001
#define VSPCI_FLAG_REGISTERED	0x00000002
#define VSPCI_FLAG_IRQ		0x00000004

/***************************************************************************
****		     S T R U C T U R E S / T Y P E D E F S		****
***************************************************************************/
typedef struct voicesync_private 
{
	uint32_t		magic;
	os_pci_dev_t		dev;

	uint32_t		flags;
	uint32_t		ctrl;
	uint32_t		irqcount, irqcount_max;

#if defined(ENABLE_STATS)
	voicesync_stats_t	stats;
#endif
	voicesync_dahdi_t	dahdi;

} vspci_private_t;

/***************************************************************************
****			F U N C T I O N  P R O T O T Y P E S		****
***************************************************************************/

static int __devinit init_voicesync_dev(struct pci_dev*, const struct pci_device_id*);
static void __devexit remove_voicesync_dev(struct pci_dev*);

static void voicesync_remove_interface(struct pci_dev*, vspci_private_t*);
#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,20)  
static irqreturn_t voicesync_irq (int irq, void* dev_id, struct pt_regs *regs);
#else
static irqreturn_t voicesync_irq (int irq, void* dev_id);
#endif

/***************************************************************************
****		       G L O B A L  V A R I A B L E S			****
***************************************************************************/

static struct pci_device_id voicesync_pci_tbl[] = {
	{	VSPCI_VENDOR_ID, VSPCI_DEVICE_ID,
		PCI_ANY_ID, PCI_ANY_ID,
		0, 0, 0 },
	{ 0,0 }
};

static struct pci_driver voicesync_pci_driver = {
	.name		= "voicesync_pci",
	.probe		= init_voicesync_dev,
	.remove		= __devexit_p(remove_voicesync_dev),
	.id_table	= voicesync_pci_tbl,
};


/***************************************************************************
****			 F U N C T I O N  D E F I N I T I O N		****
***************************************************************************/

int voicesync_pci_register(void)
{
	return pci_register_driver(&voicesync_pci_driver);
}

int voicesync_pci_unregister(void)
{
	pci_unregister_driver(&voicesync_pci_driver);
	return 0;
}

/*===========================================================================*/
/*============================ PCI initialization ===========================*/

static int __devinit
init_voicesync_dev(struct pci_dev *dev, const struct pci_device_id *ent)
{
	vspci_private_t	*priv;
	uint32_t	div = 0x00000000;
	uint8_t		rev;
	int 		rc;
			
	DEBUG_EVENT("Initializing Amfeltec VoiceSync (pci) interface (%s) ...\n",
					VSPCI_NAME);
	if (voicesync_register() != 0){
		return -EINVAL;
	}
	priv = os_malloc(sizeof(vspci_private_t));
	if (!priv){
		return -ENOMEM;
	}
	
	priv->magic	= VS_MAGIC;
	priv->dev 	= dev;

	pci_set_drvdata(dev, priv);

	if (voicesync_initialize(&priv->dahdi)){
		DEBUG_EVENT("%s: Error: Failed to register voicesync interface into Dahdi\n",
					VSPCI_NAME); 
		rc = -EINVAL;
		goto init_error;
	}
	priv->flags |= VSPCI_FLAG_REGISTERED;

	/* Read current control register */
	os_pci_read_config_dword(dev,VSPCI_REG_CTRL, &priv->ctrl);

	/* Detect oscillator type*/
	rev = VSPCI_REV(priv->ctrl);
	switch(rev){
	case VSPCI_REV_FREQ_1MHZ:
		if (ZT_CHUNKSIZE == 8){
			div = VSPCI_CTRL_DIV_1ms;
		}else if (ZT_CHUNKSIZE == 16){
			div = VSPCI_CTRL_DIV_2ms;
		}else if (ZT_CHUNKSIZE == 40){
			div = VSPCI_CTRL_DIV_5ms;
		}else if (ZT_CHUNKSIZE == 80){
			div = VSPCI_CTRL_DIV_10ms;
		}else{
			DEBUG_EVENT("%s: Unsupported Zaptel/Dahdi chunksize (%d)!\n",
					VSPCI_NAME, ZT_CHUNKSIZE);
			rc = -EINVAL;
			goto init_error;
		}
		break;
	default:
		DEBUG_EVENT("%s: Unsupported board revision (%02X)!\n",
					VSPCI_NAME, rev);
		rc = -EINVAL;
		goto init_error;
	}

	priv->ctrl &= ~VSPCI_CTRL_DIV_CLR;
	priv->ctrl |= div;
	os_pci_write_config_dword(dev,VSPCI_REG_CTRL, priv->ctrl);

	if (ZT_CHUNKSIZE == 8){
		priv->irqcount_max = 1000;
	}else if (ZT_CHUNKSIZE == 16){
		priv->irqcount_max = 500;
	}else if (ZT_CHUNKSIZE == 40){
		priv->irqcount_max = 200;
	}else if (ZT_CHUNKSIZE == 80){
		priv->irqcount_max = 100;
	}

#if defined(ENABLE_LED)
	priv->ctrl |= VSPCI_CTRL_LED;
	os_pci_write_config_dword(dev,VSPCI_REG_CTRL, priv->ctrl);		
#endif

	/* Register interrupt */
	rc = request_irq(	dev->irq, 
				voicesync_irq, 
				IRQF_SHARED, 
				"voicesync_pci", 
				priv);
	if (rc < 0){
		DEBUG_EVENT("%s: Error: Failed to register ISR!\n",
					VSPCI_NAME);
		goto init_error;		
	}
	priv->flags |= VSPCI_FLAG_IRQ;

	/* Enable interrupts */
//	priv->ctrl |= VSPCI_CTRL_IRQ;
//	os_pci_write_config_dword(dev,VSPCI_REG_CTRL, priv->ctrl);

#if defined(ENABLE_STATS)
	voicesync_stats_init(&priv->stats);
#endif

	DEBUG_EVENT("Amfeltec PCI System Timer (rev %d) is ready (%s:chunksize %d)!\n",
			rev, VSPCI_REV_FREQ_DECODE(rev), ZT_CHUNKSIZE);
	return 0;

init_error:

	pci_set_drvdata(dev, NULL);
	voicesync_remove_interface(dev, priv);
	return rc;
}

static void __devexit remove_voicesync_dev(struct pci_dev *dev)
{
	vspci_private_t	*priv = pci_get_drvdata(dev);

	OS_ASSERT_VOID(priv == NULL);

#if defined(ENABLE_STATS)
	voicesync_stats_release(&priv->stats);
#endif
	pci_set_drvdata(dev, NULL);

	voicesync_remove_interface(dev, priv);
	voicesync_unregister();
}

static void voicesync_remove_interface(struct pci_dev *dev, vspci_private_t *priv)
{
	OS_ASSERT_VOID(dev == NULL);
	OS_ASSERT_VOID(priv == NULL);

	priv->ctrl &= ~(VSPCI_CTRL_IRQ | VSPCI_CTRL_LED);
	os_pci_write_config_dword(dev,VSPCI_REG_CTRL, priv->ctrl);		

	if (priv->flags & VSPCI_FLAG_IRQ){
		free_irq(dev->irq, priv);
	} 
	if (priv->flags & VSPCI_FLAG_REGISTERED){
		zt_unregister(&priv->dahdi.span);
	}
	os_free(priv);
	return;
}

#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,20)  
static irqreturn_t voicesync_irq (int irq, void* dev_id, struct pt_regs *regs)
#else
static irqreturn_t voicesync_irq (int irq, void* dev_id)
#endif
{
	vspci_private_t	*priv = (vspci_private_t*)dev_id;
	os_pci_dev_t	dev = NULL;
	uint32_t	ireg;

	// This is not our device
	if (!dev_id || priv->magic != VS_MAGIC || !priv->dev) return IRQ_NONE;

	dev = priv->dev;
	os_pci_read_config_dword(dev,VSPCI_REG_CTRL, (uint32_t*)&ireg);
	if (!VSPCI_IRQ(ireg)) return IRQ_NONE;

	priv->irqcount++;
	zt_receive(&priv->dahdi.span);
	zt_transmit(&priv->dahdi.span);

#if defined(ENABLE_STATS)
	priv->stats.rx_bytes+=VS_RX_LEN;
#endif

#if defined(ENABLE_LED)
	if (priv->irqcount % priv->irqcount_max == 0){
		if (VSPCI_LED(priv->ctrl)){
			priv->ctrl &= ~VSPCI_CTRL_LED;
		}else{
			priv->ctrl |= VSPCI_CTRL_LED;
		}
		os_pci_write_config_dword(dev,VSPCI_REG_CTRL, priv->ctrl);
	}
#endif

	return IRQ_HANDLED;
}

