/***************************************************
 *                                                 *
 * (c) 2001 Ronald Schmidt                         *
 *          mailto:ronsc@ronsc.de                  *
 *          http://www.ronsc.de                    *  
 *                                                 *
 ***************************************************/

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/version.h>
#include <linux/sched.h>
#include <asm/io.h>
#include <linux/proc_fs.h>
#include <linux/sched.h>
#include <linux/serial_reg.h>
#include <linux/in.h>
#include <linux/tty.h>
#include <linux/errno.h>
#include <linux/netdevice.h>
#include <linux/skbuff.h>
#include <linux/rtnetlink.h>
#include <linux/if_arp.h>
#include <linux/if_slip.h>
#include <linux/init.h>
#include <linux/skbuff.h>
#include <linux/delay.h>
#include <linux/proc_fs.h>
#include <linux/timer.h>
#include "rs485.h"


#if (LINUX_VERSION_CODE < VERSION(2,4,0))
#include <linux/kcomp.h>
#endif

int last_i;

static int iobase=0;
static int irq=0;
static int nodeid=0;
static int option=0;
static int divisor=1;
static int max_nodes=15;

static int d1=100;
static int d5=0;
static int d2=50;
static int n2=40;
static int d3=1500;
static int d4=1500;
static int d4short=300;
static int irqto=10;
static int rxto=100;
static int shortvector=0;

static int node_timeout=200;
static int scan_intervall=300;


MODULE_PARM(iobase,"i");
MODULE_PARM(irq,"i");
MODULE_PARM(nodeid,"i");
MODULE_PARM(option,"i");
MODULE_PARM(max_nodes,"i");
MODULE_PARM(divisor,"i");

MODULE_PARM(d1,"i");
MODULE_PARM(d5,"i");
MODULE_PARM(d2,"i");
MODULE_PARM(d3,"i");
MODULE_PARM(d4,"i");
MODULE_PARM(d4short,"i");
MODULE_PARM(shortvector,"i");
MODULE_PARM(n2,"i");
MODULE_PARM(irqto,"i");
MODULE_PARM(rxto,"i");
MODULE_PARM(node_timeout,"i");
MODULE_PARM(scan_intervall,"i");


spinlock_t  lock;

static node_t      nodes[MAX_NODES];
static int         status;                      
//static int         last_bad_check=0;

static int         current_nodeid=0;                 // nodeif of current node

static unsigned long int last_interrupt=0;
static unsigned long int last_rx=0;

static struct timer_list  rs485_watcher_timer;
static struct timer_list  rs485_watchdog_timer;

static int esc_status;

static rs485stat_t stat;
static int tx_status=TXSTATUS_IDLE;

static int d4vector[MAX_NODES];

/********************************************************************
 *   Routines looking at netdevice side.
 ******************************************/

static int rs485_net_close(struct net_device *dev){
   node_t* node=(node_t*)dev->priv;
   printk("net close%s \n",dev->name);
   netif_stop_queue(dev);
   node->in_use=0;
   MOD_DEC_USE_COUNT;
   return 0;
}

static int rs485_net_open(struct net_device *dev){
   node_t* node=(node_t*)dev->priv;
   printk("net open %s \n",dev->name);
   node->in_use=1;
   MOD_INC_USE_COUNT;
   netif_start_queue(dev);
   return 0;
}

static int rs485_net_change_mtu(struct net_device *dev, int new_mtu) {
   //node_t* node=(node_t*)dev->priv;
   printk("net chmtu %s \n",dev->name);
   dev->mtu=new_mtu;
   return 0;
}

static struct net_device_stats * rs485_net_get_stats(struct net_device *dev){
   node_t* node=(node_t*)dev->priv;
   return (&(node->stats));
}
      
#define OUT_LEN         (MAX_BUFFER + MAX_BUFFER / 64 + 16 + 3)
      


