/*--------------------------------------------------------------------------*\

    FILE....: VPB.C
    TYPE....: Linux device driver
    AUTHOR..: David Rowe
    DATE....: 22/9/99

    Linux kernel mode device driver for ISA & PCI VPB cards.  Interfaces user
    mode driver (VBPAPI).  This version compiles on 2.2.14 Linux Kernel.  

    Compile with:

    cc vpb.c -c -O6 -m486 -I/usr/src/linux/include
    
    Note on R/H 7.1 I needed to use:
    
    cc vpb.c -c -O6 -m486 -I/usr/src/linux-2.4/include

    Install with:

    insmod vpb.o (driver wil print out major node number, eg 254)
    mknod /dev/vpb0 c 254 0 (subs 254 for your major node number)

    Use:

    cat /proc/msg

    to view debug info.

    Thanks to:
    Fred Leeflang & David Sugar for helping with the kernel 2.4 port.

\*--------------------------------------------------------------------------*/

/*---------------------------------------------------------------------------*\

         Voicetronix Voice Processing Board (VPB) Software

         Copyright (C) 1999-2018 Voicetronix www.voicetronix.com.au
         Copyright (C) 2006-2018 Ron Lee <ron@voicetronix.com.au>

         This library 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 library 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 library; if not, write to the Free Software
         Foundation, Inc., 51 Franklin St, Fifth Floor, Boston,
         MA  02110-1301  USA

\*---------------------------------------------------------------------------*/

#define	NAME	     "vpb"
#define SIZE_WD      0x10000    // size of DSP SRAM block in words
#define MAX_V4PCI    32         // max number of V4PCI's
#define BLOCK        1          // block size for read/writes
#define BLOCK_DELAY  2          // delay (us) between adjacent blocks
#define SIZE_LCR     128        // size of 9050 local config reg space in bytes

#define EEPROM_SIZE  64
#define EEPROM_CS    25		// Chip select bit
#define EEPROM_CLK   24
#define EEPROM_RDBIT 27
#define EEPROM_WRBIT 26

#define IIC_SDA_CTL	6	// USER2 Pin function select
#define IIC_SDA_DIR	7	// USER2 Pin input/output select
#define IIC_SDA_IO	8	// USER2 Pin data in/out
#define IIC_SCL_CTL	9	// USER3 Pin function select
#define IIC_SCL_DIR	10	// USER3 Pin input/output select
#define IIC_SCL_IO	11	// USER3 Pin data in/out

#ifndef MODULE
#define MODULE
#endif
#ifndef __KERNEL__
#define __KERNEL__
#endif


#include <linux/version.h>

#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,33))
 #include <generated/autoconf.h>
#else
 #include <linux/autoconf.h>
#endif

// Module version boilerplate
#if defined( CONFIG_MODVERSIONS) && !defined(MODVERSIONS)
#define MODVERSIONS
#endif

#ifdef MODVERSIONS
#if (LINUX_VERSION_CODE > KERNEL_VERSION(2,6,4))
#include <config/modversions.h>
#else
#include <linux/modversions.h>
#endif
#endif

#include <linux/ioport.h>
#include <linux/errno.h>
#include <linux/mm.h>
#include <linux/fs.h>
#include "vpb_ioctl.h"
//#include "iic.h"		// s/w IIC emulation
#include "iic.c"
#include <asm/segment.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <asm/io.h>
#include <linux/vmalloc.h>
#include <linux/pci.h>
#include <linux/delay.h>
#include <linux/uaccess.h>


#if LINUX_VERSION_CODE > KERNEL_VERSION(2,4,9)
#ifdef MODULE_LICENSE
MODULE_LICENSE("GPL");
#endif
#endif

#if LINUX_VERSION_CODE > KERNEL_VERSION(2,6,0)
#define USE_UDEV
#define vpbmemcpy_toio(dest,src,bcount) iowrite16_rep(dest,src,bcount)
#define vpbmemcpy_fromio(dest,src,bcount) ioread16_rep(src,dest,bcount)
#else
#define vpbmemcpy_toio(dest,src,bcount) memcpy_toio(dest,src,bcount*sizeof(short))
#define vpbmemcpy_fromio(dest,src,bcount) memcpy_fromio(dest,src,bcount*sizeof(short))
#endif

