//****************************************************************************
// 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)
//
//****************************************************************************
//****************************************************************************
//
// pcicc32.c -- the driver module for the PCICC32 PCI to CAMAC Interface
// Thanks to A.Rubini's Book and Dirk Muelhlenberg and H.J.Mathes
// for their arwvme driver
//
// $Log: main.c,v $
// Revision 1.29 2006/06/04 12:26:07 klaus
// release_20060604; Version 6.9; pci_{en|dis}able_device() added; remap_page_range reorganized
//
// Revision 1.28 2006/03/28 21:49:49 klaus
// release_20060328; Version 6.8; Support for AMD64 and Kernel 2.6.15
//
// Revision 1.27 2005/10/08 13:21:52 klaus
// release 6.7, removed "requested io-memory still claimed" bug at incomplete termination
//
// Revision 1.26 2005/10/07 16:57:10 klaus
// fixed a bug with request_irq with IRQs greater than 127
//
// Revision 1.25 2005/03/11 13:23:26 klaus
// simple corrections for to use with kernels 2.4.21
//
// Revision 1.24 2004/08/12 19:59:19 klaus
// conversion to kernel-version 2.6, released version 6.0
//
// Revision 1.23 2004/01/16 18:42:26 klaus
// converted remap_page_range call for kernels >= 2.4.20, contributed by Roberto Bertoni,
// release of version 5.3
//
// Revision 1.22 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.21 2003/06/19 08:23:38 klaus
// re-compiled with RH-7.2 (kernel 2.4.10)
//
// Revision 1.20 2003/05/12 20:59:50 klaus
// another improvement at previous place
//
// Revision 1.19 2003/05/12 20:38:28 klaus
// improved debug messages from test of connection
//
//****************************************************************************
#define VERSION_HI 6
#define VERSION_LO 9
/*--- INCLUDES ---------------------------------------------------------------------------*/
#include "common.h" /* must be the first include */
//#include <linux/config.h>
#include <linux/sched.h>
#include <linux/proc_fs.h>
#include <linux/pci.h>
#include <asm/types.h>
#include <linux/delay.h>
#include <linux/wait.h>
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,0)
#include <linux/interrupt.h>
#endif
#include "list.h"
#include "askpci.h"
#include "plxbug.h"
#include "plx9050.h"
#include "fops.h"
#include "pcicc32.h"
/*--- DEFINES -----------------------------------------------------------------------------*/
#define MAJOR_NO 0 /* use dynamic assignment */
#define DEVICE_NAME "pcicc32"
#if (VERSION_HI < 3)
#define ARWVME_DEVICE_ID 0x9050
#define ARWVME_VENDOR_ID 0x10B5
#define ARWVME_SUBSYSTEM_ID 0x1167
#define ARWVME_SUBSYSTEM_VENDOR_ID 0x9050
#else
#define ARWVME_DEVICE_ID 0x2258
#define ARWVME_VENDOR_ID 0x10B5
#define ARWVME_SUBSYSTEM_ID 0x2258
#define ARWVME_SUBSYSTEM_VENDOR_ID 0x9050
#endif
#define MAKE_CC32_ADR(N, A, F) (u16)((N << 10) + (A << 6) + ((F & 0xf) << 2))
/*--- TYPEDEFS ----------------------------------------------------------------------------*/
extern struct file_operations pcicc32_fops
;
/*--- DRIVER GLOBALS ----------------------------------------------------------------------*/
static List
*pci_device_header
= NULL
; /* list of all PCIADAs found */
List
*pcicc32_work_device_header
= NULL
; /* list of working device interfaces */
static unsigned int nMajor
;
/*--- FUNCTIONS ---------------------------------------------------------------------------*/
static int my_interrupt
(u16 intCSR
)
{
int result
= NOT_MY_INTERRUPT
;
if (intCSR
& 0x0040) // it is global enabled
{
if ((intCSR
& 0x0028) == 0x0028) // it is a enabled PCIADA interrupt
result
= PCIADA_INTERRUPT
;
else
if ((intCSR
& 0x0005) == 0x0005) // it is a enabled CC32 interrupt
result
= CC32_INTERRUPT
;
}
return result
;
}
static irqreturn_t cc32_irqhandler
(int irq
, void *dev_id
, struct pt_regs
*regs
)
{
CC32_DESCRIPTOR
*wd
= (CC32_DESCRIPTOR
*)dev_id
;
if (wd
)
{
// evaluate the reason of the interrupt - if it is mine
u16 intCSR
= readw
(wd
->pLCR
+ PLX9050_INTCSR
);
int which_interrupt
= my_interrupt
(intCSR
);
if (which_interrupt
)
{
writew
(intCSR
& ~
0x40, wd
->pLCR
+ PLX9050_INTCSR
); /* disable global interrupts */
wd
->wIrqStatus
= (u16
)which_interrupt
;
wd
->dwInterruptCount
++;
wake_up_interruptible
(&wd
->event_queue
); /* stop blocking if any */
}
return IRQ_RETVAL
(1);
}
return IRQ_RETVAL
(0);
}
static int request_io_memory
(PCIConfigHeader
*pPch
)
{
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,3,0)
if (check_mem_region
(pPch
->desc
[0].
base_address, pPch
->desc
[0].
size))
return -EBUSY
;
if (check_mem_region
(pPch
->desc
[3].
base_address, pPch
->desc
[3].
size))
return -EBUSY
;
request_mem_region
(pPch
->desc
[0].
base_address, pPch
->desc
[0].
size, DEVICE_NAME
);
request_mem_region
(pPch
->desc
[3].
base_address, pPch
->desc
[3].
size, DEVICE_NAME
);
#endif
return 0;
}
static void release_io_memory
(PCIConfigHeader
*pPch
)
{
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,3,0)
release_mem_region
(pPch
->desc
[0].
base_address, pPch
->desc
[0].
size);
release_mem_region
(pPch
->desc
[3].
base_address, pPch
->desc
[3].
size);
#endif
}
static int translate_addresses
(CC32_DESCRIPTOR
*pWd
, PCIConfigHeader
*pPch
) /* differs from PCIVME */
{
if (pPch
->desc
[0].
type == PCI_BASE_ADDRESS_MEM_TYPE_1M
) /* LCR ISA base addresses */
pWd
->pLCR
= bus_to_virt
(pPch
->desc
[0].
base_address);
else
pWd
->pLCR
= ioremap
(pPch
->desc
[0].
base_address, pPch
->desc
[0].
size);
if (pPch
->desc
[3].
type == PCI_BASE_ADDRESS_MEM_TYPE_1M
) /* User ISA base addresses */
pWd
->pUsr
= bus_to_virt
(pPch
->desc
[3].
base_address);
else
pWd
->pUsr
= ioremap
(pPch
->desc
[3].
base_address, pPch
->desc
[3].
size);
return 0;
}
static void un_translate_addresses
(CC32_DESCRIPTOR
*pWd
, PCIConfigHeader
*pPch
)
{
if (pPch
->desc
[0].
type != PCI_BASE_ADDRESS_MEM_TYPE_1M
) /* no LCR ISA base addresses */
iounmap
((void *)pWd
->pLCR
);
if (pPch
->desc
[3].
type != PCI_BASE_ADDRESS_MEM_TYPE_1M
)
iounmap
((void *)pWd
->pUsr
);
}
static void soft_init
(CC32_DESCRIPTOR
*pd
)
{
if (pd
)
{
init_waitqueue_head
(&pd
->event_queue
);
pd
->pLCR
= pd
->pUsr
= 0;
pd
->pPch
= (PCIConfigHeader
*)NULL
;
pd
->cModuleNumber
= 255;
pd
->cFPGAVersion
= 255;
pd
->bConnected
= 0;
pd
->wInitStep
= 0;
pd
->wIrq
= 0xFFFF;
pd
->dwInterruptCount
= 0;
pd
->wIrqStatus
= 0;
}
}
int test_connection
(CC32_DESCRIPTOR
*wd
)
{
u16 intCSR_store
;
u16 cntrl_store
;
int i
= 1000;
u32 access_store
;
int error
= 0;
__u32 dwData
;
void *dwAdr
= wd
->pUsr
+ MAKE_CC32_ADR
(26,0,0);
cntrl_store
= readw
(wd
->pLCR
+ PLX9050_CNTRL
); /* read CONTROL register */
intCSR_store
= readw
(wd
->pLCR
+ PLX9050_INTCSR
); /* read interrupt + CSR register */
writew
(0, wd
->pLCR
+ PLX9050_INTCSR
); /* disable interrupts */
writew
(cntrl_store
| 0x0180, wd
->pLCR
+ PLX9050_CNTRL
); /* enable access */
access_store
= readl
(dwAdr
);
while (i
--)
{
writel
(0x55555555, dwAdr
);
dwData
= readl
(dwAdr
) & 0x00FFFFFF;
if (0x00555555 != dwData
)
{
DPRINTK
(KERN_DEBUG
"pcicc32 : write 0x55555555, read 0x%08x\n", dwData
);
error
= 1;
break;
}
writel
(0xAAAAAAAA, dwAdr
);
dwData
= readl
(dwAdr
) & 0x00FFFFFF;
if (0x00AAAAAA != dwData
)
{
DPRINTK
(KERN_DEBUG
"pcicc32 : write 0xAAAAAAAA, read 0x%08x\n", dwData
);
error
= 1;
break;
}
writel
(0xFFFFFFFF, dwAdr
);
dwData
= readl
(dwAdr
) & 0x00FFFFFF;
if (0x00FFFFFF != dwData
)
{
DPRINTK
(KERN_DEBUG
"pcicc32 : write 0xFFFFFFFF, read 0x%08x\n", dwData
);
error
= 1;
break;
}
writel
(0x00000000, dwAdr
);
dwData
= readl
(dwAdr
) & 0x00FFFFFF;
if (0x00000000 != dwData
)
{
DPRINTK
(KERN_DEBUG
"pcicc32 : write 0x00000000, read 0x%08x\n", dwData
);
error
= 1;
break;
}
}
writew
(cntrl_store
& ~
0x0100, wd
->pLCR
+ PLX9050_CNTRL
); /* clear potential interrupt */
/* restore all contents */
writel
(access_store
, dwAdr
);
writew
(cntrl_store
, wd
->pLCR
+ PLX9050_CNTRL
);
writew
(intCSR_store
, wd
->pLCR
+ PLX9050_INTCSR
);
return error
;
}
int get_module_info
(CC32_DESCRIPTOR
*wd
, u8
*cModuleNumber
, u8
*cFPGAVersion
)
{
u16 intCSR_store
;
u16 cntrl_store
;
int found
= 0;
u16 data
;
cntrl_store
= readw
(wd
->pLCR
+ PLX9050_CNTRL
); /* read CONTROL register */
intCSR_store
= readw
(wd
->pLCR
+ PLX9050_INTCSR
); /* read interrupt + CSR register */
DPRINTK
(KERN_DEBUG
"pcicc32 : get_module_info(), cntrl=0x%04x, intCSR=0x%04x\n", cntrl_store
, intCSR_store
);
if (cntrl_store
& 0x0800) /* a CC32 is connected */
{
u16 bla
= cntrl_store
| 0x0180;
writew
(0, wd
->pLCR
+ PLX9050_INTCSR
); /* disable interrupts */
writew
(bla
, wd
->pLCR
+ PLX9050_CNTRL
); /* enable access */
data
= readw
((wd
->pUsr
+ MAKE_CC32_ADR
(0,0,0)));
DPRINTK
(KERN_DEBUG
"pcicc32 : CC32 status=0x%04x\n", data
);
if ((data
& 0xF000) != 0x8000)
{
*cModuleNumber
= *cFPGAVersion
= 255;
printk
(KERN_ERR
"pcicc32 : Wrong module type connected @ index %d (0x%04x)!\n", wd
->wIndex
, data
);
}
else
{
found
= 1;
*cModuleNumber
= (data
>> 4) & 0xF;
*cFPGAVersion
= (data
>> 8) & 0xF;
}
writew
(cntrl_store
& ~
0x0080, wd
->pLCR
+ PLX9050_CNTRL
); /* clear potential interrupt */
/* restore all contents */
writew
(cntrl_store
, wd
->pLCR
+ PLX9050_CNTRL
);
writew
(intCSR_store
, wd
->pLCR
+ PLX9050_INTCSR
);
}
return found
;
}
#if LINUX_VERSION_CODE < KERNEL_VERSION(2,2,19)
static int pcicc32_read_proc
(char *buf
, char **start
, off_t offset
, int len
)
#else
static int pcicc32_read_proc
(char *buf
, char **start
, off_t offset
, int len
, int *eof
, void *data
)
#endif
{
int i
;
int pos
= 0;
CC32_DESCRIPTOR
*pd
;
PCIConfigHeader
*ch
;
Node
*n
;
u16 cntrl
;
char *cause
= "None";
DPRINTK
(KERN_DEBUG
"pcicc32 : pcicc32_read_proc()\n");
pos
+= sprintf(buf
+ pos
, "\npcicc32 information. Version %d.%d of %s from Klaus Hitschler.\n", VERSION_HI
, VERSION_LO
, __DATE__
);
i
= getNumOfNodesInList
(pcicc32_work_device_header
);
pos
+= sprintf(buf
+ pos
, " ---------------------\n");
pos
+= sprintf(buf
+ pos
, " Interfaces found : %d\n", i
);
n
= getFirstNode
(pcicc32_work_device_header
);
while (i
--)
{
pd
= (CC32_DESCRIPTOR
*)getContent
(n
);
ch
= pd
->pPch
;
cntrl
= readw
(pd
->pLCR
+ PLX9050_CNTRL
);
pos
+= sprintf(buf
+ pos
, " --- %d ---------------\n", i
+ 1);
pos
+= sprintf(buf
+ pos
, " LCR phys/virt/size : 0x%08x/0x%p/%d\n", ch
->desc
[0].
base_address, pd
->pLCR
, ch
->desc
[0].
size);
pos
+= sprintf(buf
+ pos
, " User phys/virt/size : 0x%08x/0x%p/%d\n", ch
->desc
[3].
base_address, pd
->pUsr
, ch
->desc
[3].
size);
pos
+= sprintf(buf
+ pos
, " Irq : %d\n", pd
->wIrq
);
if (pd
->bConnected
)
{
pos
+= sprintf(buf
+ pos
, " CC32 is or was : (software) connected.\n");
pos
+= sprintf(buf
+ pos
, " Module-Number : %d\n", pd
->cModuleNumber
);
pos
+= sprintf(buf
+ pos
, " FPGA-Version : %d\n", pd
->cFPGAVersion
);
}
else
pos
+= sprintf(buf
+ pos
, " CC32 is or was : not (software) connected.\n");
if (!((cntrl
& 0x0800) && (!(cntrl
& 0x0600))))
pos
+= sprintf(buf
+ pos
, " CC32 is : powered off or cable disconnected.\n");
pos
+= sprintf(buf
+ pos
, " IrqCount : %d\n", pd
->dwInterruptCount
);
if (pd
->wIrqStatus
& PCIADA_INTERRUPT
)
cause
= "Timeout";
else
if (pd
->wIrqStatus
& CC32_INTERRUPT
)
cause
= "LAM";
pos
+= sprintf(buf
+ pos
, " Pending IrqStatus : %s\n", cause
);
n
= getNextNode
(n
);
}
pos
+= sprintf(buf
+ pos
, "\n");
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,2,19)
*eof
= 1;
#endif
return pos
;
}
#if LINUX_VERSION_CODE <= KERNEL_VERSION(2,2,19)
//----------------------------------------------------------------------------
// replacement for kernel 2.4.x function
// original from sysdep.h out from the book
// "Linux Device Drivers" by Alessandro Rubini and Jonathan Corbet, published by O'Reilly & Associates.
static struct proc_dir_entry
*create_proc_read_entry
(const char *name
, mode_t mode
,
struct proc_dir_entry
*base
, read_proc_t
*read_proc
, void * data
)
{
struct proc_dir_entry
*res
= create_proc_entry
(name
, mode
, base
);
if (res
)
{
res
->read_proc
=read_proc
;
res
->data
=data
;
}
return res
;
}
#endif
int init_module
(void)
{
int i
= 0;
PCIConfigHeader
*ch
;
CC32_DESCRIPTOR
*wd
;
Node
*n
;
int result
= 0;
printk
(KERN_INFO
"pcicc32 : init_module\n");
/* create list of PCIADAs and work devices */
pci_device_header
= newList
();
pcicc32_work_device_header
= newList
();
/* search for all PCIADA modules */
while ((ch
= GetPCIConfigHeader
(ARWVME_VENDOR_ID
, ARWVME_DEVICE_ID
, i
)))
{
if (ch
->subsystem_id
!= ARWVME_SUBSYSTEM_ID
)
{
printk
(KERN_ERR
"pcicc32 : found 0x%x, 0x%x , but wrong subsystem id 0x%04x!\n",
ARWVME_VENDOR_ID
, ARWVME_DEVICE_ID
, ch
->subsystem_id
);
i
++;
kfree_s
(ch
, sizeof(*ch
)); // FREE(ch);
continue;
}
if (ch
->subsystem_vendor_id
!= ARWVME_SUBSYSTEM_VENDOR_ID
)
{
printk
(KERN_ERR
"pcicc32 : found 0x%x, 0x%x , but wrong subsystem vendor Id 0x%04x!\n",
ARWVME_VENDOR_ID
, ARWVME_DEVICE_ID
, ch
->subsystem_vendor_id
);
i
++;
kfree_s
(ch
, sizeof(*ch
)); // FREE(ch);
continue;
}
/* test if it is configured as PCICC32 or a PCIVME interface card */
if (ch
->desc
[3].
size != 0x8000)
{
printk
(KERN_ERR
"pcicc32 : found, but wrong memory window size 0x%08x!\n", ch
->desc
[3].
size);
i
++;
kfree_s
(ch
, sizeof(*ch
)); // FREE(ch);
continue;
}
printk
(KERN_INFO
"pcicc32 : found %d. PCIADA card\n", i
+ 1);
addTail
(pci_device_header
, ch
); /* add this header to list */
i
++;
}
/* fix the PLX bug in all PCIADAs */
i
= getNumOfNodesInList
(pci_device_header
);
n
= getFirstNode
(pci_device_header
);
while (i
--)
{
ch
= (PCIConfigHeader
*)getContent
(n
);
PLX9050BugFix
(ch
);
n
= getNextNode
(n
);
}
/* create work_devices and translate the access addresses */
i
= getNumOfNodesInList
(pci_device_header
);
n
= getFirstNode
(pci_device_header
);
while (i
--)
{
wd
= (CC32_DESCRIPTOR
*)kmalloc
(sizeof(CC32_DESCRIPTOR
), GFP_ATOMIC
);
soft_init
(wd
);
ch
= (PCIConfigHeader
*)getContent
(n
);
wd
->pPch
= ch
;
wd
->wIndex
= i
;
if (!request_io_memory
(ch
))
{
// successful request_io_memory
wd
->wInitStep
= 1;
if (translate_addresses
(wd
, ch
))
{
printk
(KERN_ERR
"pcicc32 : translation of addresses failed!\n");
release_io_memory
(ch
);
kfree_s
(wd
, sizeof(*wd
)); // FREE(wd);
}
else
{
// successful translate_addresses
wd
->wInitStep
= 2;
if (request_irq
(wd
->pPch
->PCI_dev
->irq
, cc32_irqhandler
, IRQF_DISABLED
| IRQF_SHARED
, DEVICE_NAME
, wd
))
{
printk
(KERN_ERR
"pcicc32 : can't get irq @ %d\n", wd
->pPch
->PCI_dev
->irq
);
un_translate_addresses
(wd
, ch
);
release_io_memory
(ch
);
kfree_s
(wd
, sizeof(*wd
)); // FREE(wd);
}
else
{
// successful request_irq
wd
->wInitStep
= 3;
wd
->wIrq
= wd
->pPch
->PCI_dev
->irq
;
addTail
(pcicc32_work_device_header
, wd
); /* add this device to list */
wd
->bConnected
= get_module_info
(wd
, &wd
->cModuleNumber
, &wd
->cFPGAVersion
);
if (wd
->bConnected
&& test_connection
(wd
))
{
printk
(KERN_ERR
"pcicc32 : connection test failed!\n");
wd
->bConnected
= 0;
}
}
}
}
else
{
printk
(KERN_ERR
"pcicc32 : requested io-memory still claimed!\n");
kfree_s
(wd
, sizeof(*wd
)); // FREE(wd);
}
n
= getNextNode
(n
);
}
nMajor
= MAJOR_NO
;
result
= register_chrdev
(nMajor
, DEVICE_NAME
, &pcicc32_fops
);
if (result
< 0)
{
printk
(KERN_ERR
"pcicc32: Can't install driver (%d)\n", result
);
/* untranslate translated addresses */
i
= getNumOfNodesInList
(pcicc32_work_device_header
);
n
= getFirstNode
(pcicc32_work_device_header
);
while (i
--)
{
wd
= (CC32_DESCRIPTOR
*)getContent
(n
);
ch
= wd
->pPch
;
un_translate_addresses
(wd
, ch
);
n
= getNextNode
(n
);
}
/* delete my lists */
deleteList
(pcicc32_work_device_header
, (void (*)(void *))NULL
);
deleteList
(pci_device_header
, (void (*)(void *))NULL
);
return result
;
}
else
{
if (nMajor
== 0) nMajor
= result
;
printk
(KERN_INFO
"pcicc32 : major #%d assigned.\n", nMajor
);
}
/* register the proc device */
return proc_create_data
(DEVICE_NAME
, 0, NULL
, &pcicc32_fops
, NULL
) ? 0 : -ENODEV
;
}
void cleanup_module
(void)
{
int i
;
PCIConfigHeader
*ch
;
CC32_DESCRIPTOR
*wd
;
Node
*n
;
unregister_chrdev
(nMajor
, DEVICE_NAME
);
DPRINTK
(KERN_DEBUG
"pcicc32 : cleanup_module()\n");
/* unregister the proc device */
#if LINUX_VERSION_CODE < KERNEL_VERSION(2,2,19)
proc_unregister
(&proc_root
, pcimod_proc_entry.
low_ino);
#else
remove_proc_entry
(DEVICE_NAME
, NULL
);
#endif
/* untranslate translated addresses */
i
= getNumOfNodesInList
(pcicc32_work_device_header
);
n
= getFirstNode
(pcicc32_work_device_header
);
while (i
--)
{
wd
= (CC32_DESCRIPTOR
*)getContent
(n
);
ch
= wd
->pPch
;
switch (wd
->wInitStep
)
{
case 3: writew
(readw
(wd
->pLCR
+ PLX9050_INTCSR
) & ~
0x40, wd
->pLCR
+ PLX9050_INTCSR
); // disable global interrupts
free_irq
(wd
->wIrq
, wd
);
case 2: un_translate_addresses
(wd
, ch
);
case 1: release_io_memory
(ch
);
default: wd
->wInitStep
= 0;
}
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,10)
if ((ch
) && (ch
->PCI_dev
))
pci_disable_device
(ch
->PCI_dev
);
#endif
n
= getNextNode
(n
);
}
/* delete my lists */
deleteList
(pcicc32_work_device_header
, (void (*)(void *))NULL
);
deleteList
(pci_device_header
, (void (*)(void *))NULL
);
printk
(KERN_INFO
"pcicc32 : cleanup_module successful.\n");
return;
}