/*****************************************************************************
* 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"
#include "voicesync_pci152.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_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		irqcount, irqcount_max;
	os_mem_handle_t		memh;
	os_base_addr_t		base_addr;

#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(os_pci_dev_t, const struct pci_device_id*);
static void __devexit remove_voicesync_dev(os_pci_dev_t);

static void voicesync_remove_interface(os_pci_dev_t, 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[] = {
	{	XR17D152_VENDOR_ID, 0x300/*XR17D152_DEVICE_ID*/,
		PCI_ANY_ID, PCI_ANY_ID,
		0, 0, 0 },
	{ 0,0 }
};

static struct pci_driver voicesync_pci_driver = {
	.name		= "voicesync_pci152",
	.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_pci152_register(void)
{
	return pci_register_driver(&voicesync_pci_driver);
}

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

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

static int __devinit
init_voicesync_dev(os_pci_dev_t dev, const struct pci_device_id *ent)
{
	vspci_private_t	*priv;
	uint16_t		div = 0x00;
	uint8_t			drev, dvid, rev, ctrl;
	int rc;
			
	DEBUG_EVENT("Initializing Amfeltec VoiceSync (pci152) 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;

	rc = pci_enable_device(dev);
	if (rc){
		goto init_error;
	}
	pci_set_drvdata(dev, priv);

	os_pci_read_config_dword(dev,PCI_BASE_ADDRESS_0, (uint32_t*)&priv->base_addr);
	if (!priv->base_addr){
		DEBUG_EVENT("%s: Error: Failed to allocate PCI memory\n",
					VSPCI_NAME);
		rc = -ENOMEM;
		goto init_error;
	}
#if VS_VERBOSE > 1
	DEBUG_EVENT("%s: PCI memory at 0x%lX\n",
				VSPCI_NAME, (unsigned long)priv->base_addr);
#endif

	/* map the physical PCI memory to virtual memory */
	os_bus_space_map(priv->base_addr, 0x0, XR17D152_MEMORY_SIZE, &priv->memh);
	if (!priv->memh){ 
		DEBUG_EVENT("%s: Error: PCI virtual memory allocation failed\n",
					VSPCI_NAME); 
		rc = -ENOMEM;
		goto init_error;
	}
	priv->flags |= VSPCI_FLAG_MEMORY;

	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;

	os_bus_read_1(priv->memh, REG_XR17D152_DVID, &dvid);
	os_bus_read_1(priv->memh, REG_XR17D152_DREV, &drev);
	if (dvid != BITS_XR17D152_DVID){
		DEBUG_EVENT("%s: Error: Invalid Device identification (%02X:%02X)\n",
					VSPCI_NAME, dvid, BITS_XR17D152_DVID); 
		rc = -EINVAL;
		goto init_error;
	}

	/* Read oscillator value and initialize divider */
	os_bus_read_1(priv->memh, REG_XR17D152_MPIOLVL, &ctrl);
	rev = VSPCI_REV(ctrl);
	switch(rev){
	case VSPCI_REV_FREQ_8MHZ:
		if (ZT_CHUNKSIZE == 8){
			div = VSPCI_DIV_8MHZ_8;
		}else if (ZT_CHUNKSIZE == 16){
			div = VSPCI_DIV_8MHZ_16;
		}else if (ZT_CHUNKSIZE == 40){
			div = VSPCI_DIV_8MHZ_40;
		}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;
	}
	os_bus_write_2(priv->memh, REG_XR17D152_TIMERLSB, div);

	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)
	/* Enable LED */
	os_bus_read_1(priv->memh, REG_XR17D152_MPIOSEL, &ctrl);
	ctrl &= ~VSPCI_CTRL_LED;
	os_bus_write_1(priv->memh, REG_XR17D152_MPIOSEL, 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;

	os_bus_write_1(priv->memh, 
		REG_XR17D152_TIMERCNTL, BITS_XR17D152_TCNTL_ENABLE | BITS_XR17D152_TCNTL_START); 

#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);
	pci_disable_device(dev);
	return rc;
}

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

	if (priv->flags & VSPCI_FLAG_IRQ){
		free_irq(dev->irq, priv);
	} 
	if (priv->flags & VSPCI_FLAG_REGISTERED){
		zt_unregister(&priv->dahdi.span);
	}
	if (priv->flags & VSPCI_FLAG_MEMORY){
		os_bus_space_unmap(priv->memh, XR17D152_MEMORY_SIZE);
	}

	os_free(priv);

	return;
}

static void __devexit remove_voicesync_dev(os_pci_dev_t dev)
{
	vspci_private_t	*priv = pci_get_drvdata(dev);
#if defined(ENABLE_LED)
	uint8_t		ctrl;
#endif

	OS_ASSERT_VOID(priv == NULL);

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

#if defined(ENABLE_LED)
	/* clear led */
	os_bus_read_1(priv->memh, REG_XR17D152_MPIOLVL, &ctrl);
	ctrl &= ~VSPCI_CTRL_LED;
	os_bus_write_1(priv->memh, REG_XR17D152_MPIOLVL, ctrl);
#endif
	/* clear timer */
	os_bus_write_1(priv->memh, REG_XR17D152_TIMERCNTL, 0x00);
	pci_set_drvdata(dev, NULL);

	voicesync_remove_interface(dev, priv);

	pci_disable_device(dev);

	voicesync_unregister();
}

#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;
	uint32_t		ireg;
	uint8_t			ctrl;

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

	os_bus_read_4(priv->memh, REG_XR17D152_INT0, &ireg);
	if (IS_XR17D152_ITIMER(ireg) == 0){
		return IRQ_NONE;
	}

	os_bus_read_1(priv->memh, REG_XR17D152_TIMERCNTL, &ctrl);	//start timer 
	if (IS_XR17D152_TIMERPENDING(ctrl) == 0){
#if VS_VERBOSE > 2
		DEBUG_EVENT("%s: Warning: Voicesync IRQ Timer (not ready)\n", VSPCI_NAME);
#endif
		return IRQ_HANDLED;
	}

	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){
		os_bus_read_1(priv->memh, REG_XR17D152_MPIOLVL, &ctrl);
		if (VSPCI_LED(ctrl)){
			ctrl &= ~VSPCI_CTRL_LED;
		}else{
			ctrl |= VSPCI_CTRL_LED;
		}
		os_bus_write_1(priv->memh, REG_XR17D152_MPIOLVL, ctrl);
	}
#endif

	return IRQ_HANDLED;
}