/*---------------------------------------------------------------------------*\

				GLOBALS
							
\*---------------------------------------------------------------------------*/

// ensure dynamic major number picked
unsigned int major = 0;

static int vpb_open( struct inode*, struct file* );
static int vpb_release( struct inode*, struct file* );
static long vpb_ioctl(struct file*, unsigned int, unsigned long);

void card_detail(int pci_n);
unsigned int eeread(int PCIn, int addr);
void cntrlbit(int PCInn, int bit, int val);
void wrbit(int PCInn, int bit);
int rdbit(int PCInn);
void iicout(int PCInn, int sig,int state);
char iicin(int PCInn, int sig);

#ifdef USE_UDEV
 #if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,13)
  static struct class_simple *vpb_class;
  #define class_create(x,y)              class_simple_create(x,y)
  #define class_destroy(x)               class_simple_destroy(x)
  #define device_create(c,p,n,d,u,...)   class_simple_device_add(c,n,NULL,u , ## __VA_ARGS__)
  #define device_destroy(c,n)            class_simple_device_remove(n)

 #elif LINUX_VERSION_CODE < KERNEL_VERSION(2,6,26)
  static struct class *vpb_class;
  #define device_create(c,p,n,d,u,...)   class_device_create(c,p,n,NULL,u , ## __VA_ARGS__)
  #define device_destroy(c,n)            class_device_destroy(c,n)

 #elif LINUX_VERSION_CODE < KERNEL_VERSION(2,6,27)
  static struct class *vpb_class;
  #define device_create(c,p,n,d,u,...)   device_create_drvdata(c,p,n,d,u , ## __VA_ARGS__)

 #else
  static struct class *vpb_class;

 #endif
#endif

static struct file_operations vpb_fops={
	.owner		= THIS_MODULE,
	.llseek		= NULL,
	.read		= NULL,
	.write		= NULL,
	.poll		= NULL,
	.unlocked_ioctl	= vpb_ioctl,
	.open		= vpb_open,
	.release	= vpb_release,
	.fasync		= NULL
};

// number of valid PCI devices detected
static int numPCI;

// translated base address of PLX9050 regions
static unsigned char  *base0[MAX_V4PCI];
static u32             base1[MAX_V4PCI];
static unsigned short *base2[MAX_V4PCI];

// intermediate kernel space buffer from transfers between I/O memory (PCI
// card DSP memory) and user memory (application)
static short buf[SIZE_WD];

static atomic_t vpb_inuse;


/*---------------------------------------------------------------------------*\

				   FUNCTIONS
							
\*---------------------------------------------------------------------------*/

/*--------------------------------------------------------------------------*\

	FUNCTION.: init_module()
	AUTHOR...: David Rowe
	DATE.....: 15/4/00

	Called by the O/S when the module is loaded. Searches for a 
	valid PCI device, no action for ISA cards.

\*--------------------------------------------------------------------------*/