static int rs485_net_xmit(struct sk_buff *skb, struct net_device *dev) {

   
   node_t* node=(node_t*)dev->priv;
   unsigned short size=skb->len;
   int a;
   int count=0;
   unsigned char *src_buffer=NULL;
   int src_size=0;
   //unsigned long int flags;
   
   
   netif_stop_queue(dev);
   
   spin_lock(&(node->lock));

   if (node->tx_size) { // busy
      printk("busy %i \n",node->tx_size);
      spin_unlock(&(node->lock));
      return -EAGAIN;
   }

   if (size>node->mtu) {
      printk("frame to big size=%i\n",size);
      node->stats.tx_dropped++;
      netif_wake_queue(dev);
      spin_unlock(&(node->lock));
      dev_kfree_skb(skb);
      return 0;
   }

   src_buffer=skb->data;
   src_size=size;
   
   for (a=0;a<src_size;a++) {
      
      //printk("%.2x  ",skb->data[a]);
      switch (src_buffer[a]) {
         case FRAME_SS: 
            node->tx_buf[count++]=CHAR_ESC;
            node->tx_buf[count++]=CHAR_FSS;
            break;
         case FRAME_ES: 
            node->tx_buf[count++]=CHAR_ESC;
            node->tx_buf[count++]=CHAR_FES;
            break;
         case CHAR_ESC: 
            node->tx_buf[count++]=CHAR_ESC;
            node->tx_buf[count++]=CHAR_ESC;
            break;
         default:
            node->tx_buf[count++]=skb->data[a];
      }
   }

   if (option & OPTION_DEBUG) {
      printk("tx ip to nodeid=%i size=%i count=%i src_size=%i\n",node->id,size,count,src_size);
   }
   

   node->tx_size=count;
   
   spin_unlock(&(node->lock));
 
   dev_kfree_skb(skb);
   node->stats.tx_packets++;
   node->stats.tx_bytes+=skb->len;
  
  return 0;
}
   
static int rs485_net_init(struct net_device *dev) {
   node_t *node=(node_t*)(dev->priv);

   if (max_nodes>MAX_NODES)
      max_nodes=MAX_NODES;

   
   dev->open               = rs485_net_open;
   dev->hard_start_xmit    = rs485_net_xmit;
   dev->stop               = rs485_net_close;
   dev->get_stats          = rs485_net_get_stats;
   
   //ether_setup(dev);
   
   dev->hard_header_len    = 0;
   dev->addr_len           = 0;
   dev->mtu                = DEFAULT_MTU;
   dev->change_mtu         = rs485_net_change_mtu;
   //dev->do_ioctl           = 0;
   dev->tx_queue_len       = 10;
   dev->type               = ARPHRD_SLIP;
   dev->flags              = IFF_NOARP|IFF_POINTOPOINT;
   
   //memset(dev->dev_addr, node->id, 1);
   

   //dev->hard_header_cache = NULL;

   node->in_use=1;
   node->mtu=DEFAULT_MTU;

   //dev_init_buffers(dev);
   
   printk("net init %s \n",dev->name);
   return 0;
}



/********************************************************************
 *   Routines looking at rs485 side.
 ******************************************/

int  rs485_init_response() {

   tx_status=TXSTATUS_SS;
   nodes[current_nodeid].tx_pos=0;

   // printk(" rs485_init_response \n");
  
   // call interrupt
   outb(UART_IER_RLSI|UART_IER_RDI|UART_IER_THRI, iobase+UART_IER);
   return 0;
}

int  rs485_next_node() {

   int old_nodeid=current_nodeid;
   int ok=0;

   // find next node

   if (option & OPTION_PING_ALL ) {
      current_nodeid++;
      if (current_nodeid>=max_nodes) current_nodeid=1;
   } else {
      // find active node
      do {
         current_nodeid++;
         if (current_nodeid>=max_nodes) current_nodeid=1;

         if (jiffies-nodes[current_nodeid].last_scan>scan_intervall)
            ok=1;
         
      } while ( (!ok) && 
                (old_nodeid!=current_nodeid) &&
                (nodes[current_nodeid].status!=NODE_AKTIVE));
   }
   

   nodes[current_nodeid].last_scan=jiffies;

    rs485_init_response();
   nodes[current_nodeid].query++;
   
   status=STATUS_WAIT;

   return 0;
}

