//****************************************************************************
 
// Copyright (C) 2000-2006  ARW Elektronik Germany
 
//
 
//
 
// This program 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 program 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 program; if not, write to the Free Software
 
// Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 
//
 
// This product is not authorized for use as critical component in 
 
// life support systems without the express written approval of 
 
// ARW Elektronik Germany.
 
//  
 
// Please announce changes and hints to ARW Elektronik
 
//
 
// Maintainer(s): Klaus Hitschler (klaus.hitschler@gmx.de)
 
//
 
//****************************************************************************
 
 
 
//****************************************************************************
 
// Revision 1.20 support for kernel version=3.13.0-37-generic 2014/10/19  Rok Pestotnik
 
// fops.c -- the file operations module for the PCICC32 PCI to CAMAC Interface
 
//
 
// $Log: fops.c,v $
 
// Revision 1.19  2006/06/04 12:26:07  klaus
 
// release_20060604; Version 6.9; pci_{en|dis}able_device() added; remap_page_range reorganized
 
//
 
// Revision 1.18  2006/03/28 21:49:49  klaus
 
// release_20060328; Version 6.8; Support for AMD64 and Kernel 2.6.15
 
//
 
// Revision 1.17  2005/03/11 13:23:26  klaus
 
// simple corrections for to use with kernels 2.4.21
 
//
 
// Revision 1.16  2004/08/12 19:59:19  klaus
 
// conversion to kernel-version 2.6, released version 6.0
 
//
 
// Revision 1.15  2004/01/16 18:40:34  klaus
 
// converted remap_page_range call for kernels >= 2.4.20, contributed by Roberto Bertoni
 
//
 
// Revision 1.14  2003/12/04 20:34:49  klaus
 
// minor change: restore the hardware set intCSR content at release of path, release of version 5.2
 
//
 
// Revision 1.13  2003/06/19 08:23:38  klaus
 
// re-compiled with RH-7.2 (kernel 2.4.10)
 
//
 
// Revision 1.12  2003/05/11 11:12:03  klaus
 
// matched to kernel 2.4 PCI handling, debug messages improved
 
//
 
// Revision 1.11  2002/10/08 18:01:46  klaus
 
// corrections mainly for use with kernel 2.2.19
 
//
 
// Revision 1.10  2002/08/06 19:06:50  klaus
 
// Small changes
 
//
 
// Revision 1.9  2002/04/17 19:41:06  klaus
 
// added support for autoread
 
//
 
// Revision 1.8  2002/04/17 18:57:33  klaus
 
// last changes to release 4.4
 
//
 
// Revision 1.7  2002/04/14 18:25:38  klaus
 
// added interrupt handling, driver 4.4. ...3.5.tar.gz
 
//
 
// Revision 1.6  2001/11/20 21:29:24  klaus
 
// Small changes to correct versioning down to kernel 2.3
 
//
 
// Revision 1.5  2001/11/20 20:25:23  klaus
 
// inserted versioning for kernel 2.4
 
//
 
// Revision 1.4  2001/11/20 20:12:50  klaus
 
// included new header and CVS log
 
//
 
// first steps (on my mothers birthday)                        AR   23.02.2000
 
// MAKE_CC32_ADR serves only F from 0..15                      AR   24.04.2000
 
// MODVERSIONS included                                        AR   24.04.2000
 
//   
 
//****************************************************************************
 
 
 
/*--- INCLUDES -----------------------------------------------------------------------------------*/
 
#include "common.h"  /* must be the first include */
 
 
 
#include <linux/kernel.h> /* printk() */
 
#include <linux/mm.h>
 
#include <linux/module.h> /* MODULE_AUTHOR and MAJOR ... */
 
#include <linux/pci.h>
 
#include <asm/errno.h>
 
#include <asm/types.h>
 
#include <asm/uaccess.h>
 
#include <linux/sched.h>
 
#include <linux/wait.h>
 
#include <linux/poll.h>
 
 
 
#include "fops.h"
 
#include "list.h"
 
#include "plx9050.h"
 
#include "pcicc32.h"      /* the common ioctl commands and structures between driver and application */
 
 
 
/*--- DEFINES ------------------------------------------------------------------------------------*/
 