int init_module(void)
{
	char s[4];
	struct pci_dev *dev = NULL;
	unsigned short sn_low=0, sn_high=0;
	int ret;
	u32 reg28;

	unsigned long tmp;

	atomic_set( &vpb_inuse, 0 );

	major = register_chrdev(major, NAME, &vpb_fops);
	printk(KERN_INFO NAME ": Driver Version = %s\n",VT_VERSION);
	printk(KERN_INFO NAME ": major = %d\n",major);

	// search for V4PCI devices on PCI bus

	numPCI = 0;
	while((dev = pci_get_device(PCI_VENDOR_ID_PLX, PCI_DEVICE_ID_PLX_9050,
				     dev)) != NULL) {

		// check that subsytem ID & Subsytem Vendor matches
		pci_read_config_dword(dev, 0x2c, (u32*)s);
//		printk("s[3] = %c 0x%02x s[2] = %c 0x%02x\n",s[3],s[3]&0xff, s[2],s[2]&0xff);

		if ((s[3] == 'V') && (s[2] == '4') && (s[1] == 'V') &&
		    (s[0] == 'T')) {

			// OK, V4PCI found, so map address regions.....

			// PLX9050 bug work-around.  The 9050 disables access
			// to LCR when phys addr has bit 7 set by bios, an
			// event that is machine and motherboard dependant.
			// The fix is to declare another region in the eeprom
			// (base address 3) that is 256 bytes long, then let
			// base 0 use that address, which is guaranteed to 
			// have bit 7 = 0 due to it's length.
			tmp = pci_resource_start(dev,3);
			printk(KERN_INFO NAME ": tmp [0x%lx] dev->res3 [0x%lx]\n",
                                              tmp, (unsigned long)dev->resource[3].start);

                        dev->resource[0].start = dev->resource[3].start;
                        pci_write_config_dword(dev, PCI_BASE_ADDRESS_0,
                                               dev->resource[0].start);

                        base0[numPCI] = ioremap(dev->resource[0].start &
                                                PCI_BASE_ADDRESS_MEM_MASK,
                                                SIZE_LCR);

			tmp = pci_resource_start(dev,2);
			printk(KERN_INFO NAME ": tmp [0x%lx] dev->res2 [0x%lx]\n",
                                              tmp, (unsigned long)dev->resource[2].start);

                        base2[numPCI] = ioremap_nocache(dev->resource[2].start &
                                                PCI_BASE_ADDRESS_MEM_MASK,
                                                sizeof(short)*SIZE_WD);

                        // make sure no-one access LCR via I/O space mapping
                        base1[numPCI] = dev->resource[1].start &
                                        PCI_BASE_ADDRESS_IO_MASK;
#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,0)
                        ret = check_region(base1[numPCI], SIZE_LCR);
                        printk(KERN_INFO NAME ":check_region ret = %d\n",ret);
#endif
                        request_region(base1[numPCI], SIZE_LCR, NAME);

			// set wait-states
/*
    0    0    4    4    0    4    2    2  <- HEX
 0000 0000 0100 0100 0000 0100 0010 0010  [LAS0BRD 28]
 ||   ||   ||   |    |  | |      |   ||+- 0	  0	Burst Disabled   						
 ||   ||   ||   |    |  | |      |   |+-- 1	  1	READY i/p Enabled
 ||   ||   ||   |    |  | |      || |+--- 0	  0	Bterm Disabled 
 ||   ||   ||   |    |  | |      |+ +---- 00	  0	No Prefetch
 ||   ||   ||   |    |  | |||| ||+------- 1	  1	Prefetch set by[4:3]
 ||   ||   ||   |    |  | |+++ ++-------- 10000	 16 *	Read Addr to Data Wait
 ||   ||   ||   |    |||+ +-------------- 00	  0	Read Data to Data Wait
 ||   ||   ||   |    |++----------------- 00	  0	R/W Data to Addr Wait 
 ||   ||   ||   ++++ +------------------- 01000   8 *	Write Addr to Data Wait 
 ||   ||   ||++-------------------------- 00	  0	Write Data to Data Wait
 ||   ||   ++---------------------------- 01      1 	Bus Width 16Bit
 ||   || +------------------------------- 0	  0	Byte order Little Ended
 ||   ||+-------------------------------- 0	  0	Byte Lane Mode
 ||   ++--------------------------------- 00	  0	Read Strobe Delay
 ||++------------------------------------ 00	  0	Write Strobe Delay
 ++-------------------------------------- 00	  0	Write Cycle Hold
                                          [BIN]  [DEC]
*/
			// This value has been in driver for a long time
			reg28 = 0x00440422;
//			writel(0x00440422, base0[0]+0x28);    
			// does it have xilinx code 4 or greater?
			sn_high = eeread(numPCI,54);
			sn_low = eeread(numPCI,55);
			if(sn_low < 0x1362) {
				// Now adding 1 wait state to Write cycle hold
				writel(0x070440422, base0[0]+0x28);
				printk(KERN_INFO NAME": 1WS Write cycle\n");
			}
			else {
				// New Xilinx code runs Zero Wait state 
				writel(0x00400022, base0[0]+0x28);
				printk(KERN_INFO NAME": 0WS Write cycle\n");
			}
			// Set IIC control for user i/o 2 & 3.
			// Note: This should be the default after reset.
                        cntrlbit(numPCI,IIC_SDA_IO, 0);  //Ensure =low
                        cntrlbit(numPCI,IIC_SDA_DIR,0);  // i/p Hi-Z
			cntrlbit(numPCI,IIC_SDA_CTL,0);  // User I/O
                        cntrlbit(numPCI,IIC_SCL_IO, 0);  //Ensure =low
                        cntrlbit(numPCI,IIC_SCL_DIR,0);  // i/p Hi-Z
			cntrlbit(numPCI,IIC_SCL_CTL,0);  // User I/O
			// Display card details 
			card_detail(numPCI);
			numPCI++;
		}
	}

	#ifdef USE_UDEV
	printk(KERN_INFO NAME": Setting up udev...\n");

	vpb_class = class_create(THIS_MODULE, NAME);

	{
	    struct device *d = device_create(vpb_class, NULL, MKDEV(major, 0), NULL, "vpb0");
	    if( IS_ERR(d) ) {
		    ret = PTR_ERR(d);
		    goto hell;
	    }
	}
	#endif
	printk(KERN_INFO NAME ":%d V4PCI's detected on PCI bus\n",numPCI);

	return 0;

hell:
	pci_dev_put(dev);
	cleanup_module();
	return ret;
}