int SET_RTS(int status) {

   if (option & OPTION_REVERSE_RTS)
      status=1-status;

   if (status & OPTION_REVERSE_RTS) {
      outb((UART_MCR_DTR| UART_MCR_RTS | UART_MCR_OUT2),
         iobase+UART_MCR);
   } else {
      outb(UART_MCR_OUT2, iobase+UART_MCR);
   }
   return 0;
}


int  rs485_ip_add_byte(int byte) {

   if (nodes[current_nodeid].rx_size>nodes[current_nodeid].mtu) {
      status=STATUS_READ;
      printk(" rs485_ip_add_byte overflow from nodeid=%i\n",current_nodeid);
      return 0;
   }

   if (esc_status==0) {
      if (byte==CHAR_ESC) {
         esc_status=1;
      } else {
         nodes[current_nodeid].rx_buf[nodes[current_nodeid].rx_size++]=byte;
      }
      
   } else {
      switch (byte) {
         case CHAR_FSS:
            nodes[current_nodeid].rx_buf[nodes[current_nodeid].rx_size++]=FRAME_SS;
            break;
         case CHAR_FES:
            nodes[current_nodeid].rx_buf[nodes[current_nodeid].rx_size++]=FRAME_ES;
            break;
         case CHAR_ESC:
            nodes[current_nodeid].rx_buf[nodes[current_nodeid].rx_size++]=CHAR_ESC;
            break;
      }
      esc_status=0;
   };
   return 0;
}

int  rs485_ip_finish() {
   struct sk_buff* skb;
   int count;
   int src_size=0;
   unsigned char *src_buff=NULL;


   if (nodes[current_nodeid].in_use==0)
      return 0;

   if (nodes[current_nodeid].rx_size<=0)
      return 0;
   
   count=nodes[current_nodeid].rx_size; 
//   status=STATUS_WAIT;

   if( !(skb = dev_alloc_skb(count+2)) ){
      printk(" Can't allocate memory, dropping packet.\n");
      
      nodes[current_nodeid].stats.rx_dropped++;
      
      return -ENOMEM;
   }

   skb->dev=&(nodes[current_nodeid].dev);

   
   src_buff=nodes[current_nodeid].rx_buf;
   src_size=count;

   memcpy(skb_put(skb,src_size),src_buff,src_size); 
   
   skb->mac.raw = skb->data;
   skb->protocol = htons(ETH_P_IP);
   //skb->protocol = eth_type_trans(skb, skb->dev);
   
   // skb_pull(skb, nodes[current_nodeid].dev.hard_header_len);
   
   if (option &OPTION_DEBUG) {
      printk("rx ip from nodeid=%i size=%i\n",current_nodeid,count);
   }
   nodes[current_nodeid].stats.rx_packets++;
   nodes[current_nodeid].stats.rx_bytes+=count;
   
   netif_rx(skb);

   return 0;
}

