]> rtime.felk.cvut.cz Git - lincan.git/commitdiff
The first chunk to support for PEAK's LPT dongle adapter
authorppisa <ppisa>
Wed, 25 Aug 2004 02:59:51 +0000 (02:59 +0000)
committerppisa <ppisa>
Wed, 25 Aug 2004 02:59:51 +0000 (02:59 +0000)
Code has been ported to LinCAN by Jose Pascual Ramirez
(josepascual _AT_ almudi _DOT_ com).

lincan/include/pcan_dongle.h [new file with mode: 0644]
lincan/src/Makefile.std
lincan/src/boardlist.c
lincan/src/pcan_dongle.c [new file with mode: 0644]

diff --git a/lincan/include/pcan_dongle.h b/lincan/include/pcan_dongle.h
new file mode 100644 (file)
index 0000000..307071a
--- /dev/null
@@ -0,0 +1,103 @@
+#ifndef __PCAN_DONGLE_H__
+#define __PCAN_DONGLE_H__
+
+/****************************************************************************/
+// Ingenieria Almudi (www.almudi.com)
+// Ported to LinCAN by Jose Pascual Ramírez (josepascual@almudi.com)
+//
+//
+// Copyright (C) 2001,2002,2003,2004  PEAK System-Technik GmbH
+//
+// linux@peak-system.com
+// www.peak-system.com
+//
+// 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.
+//
+// Maintainer(s): Klaus Hitschler (klaus.hitschler@gmx.de)
+/****************************************************************************/
+
+/****************************************************************************/
+//
+// all parts to handle device interface specific parts of pcan-dongle
+//
+// Revision 1.13  2004/04/11 22:03:29  klaus
+// cosmetic changes
+//
+// Revision 1.12  2003/03/02 10:58:07  klaus
+// merged USB thread into main path
+//
+// Revision 1.11  2003/03/02 10:58:07  klaus
+// merged USB thread into main path
+//
+// Revision 1.10.2.3  2003/01/29 20:34:20  klaus
+// release_20030129_a and release_20030129_u released
+//
+// Revision 1.10.2.2  2003/01/29 20:34:19  klaus
+// release_20030129_a and release_20030129_u released
+//
+// Revision 1.10.2.1  2003/01/28 23:28:26  klaus
+// reorderd pcan_usb.c and pcan_usb_kernel.c, tidied up
+//
+// Revision 1.10  2002/01/30 20:54:27  klaus
+// simple source file header change
+//
+/****************************************************************************/
+
+/****************************************************************************/
+// parameter wHardwareType, used by open 
+#define HW_ISA             1 // not supported with LINUX, 82C200 chip 
+#define HW_DONGLE_SJA      5
+#define HW_DONGLE_SJA_EPP  6 
+#define HW_DONGLE_PRO      7 // not yet supported with LINUX
+#define HW_DONGLE_PRO_EPP  8 // not yet supported with LINUX
+#define HW_ISA_SJA         9 // use this also for PC/104
+#define HW_PCI                 10 // PCI carries always SJA1000 chips
+#define HW_USB            11 // don't know if this is common over peak products
+
+
+struct DONGLE_PORT
+{
+  u32  dwPort;                                             // the port of the transport layer
+  u16  wIrq;                                               // the associated irq 
+  struct pardevice *pardev;                                // points to the associated parallel port (PARPORT subsytem)
+  u16  wEcr;                                               // ECR register in case of EPP
+  u8   ucOldDataContent;                                   // the overwritten contents of the port registers
+  u8   ucOldControlContent;
+  u8   ucOldECRContent;
+
+
+  u16  wInitStep;                                          // device specific init state
+  u16  wType;                                              // (number type) to distinguish sp and epp
+  int  nMinor;                                             // the associated minor
+  char *type;                                              // the literal type of the device, info only
+
+  struct chip_t *chip;
+};
+
+
+/****************************************************************************/
+// DEFINES
+
+int pcan_dongle_request_io(struct candevice_t *candev);
+int pcan_dongle_release_io(struct candevice_t *candev);
+int pcan_dongle_reset(struct candevice_t *candev); 
+int pcan_dongle_init_hw_data(struct candevice_t *candev);
+int pcan_dongle_init_chip_data(struct candevice_t *candev, int chipnr);
+int pcan_dongle_init_obj_data(struct chip_t *chip, int objnr);
+void pcan_dongle_write_register(unsigned data, unsigned long address);
+unsigned pcan_dongle_read_register(unsigned long address);
+int pcan_dongle_program_irq(struct candevice_t *candev);
+
+#endif // __PCAN_DONGLE_H__
index 29a2b3088049284e08f961794b3d7db229878482..296e65972c551af43944713936a1f7f0982c94bf 100644 (file)
@@ -66,7 +66,7 @@ SUPPORTED_CARDS = pip pccan smartcan nsi cc_can104 \
                  unican unican_cl2
 #                ems_cpcpci # compiles only for 2.6 kernels now
 #                hms30c7202_can c_can c_can_irq