/*--------------------------------------------------------------------------*\

	FUNCTION.: cleanup_module()
	AUTHOR...: David Rowe
	DATE.....: 15/4/00

	Called by the O/S when the module is unloaded. Frees any memory
	mapped areas for the PCI cards.

	TODO: DR 11/9/01: This function gives an error message on vfree under 
	2.4 kernel, not sure why....

\*--------------------------------------------------------------------------*/

void cleanup_module(void)
{
	int i;

	// unmap any PCI regions

	for(i=0; i<numPCI; i++) {
//		vfree(base0[i]);    
		iounmap(base0[i]);
		release_region(base1[i], SIZE_LCR);
//		vfree(base2[i]);
		iounmap(base2[i]);
	}

	#ifdef USE_UDEV
	device_destroy(vpb_class,MKDEV(major, 0));
	class_destroy(vpb_class);
	#endif

	unregister_chrdev(major, NAME);

	printk(KERN_INFO NAME": unloaded succesfully\n");
}

static int vpb_open( struct inode *inode, struct file *filep )
{ //{{{

	if( atomic_inc_return( &vpb_inuse ) > 1 ){
		atomic_dec( &vpb_inuse );
		printk(KERN_WARNING NAME ": vpb0 device in use");
		return -EBUSY;
	}
	return 0;

} //}}}

static int vpb_release( struct inode *inode, struct file *filep )
{ //{{{

	atomic_dec( &vpb_inuse );
	return 0;

} //}}}


/*--------------------------------------------------------------------------*\

	FUNCTION.: vpb_iotcl()
	AUTHOR...: David Rowe
	DATE.....: 15/4/00

	Called when a user mode ioctl is issued, used to perform all ISA and
	PCI commands based on ioctl command.

\*--------------------------------------------------------------------------*/

static long vpb_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
	int      ret;
	VPB_DATA vpb_data;  // ioctl parameters from user space
	int      port;      // I/O port (ISA)
	int      dsp_addr;  // addr in DSP memory (PCI)
	int      length;    // length of transfer in words
	void    *data;     // ptr to user space data
	int      pci_num;   // pci card number (PCI)
	int      ctrl;      // value of PLX9050 control reg
	int      i;
	int      l;         // remaining length
	short    *pbuf;     // source/dest for next block

	// copy parameter data from user address space

	ret = copy_from_user(&vpb_data, (void*)arg, sizeof(VPB_DATA));
	if (ret != 0) {
		printk(KERN_CRIT NAME ":verify area VPB_DATA failed\n");
		return -ENODEV;
	}

	// local copies of parameters

	port = vpb_data.port;
	dsp_addr = vpb_data.dsp_addr;
	length = vpb_data.length;
	data = vpb_data.data;
	pci_num = vpb_data.pci_num;

