//****************************************************************************
 
// 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);
 
  }
 
 
 
  
 
  #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;
 
}