MODULE_AUTHOR("klaus.hitschler@gmx.de");
 
MODULE_DESCRIPTION("Driver for ARW Elektronik PCI CAMAC interface.");
 
MODULE_SUPPORTED_DEVICE("PCICC32");
 
 
 
#if LINUX_VERSION_CODE > KERNEL_VERSION(2,4,10)
 
MODULE_LICENSE("GPL");               // since 2.4.?
 
#endif
 
 
 
#ifndef MINOR
 
#define MINOR(x) minor(x)            // since 2.5.?
 
#endif
 
 
 
#define MAKE_CC32_ADR(N, A, F) (u16)((N << 10) + (A << 6) + ((F & 0xf) << 2))
 
 
 
#define ENABLE_PCIADA_IRQS  (u16)0x0049 /* enable PCIADA IRQs            */
 
#define DISABLE_PCIADA_IRQS (u16)0x0009 /* disable PCIADA IRQs           */
 
 
 
/*--- EXTERNALS ----------------------------------------------------------------------------------*/
 
extern int get_module_info(CC32_DESCRIPTOR *wd, u8 *cModuleNumber, u8 *cFPGAVersion);
 
extern int test_connection(CC32_DESCRIPTOR *wd);
 
extern List *pcicc32_work_device_header;
 
 
 
/*--- TYPEDEFS -----------------------------------------------------------------------------------*/
 
typedef struct
 
{
 
        CC32_DESCRIPTOR *desc;  /* pointer to my PCIADA & CC32 */
 
} PRIVATE_DATA;
 
 
 
/*--- FUNCTIONS ----------------------------------------------------------------------------------*/
 
static void switch_CC32_on(CC32_DESCRIPTOR *desc)
 
{
 
        u16 cntrl;
 
        
 
        DPRINTK(KERN_DEBUG "pcicc32 : switch_CC32_on()\n");
 
        
 
        cntrl  = readw(desc->pLCR + PLX9050_CNTRL);  /* read CONTROL register */
 
        cntrl |= 0x0180;
 
        writew(cntrl, desc->pLCR + PLX9050_CNTRL); /* enable access */
 
}
 
 
 
static void switch_CC32_off(CC32_DESCRIPTOR *desc)
 
{
 
        u16 cntrl  = readw(desc->pLCR + PLX9050_CNTRL);  /* read CONTROL register */
 
        
 
        DPRINTK(KERN_DEBUG "pcicc32 : switch_CC32_off()\n");
 
        
 
  writew(DISABLE_PCIADA_IRQS, desc->pLCR + PLX9050_INTCSR);  /* disable global interrupts */
 
        cntrl &= ~0x0100;
 
        writew(cntrl, desc->pLCR + PLX9050_CNTRL);       /* disable access */
 
}
 
 
 
int pcicc32_ioctl(struct inode *pInode, struct file *pFile, unsigned int cmd, unsigned long arg)
 
