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

    FILE....: vpb.c
    TYPE....: FreeBSD device driver
    AUTHOR..: David Rowe
    DATE....: 10/12/01
    AUTHOR..: Ben Kramer
    AUTHOR..: Peter Wintulich


    FreeBSD kernel mode device driver for Voicetronix V4PCI card.  Interfaces
    with the mode driver (libvpb).  This version is compiled as part of the
    FreeBSD 4.4 Kernel.  

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

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

         Voicetronix Voice Processing Board (VPB) Software

         Copyright (C) 1999-2001 Voicetronix www.voicetronix.com.au

         This library is free software; you can redistribute it and/or
         modify it under the terms of the GNU Lesser General Public
         License as published by the Free Software Foundation; either
         version 2.1 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
         Lesser General Public License for more details.

         You should have received a copy of the GNU Lesser General Public
         License along with this library; if not, write to the Free Software
         Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307
	 USA

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

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

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

#define PCI_BASE_ADDR0		0x10
#define PCI_BASE_ADDR1		0x14
#define PCI_BASE_ADDR2		0x18

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

#define MIN(a,b) (((a) < (b)) ? (a) : (b))

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

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

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

#include <sys/types.h>
#include <sys/module.h>
#include <sys/systm.h>  /* uprintf                               */
#include <sys/errno.h>
#include <sys/param.h>  /* defines used in kernel.h              */
#include <sys/kernel.h> /* types used in module initialization   */
#include <sys/conf.h>   /* cdevsw struct                         */
#include <sys/uio.h>    /* uio struct                            */
#include <sys/malloc.h>
#include <sys/bus.h>    /* structs, prototypes for pci bus stuff */
#include <pci/pcivar.h> /* For get_pci macros!                   */
#include <sys/vpbio.h>	/* IOCTL definitions                     */
#include <machine/clock.h>	
#include <vm/vm.h>	
#include <vm/pmap.h>	

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

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

/* Function prototypes */
static d_open_t      vpb_open;
static d_close_t     vpb_close;
static d_read_t      vpb_read;
static d_write_t     vpb_write;
static d_ioctl_t     vpb_ioctl;

static int vpb_probe(device_t dev);
static int vpb_attach(device_t dev);
static int vpb_detach(device_t dev);
static int vpb_suspend(device_t dev);
static int vpb_shutdown(device_t dev);
static int vpb_resume(device_t dev);

static unsigned int eeread(int PCIn, int addr);
static void cntrlbit(int PCInn, int bit, int val);
static void wrbit(int PCInn, int bit); 
static int rdbit(int PCInn);
/*---------------------------------------------------------------------------*\

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

/* Character device entry points */

static struct cdevsw vpb_cdevsw = {
  vpb_open,
  vpb_close,
  vpb_read,
  vpb_write,
  vpb_ioctl,
  nopoll,
  nommap,
  nostrategy,
  "vpb",
  201,                   /* reserved for local use */
  nodump,
  nopsize,
  D_TTY,
  -1
};

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

/* translated base address of PLX9050 regions */
static unsigned char  *base0[MAX_V4PCI];
static u_int32_t      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];

/* vars */
static dev_t sdev;

static device_method_t vpb_methods[] = {
  /* Device interface */
  DEVMETHOD(device_probe,     vpb_probe),
  DEVMETHOD(device_attach,    vpb_attach),
  DEVMETHOD(device_detach,    vpb_detach),
  DEVMETHOD(device_shutdown,  vpb_shutdown),
  DEVMETHOD(device_suspend,   vpb_suspend),
  DEVMETHOD(device_resume,    vpb_resume),

  { 0, 0 }
};

static driver_t vpb_driver = {
  "vpb",
  vpb_methods,
  0,
  /*  sizeof(struct vpb_softc), */
};

static devclass_t vpb_devclass;

DRIVER_MODULE(vpb, pci, vpb_driver, vpb_devclass, 0, 0);

/* We're more interested in probe/attach than with
       open/close/read/write at this point */

static int
vpb_open(dev_t dev, int oflags, int devtype, struct proc *p)
{
  int err = 0;

  return(err);
}

static int
vpb_close(dev_t dev, int fflag, int devtype, struct proc *p)
{
  int err=0;

  return(err);
}

static int
vpb_read(dev_t dev, struct uio *uio, int ioflag)
{
  int err = 0;

  return err;
}

static int
vpb_write(dev_t dev, struct uio *uio, int ioflag)
{
  int err = 0;

  return(err);
}