//	printk(KERN_INFO NAME ": Got IOCTL[%x] card[%02d] data[0x%x] length[0x%x] dsp_addr[0x%x]\n",cmd,pci_num,data,length,dsp_addr);
	switch(cmd) {
	// PCI commands ----------------------------------------------------------
	case VPB_IOC_PCI_GET_NUM_CARDS:
		ret = copy_to_user(data, &numPCI, sizeof(int));
		break;

	case VPB_IOC_PCI_BLOCK_WRITE:
		if (numPCI==0)
			return -ENODEV;
		ret = copy_from_user(buf, data, length*sizeof(short));
		if (ret != 0){
			printk(KERN_CRIT NAME ":copy_from_user failed\n");
		}

		// divide write into blocks to avoid upsetting DSP interrupts
		l = length;
		pbuf = buf;
		while(l>0) {
			if (l > BLOCK)
				vpbmemcpy_toio(base2[pci_num]+dsp_addr, pbuf, BLOCK);
			else
				vpbmemcpy_toio(base2[pci_num]+dsp_addr, pbuf, 1);
			l -= BLOCK;
			pbuf += BLOCK;
			dsp_addr += BLOCK;
			udelay(BLOCK_DELAY);
		}
		break;

	case VPB_IOC_PCI_BLOCK_READ:
		if (numPCI==0)
			return -ENODEV;
		// divide read into blocks to avoid upsetting DSP interrupts

		l = length;
		pbuf = buf;
		while(l>0) {
			if (l > BLOCK)
				vpbmemcpy_fromio(pbuf, base2[pci_num]+dsp_addr, BLOCK);
			else
				vpbmemcpy_fromio(pbuf, base2[pci_num]+dsp_addr, l);
			l -= BLOCK;
			pbuf += BLOCK;
			dsp_addr += BLOCK;
			udelay(BLOCK_DELAY);
		}
		ret = copy_to_user(data, buf, length*sizeof(short));
		if (ret != 0){
			printk(KERN_CRIT NAME ":copy_to_user failed\n");
		}
		break;

	case VPB_IOC_PCI_DSP_RESET:
		if (numPCI==0)
			return -ENODEV;

		// reset PLX9050 pin user0, connected to DSP reset, placing DSP in reset
		ctrl = readl(base0[pci_num]+0x50);
		ctrl &= ~(1<<2);
		writew(ctrl,base0[pci_num]+0x50);
		break;

	case VPB_IOC_PCI_DSP_RUN:
		if (numPCI==0)
			return -ENODEV;

		// set PLX9050 pin user0, connected to DSP reest, placing DSP in run mode
		ctrl = readl(base0[pci_num]+0x50);
		ctrl |= (1<<2);
		writew(ctrl,base0[pci_num]+0x50);
		break;

	case VPB_IOC_PCI_BLOCK_EEREAD:
		if (numPCI==0)
			return -ENODEV;
		pbuf = buf;
		for(i=0; i<length; i++) {
			*(pbuf++) =eeread(pci_num,dsp_addr+i);
		}
		ret = copy_to_user(data, buf, length*sizeof(short));
		break;

	case VPB_IOC_PCI_BLOCK_IICWRITE:
		if (numPCI==0)
			return -ENODEV;
		ret = copy_from_user(buf+1, data, length*sizeof(short));
		// printk("IOC %d,buf %x,len %x,addr %x\n",pci_num, buf,length,dsp_addr);
		pbuf = buf;
		*(pbuf)= Iic_Blk_Write(pci_num, buf+1, length, dsp_addr);
		ret = copy_to_user(data, buf, 1*sizeof(short));	// Return status byte
		break;

	case VPB_IOC_PCI_BLOCK_IICREAD:
		if (numPCI==0)
			return -ENODEV;

		pbuf = buf;
		*(pbuf)= Iic_Blk_Read(pci_num, buf+1, length, dsp_addr);
		ret = copy_to_user(data, buf, (1+length)*sizeof(short));
		break;
	}
	return 0;
}

