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