int  rs485_handle_incoming_byte(int byte) {

   stat.rx++;

   //printk("%0.2x %i \n",byte,byte);

/*
   if ((nodeid==0) && (status==STATUS_WAIT) &&
      (byte!=FRAME_SS)) {
      stat.wss++;
      udelay(2000); rs485_next_node();
   }
*/
   
   if (byte==FRAME_SS) {
      status=STATUS_SYN;
      stat.ss++;
      
      if (nodes[current_nodeid].rx_size>0) {
         nodes[current_nodeid].stats.rx_errors++;
      }
      nodes[current_nodeid].rx_size=0;

      return 0;
   }

   if (byte==FRAME_ES) {
      stat.es++;
      
      if (status==STATUS_READ) {
         
          rs485_ip_finish();
         nodes[current_nodeid].rx_size=0;
         esc_status=0;
         
         status=STATUS_IDLE;

         nodes[current_nodeid].last=jiffies;
               

         if (nodeid!=0) {
            if (!(option & OPTION_RO)) {
               udelay(d3);
                rs485_init_response();
            }
            return 0;
         } else {
            nodes[current_nodeid].resp++;
         }
         
      }
      if (nodeid==0) {
         //udelay(d4);
         udelay(d4vector[current_nodeid]);
          rs485_next_node();
         return 0;
      }
      
   }
   
   if (status==STATUS_IDLE) return 0;
   
   switch (status) {
      case STATUS_SYN:
         if ((nodeid!=0) && (byte!=nodeid)) {
            status=STATUS_IDLE;
            break;
         }
         
         if (nodeid!=0) current_nodeid=0;
         else {
            if (byte!=current_nodeid) {
               //printk("wrong sender \n");
               stat.wsend++;
               status=STATUS_IDLE;
            }
         }

         stat.fme++;
         nodes[current_nodeid].rx_size=0;
         
         status=STATUS_READ;
         return 0;
         
      case STATUS_READ:
          rs485_ip_add_byte(byte);
         stat.prx++;
         
         return 0;
   }

   return 0;
}

int rs485_receive() {
   int boguscount = 0; 

   last_rx=jiffies;

   do {

       rs485_handle_incoming_byte(inb(iobase));
 
      if (boguscount++ > 32) {
         printk("%s(), breaking!\n",__FUNCTION__);
         return 0;
      }

   } while (inb(iobase+UART_LSR) & UART_LSR_DR);

   return boguscount;
}


int rs485_write(int fifo_size) {
   int actual = 0;

   /* Tx FIFO should be empty! */
   if (!(inb(iobase+UART_LSR) & UART_LSR_THRE)) {
      printk("%s(), failed, fifo not empty!\n",__FUNCTION__);
      return -1;
   }

   /* Fill FIFO with current frame */
   while ( (fifo_size-- > 0) && 
           (tx_status!=TXSTATUS_END) &&
           (tx_status!=TXSTATUS_IDLE)) {
      
      switch (tx_status) {

         case TXSTATUS_SS:
            SET_RTS(1);
            if (d5>0) udelay(d5);
            outb(FRAME_SS,iobase);
            stat.tss++;
            tx_status=TXSTATUS_ADDR;
            break;
         case TXSTATUS_ADDR:
            if (nodeid==0) {
               outb(current_nodeid,iobase);
            } else
               outb(nodeid,iobase);
            
            if (nodes[current_nodeid].tx_size>0) {
               tx_status=TXSTATUS_PAY;
            } else {
               tx_status=TXSTATUS_ES;
            }
            
            break;
         case TXSTATUS_PAY:
            
            outb(nodes[current_nodeid].tx_buf[
                  nodes[current_nodeid].tx_pos++],iobase);
            stat.ptx++;

            if (nodes[current_nodeid].tx_pos>=nodes[current_nodeid].tx_size) {
               nodes[current_nodeid].tx_size=0;
      
               netif_wake_queue(&(nodes[current_nodeid].dev));
               tx_status=TXSTATUS_ES;
            }
            
            break;
         case TXSTATUS_ES:
            outb(FRAME_ES,iobase);
            stat.tes++;
            tx_status=TXSTATUS_END;
            break;
      }
            
      stat.tx++;
   }

   return actual;
}