{
 
        u16 cntrl;
 
        PRIVATE_DATA *pd    = (PRIVATE_DATA *)pFile->private_data;
 
  CC32_DESCRIPTOR *wd = pd->desc;
 
        
 
        DPRINTK(KERN_DEBUG "pcicc32 : pcicc32_ioctl(0x%08x, 0x%08lx)\n", cmd, arg);
 
        
 
        if (_IOC_TYPE(cmd) != PCICC32_MAGIC) 
 
          return -EINVAL;
 
        
 
        switch(_IOC_NR(cmd))
 
        {
 
    case 1: // PCICC32_IOSTATE
 
                  {
 
              PCICC32_STATUS *ptr = (PCICC32_STATUS *)arg;  /* makes it simple */
 
                  
 
                    if (_IOC_SIZE(cmd) < sizeof(PCICC32_STATUS)) 
 
                      return -EINVAL;
 
                          if (!access_ok(VERIFY_WRITE, ptr, sizeof(*ptr))) 
 
                            return -EFAULT;
 
                                
 
                          ptr->bConnected = ((wd->bConnected) && (readw(wd->pLCR + PLX9050_CNTRL) & 0x0800)) ? 1 : 0;
 
                          ptr->bFail      = (readw(wd->pLCR + PLX9050_INTCSR) & 0x0020) ? 1 : 0;
 
                          ptr->bIrq       = (readw(wd->pLCR + PLX9050_INTCSR) & 0x0004) ? 1 : 0; 
 
                          
 
                                wd->wIrqStatus  = 0;  // clear the status since it is read                              
 
                        }
 
                        break;
 
                case 2: // PCICC32_IOCNTRL
 
                  {
 
                    if (_IOC_SIZE(cmd)) 
 
                      return -EINVAL;
 
                                
 
                          cntrl  = readw(wd->pLCR + PLX9050_CNTRL);  /* read CONTROL register */
 
                          writew(cntrl & ~0x0100, wd->pLCR + PLX9050_CNTRL); /* clear USRIO2 */
 
                          writew(cntrl          , wd->pLCR + PLX9050_CNTRL); /* restore it   */         
 
                        }
 
                  break;
 
          case 3: // PCICC32_CONTROL_INTERRUPTS
 
            {
 
              PCICC32_IRQ_CONTROL *ptr = (PCICC32_IRQ_CONTROL *)arg;  /* makes it simple */
 
              char bEnable = (readw(wd->pLCR + PLX9050_INTCSR) & 0x40) ? 1 : 0;
 
              
 
              if (_IOC_SIZE(cmd) < sizeof(PCICC32_IRQ_CONTROL)) 
 
                return -EINVAL;
 
                          if (!access_ok(VERIFY_WRITE, ptr, sizeof(*ptr))) 
 
                            return -EFAULT;
 
              
 
              DPRINTK(KERN_DEBUG "pcicc32 : Interrupt enable = %d\n", ptr->bEnable);
 
              
 
              if (ptr->bEnable)
 
                writew(ENABLE_PCIADA_IRQS  ,wd->pLCR + PLX9050_INTCSR); // local and global enable
 
                    else
 
                      writew(DISABLE_PCIADA_IRQS ,wd->pLCR + PLX9050_INTCSR); // global disable 
 
                      
 
                    // return last interrupt enable status  
 
                          ptr->bEnable = bEnable;   
 
            }
 
            break;
 
          case 4: // PCICC32_IOSTATE_BLOCKING
 
            {
 
              PCICC32_STATUS *ptr = (PCICC32_STATUS *)arg;  /* makes it simple */
 
              int err;
 
                          
 
                    if (_IOC_SIZE(cmd) < sizeof(PCICC32_STATUS)) 
 
                      return -EINVAL;
 
                    
 
        // support nonblocking read if requested
 
        if ((pFile->f_flags & O_NONBLOCK) && (!wd->wIrqStatus))
 
          return -EAGAIN;
 
          
 
        // sleep until data are available
 
        if ((err = wait_event_interruptible(wd->event_queue, (wd->wIrqStatus))))
 
                return err;
 
                
 
                          if (!access_ok(VERIFY_WRITE, ptr, sizeof(*ptr))) 
 
                            return -EFAULT;
 
                                
 
                          ptr->bConnected = ((wd->bConnected) && (readw(wd->pLCR + PLX9050_CNTRL) & 0x0800)) ? 1 : 0;
 
                          ptr->bFail      = (readw(wd->pLCR + PLX9050_INTCSR) & 0x0020) ? 1 : 0;
 
                          ptr->bIrq       = (readw(wd->pLCR + PLX9050_INTCSR) & 0x0004) ? 1 : 0; 
 
                          
 
                                wd->wIrqStatus  = 0;  // clear the status since it is read                              
 
            }
 
            break;
 
          case 5: // PCICC32_AUTOREAD_SWITCH
 
            {
 
              u16 ctrl, erg;
 
              
 
              PCICC32_AUTOREAD *ptr = (PCICC32_AUTOREAD *)arg;  /* makes it simple */
 
                          
 
                    if (_IOC_SIZE(cmd) < sizeof(PCICC32_AUTOREAD)) 
 
                      return -EINVAL;
 
                    
 
                          if (!access_ok(VERIFY_WRITE, ptr, sizeof(*ptr))) 
 
                            return -EFAULT;
 
                          
 
                          ctrl = readw(wd->pLCR + PLX9050_CNTRL);  
 
                          if (ptr->bOn)
 
                          {
 
                            erg  = ctrl & ~0x0004;
 
                            erg |= 0x0002;         // switch output to 1, pin to 0 = on
 
                            writew(erg, wd->pLCR + PLX9050_CNTRL);
 
                          }
 
                          else
 
                          {
 
                            erg  = ctrl | 0x0006;  // switch output = 1, pin to 1 = off
 
                            writew(erg, wd->pLCR + PLX9050_CNTRL);
 
                          }
 
                          
 
                                // respond the last state
 
                          ptr->bOn = (ctrl & 0x0004) ? 0 : 1;
 
            }
 
            break;
 
                
 
                default: 
 
                  return -EINVAL;
 
        }
 
        
 
        return 0;
 
}
 
 
 
 
 
