/*
 * Copyright (c) 2003 Linuxant inc.
 * Copyright (c) 2001-2003 Conexant Systems, Inc.
 *
 * NOTE: The use and distribution of this software is governed by the terms in
 * the file LICENSE, which is included in the package. You must read this and
 * agree to these terms before using or distributing this software.
 *
 */
#include "oscompat.h"
#include <linux/slab.h>
#include <linux/pci.h>
#include <linux/time.h>
#include <asm/irq.h>
#include <asm/io.h>
#include <linux/interrupt.h>
#include <linux/pm.h>

#include "ossysenv.h"
#include "linuxres.h"


BOOL	LinuxGetHwPciRes(void **pPciDev, unsigned long *pbaseaddr0, unsigned long *pbaseaddr1, unsigned short DeviceId, unsigned short VendorId)
{
	struct pci_dev *pdev;
#if ( LINUX_VERSION_CODE >= KERNEL_VERSION(2,4,0) )
	int err;
#endif

	//printk(KERN_DEBUG "LinuxGetHwPciRes DevId=0x%x VendId=0x%x\n", DeviceId, VendorId );

	if (!pcibios_present())
		return FALSE;


	pdev = pci_find_device(VendorId, DeviceId, NULL);
	if (pdev == NULL)
		return FALSE;

	*pPciDev = pdev;
#if ( LINUX_VERSION_CODE < KERNEL_VERSION(2,4,0) )
	*pbaseaddr0 = pdev->base_address[0];
	*pbaseaddr1 = pdev->base_address[1];
#else
	*pbaseaddr0 = pci_resource_start(pdev, 0);
	*pbaseaddr1 = pci_resource_start(pdev, 1);
  	if ( (err = pci_enable_device(pdev)) ) 
  	{ 

  		printk(KERN_ERR "LinuxGetHwPciRes: pci_enable_device() failed (err=%d), DeviceId=0x%x VendorId=0x%x\n", err, DeviceId, VendorId);
		return FALSE;
  	} 
#endif
 	pci_set_master(pdev); 
			
	// cancel the LSB for io address 
	*pbaseaddr0 = (*pbaseaddr0) & 0xFFFFFFFE;
	*pbaseaddr1 = (*pbaseaddr1) & 0xFFFFFFFE;
 
#if 0
	printk(KERN_DEBUG "LinuxGetHwPciRes: found device, addr0=0x%lx addr1=0x%lx irq=%d\n",
				*pbaseaddr0,
				*pbaseaddr1,
				pdev->irq);
#endif

	return TRUE;
}

static int 
linux_pm_callback(struct pm_dev *dev, pm_request_t rqst, void *data) 
{
	PLINUX_PM_CONFIG pPmConf = (PLINUX_PM_CONFIG) dev->data;

	if ( ! pPmConf )
		return 0;

	//printk(KERN_DEBUG "%s: pm event 0x%x received\n", __FUNCTION__, rqst);
	
	switch (rqst) {
		case PM_SUSPEND: 
			if(pPmConf->SuspendOKCB && !pPmConf->SuspendOKCB(pPmConf->SuspendOKInst))
				return -EBUSY;


			if(pPmConf->SuspendCB && !pPmConf->SuspendCB(pPmConf->SuspendInst))
				return -EIO;
		break;
		case PM_RESUME: 
			if(pPmConf->ResumeCB && !pPmConf->ResumeCB(pPmConf->ResumeInst))
				return -EIO;
		break;
		/*
		 * we'd also like to find out about
		 * power level changes because some biosen
		 * could do mean things to our device when they
		 * change their power state.
		 */
        }
	return 0;
}

void *LinuxInitPowerManagement(void *pPciDev, PLINUX_PM_CONFIG pPmConf)
{
	struct pm_dev *pmdev;
	struct pci_dev *pcidev = (struct pci_dev *)pPciDev;

	pmdev = pm_register(PM_PCI_DEV, PM_PCI_ID(pcidev), linux_pm_callback);
	if (pmdev) {
		pmdev->data = pPmConf;
	}

	return (void *)pmdev;
}

void	LinuxTermPowerManagement(void *pmdev)
{
#if LINUX_VERSION_CODE < KERNEL_VERSION(2,4,0)
	pm_unregister_all(linux_pm_callback);
#else
	pm_unregister((struct pm_dev *)pmdev);
#endif
}

BOOL	LinuxHookInterrupt(void *pPciDev, void (*isr)(int irq, void *devidp, void *unused), void *devid)
{
	struct pci_dev *dev = (struct pci_dev *)pPciDev;
	int err;

	//printk(KERN_DEBUG "%s\n", __FUNCTION__);
	err = request_irq(dev->irq,
			/* for efficiency we assume that the isr function passed has the
			 * same calling conventions and arguments as standard Linux
			 * interrupt handlers. If these ever change, a stub-function to
			 * convert would be required.
			 */
			(void (*)(int irq, void *devidp, struct pt_regs *))isr,
			SA_SHIRQ, "hsf", devid); 

  	if (err) {
		printk(KERN_ERR "LinuxHookInterrupt: request for IRQ %d failed\n", dev->irq);
		return FALSE;
	}

	return TRUE;
}