/*--------------------------------------------------------------------------*\

	FUNCTION.: eeread(int addr)
	AUTHOR...: Peter Wintulich
	DATE.....: 5-FEB-2003

	Card Data Read function. Reads a block from

\*--------------------------------------------------------------------------*/

unsigned int eeread(int PCIn, int addr) {
	int i;
	int d;

	cntrlbit(PCIn,EEPROM_CS,1);
	cntrlbit(PCIn,EEPROM_CLK,0);

	wrbit(PCIn,1); wrbit(PCIn,1); wrbit(PCIn,0);
	for(i=0; i<6; i++)
	    wrbit(PCIn,addr>>(5-i));

	d = 0;
	for(i=0; i<16; i++)
       	{
	    d <<= 1;
	    d += rdbit(PCIn);
	}
        cntrlbit(PCIn,EEPROM_CS,0);
        return(d);
}

/*--------------------------------------------------------------------------*\

	FUNCTION.: cntrlbit(int PCInn, int bit, int val)
	AUTHOR...: David Rowe
	DATE.....: 5-FEB-2003

	Support function for eeprom & I2C access.

\*--------------------------------------------------------------------------*/

void cntrlbit(int PCInn, int bit, int val)
{
	unsigned int cntrl;

	val &= 1;
     	// first reset bit
	cntrl = readl(base0[PCInn]+0x50);
	cntrl &= ~(1<<bit);
	// now set to val
	cntrl |= val<<bit;
	writel(cntrl,base0[PCInn]+0x50);
}

/*--------------------------------------------------------------------------*\

	FUNCTION.: wrbit(int PCInn, int bit)
	AUTHOR...: David Rowe
	DATE.....: 5-FEB-2003

	Support function for eeprom read

\*--------------------------------------------------------------------------*/

void wrbit(int PCInn, int bit)
{
        cntrlbit(PCInn,EEPROM_WRBIT, bit);
        cntrlbit(PCInn,EEPROM_CLK, 1);
        cntrlbit(PCInn,EEPROM_CLK, 0);
}

/*--------------------------------------------------------------------------*\

	FUNCTION.: rdbit(int PCInn)
	AUTHOR...: David Rowe
	DATE.....: 5-FEB-2003

	Support function for eeprom read

\*--------------------------------------------------------------------------*/

int rdbit(int PCInn)
{
        unsigned int cntrl;

        cntrlbit(PCInn,EEPROM_CLK, 1);
        cntrlbit(PCInn,EEPROM_CLK, 0);
        cntrl = readl(base0[PCInn]+0x50);
        cntrl >>= EEPROM_RDBIT;
        cntrl &= 1;
        return(cntrl);
}

/*--------------------------------------------------------------------------*\

	FUNCTION.: card_detail(int pci_n)
	AUTHOR...: Peter Wintulich
	DATE.....: 5-FEB-2003

	Print details of card to console & log

\*--------------------------------------------------------------------------*/

void card_detail(int pci_n)
{
	int 	i, x=0;
	char	st[16];
	char	hex[17]={"0123456789abcdef"};
	unsigned short eebuf[20];

	i=50;
	while(i<56)
	    eebuf[x++] = eeread(pci_n,i++);

	// Decode date
	x=i=0;
	st[x++]=hex[((eebuf[i]>>12)& 0x0f)];	//day
	st[x++]=hex[((eebuf[i]>>8)& 0x0f)];
	st[x++]='/';
	st[x++]=hex[((eebuf[i]>>4)& 0x0f)];	//month
	st[x++]=hex[(eebuf[i] & 0x0f)];
	st[x++]='/';
	i++;
	st[x++]=hex[((eebuf[i]>>12)& 0x0f)];	//year
	st[x++]=hex[((eebuf[i]>>8)& 0x0f)];
	st[x++]=hex[((eebuf[i]>>4)& 0x0f)];
	st[x++]=hex[(eebuf[i] & 0x0f)];
	st[x]=0;
	i++;
	printk(KERN_INFO NAME ": Manufactured %s\n",st);

	x=0;
	st[x++]=hex[((eebuf[i]>>12)& 0x0f)];	//card
	st[x++]=hex[((eebuf[i]>>8)& 0x0f)];
	st[x++]=0;
	printk(KERN_INFO NAME ": Card version %s.",st);

	x=0;
	st[x++]=hex[((eebuf[i]>>4)& 0x0f)];	//Rev.
	st[x++]=hex[(eebuf[i] & 0x0f)];
	st[x++]=0;
	i++;
	printk(KERN_INFO "%s\n",st);

	i++;					// Resurved word;

	x=0;
	st[x++]=hex[((eebuf[i]>>12)& 0x0f)];	//SN:high
	st[x++]=hex[((eebuf[i]>>8)& 0x0f)];
	st[x++]=hex[((eebuf[i]>>4)& 0x0f)];
	st[x++]=hex[(eebuf[i] & 0x0f)];
	i++;
	st[x++]=hex[((eebuf[i]>>12)& 0x0f)];	//SN:low
	st[x++]=hex[((eebuf[i]>>8)& 0x0f)];
	st[x++]=hex[((eebuf[i]>>4)& 0x0f)];
	st[x++]=hex[(eebuf[i] & 0x0f)];
	st[x]=0;
	printk(KERN_INFO NAME ": Serial number %s\n",st);
}