int  rs485_finish_frame() {

   tx_status=TXSTATUS_IDLE;
  
   udelay(d1); // RS485-converte from conrad have a timeout (1byte*9600baud)
   SET_RTS(0);
   
   //outb(UART_FCR_ENABLE_FIFO|UART_FCR_CLEAR_RCVR|UART_FCR_CLEAR_XMIT|
   //   UART_FCR_TRIGGER_1, iobase+UART_FCR);

   stat.ff++;

   if (nodeid==0) { // wait 10*100usec for response
      int bogus=n2;
      int tmp_lsr=0;

      while ( (bogus--) && 
              (!( (tmp_lsr=inb(iobase+UART_LSR)) & UART_LSR_DR))) 
         udelay(d2);

      if ( ! (tmp_lsr & UART_LSR_DR)) { // no rx-byte!
         stat.mto++;

          rs485_next_node();
      }
   }

   return 0;
}


/*********************************************************
 * interrupt handler
 * as fast as posible
 */
void  rs485_interrupt_handler(int _irq, void * dev_id, struct pt_regs * regs)
{
   int uart_iir, uart_lsr;
   int boguscount = 0;

   if (_irq!=irq) return;

   sti();
	spin_lock(&lock);


   uart_iir = inb(iobase+UART_IIR) & UART_IIR_ID;

   while (uart_iir) {
      last_interrupt=jiffies;
      
      uart_lsr = inb(iobase+UART_LSR);    /* Clear interrupt */
      last_i=uart_iir;

      //printk("%.2x \n",uart_lsr);
     
      switch (uart_iir) {
         case UART_IIR_RLSI: // line status changed

            if (uart_lsr & UART_LSR_BI)
               stat.br++;

            break;
            
         case UART_IIR_RDI: /* Receive interrupt */
            //if (uart_lsr & UART_LSR_DR) {
               rs485_receive();
            //}
            //else printk("haee ?\n");   
               
            break;
         case UART_IIR_THRI: // transmitter empty
            
            if (uart_lsr & UART_LSR_THRE) {
               
               if (tx_status!=TXSTATUS_IDLE) {
                  
                  if (tx_status==TXSTATUS_END) {
                      rs485_finish_frame();
                  } else {
                     rs485_write(16);
                  }
               }
            }
            
            break;
         default:
            printk("%s(), unhandled IIR=%x\n", __FUNCTION__,uart_iir);
            break;
      }

      /* Make sure we don't stay here to long */
      if (boguscount++ > 32) {
         printk("bogus in irq \n");
         break;
      }
      
      uart_iir = inb(iobase + UART_IIR) & UART_IIR_ID;
      last_interrupt=jiffies;
   }

   spin_unlock(&lock);

}


void  rs485_init_serial_port_old() {
   
   int bogus;
   unsigned long int flags;

   save_flags(flags); cli();
   
   // RESET
   outb(0xBF,iobase+UART_LSR);
   outb(UART_EFR_ECB,iobase+UART_EFR);
   outb(0,iobase+UART_IER);
   outb(0,iobase+UART_EFR);
   outb(0,iobase+UART_LCR);
   inb(iobase+UART_LSR);                        
   inb(iobase+UART_RX);                        
   inb(iobase+UART_IIR);                        
   inb(iobase+UART_MSR);                        
   
   /* Reset UART */
   outb(0, iobase+UART_MCR);
        
   /* Turn off interrupts */
   outb(0, iobase+UART_IER);
   
   /* Initialize UART */
   outb(UART_LCR_DLAB, iobase+UART_LCR);
   
   outb(divisor, iobase+UART_DLL);
   outb(0, iobase+UART_DLM);

//   outb(UART_LCR_WLEN8|UART_LCR_SBC
//         , iobase+UART_LCR);
   outb(UART_LCR_WLEN8
         , iobase+UART_LCR);
   
   SET_RTS(0);
   //outb((UART_MCR_DTR | UART_MCR_RTS | UART_MCR_OUT2), iobase+UART_MCR);
   
 
   bogus=30;
   while ( (inb(iobase+UART_LSR) & UART_LSR_DR ) && (bogus)) {
      inb(iobase);
      bogus--;
   }
   
   outb(UART_FCR_ENABLE_FIFO|UART_FCR_CLEAR_RCVR|UART_FCR_CLEAR_XMIT|
      UART_FCR_TRIGGER_1, iobase+UART_FCR);

   /* Turn on interrups */
   outb(UART_IER_RLSI|UART_IER_RDI|UART_IER_THRI, iobase+UART_IER);

   restore_flags(flags);

}