static long  pcicc32_unlocked_ioctl(struct file *pFile, unsigned int cmd, unsigned long arg){
 
long retval=0;
 
 
 
 
 
#if HAVE_UNLOCKED_IOCTL
 
    struct mutex  fs_mutex;
 
   mutex_init(&fs_mutex);
 
   mutex_lock(&fs_mutex);
 
#else
 
   lock_kernel();
 
#endif
 
 
 
DPRINTK(KERN_DEBUG "%s : pcicc32_unlocked_ioctl(0x%08x), size = %d\n", DEVICE_NAME, cmd, _IOC_SIZE(cmd));
 
retval = pcicc32_ioctl(NULL, pFile, cmd,arg);
 
 
 
#if HAVE_UNLOCKED_IOCTL
 
   mutex_unlock(&fs_mutex);
 
#else
 
   unlock_kernel();
 
#endif
 
 
 
return retval;
 
}
 
 
 
int pcicc32_mmap(struct file *pFile, struct vm_area_struct *vma)
 
{
 
        PRIVATE_DATA *pd = (PRIVATE_DATA *)pFile->private_data;
 
        
 
        DPRINTK(KERN_DEBUG "pcicc32 : pcicc32_mmap()\n");
 
        
 
        if (pd->desc->pPch->desc[3].type == PCI_BASE_ADDRESS_MEM_TYPE_1M)
 
        {
 
                DPRINTK(KERN_DEBUG "pcicc32 : mmap(address=%p size=%d)\n", 
 
                        virt_to_phys((void *)pd->desc->pUsr), pd->desc->pPch->desc[3].size);
 
 
 
                if (pcicc32_remap_page_range(vma, vma->vm_start, 
 
                        virt_to_phys((void *)pd->desc->pUsr), pd->desc->pPch->desc[3].size, PAGE_SHARED))
 
                        return -EAGAIN;
 
        }
 
        else
 
        {
 
                DPRINTK(KERN_DEBUG "pcicc32 : mmap(address=%p size=%d)\n", 
 
                        virt_to_phys((void *)pd->desc->pUsr), pd->desc->pPch->desc[3].size);
 
 
 
                if (pcicc32_remap_page_range(vma, vma->vm_start, 
 
                        pd->desc->pPch->desc[3].base_address, pd->desc->pPch->desc[3].size, PAGE_SHARED))
 
                        return -EAGAIN;
 
        }
 
        
 
        return 0;
 
}
 
 
 
 
 
 
 
int pcicc32_open(struct inode *pInode, struct file *pFile)
 