-
+#                pcan_dongle
 
 #SUPPORTED_CARDS = pcm3680 bfadcan pikronisa template
 
index a446d4d4d0205178011e22f49b9ff1c4ea262935..2afcc1750d22de8ac768b43eef449176a5d722ed 100644 (file)
@@ -34,6 +34,7 @@ extern int msmcan_register(struct hwspecops_t *hwspecops);
 extern int unican_register(struct hwspecops_t *hwspecops);
 extern int unican_pci_register(struct hwspecops_t *hwspecops);
 extern int unican_vme_register(struct hwspecops_t *hwspecops);
+extern int pcan_dongle_register(struct hwspecops_t *hwspecops);
 
 const struct boardtype_t can_boardtypes[]={
     #ifdef CONFIG_OC_LINCAN_CARD_template
@@ -104,6 +105,9 @@ const struct boardtype_t can_boardtypes[]={
     #endif
     #if defined(CONFIG_OC_LINCAN_CARD_unican)&&defined(CAN_ENABLE_VME_SUPPORT)
        {"unican-vme", unican_vme_register, 1},
+    #endif
+    #if defined(CONFIG_OC_LINCAN_CARD_pcan_dongle)
+       {"pcan_dongle", pcan_dongle_register, 1},
     #endif
        {NULL}
 };
diff --git a/lincan/src/pcan_dongle.c b/lincan/src/pcan_dongle.c
new file mode 100644 (file)
index 0000000..10b6efa
--- /dev/null
@@ -0,0 +1,759 @@
+/****************************************************************************/
+// Ingenieria Almudi (www.almudi.com)
+// Ported to LinCAN by Jose Pascual Ramírez (josepascual@almudi.com)
+// 
+//
+// Copyright (C) 2001,2002,2003,2004  PEAK System-Technik GmbH
+//
+// linux@peak-system.com
+// www.peak-system.com
+//
+// 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.
+//
+// Maintainer(s): Klaus Hitschler (klaus.hitschler@gmx.de)
+//
+// Contributions: Marcel Offermans (marcel.offermans@luminis.nl)
+//                Philipp Baer (philipp.baer@informatik.uni-ulm.de)
+/****************************************************************************/
+
+/****************************************************************************/
+//
+// all parts to handle the interface specific parts of pcan-dongle
+//
+// Revision 1.38  2004/07/28 22:03:29  jose pascual
+// ported to LinCAN
+//
+// Revision 1.37  2004/04/11 22:03:29  klaus
+// cosmetic changes
+//
+// Revision 1.36  2004/04/10 12:25:39  klaus
+// merge polished between HEAD and kernel-2.6 branch
+//
+// Revision 1.35  2004/04/10 08:57:26  klaus
+// merge finished between HEAD and kernel-2.6 branch
+//
+// Revision 1.32.2.1  2004/03/21 12:09:09  klaus
+// first commit for branch to kernel 2.6 code
+//
+// Revision 1.34  2004/03/27 16:57:06  klaus
+// modified for use with kernels <= 2.2.14
+//
+// Revision 1.33  2004/03/27 15:10:54  klaus
+// prepared for use with gcc 3.x, modified for use with kernels < 2.2.4
+//
+// Revision 1.32  2004/03/04 18:50:08  klaus
+// renamed PA,PB,PC to _PA_, ... to support (partially) cross-compiling for MIPS
+//
+// Revision 1.31  2003/06/22 15:34:50  klaus
+// added parts to support devfs provided by Philipp Baer (partially untested)
+//
+// Revision 1.30  2003/06/04 19:26:15  klaus
+// adapted to kernel 2.5.69 using GCC 3.2.3 (marcel), released release_20030604_x
+//
+// Revision 1.28  2003/03/02 10:58:07  klaus
+// merged USB thread into main path
+//
+// Revision 1.27  2003/03/02 10:58:07  klaus
+// merged USB thread into main path
+//
+// Revision 1.26.2.5  2003/01/29 20:34:20  klaus
+// release_20030129_a and release_20030129_u released
+//
+// Revision 1.26.2.4  2003/01/29 20:34:19  klaus
+// release_20030129_a and release_20030129_u released
+//
+// Revision 1.26.2.3  2003/01/28 23:28:26  klaus
+// reorderd pcan_usb.c and pcan_usb_kernel.c, tidied up
+//
+// Revision 1.26.2.2  2003/01/14 20:31:53  klaus
+// read/write/minor assigment is working
+//
+/****************************************************************************/
+
+/****************************************************************************/
+// INCLUDES
+
+
+#include "../include/can.h"
+#include "../include/can_sysdep.h"
+#include "../include/main.h"
+#include "../include/sja1000p.h"
+
+#include <linux/parport.h>
+
+#include "../include/pcan_dongle.h"
+
+
+
+/****************************************************************************/
+// DEFINES
+#define PCAN_DNG_SP_MINOR_BASE  16  // starting point of minors for SP devices
+#define PCAN_DNG_EPP_MINOR_BASE 24  // starting point of minors for EPP devices
+#define DNG_PORT_SIZE            4  // the address range of the dongle-port
+#define ECR_PORT_SIZE            1  // size of the associated ECR register
+#define DNG_DEFAULT_COUNT        4  // count of defaults for init
+
+typedef void (*PARPORT_IRQ_HANLDER)(int, void *, struct pt_regs *);
+
+/****************************************************************************/
+// GLOBALS
+spinlock_t pcan_lock = SPIN_LOCK_UNLOCKED;
+
+/****************************************************************************/
+// LOCALS
+static u16 dng_ports[] = {0x378, 0x278, 0x3bc, 0x2bc};
+static u8  dng_irqs[]  = {7, 5, 7, 5};
+static u16 dng_devices = 0;        // the number of accepted dng_devices
+static u16 epp_devices = 0;        // ... epp_devices
+static u16 sp_devices  = 0;        // ... sp_devices
+
+static unsigned char nibble_decode[32] =
+{
+  0x8, 0x9, 0xa, 0xb, 0xc, 0xd, 0xe, 0xf,
+  0x8, 0x9, 0xa, 0xb, 0xc, 0xd, 0xe, 0xf,
+  0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7,
+  0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7
+};
+
+struct DONGLE_PORT dongle_port;
+
+char dongle_type[] = "epp_dongle";
+//char dongle_type[] = "sp_dongle";
+
+/****************************************************************************/
+// CODE
+
+//----------------------------------------------------------------------------
+// enable and disable irqs
+static void _parport_disable_irq(struct DONGLE_PORT *dng)
+{
+  u16 _PC_ = (u16)dng->dwPort + 2;
+  outb(inb(_PC_) & ~0x10, _PC_);
+}
+
+static void _parport_enable_irq(struct DONGLE_PORT *dng)
+{
+  u16 _PC_ = (u16)dng->dwPort + 2;
+  outb(inb(_PC_) | 0x10, _PC_);
+}
+
+
+// functions for SP port
+static u8 pcan_dongle_sp_readreg(struct DONGLE_PORT *dng, u8 port) // read a register
+{
+  u16 _PA_ = (u16)dng->dwPort;
+  u16 _PB_ = _PA_ + 1;
+  u16 _PC_ = _PB_ + 1;
+  u8  b0, b1 ;
+  u8  irqEnable = inb(_PC_) & 0x10; // don't influence irqEnable
+  unsigned long flags;
+
+  #if LINUX_VERSION_CODE < KERNEL_VERSION(2,4,18)
+  save_flags(flags);
+  cli();
+  #else
+  spin_lock_irqsave(&pcan_lock, flags);
+  #endif
+
+  outb((0x0B ^ 0x0D) | irqEnable, _PC_);
+  outb((port & 0x1F) | 0x80,      _PA_);
+  outb((0x0B ^ 0x0C) | irqEnable, _PC_);
+  b1=nibble_decode[inb(_PB_)>>3];
+  outb(0x40, _PA_);
+  b0=nibble_decode[inb(_PB_)>>3];
+  outb((0x0B ^ 0x0D) | irqEnable, _PC_);
+
+  #if LINUX_VERSION_CODE < KERNEL_VERSION(2,4,18)
+  restore_flags(flags);
+  #else
+  spin_unlock_irqrestore(&pcan_lock, flags);
+  #endif
+
+  return  (b1 << 4) | b0 ;
+}
+
+static void pcan_dongle_writereg(struct DONGLE_PORT *dng, u8 port, u8 data) // write a register
+{
+  u16 _PA_ = (u16)dng->dwPort;
+  u16 _PC_ = _PA_ + 2;
+  u8  irqEnable = inb(_PC_) & 0x10; // don't influence irqEnable
+  unsigned long flags;
+
+  #if LINUX_VERSION_CODE < KERNEL_VERSION(2,4,18)
+  save_flags(flags);
+  cli();
+  #else
+  spin_lock_irqsave(&pcan_lock, flags);
+  #endif
+
+  outb((0x0B ^ 0x0D) | irqEnable, _PC_);
+  outb(port & 0x1F,               _PA_);
+  outb((0x0B ^ 0x0C) | irqEnable, _PC_);
+  outb(data,                      _PA_);
+  outb((0x0B ^ 0x0D) | irqEnable, _PC_);
+
+  #if LINUX_VERSION_CODE < KERNEL_VERSION(2,4,18)
+  restore_flags(flags);
+  #else
+  spin_unlock_irqrestore(&pcan_lock, flags);
+  #endif
+}
+
+// functions for EPP port
+static u8 pcan_dongle_epp_readreg(struct DONGLE_PORT *dng, u8 port) // read a register
+{
+  u16 _PA_ = (u16)dng->dwPort;
+  u16 _PC_ = _PA_ + 2;
+  u8  wert;
+  u8  irqEnable = inb(_PC_) & 0x10; // don't influence irqEnable
+  unsigned long flags;
+
+  #if LINUX_VERSION_CODE < KERNEL_VERSION(2,4,18)
+  save_flags(flags);
+  cli();
+  #else
+  spin_lock_irqsave(&pcan_lock, flags);
+  #endif
+
+  outb((0x0B ^ 0x0F) | irqEnable, _PC_);
+  outb((port & 0x1F) | 0x80,      _PA_);
+  outb((0x0B ^ 0x2E) | irqEnable, _PC_);
+  wert = inb(_PA_);
+  outb((0x0B ^ 0x0F) | irqEnable, _PC_);
+
+  #if LINUX_VERSION_CODE < KERNEL_VERSION(2,4,18)
+  restore_flags(flags);
+  #else
+  spin_unlock_irqrestore(&pcan_lock, flags);
+  #endif
+
+  return wert;
+}
+
+static int pcan_dongle_req_irq(struct DONGLE_PORT *dng)
+{
+  if (dng->wInitStep == 3)
+  {
+    dng->wInitStep++;
+  }
+
+  return 0;
+}
+
+static void pcan_dongle_free_irq(struct DONGLE_PORT *dng)
+{
+  if (dng->wInitStep == 4)
+  {
+    dng->wInitStep--;
+  }
+}
+
+// release and probe functions
+static int pcan_dongle_cleanup(struct DONGLE_PORT *dng)
+{
+  DEBUGMSG("%s: pcan_dongle_cleanup()\n", DEVICE_NAME);
+
+  switch(dng->wInitStep)
+  {
+    case 4: pcan_dongle_free_irq(dng);
+    case 3: if (dng->wType == HW_DONGLE_SJA)
+              sp_devices--;
+           else
+             epp_devices--;
+           dng_devices = sp_devices + epp_devices;
+    case 2:
+    case 1:
+            parport_unregister_device(dng->pardev);
+    case 0: dng->wInitStep = 0;
+  }
+
+  return 0;
+}
+
+// to switch epp on or restore register
+static void setECR(struct DONGLE_PORT *dng)
+{
+       u16 wEcr = dng->wEcr;
+
+       dng->ucOldECRContent = inb(wEcr);
+       outb((dng->ucOldECRContent & 0x1F) | 0x20, wEcr);
+
+       if (dng->ucOldECRContent == 0xff)
+               DEBUGMSG("%s: realy ECP mode configured?\n", DEVICE_NAME);
+}
+
+static void restoreECR(struct DONGLE_PORT *dng)
+{
+  u16 wEcr = dng->wEcr;
+
+  outb(dng->ucOldECRContent, wEcr);
+
+  DEBUGMSG("%s: restore ECR\n", DEVICE_NAME);
+}
+
+static int pcan_dongle_probe(struct DONGLE_PORT *dng) // probe for type
+{
+  struct parport *p;
+
+  DEBUGMSG("%s: pcan_dongle_probe() - PARPORT_SUBSYSTEM\n", DEVICE_NAME);
+  
+  // probe does not probe for the sja1000 device here - this is done at sja1000_open()
+  p = parport_find_base(dng->dwPort);
+  if (!p)
+  {
+    DEBUGMSG("found no parport\n");
+    return -ENXIO;
+  }
+  else
+  {
+       dng->pardev = parport_register_device(p, "can", NULL, NULL, 
+                          (PARPORT_IRQ_HANLDER)dng->chip->chipspecops->irq_handler,
+                         0, (void *)dng->chip);
+       
+//    DEBUGMSG("datos IRQ: irq_handler=0x%x p=0x%x dng->chip=0x%x dng->pardev->port->irq=0x%x irq_handler2=0x%x\n",
+//             dng->chip->chipspecops->irq_handler,
+//             p,dng->chip,dng->pardev->port->irq, &sja1000p_irq_handler);
+                                     
+    if (!dng->pardev)
+    {
+      DEBUGMSG("found no parport device\n");
+      return -ENODEV;
+    }
+
+  }
+  
+  return 0;
+}
+
+// interface depended open and close
+static int pcan_dongle_open(struct DONGLE_PORT *dng)
+{
+  int result = 0;
+  u16 wPort;
+  
+  DEBUGMSG("%s: pcan_dongle_open()\n", DEVICE_NAME);
+  
+  result = parport_claim(dng->pardev);
+  
+  if (!result)
+  {
+    if (dng->pardev->port->irq == PARPORT_IRQ_NONE)
+    {
+      DEBUGMSG(KERN_ERR "%s: no irq associated to parport.\n", DEVICE_NAME);
+      result = -ENXIO;
+    }
+  }
+  else
+   DEBUGMSG(KERN_ERR "%s: can't claim parport.\n", DEVICE_NAME);       
+  
+  // save port state
+  if (!result)
+   {
+      wPort    = (u16)dng->dwPort;
+         
+     // save old port contents
+     dng->ucOldDataContent     = inb(wPort);
+     dng->ucOldControlContent  = inb(wPort + 2);
+         
+     // switch to epp mode if possible
+     if (dng->wType == HW_DONGLE_SJA_EPP)
+        setECR(dng); 
+  
+    // enable irqs
+    _parport_enable_irq(dng); // parport_enable_irq(dng->pardev->port); not working since 2.4.18
+  }    
+       
+  return result;
+}
+
+static int pcan_dongle_release(struct DONGLE_PORT *dng)
+{
+  u16 wPort = (u16)dng->dwPort;
+
+  DEBUGMSG("%s: pcan_dongle_release()\n", DEVICE_NAME);
+  
+  // disable irqs
+  _parport_disable_irq(dng); // parport_disable_irq(dng->pardev->port); not working since 2.4.18
+
+  if (dng->wType == HW_DONGLE_SJA_EPP)
+    restoreECR(dng);
+    
+  // restore port state
+  outb(dng->ucOldDataContent, wPort);
+  outb(dng->ucOldControlContent, wPort + 2);
+      
+  parport_release(dng->pardev);
+  
+  return 0;
+}
+
+int  pcan_dongle_init(struct DONGLE_PORT *dng, u32 dwPort, u16 wIrq, char *type)
+{
+  int err;
+  
+  DEBUGMSG("%s: pcan_dongle_init(), dng_devices = %d\n", DEVICE_NAME, dng_devices);
+  
+  dng->type = type;
+
+  dng->wType = (!strncmp(dongle_type, "sp", 4)) ? HW_DONGLE_SJA : HW_DONGLE_SJA_EPP;
+  
+  // set this before any instructions, fill struct pcandev, part 1 
+  dng->wInitStep   = 0;  
+        
+  // fill struct pcandev, 1st check if a default is set
+  if (!dwPort)
+  {
+    // there's no default available
+    if (dng_devices >= DNG_DEFAULT_COUNT)
+      return -ENODEV;
+    
+    dng->dwPort = dng_ports[dng_devices];
+  }
+  else
+    dng->dwPort = dwPort;
+  
+  if (!wIrq)
+  {
+    if (dng_devices >= DNG_DEFAULT_COUNT)
+      return -ENODEV;
+    
+    dng->wIrq   = dng_irqs[dng_devices];    
+  }
+  else
+    dng->wIrq   = wIrq;    
+  
+  if (dng->wType == HW_DONGLE_SJA)
+    {
+          dng->nMinor        = PCAN_DNG_SP_MINOR_BASE + sp_devices; 
+          dng->wEcr = 0; // set to anything
+    }
+  else
+    {
+           dng->nMinor        = PCAN_DNG_EPP_MINOR_BASE + epp_devices;  
+           dng->wEcr = (u16)dng->dwPort + 0x402;
+    }
+         
+       // is the device really available?              
+  if ((err = pcan_dongle_probe(dng)) < 0)
+    return err;
+  
+  if (dng->wType == HW_DONGLE_SJA)
+    sp_devices++;
+  else
+    epp_devices++;
+         
+  dng_devices = sp_devices + epp_devices;
+  
+  dng->wInitStep = 3;
+
+  DEBUGMSG(KERN_INFO "%s: %s device minor %d prepared (io=0x%04x,irq=%d)\n", DEVICE_NAME, 
+                               dng->type, dng->nMinor, dng->dwPort, dng->wIrq);
+       
+  return 0;
+}
+
+
+
+
+/**
+ * template_request_io: - reserve io or memory range for can board
+ * @candev: pointer to candevice/board which asks for io. Field @io_addr
+ *     of @candev is used in most cases to define start of the range
+ *
+ * The function template_request_io() is used to reserve the io-memory. If your
+ * hardware uses a dedicated memory range as hardware control registers you
+ * will have to add the code to reserve this memory as well. 
+ * %IO_RANGE is the io-memory range that gets reserved, please adjust according
+ * your hardware. Example: #define IO_RANGE 0x100 for i82527 chips or
+ * #define IO_RANGE 0x20 for sja1000 chips in basic CAN mode.
+ * Return Value: The function returns zero on success or %-ENODEV on failure
+ * File: src/template.c
+ */
+int pcan_dongle_request_io(struct candevice_t *candev)
+{
+        int res_init;
+
+       dongle_port.chip = candev->chip[0];
+
+       res_init = pcan_dongle_init(&dongle_port, 0, 0, dongle_type);
+
+       return res_init;
+}
+
+/**
+ * template_elease_io - free reserved io memory range
+ * @candev: pointer to candevice/board which releases io
+ *
+ * The function template_release_io() is used to free reserved io-memory.
+ * In case you have reserved more io memory, don't forget to free it here.
+ * IO_RANGE is the io-memory range that gets released, please adjust according
+ * your hardware. Example: #define IO_RANGE 0x100 for i82527 chips or
+ * #define IO_RANGE 0x20 for sja1000 chips in basic CAN mode.
+ * Return Value: The function always returns zero
+ * File: src/template.c
+ */
+int pcan_dongle_release_io(struct candevice_t *candev)
+{
+       /* release I/O port */
+       pcan_dongle_release(&dongle_port);
+       
+       pcan_dongle_cleanup(&dongle_port);
+
+       return 0;
+}
+
+/**
+ * template_reset - hardware reset routine
+ * @candev: Pointer to candevice/board structure
+ *
+ * The function template_reset() is used to give a hardware reset. This is 
+ * rather hardware specific so I haven't included example code. Don't forget to 
+ * check the reset status of the chip before returning.
+ * Return Value: The function returns zero on success or %-ENODEV on failure
+ * File: src/template.c
+ */
+int pcan_dongle_reset(struct candevice_t *candev)
+{
+       int i=0;
+       struct chip_t *chip;
+       int chipnr;
+       unsigned cdr;
+       
+       DEBUGMSG("Resetting pcan_dongle hardware ...\n");
+       for(chipnr=0;chipnr<candev->nr_sja1000_chips;chipnr++) {
+               chip=candev->chip[chipnr];
+
+         pcan_dongle_write_register(sjaMOD_RM, chip->chip_base_addr+SJAMOD);
+         udelay(1000);
+       
+         cdr=pcan_dongle_read_register(chip->chip_base_addr+SJACDR);
+         pcan_dongle_write_register(cdr|sjaCDR_PELICAN, chip->chip_base_addr+SJACDR);
+
+         pcan_dongle_write_register(0, chip->chip_base_addr+SJAIER);
+
+         i=20;
+         pcan_dongle_write_register(0, chip->chip_base_addr+SJAMOD);
+         while (pcan_dongle_read_register(chip->chip_base_addr+SJAMOD)&sjaMOD_RM) {
+               if(!i--) {
+                       CANMSG("Reset status timeout!\n");
+                       CANMSG("Please check your hardware.\n");
+                       return -ENODEV;
+               }
+               udelay(1000);
+               pcan_dongle_write_register(0, chip->chip_base_addr+SJAMOD);
+         }
+
+         cdr = pcan_dongle_read_register(chip->chip_base_addr+SJACDR);
+         pcan_dongle_write_register(cdr|sjaCDR_PELICAN, chip->chip_base_addr+SJACDR);
+
+         pcan_dongle_write_register(0, chip->chip_base_addr+SJAIER);
+       }
+       return 0;
+}
+
+#define RESET_ADDR 0x0
+#define NR_82527 0
+#define NR_SJA1000 1
+
+/**
+ * template_init_hw_data - Initialize hardware cards
+ * @candev: Pointer to candevice/board structure
+ *
+ * The function template_init_hw_data() is used to initialize the hardware
+ * structure containing information about the installed CAN-board.
+ * %RESET_ADDR represents the io-address of the hardware reset register.
+ * %NR_82527 represents the number of intel 82527 chips on the board.
+ * %NR_SJA1000 represents the number of philips sja1000 chips on the board.
+ * The flags entry can currently only be %CANDEV_PROGRAMMABLE_IRQ to indicate that
+ * the hardware uses programmable interrupts.
+ * Return Value: The function always returns zero
+ * File: src/template.c
+ */
+int pcan_dongle_init_hw_data(struct candevice_t *candev) 
+{
+       candev->res_addr=RESET_ADDR;
+       candev->nr_82527_chips=NR_82527;
+       candev->nr_sja1000_chips=NR_SJA1000;
+       candev->nr_all_chips=NR_82527+NR_SJA1000;
+//     candev->flags &= ~CANDEV_PROGRAMMABLE_IRQ;
+       candev->flags |= CANDEV_PROGRAMMABLE_IRQ;
+
+       return 0;
+}
+
+#define CHIP_TYPE "sja1000p"
+/**
+ * template_init_chip_data - Initialize chips
+ * @candev: Pointer to candevice/board structure
+ * @chipnr: Number of the CAN chip on the hardware card
+ *
+ * The function template_init_chip_data() is used to initialize the hardware
+ * structure containing information about the CAN chips.
+ * %CHIP_TYPE represents the type of CAN chip. %CHIP_TYPE can be "i82527" or
+ * "sja1000".
+ * The @chip_base_addr entry represents the start of the 'official' memory map
+ * of the installed chip. It's likely that this is the same as the @io_addr
+ * argument supplied at module loading time.
+ * The @clock entry holds the chip clock value in Hz.
+ * The entry @sja_cdr_reg holds hardware specific options for the Clock Divider
+ * register. Options defined in the %sja1000.h file:
+ * %CDR_CLKOUT_MASK, %CDR_CLK_OFF, %CDR_RXINPEN, %CDR_CBP, %CDR_PELICAN
+ * The entry @sja_ocr_reg holds hardware specific options for the Output Control
+ * register. Options defined in the %sja1000.h file:
+ * %OCR_MODE_BIPHASE, %OCR_MODE_TEST, %OCR_MODE_NORMAL, %OCR_MODE_CLOCK,
+ * %OCR_TX0_LH, %OCR_TX1_ZZ.
+ * The entry @int_clk_reg holds hardware specific options for the Clock Out
+ * register. Options defined in the %i82527.h file:
+ * %iCLK_CD0, %iCLK_CD1, %iCLK_CD2, %iCLK_CD3, %iCLK_SL0, %iCLK_SL1.
+ * The entry @int_bus_reg holds hardware specific options for the Bus 
+ * Configuration register. Options defined in the %i82527.h file:
+ * %iBUS_DR0, %iBUS_DR1, %iBUS_DT1, %iBUS_POL, %iBUS_CBY.
+ * Return Value: The function always returns zero
+ * File: src/template.c
+ */
+int pcan_dongle_init_chip_data(struct candevice_t *candev, int chipnr)
+{
+//    if (chipnr == 0)
+     {
+       /* initialize common routines for the SJA1000 chip */
+       sja1000p_fill_chipspecops(candev->chip[chipnr]);
+
+
+       candev->chip[chipnr]->chip_type=CHIP_TYPE;
+       candev->chip[chipnr]->chip_base_addr=candev->io_addr;
+       candev->chip[chipnr]->clock = 16000000;
+       candev->chip[chipnr]->int_clk_reg = 0x0;
+       candev->chip[chipnr]->int_bus_reg = 0x0;
+       candev->chip[chipnr]->sja_cdr_reg = sjaCDR_CBP | sjaCDR_CLK_OFF;
+       candev->chip[chipnr]->sja_ocr_reg = sjaOCR_MODE_NORMAL | sjaOCR_TX0_LH;
+
+       candev->chip[chipnr]->flags |= CHIP_IRQ_VME;  // I don't want setup call request_irq 
+                                     // I'm going to do it through parport_register_device 
+
+     }
+
+    return 0;
+}
+
+/**
+ * template_init_obj_data - Initialize message buffers
+ * @chip: Pointer to chip specific structure
+ * @objnr: Number of the message buffer
+ *
+ * The function template_init_obj_data() is used to initialize the hardware
+ * structure containing information about the different message objects on the
+ * CAN chip. In case of the sja1000 there's only one message object but on the
+ * i82527 chip there are 15.
+ * The code below is for a i82527 chip and initializes the object base addresses
+ * The entry @obj_base_addr represents the first memory address of the message 
+ * object. In case of the sja1000 @obj_base_addr is taken the same as the chips
+ * base address.
+ * Unless the hardware uses a segmented memory map, flags can be set zero.
+ * Return Value: The function always returns zero
+ * File: src/template.c
+ */
+int pcan_dongle_init_obj_data(struct chip_t *chip, int objnr)
+{
+       chip->msgobj[objnr]->obj_base_addr=chip->chip_base_addr;
+       chip->msgobj[objnr]->obj_flags=0;
+       
+       return 0;
+}
+
+/**
+ * template_program_irq - program interrupts
+ * @candev: Pointer to candevice/board structure
+ *
+ * The function template_program_irq() is used for hardware that uses 
+ * programmable interrupts. If your hardware doesn't use programmable interrupts
+ * you should not set the @candevices_t->flags entry to %CANDEV_PROGRAMMABLE_IRQ and 
+ * leave this function unedited. Again this function is hardware specific so 
+ * there's no example code.
+ * Return value: The function returns zero on success or %-ENODEV on failure
+ * File: src/template.c
+ */
+int pcan_dongle_program_irq(struct candevice_t *candev)
+{
+       int ret_open;
+
+       pcan_dongle_req_irq(&dongle_port);
+       ret_open = pcan_dongle_open(&dongle_port);
+
+       return ret_open;
+}
+
+
+/**
+ * template_write_register - Low level write register routine
+ * @data: data to be written
+ * @address: memory address to write to
+ *
+ * The function template_write_register() is used to write to hardware registers
+ * on the CAN chip. You should only have to edit this function if your hardware
+ * uses some specific write process.
+ * Return Value: The function does not return a value
+ * File: src/template.c
+ */
+void pcan_dongle_write_register(unsigned data, unsigned long address)
+{
+   address -= dongle_port.chip->chip_base_addr;  // it's in mutiplexed mode
+
+   pcan_dongle_writereg(&dongle_port, (u8) address, (u8) data); // write a register
+
+//   DEBUGMSG("Write Reg at: 0x%lx data 0x%x \n", address, (unsigned) data);
+}
+
+/**
+ * template_read_register - Low level read register routine
+ * @address: memory address to read from
+ *
+ * The function template_read_register() is used to read from hardware registers
+ * on the CAN chip. You should only have to edit this function if your hardware
+ * uses some specific read process.
+ * Return Value: The function returns the value stored in @address
+ * File: src/template.c
+ */
+unsigned pcan_dongle_read_register(unsigned long address)
+{
+   u8 val;
+
+  address -= dongle_port.chip->chip_base_addr;  // it's in mutiplexed mode
+       
+  if (dongle_port.wType == HW_DONGLE_SJA)
+     val = pcan_dongle_sp_readreg(&dongle_port, (u8) address); // functions for SP port
+  else 
+     val = pcan_dongle_epp_readreg(&dongle_port, (u8) address); // functions for EPP port
+
+//  DEBUGMSG("Read Reg at: 0x%lx data 0x%x \n", address, val);
+
+  return ((unsigned)val);
+}
+
+/* !!! Don't change this function !!! */
+int pcan_dongle_register(struct hwspecops_t *hwspecops)
+{
+       hwspecops->request_io = pcan_dongle_request_io;
+       hwspecops->release_io = pcan_dongle_release_io;
+       hwspecops->reset = pcan_dongle_reset;
+       hwspecops->init_hw_data = pcan_dongle_init_hw_data;
+       hwspecops->init_chip_data = pcan_dongle_init_chip_data;
+       hwspecops->init_obj_data = pcan_dongle_init_obj_data;
+       hwspecops->write_register = pcan_dongle_write_register;
+       hwspecops->read_register = pcan_dongle_read_register;
+       hwspecops->program_irq = pcan_dongle_program_irq;
+       return 0;
+}
+
+