void  rs485_init_serial_port() {
   
   int bogus;
   unsigned long int flags;

   save_flags(flags); cli();

   //init
   outb( 0,	iobase+UART_IER);
   outb( 0x0F,	iobase+UART_IER);
   outb( 0,	iobase+UART_IER);
   outb( 0xBF,	iobase+UART_LCR);
   outb( 0,	iobase+UART_EFR);
   outb( 0,	iobase+UART_LCR);
   outb( UART_FCR_ENABLE_FIFO,	iobase+UART_FCR);
   outb( UART_LCR_DLAB,	iobase+UART_LCR);
   outb( 0xBF,	iobase+UART_LCR);
   outb( 128,	iobase+UART_LCR);
   outb( UART_FCR_ENABLE_FIFO,	iobase+UART_FCR);
   outb( 0,	iobase+UART_LCR);
   outb( 0,	iobase+UART_RSA_FRR);
   outb( 0,	iobase+UART_MCR);
   outb( 0,	iobase+UART_FCR);
   outb( 0,	iobase+UART_IER);


    // open
 outb( UART_FCR_ENABLE_FIFO,	iobase+UART_FCR);
 outb( 7,	iobase+UART_FCR);
 outb( 0,	iobase+UART_FCR);
 outb( UART_LCR_WLEN8,	iobase+UART_LCR);
 outb( 11,	iobase+UART_MCR);
 outb( 13,	iobase+UART_IER);
 outb( 5,	iobase+ UART_IER);
 outb( 147,	iobase+UART_LCR);
 outb( 12,	iobase+UART_DLL);
 outb( 0,	iobase+UART_DLM);
 outb( 19,	iobase+UART_LCR);
 outb( UART_FCR_ENABLE_FIFO,	iobase+UART_FCR);
 outb( 129,	iobase+UART_FCR);
 outb( 5,	iobase+ UART_IER);


// set speed

 outb( 5,	iobase+ UART_IER);
 outb( 147,	iobase+ UART_LCR);
 outb( 1,	iobase+ UART_DLL);
 outb( 0,	iobase+ UART_DLM);
 outb( 19,	iobase+ UART_LCR);
 outb( UART_FCR_ENABLE_FIFO,	iobase+ UART_FCR);
 outb( 129,	iobase+ UART_FCR);
 outb( 5,	iobase+ UART_IER);

// connect

 outb( 5,	iobase+ UART_IER);
 outb( 147,	iobase+ UART_LCR);
 outb( 1,	iobase+ UART_DLL);
 outb( 0,	iobase+ UART_DLM);
 outb( 19,	iobase+ UART_LCR);
 outb( UART_FCR_ENABLE_FIFO,	iobase+ UART_FCR);
 outb( 129,	iobase+ UART_FCR);
 outb( 5,	iobase+ UART_IER);
   
   
   outb(UART_LCR_WLEN8
         , iobase+UART_LCR);
   
   SET_RTS(0);
   
   

   outb(UART_FCR_ENABLE_FIFO|UART_FCR_CLEAR_RCVR|UART_FCR_CLEAR_XMIT|
      UART_FCR_TRIGGER_1, iobase+UART_FCR);

   /* Turn on interrups */
   outb(UART_IER_RLSI|UART_IER_RDI|UART_IER_THRI, iobase+UART_IER);

   bogus=30;
   while ( (inb(iobase+UART_LSR) & UART_LSR_DR ) && (bogus)) {
      inb(iobase);
      bogus--;
   }

   restore_flags(flags);

}