{
 
        CC32_DESCRIPTOR *wd = 0;
 
        CC32_DESCRIPTOR *desc = 0;
 
        int i;
 
        Node *n;
 
        int nMinor = MINOR(pInode->i_rdev);
 
        PRIVATE_DATA *pd;
 
        
 
        DPRINTK(KERN_DEBUG "pcicc32 : pcicc32_open(), Major=%d, Minor=%d\n", MAJOR(pInode->i_rdev), nMinor);
 
        
 
        /* search for device */
 
        i = getNumOfNodesInList(pcicc32_work_device_header);
 
        n = getFirstNode(pcicc32_work_device_header);
 
        
 
        DPRINTK(KERN_DEBUG "pcicc32 : scanning %d devices\n", i);
 
        while (i--)
 
        {
 
                wd  = (CC32_DESCRIPTOR *)getContent(n);
 
                
 
                wd->bConnected =  get_module_info(wd, &wd->cModuleNumber, &wd->cFPGAVersion);
 
                if (wd->bConnected)
 
                {
 
                        if  (test_connection(wd))
 
                        {
 
                                printk(KERN_ERR "pcicc32 : connection test for module %d failed!\n", wd->cModuleNumber);
 
                                wd->bConnected = 0;
 
                        }
 
                        else
 
                                if (wd->cModuleNumber == nMinor)
 
                                {
 
                                        desc = wd;
 
                                        break;
 
                                }
 
                }
 
                else
 
                {
 
                        DPRINTK(KERN_DEBUG "pcicc32 : module %d not connected!\n", nMinor);
 
                }
 
                          
 
                n = getNextNode(n);
 
        }
 
        
 
        if (desc)
 
        {
 
                pFile->private_data = (void *)kmalloc(sizeof(PRIVATE_DATA), GFP_ATOMIC);
 
                if (!pFile->private_data)
 
                        return -ENOMEM;
 
                
 
                pd = (PRIVATE_DATA *)pFile->private_data;
 
                pd->desc = wd;
 
                
 
                DPRINTK(KERN_DEBUG "pcicc32 : found CC32 module with number %d.\n", nMinor);
 
        }
 
        else
 
        {
 
                DPRINTK(KERN_DEBUG "pcicc32 : No CC32 module found.\n");
 
 
 
                return -ENODEV;
 
        }               
 
                
 
        switch_CC32_on(wd);
 
                        
 
        __MOD_INC_USE_COUNT__;
 
        return 0;
 
}
 
 
 
int pcicc32_release(struct inode *pInode, struct file *pFile)
 
{
 
        PRIVATE_DATA *pd;
 
 
 
        DPRINTK(KERN_DEBUG "pcicc32 : pcicc32_release()\n");
 
                                
 
        if (pFile->private_data) 
 
        {
 
                pd = (PRIVATE_DATA *)pFile->private_data;
 
                if (pd && pd->desc)     
 
                {
 
                        switch_CC32_off(pd->desc);
 
                        pd->desc = 0;
 
                }
 
                        
 
                kfree_s(pd, sizeof(*pd));    // FREE(pFile->private_data);
 
        }
 
        
 
        __MOD_DEC_USE_COUNT__;
 
        return 0;
 
}
 
 
 
//----------------------------------------------------------------------------
 
// is called at poll or select
 
static unsigned int pcicc32_poll(struct file *pFile, poll_table *wait)
 
{
 
        PRIVATE_DATA    *pd = (PRIVATE_DATA *)pFile->private_data;
 
  CC32_DESCRIPTOR *wd = pd->desc;
 
  unsigned int mask   = 0;
 
  
 
        DPRINTK(KERN_DEBUG "pcicc32 : pcicc32_poll()\n");
 
        
 
  poll_wait(pFile, &wd->event_queue, wait);
 
  
 
  // return on ops that could be performed without blocking
 
  if (wd->wIrqStatus)      
 
    mask |= (POLLIN  | POLLRDNORM);
 
  
 
  return mask; 
 
}
 
 
 
#if LINUX_VERSION_CODE < KERNEL_VERSION(2,3,0)
 
struct file_operations pcicc32_fops =
 
{
 
        NULL,                   /* lseek */
 
        NULL,                   /* read  */
 
        NULL,                   /* write */
 
        NULL,                   /* readdir */
 
        pcicc32_poll,   /* poll, since 2.2., replaces select */ 
 
        pcicc32_ioctl,  /* ioctl */
 
        pcicc32_mmap,   /* mmap */
 
        pcicc32_open,   /* open */
 
        NULL,           /* flush */
 
        pcicc32_release,/* release */
 
};
 
#else
 
struct file_operations pcicc32_fops =
 
{
 
        unlocked_ioctl :   pcicc32_unlocked_ioctl,              /* ioctl */
 
//      ioctl:   pcicc32_ioctl,         /* ioctl */
 
        mmap:    pcicc32_mmap,          /* mmap */
 
        open:    pcicc32_open,          /* open */
 
        release: pcicc32_release,       /* release */ 
 
  poll:    pcicc32_poll,    /* poll */
 
};
 
#endif