Subversion Repositories f9daq

Rev

Go to most recent revision | Blame | Last modification | View Log | RSS feed

//****************************************************************************
// 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