static void  rs485_watchdog(unsigned long dummy) {

   spin_lock(&lock);

   if (jiffies-last_interrupt>irqto) {
      stat.ito++;
       rs485_init_serial_port();

      if (nodeid==0)
          rs485_next_node();
      last_interrupt=jiffies;
      
   } else
   if (jiffies-last_rx>rxto) {
      stat.rto++;
       rs485_init_serial_port();

      if (nodeid==0)
         rs485_next_node();
      // printk("init \n");
      last_rx=jiffies;
   }

   spin_unlock(&lock);
   
   mod_timer(& rs485_watchdog_timer,jiffies + WATCHDOG_INTERVALL);
   
}

/**************************************************
 *
 * handle expiring 
 */
static void  rs485_watcher(unsigned long dummy)  {
   int a;

   for (a=0;a<max_nodes;a++) if (a!=nodeid) {

      if ((nodes[a].status==NODE_INAKTIVE) &&
          (jiffies-nodes[a].last<node_timeout-2))  {
         
         nodes[a].status=NODE_AKTIVE;
         printk("found new node %i \n",a);
         nodes[a].on++;
         
      } else
      if ( (nodes[a].status==NODE_AKTIVE) &&
           (jiffies-nodes[a].last>node_timeout)) {
         
         nodes[a].status=NODE_INAKTIVE;
         printk("node %i expired \n",a);
         
      }

   }
   
   mod_timer(& rs485_watcher_timer,jiffies + WATCHER_INTERVALL);
}


/****************
 *
 * just for debugging
 *
 */
static int rs485_get_info(char *buf, char **start, off_t fpos, int length){
   
   unsigned int a;
   int len=0;
   
   len+=sprintf(buf+len,"Receive:\n");
   len+=sprintf(buf+len,"bytes          : %i\n",stat.rx);
   len+=sprintf(buf+len,"payload bytes  : %i\n",stat.prx);
   len+=sprintf(buf+len,"start sentinel : %i\n",stat.ss);
   len+=sprintf(buf+len,"end sentinel   : %i\n",stat.es);
   len+=sprintf(buf+len,"frames for me  : %i\n",stat.fme);
   len+=sprintf(buf+len,"wrong SS       : %i\n",stat.wss);
   len+=sprintf(buf+len,"wrong sender   : %i\n",stat.wsend);

   len+=sprintf(buf+len,"\nTransmit:\n");
   len+=sprintf(buf+len,"bytes          : %i\n",stat.tx);
   len+=sprintf(buf+len,"payload bytes  : %i\n",stat.ptx);
   len+=sprintf(buf+len,"start sentinel : %i\n",stat.tss);
   len+=sprintf(buf+len,"end sentinel   : %i\n",stat.tes);
   len+=sprintf(buf+len,"finish frame   : %i\n",stat.ff);

   len+=sprintf(buf+len,"\nTimeouts:\n");
   len+=sprintf(buf+len,"master-rx-to   : %i\n",stat.mto);
   len+=sprintf(buf+len,"irq-to         : %i\n",stat.ito);
   len+=sprintf(buf+len,"rx-to          : %i\n",stat.rto);

   len+=sprintf(buf+len,"\nNodes:\n");
   for (a=0;a<max_nodes;a++) {

      len+=sprintf(buf+len,"[%.2x] %s \tscan=%ld/%ld ",
         a,
         (nodes[a].status==NODE_AKTIVE) ? "ONLINE" : "offline",
         nodes[a].last_scan,
         nodes[a].last);
      len+=sprintf(buf+len,"\ton=%i \t%lu-%lu=%lu \n",
            nodes[a].on,
            nodes[a].query,nodes[a].resp,
            nodes[a].query-nodes[a].resp);
   }

   len+=sprintf(buf+len,"\nli %i \n",last_i);
   len+=sprintf(buf+len,"\n br=%i tx_status=%i\n",stat.br,tx_status);

   len+=sprintf(buf+len,"\n");
   return len;
}