void	LinuxUnhookInterrupt(void *pPciDev, void *devid)
{
	struct pci_dev *dev = (struct pci_dev *)pPciDev;

	//printk(KERN_DEBUG "%s\n", __FUNCTION__);
	ASSERT(dev);
	if(dev) {
		ASSERT(dev->irq);
		if(dev->irq)
			free_irq(dev->irq, devid); 
	}
}

void	*LinuxMapPhys(unsigned long offset, unsigned long size)
{
//	printk(KERN_DEBUG "%s: offset=0x%lx size=%lu\n", __FUNCTION__, offset, size);
	return ioremap(offset, size);
}

unsigned short LinuxReadw(void *addr)
{
	unsigned short val = readw(addr);
/*  	printk(KERN_DEBUG "LinuxReadw read 0x%x from 0x%x\n",val,addr);  */
	return val;
}

unsigned int LinuxReadl(void *addr)
{
	unsigned int val = readl(addr);
/*  	printk("LinuxRead read 0x%x from 0x%x\n",val,addr); */
	return (val);
}

void	LinuxWritew(void *addr, unsigned short wVal)
{
/*  	printk(KERN_DEBUG "LinuxWritew writes 0x%x to 0x%x\n",wVal,addr); */
	writew(wVal, addr);
}
 
 
void	LinuxWritel(void *addr, unsigned int dwVal)
{
/*  	printk(KERN_DEBUG "LinuxWritel(%x, %x)\n",addr, dwVal); */
	writel(dwVal, addr);
}
 
 
unsigned char LinuxIoReadb(unsigned int port)
{
	unsigned char val = inb(port);
/*  	printk(KERN_DEBUG "LinuxIoReadb read 0x%x from 0x%x\n",val,port);  */
	return val;
}
 
 
unsigned short LinuxIoReadw(unsigned int port)
{
	unsigned short val = inw(port);
/*  	printk(KERN_DEBUG "LinuxIoReadw read 0x%x from 0x%x\n",val,port);  */
	return val;
}
 
unsigned int LinuxIoReadl(unsigned int port)
{
	unsigned int val = inl(port);
/*  	printk(KERN_DEBUG "LinuxIoReadw read 0x%x from 0x%x\n",val,port);  */
	return val;
}
 
 
void	LinuxIoWriteb(unsigned int port, unsigned char bVal)
{
/*  	printk(KERN_DEBUG "LinuxIoWritew writes 0x%x to 0x%x\n",bVal,addr); */
	outb(bVal, port);
}
 
void	LinuxIoWritew(unsigned int port, unsigned short wVal)
{
/*  	printk(KERN_DEBUG "LinuxIoWritew writes 0x%x to 0x%x\n",wVal,addr); */
	outw(wVal, port);
}
 
 
void	LinuxIoWritel(unsigned int port, unsigned int dwVal)
{
/*  	printk(KERN_DEBUG "LinuxIoWritel(%x, %x)\n",addr, dwVal); */
	outl(dwVal, port);
}
 
void LinuxPciReadConfigdw(void *pPciDev, unsigned char Offset, unsigned long *pVal)
{
	pci_read_config_dword(pPciDev, Offset, (u32*)pVal);
}
 
void LinuxPciReadConfigw(void *pPciDev, unsigned char Offset, unsigned short *pVal)
{
	pci_read_config_word(pPciDev, Offset, pVal);
}
 
void LinuxPciReadConfigb(void *pPciDev, unsigned char Offset, unsigned char *pVal)
{
	pci_read_config_byte(pPciDev, Offset, pVal);
}
 
void LinuxPciWriteConfigdw(void *pPciDev, unsigned char Offset, unsigned long Val)
{
	pci_write_config_dword(pPciDev, Offset, Val);
}
 
void LinuxPciWriteConfigw(void *pPciDev, unsigned char Offset, unsigned short Val)
{
	pci_write_config_word(pPciDev, Offset, Val);
}
 
void LinuxPciWriteConfigb(void *pPciDev, unsigned char Offset, unsigned char Val)
{
	pci_write_config_byte(pPciDev, Offset, Val);
}
 
unsigned long crit_section_start(void)
{
	unsigned long flags;

	save_flags(flags);
	cli();

	OsObtainedLock();

	return flags;
}

void crit_section_end(unsigned long flags)
{
	restore_flags(flags);
	OsReleasedLock();
}
 
 
unsigned long LinuxCalcCpuRate(void)
{
	struct timeval tv;
	unsigned long flags;
	unsigned long time1, time2, cpurate;
	unsigned int target_usec;
	
	// first ensure that the tv_usec will not wraparound
	do_gettimeofday( &tv );
 
	if ( tv.tv_usec > 990000 ) {
		do
		{
			do_gettimeofday( &tv );
		} while ( tv.tv_usec > 990000 );
	}
 
	flags = crit_section_start();
	// calc the cpu rate 
	do_gettimeofday( &tv );
	rdtscl(time1);
	target_usec = tv.tv_usec + 1000;
	do
	{
		do_gettimeofday( &tv );
	} while ( tv.tv_usec < target_usec );
	rdtscl(time2);

	crit_section_end( flags );
 
	cpurate = ( (time2-time1+500) /1000 ) * 1000000; // to get whole number 

#ifdef DEBUG
	printk(KERN_DEBUG"%s: %lu\n", __FUNCTION__, cpurate);
#endif

	return cpurate;
}
 