static int
vpb_ioctl(dev_t dev, u_long cmd, caddr_t arg, int flag, struct proc *pr)
{	
  VPB_DATA *vpb_data;  /* ioctl parameters from user space */
  short    *data;      /* user space address of data       */
  int      dsp_addr;   /* addr in DSP memory (PCI)         */
  int      length;     /* length of transfer in words      */
  int      pci_num;    /* pci card number (PCI)            */
  int      ctrl;       /* value of PLX9050 control reg     */
  int      l;          /* remaining length                 */
  short    *pbuf;      /* source/dest for next block       */
  int      i;


  /* local copies of parameters */

  vpb_data = (VPB_DATA*)arg;
  dsp_addr = vpb_data->dsp_addr;
  length = vpb_data->length;
  data = vpb_data->data;
  pci_num = vpb_data->pci_num;

  switch(cmd) {

    case VPB_IOC_PCI_GET_NUM_CARDS:
      if (numPCI==0)
	return -ENODEV;
      copyout(&numPCI, data, sizeof(int));
    break;

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

      /* short delay between writing words to avoid upsetting DSP interrupts */

      copyin(data, buf, length*sizeof(short));
      pbuf = buf;
      for(l=0; l<length; l++) {
        *(base2[pci_num]+dsp_addr+l) = *pbuf++;
        DELAY(BLOCK_DELAY);
      }
    break;
     
    case VPB_IOC_PCI_BLOCK_READ:
      if (numPCI==0)
	return -ENODEV;

      /* short delay between writing words to avoid upsetting DSP interrupts */

      pbuf = buf;
      for(l=0; l<length; l++) {
        *pbuf++ = *(base2[pci_num]+dsp_addr+l);
        DELAY(BLOCK_DELAY);
      }
      copyout(buf, data, length*sizeof(short));
    break;
    
    case VPB_IOC_PCI_DSP_RESET:
      if (numPCI==0)
	return -ENODEV;

      /* reset PLX9050 pin user0, connected to DSP reset */

      ctrl = *(base0[pci_num]+0x50);
      ctrl &= ~(1<<2);
      *(base0[pci_num]+0x50) = ctrl;            
    break;
    
    case VPB_IOC_PCI_DSP_RUN:
      if (numPCI==0)
	return -ENODEV;

      /* set PLX9050 pin user0, connected to DSP reset */

      ctrl = *(base0[pci_num]+0x50);
      ctrl |= (1<<2);
      *(base0[pci_num]+0x50) = ctrl;            
    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);
        }
        copyout(data, buf, length*sizeof(short));
        break;
    
    case VPB_IOC_PCI_BLOCK_IICWRITE:
    case VPB_IOC_PCI_BLOCK_IICREAD:
        return -ENODEV;
        break;

    default:
      return ENXIO;
  }
    
   return 0;
}   

/* PCI Support Functions */

    /*
     * Return identification string if this is device is ours.
     */
static int
vpb_probe(device_t dev)
{
  int         subsystem;
  char        *s; 

  if ((pci_get_vendor(dev) == 0x10b5) && (pci_get_device(dev) == 0x9050)) {

    /* check that subsytem ID & Subsytem Vendor matches */
    subsystem = pci_read_config(dev, 0x2c, 4);
    s = (char*)&subsystem;
		
    if ((s[3] == 'V') && (s[2] == '4') && (s[1] == 'V') && (s[0] == 'T')) {
      
      printf("V4PCI %d found!\n", numPCI);
      device_set_desc(dev, "Voicetronix V4PCI Computer Telephony Card");
      return 0;
    }
  }
  
  return ENXIO;
}

/* Attach function is only called if the probe is successful */

static int
vpb_attach(device_t dev)
{
  vm_offset_t paddr;

  sdev = make_dev(&vpb_cdevsw,
		  0,
		  UID_ROOT,
		  GID_WHEEL,
		  0600,
		  "vpb");

  /* OK, V4PCI found, so map address regions..... */
  paddr = (vm_offset_t)pci_read_config(dev, PCI_BASE_ADDR0, 4) & ~0xf;
  base0[numPCI] = pmap_mapdev(paddr, SIZE_LCR);
  base1[numPCI] = pci_read_config(dev, PCI_BASE_ADDR1, 4) & ~0x3;
  paddr =  (vm_offset_t)pci_read_config(dev, PCI_BASE_ADDR2, 4) & ~0xf;
  base2[numPCI] = pmap_mapdev(paddr, sizeof(short)*SIZE_WD);

  /* set wait-states */
  *(base0[0]+0x28) = 0x00440422;     

  numPCI++;

  return 0;
}

/* Detach device. */

static int
vpb_detach(device_t dev)
{
  return 0;
}

/* Called during system shutdown after sync. */

static int
vpb_shutdown(device_t dev)
{
  return 0;
}

/*
     * Device suspend routine.
     */
static int
vpb_suspend(device_t dev)
{
  return 0;
}

/*
     * Device resume routine.
     */

static int
vpb_resume(device_t dev)
{
  return 0;
}

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

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

	Card Data Read function. Reads a block from

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

static 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.

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

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

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

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

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

	Support function for eeprom read

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

static 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

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

static int rdbit(int PCInn) 
{
        unsigned int cntrl;

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