/********************************************************************
 * 
 * INIT
 *
 */

int init_module(void) {

   int ireq;
   int node;
  
   spin_lock_init(&lock);
   
   spin_lock(&lock);
   
   status=STATUS_IDLE;

   //bzero(&stat,sizeof(stat));

   // init data
   for (node=0;node<max_nodes;node++) {

      nodes[node].rx_size=0;

      memset(&(nodes[node].dev), 0, sizeof(nodes[node].dev));
      memset(&(nodes[node].stats), 0, sizeof(nodes[node].stats));

      nodes[node].tx_pos=0;
      nodes[node].tx_size=0;
            
      nodes[node].status=0;
      nodes[node].last_scan=0;
      
      nodes[node].last=0;
      nodes[node].id=node;

      nodes[node].on=0;
      nodes[node].query=0;
      nodes[node].resp=0;

      if (shortvector & (1<<node))
         d4vector[node]=d4short;
      else
         d4vector[node]=d4;

      printk("d4 nodeid=%i d4=%i \n",node,d4vector[node]);   

      spin_lock_init(&(nodes[node].lock));
      /* Initialize and register net device */
      
      // skb_queue_head_init(&(nodes[node].txq));
      
      sprintf(nodes[node].dev.name,"rs%.2i",node);
      nodes[node].dev.priv=&(nodes[node]);
      nodes[node].dev.init=rs485_net_init;

      if ((nodeid==0) || (node==0))
         if( register_netdev(&(nodes[node].dev)) ){
            printk("cannot register device %s \n",nodes[node].dev.name);
         }
         
   }

   nodes[nodeid].status=NODE_AKTIVE;

   init_timer(& rs485_watchdog_timer);
    rs485_watchdog_timer.function =  rs485_watchdog;
   mod_timer(& rs485_watchdog_timer, jiffies + 100);

   init_timer(& rs485_watcher_timer);
    rs485_watcher_timer.function =  rs485_watcher;
   mod_timer(& rs485_watcher_timer, jiffies + WATCHER_INTERVALL);
   
   ireq = request_irq(irq, rs485_interrupt_handler,SA_INTERRUPT,"rs485",NULL) ;

    rs485_init_serial_port();
   
   printk("rs485: initialized iobase=%.3x irq=%i divisor=%i option=%i\n",iobase,irq,divisor,option);
   printk("rs485: d1=%i d2=%i n2=%i d3=%i d4=%i\n",
         d1,d2,n2,d3,d4);

   spin_unlock(&lock);

   if (nodeid==0) {
      current_nodeid=0;
       rs485_next_node();
   }

   create_proc_info_entry("rs485", 
      0,    0, rs485_get_info);

   return 0;
   
}




/********************************************************************
 * 
 * cleanup
 *
 */

int cleanup_module(void) {

   int node;

   remove_proc_entry("rs485", NULL /* parent dir */);
 
//   spin_lock_irqsave(&lock,flags);

   del_timer(& rs485_watcher_timer);
   del_timer(& rs485_watchdog_timer);
   
   for (node=0;node<max_nodes;node++) {

      /* Drop TX queue */
//      while( (skb = skb_dequeue(&(nodes[node].txq))) )
//         dev_kfree_skb(skb);
      
      if ((nodeid==0) || (node==0))
         unregister_netdev(&(nodes[node].dev));
   }
   

   free_irq(irq,NULL);
   
   outb((UART_MCR_DTR | UART_MCR_RTS | UART_MCR_OUT2), iobase+UART_MCR);
   outb(0, iobase+UART_IER);
   
   printk("rs485: bye. \n");
   
//   spin_unlock_irqrestore(&lock,flags);
   
   return 0;
}

// vim:expandtab:ts=3:ai