/*--------------------------------------------------------------------------*\
    FUNCTION.: iicout(int PCI, int sig, int state)
    AUTHOR...: Peter Wintulich
    DATE.....: OCT-2003
 
    Set the pin "sig' to "state" using read-modify-write to preserve all
    other bits in this word. IIC uses open collector drives, this is being
    emulated by changing the pin direction. This is also required because
    the pin state must be readable in its open state to determin if the
    line is being driven by another device on the bus. 

	In	:PCI	Target PCI device (VPB CARD)
	                :sig    Label of signal to read:
			        IIC_SDA
				IIC_SCL
			:state	State to set pin to:
				IIC_LOW
				IIC_OPEN	
 
\*--------------------------------------------------------------------------*/
void iicout(int PCInn, int sig,int state)
{
	//printk("IIC out ==>\n");
	switch (sig)
	{
		case IIC_SDA:
		    if(state == IIC_LOW)
		    {
		        cntrlbit(PCInn,IIC_SDA_DIR,1);  // o/p low
			cntrlbit(PCInn,IIC_SDA_IO, 0);	//Ensure =low
		    }
	    	    else
		        cntrlbit(PCInn,IIC_SDA_DIR,0);  // o/p Hi-Z	    
		    break;
                case IIC_SCL:
                    if(state == IIC_LOW)
                    {
                        cntrlbit(PCInn,IIC_SCL_DIR,1);  // o/p low
                        cntrlbit(PCInn,IIC_SCL_IO, 0);  //Ensure =low
                    }
                    else
                        cntrlbit(PCInn,IIC_SCL_DIR,0);  // o/p Hi-Z
                    break;
	}
}
/*--------------------------------------------------------------------------*\
    FUNCTION.: iicin(int PCI, int sig)
    AUTHOR...: Peter Wintulich
    DATE.....: OCT-2003

    Read the pin state of pin "sig" and return its state.
    
	In	:PCI	Target PCI device (VPB CARD)
		:sig	Label of signal to read:
				IIC_SDA
				IIC_SCL
	Return	:Bit logic level ( 0 or 1 )
\*--------------------------------------------------------------------------*/
char    iicin(int PCInn, int sig)
{
	int     tmp;
	char	cntrl=0;

        tmp =  readl(base0[PCInn]+0x50);	// delay
        tmp =  readl(base0[PCInn]+0x50);	// use this copy
        switch (sig)
        {
            case IIC_SDA:
		cntrl= ((tmp & (1<<IIC_SDA_IO)) ==0 ? 0:1);
		//printk("IIC in %x SDA =%s\n",tmp,(cntrl==0 ?"Lo":"Hi"));
		break;
            case IIC_SCL:
                cntrl= ((tmp & (1<<IIC_SCL_IO)) ==0 ? 0:1);
		//printk("IIC in %x SCL =%s\n",tmp,(cntrl==0 ?"Lo":"Hi"));
                break;
	}
        return(cntrl);
}


