patch-2.4.19 linux-2.4.19/drivers/net/wan/8253x/8253xutl.c

Next file: linux-2.4.19/drivers/net/wan/8253x/Makefile
Previous file: linux-2.4.19/drivers/net/wan/8253x/8253xtty.c
Back to the patch index
Back to the overall index

diff -urN linux-2.4.18/drivers/net/wan/8253x/8253xutl.c linux-2.4.19/drivers/net/wan/8253x/8253xutl.c
@@ -0,0 +1,1422 @@
+/* -*- linux-c -*- */
+/* $Id: 8253xutl.c,v 1.3 2002/02/10 22:17:26 martillo Exp $
+ * 8253xutl.c: SYNC TTY Driver for the SIEMENS SAB8253X DUSCC.
+ *
+ * Implementation, modifications and extensions
+ * Copyright (C) 2001 By Joachim Martillo, Telford Tools, Inc.
+ *
+ * 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.
+ */
+
+/* Standard in kernel modules */
+#define DEFINE_VARIABLE
+#include <linux/module.h>   /* Specifically, a module */
+#include <asm/io.h>
+#include <linux/timer.h>
+#include <linux/interrupt.h>
+#include <linux/tty.h>
+#include <linux/tty_flip.h>
+#include <linux/mm.h>
+#include <linux/version.h>
+#include <asm/uaccess.h>
+#include "8253xctl.h"
+#include "8253x.h"
+#include <linux/pci.h>
+#include <linux/fs.h>
+#include "sp502.h"
+
+#ifdef MODULE
+#undef XCONFIG_SERIAL_CONSOLE
+#endif
+
+void sab8253x_start_txS(struct sab_port *port)
+{
+	unsigned long flags;
+	register int count;
+	register int total;
+	register int offset;
+	char temporary[32];
+	register unsigned int slopspace;
+	register int sendsize;
+	unsigned int totaltransmit;
+	unsigned fifospace;
+	unsigned loadedcount;
+	struct tty_struct *tty = port->tty; /* a little gross tty flags whether
+					       invoked from a tty or the network */
+	
+	fifospace = port->xmit_fifo_size; /* This code can handle fragmented frames
+					     although currently none are generated*/
+	loadedcount = 0;
+	
+	if(port->sabnext2.transmit == NULL)
+	{
+		return;
+	}
+	
+	save_flags(flags); 
+	cli();			
+	
+	
+	if(count = port->sabnext2.transmit->Count, (count & OWNER) == OWN_SAB)
+	{
+		count &= ~OWN_SAB; /* OWN_SAB is really 0 but cannot guarantee in the future */
+		
+		if(port->sabnext2.transmit->HostVaddr)
+		{
+			total = (port->sabnext2.transmit->HostVaddr->tail - 
+				 port->sabnext2.transmit->HostVaddr->data); /* packet size */
+		}
+		else
+		{
+			total = 0;		/* the data is only the crc/trailer */
+		}
+		
+		if(tty && (tty->stopped || tty->hw_stopped) && (count == total))
+		{			/* works for frame that only has a trailer (crc) */
+			port->interrupt_mask1 |= SAB82532_IMR1_XPR;
+			WRITEB(port, imr1, port->interrupt_mask1);
+			restore_flags(flags);	/* can't send */
+			return;
+		}
+		
+		offset = (total - count);	/* offset to data still to send */
+		
+		port->interrupt_mask1 &= ~(SAB82532_IMR1_ALLS);
+		WRITEB(port, imr1, port->interrupt_mask1);
+		port->all_sent = 0;
+		
+		if(READB(port,star) & SAB82532_STAR_XFW)
+		{
+			if(count <= fifospace)
+			{
+				port->xmit_cnt = count;
+				slopspace = 0;
+				sendsize = 0;
+				if(port->sabnext2.transmit->sendcrc) 
+				/* obviously should not happen for async but might use for
+				   priority transmission */
+				{
+					slopspace = fifospace - count;
+				}
+				if(slopspace)
+				{
+					if(count)
+					{
+						memcpy(temporary, &port->sabnext2.transmit->HostVaddr->data[offset], 
+						       count);
+					}
+					sendsize = MIN(slopspace, (4 - port->sabnext2.transmit->crcindex)); 
+				/* how many bytes to send */
+					memcpy(&temporary[count], 
+					       &((unsigned char*)(&port->sabnext2.transmit->crc))
+					       [port->sabnext2.transmit->crcindex], 
+					       sendsize);
+					port->sabnext2.transmit->crcindex += sendsize;
+					if(port->sabnext2.transmit->crcindex >= 4)
+					{
+						port->sabnext2.transmit->sendcrc = 0;
+					}
+					port->xmit_buf = temporary;
+				}
+				else
+				{
+					port->xmit_buf =	/* set up wrifefifo variables */
+						&port->sabnext2.transmit->HostVaddr->data[offset];
+				}
+				port->xmit_cnt += sendsize;
+				count = 0;
+			}
+			else
+			{
+				count -= fifospace;
+				port->xmit_cnt = fifospace;
+				port->xmit_buf =	/* set up wrifefifo variables */
+					&port->sabnext2.transmit->HostVaddr->data[offset];
+				
+			}
+			port->xmit_tail= 0;
+			loadedcount = port->xmit_cnt;
+			(*port->writefifo)(port);
+			totaltransmit = Sab8253xCountTransmitDescriptors(port);
+			if(tty && (totaltransmit < (sab8253xs_listsize/2))) /* only makes sense on a TTY */
+			{
+				sab8253x_sched_event(port, SAB8253X_EVENT_WRITE_WAKEUP);
+			}
+			
+			if((sab8253xt_listsize - totaltransmit) > (sab8253xt_listsize/2))
+			{
+				port->buffergreedy = 0;
+			}
+			else
+			{
+				port->buffergreedy = 1;
+			}
+			
+			port->xmit_buf = NULL; /* this var is used to indicate whether to call kfree */
+			
+			/* fifospace -= loadedcount;*/
+			/* Here to make mods to handle arbitrarily fragmented frames look to 8253xtty.c for help */
+			
+			if ((count <= 0) && (port->sabnext2.transmit->sendcrc == 0))
+			{
+				port->sabnext2.transmit->Count = OWN_DRIVER;
+				if(!tty)
+				{		/* called by network driver */
+					++(port->Counters.transmitpacket);
+				}
+#ifdef FREEININTERRUPT		/* treat this routine as if taking place in interrupt */
+				if(port->sabnext2.transmit->HostVaddr)
+				{
+					skb_unlink(port->sabnext2.transmit->HostVaddr);
+					dev_kfree_skb_any(port->sabnext2.transmit->HostVaddr);
+					port->sabnext2.transmit->HostVaddr = 0; /* no skb */
+				}
+				port->sabnext2.transmit->crcindex = 0; /* no single byte */
+#endif
+				sab8253x_cec_wait(port);
+				WRITEB(port, cmdr, SAB82532_CMDR_XME|SAB82532_CMDR_XTF); /* Terminate the frame */
+				
+				port->sabnext2.transmit = port->sabnext2.transmit->VNext;
+				
+				if(!tty && port->tx_full)	/* invoked from the network driver */
+				{
+					port->tx_full = 0; /* there is a free slot */
+					switch(port->open_type)
+					{
+					case OPEN_SYNC_NET:
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 4, 0)
+						port->dev->start = 1;
+						port->dev->tbusy = 0;	/* maybe need mark_bh here */
+#else
+						netif_start_queue(port->dev);
+#endif
+						break;
+						
+					case OPEN_SYNC_CHAR:
+						wake_up_interruptible(&port->write_wait);
+						break;
+						
+					default:
+						break;
+					}
+				}
+				
+				if((port->sabnext2.transmit->Count & OWNER) == OWN_SAB)
+				{		/* new frame to send */
+					port->interrupt_mask1 &= ~(SAB82532_IMR1_XPR);
+					WRITEB(port, imr1, port->interrupt_mask1);
+				}
+				else
+				{
+					port->interrupt_mask1 |= SAB82532_IMR1_XPR;
+					WRITEB(port, imr1, port->interrupt_mask1);
+					if((port->open_type == OPEN_SYNC_CHAR) && port->async_queue)
+					{		/* if indication of transmission is needed by the */
+						/* application on a per-frame basis kill_fasync */
+						/* can provide it */
+						kill_fasync(&port->async_queue, SIGIO, POLL_OUT);
+					}
+				}
+				restore_flags(flags);
+				return;
+			}
+			/* Issue a Transmit FIFO command. */
+			sab8253x_cec_wait(port);
+			WRITEB(port, cmdr, SAB82532_CMDR_XTF);	
+			port->sabnext2.transmit->Count = (count|OWN_SAB);
+		}
+		port->interrupt_mask1 &= ~(SAB82532_IMR1_XPR); /* more to send */
+		WRITEB(port, imr1, port->interrupt_mask1);
+	}
+	else
+	{				/* nothing to send */
+		port->interrupt_mask1 |= SAB82532_IMR1_XPR;
+		WRITEB(port, imr1, port->interrupt_mask1);
+	}
+	restore_flags(flags);
+	return;
+}
+
+void sab8253x_transmit_charsS(struct sab_port *port,
+			      union sab8253x_irq_status *stat)
+{
+	if (stat->sreg.isr1 & SAB82532_ISR1_ALLS) 
+	{
+		port->interrupt_mask1 |= SAB82532_IMR1_ALLS;
+		WRITEB(port, imr1, port->interrupt_mask1);
+		port->all_sent = 1;
+	}
+	sab8253x_start_txS(port);
+}
+
+/*
+ * This routine is called to set the UART divisor registers to match
+ * the specified baud rate for a serial port.
+ */
+
+/***************************************************************************
+ * sab_baudenh:      Function to compute the "enhanced" baudrate.
+ *                
+ *
+ *     Parameters   : 
+ *                  encbaud  2* the baudrate. We use the
+ *                           double value so as to support 134.5 (in only)
+ *                  clkspeed The board clock speed in Hz.
+ *                  bgr      Value of reg BGR for baudrate(output)
+ *                  ccr2     Value of reg // CCR2 for baudrate (output)
+ *                  ccr4     Value of reg CCR4 for baudrate (output)
+ *                  truebaud The actual baudrate achieved (output).
+ *
+ *
+ *     Return value : Return FALSE the parameters could not be computed, 
+ *
+ *     Prerequisite : The various ports must have been initialized
+ *
+ *     Remark       : Stolen from the Aurora ase driver.
+ *
+ *     Author       : fw
+ *
+ *     Revision     : Oct 9 2000, creation
+ ***************************************************************************/
+/*
+ * Macro to check to see if the high n bits of the given unsigned long
+ *  are zero.
+ */               
+#define HIZERO(x, n)        ( ((unsigned long) ((x) << (n)) >> (n)) == (x))
+/* form an n-bit bitmask */
+#define NBM(n)                  (~(((~(unsigned long) 0) >> (n)) << (n)))
+/* shift x by y bits to right, rounded */
+#define ROUND_SHIFT(x, y)       (((unsigned long) (x) + (NBM(y - 1) + 1)) >> (y))
+/* perform rounded division */
+#define ROUND_DIV(x, y) (((x) + ((y) >> 1)) / (y))
+#define ABSDIF(x, y)    ((x) > (y) ? ((x) - (y)) : ((y) - (x))) 
+static unsigned int
+sab8253x_baudenh(unsigned long encbaud, unsigned long clk_speed,
+		 unsigned char *bgr, unsigned char *ccr2,
+		 unsigned long *truebaudp)
+{
+	register unsigned short	 tmp;
+	register unsigned char	 ccr2tmp;
+	unsigned long		 power2, mant;
+	unsigned int			 fastclock;
+	
+	if (encbaud == 0) {
+		return FALSE;
+	}
+	
+	/*
+	 * Keep dividing quotien by two until it is between the value of 1 and 64,
+	 *  inclusive.
+	 */
+	
+	fastclock = (clk_speed >= 10000000);	/* >= 10 MHz */
+	
+	for (power2 = 0; power2 < 16; power2++) 
+	{
+		/* divisor = baud * 2^M * 16 */
+		if (!HIZERO(encbaud, power2 + 3)) 
+		{
+			if (!HIZERO(encbaud, power2)) 
+			{	/* baud rate still too big? */
+				mant = ROUND_DIV(ROUND_SHIFT(clk_speed, power2 + 3), encbaud);
+				
+				/* mant = (clk_speed / (8 * 2^M)) / (baud * 2) */
+				/*	= clk_speed / (baud * 16 * 2^M) */
+			}
+			else 
+			{
+				mant = ROUND_DIV(ROUND_SHIFT(clk_speed, 3), encbaud << power2);
+				/* mant = (clk_speed / 8) / (baud * 2 * 2^M) */
+				/*	= clk_speed / (baud * 16 * 2^M) */
+			}
+		}
+		else 
+		{
+			mant = ROUND_DIV(clk_speed, encbaud << (power2 + 3));
+			/* mant = clk_speed / (baud * 2 * 8 * 2^M) */
+			/*	    = clk_speed / (baud * 16 * 2^M) */
+		}
+		
+		/* mant = clk_speed / (baud * 2^M * 16) */
+		
+		if (mant < 2
+		    || (mant <= 64 && (!fastclock || power2 != 0))) 
+		{
+			break;
+		}
+	}
+	
+	/*
+	 * Did we not succeed?  (Baud rate is too small)
+	 */
+	if (mant > 64) 
+	{
+		return FALSE;
+	}
+	
+	/*
+	 * Now, calculate the true baud rate.
+	 */
+	
+	if (mant < 1 || (mant == 1 && power2 == 0)) 
+	{
+		/* bgr and ccr2 should be initialized to 0 */
+		*truebaudp = ROUND_SHIFT(clk_speed, 4);
+	}
+	else 
+	{
+		*truebaudp = ROUND_DIV(clk_speed, mant << (4 + power2));
+		/* divisor is not zero because mant is [1, 64] */
+		mant--; /* now [0, 63] */
+		
+		/*
+		 * Encode the N and M values into the bgr and ccr2 registers.
+		 */
+		
+		tmp = ((unsigned short) mant) | ((unsigned short) power2 << 6);
+		
+		ccr2tmp = SAB82532_CCR2_BDF;
+		if ((tmp & 0x200) != 0) 
+		{
+			ccr2tmp |= SAB82532_CCR2_BR9;
+		}
+		if ((tmp & 0x100) != 0) 
+		{
+			ccr2tmp |= SAB82532_CCR2_BR8;
+		}
+		
+		*ccr2 = ccr2tmp | (*ccr2 & ~(SAB82532_CCR2_BDF|SAB82532_CCR2_BR8|SAB82532_CCR2_BR9));
+		*bgr = (unsigned char) tmp;
+	}
+	
+	return TRUE;
+}
+
+/*
+ * Calculate the standard mode baud divisor using an integral algorithm.
+ */
+/***************************************************************************
+ * sab_baudstd:      Function to compute the "standard " baudrate.
+ *                
+ *
+ *     Parameters   : 
+ *                  encbaud  2* the baudrate. We use the
+ *                           double value so as to support 134.5 (in only)
+ *                  clkspeed The board clock speed in Hz.
+ *                  bgr      Value of reg BGR for baudrate(output)
+ *                  ccr2     Value of reg CCR2 for baudrate (output)
+ *                  ccr4     Value of reg CCR4 for baudrate (output)
+ *                  truebaud The actual baudrate achieved (output).
+ *
+ *
+ *     Return value : Return FALSE the parameters could not be computed, 
+ *
+ *     Prerequisite : The various ports must have been initialized
+ *
+ *     Remark       : Stolen from the Aurora ase driver.
+ *
+ *     Author       : fw
+ *
+ *     Revision     : Oct 9 2000, creation
+ ***************************************************************************/
+static unsigned int
+sab8253x_baudstd(unsigned long encbaud, unsigned long clk_speed,
+		 unsigned char *bgr, unsigned char *ccr2,
+		 unsigned long *truebaudp)
+{
+  register unsigned short	 quot;
+  register unsigned char	 ccr2tmp;
+  
+  if (encbaud == 0) 
+  {
+	  return FALSE;
+  }
+  
+  /*
+   * This divisor algorithm is a little strange.  The
+   *  divisors are all multiples of 2, except for the
+   *  magic value of 1.
+   *
+   * What we do is do most of the algorithm for multiples
+   *  of 1, and then switch at the last minute to multiples
+   *  of 2.
+   */
+  
+  /*
+   * Will we lose any information by left shifting encbaud?
+   *  If so, then right shift clk_speed instead.
+   */
+  if (!HIZERO(encbaud, 3)) 
+  {
+	  quot = (unsigned short) ROUND_DIV(ROUND_SHIFT(clk_speed, 3),
+					    encbaud);
+	  /* quot = (clk_speed / 8) / (baud * 2) = clk_speed / (16 * baud) */
+  }
+  else 
+  {
+	  /* encbaud isn't a multiple of 2^29 (baud not mult. of 2^28) */
+	  quot = (unsigned short) ROUND_DIV(clk_speed, encbaud << 3);
+  }
+  
+  /* quot = clk_speed / (baud * 16) */
+  if (quot < 2) 
+  {
+	  /* bgr and ccr2 should be initialized to 0 */
+	  *truebaudp = ROUND_SHIFT(clk_speed, 4);
+	  return TRUE;
+  }
+  
+  /*
+   * Divide the quotient by two.
+   */
+  quot = ROUND_SHIFT(quot, 1);
+  
+  if (quot <= 0x400) 
+  {
+	  /* quot = [1, 0x400]  -> (quot << 5) != 0 */
+	  *truebaudp = ROUND_DIV(clk_speed, ((unsigned long) quot << 5));
+	  quot--;
+	  
+	  ccr2tmp = SAB82532_CCR2_BDF;
+	  if ((quot & 0x200) != 0) 
+	  {
+		  ccr2tmp |= SAB82532_CCR2_BR9;
+	  }
+	  if ((quot & 0x100) != 0) 
+	  {
+		  ccr2tmp |=SAB82532_CCR2_BR8;
+	  }
+	  
+	  *ccr2 = ccr2tmp | (*ccr2 & ~(SAB82532_CCR2_BDF|SAB82532_CCR2_BR8|SAB82532_CCR2_BR9));
+	  *bgr = (unsigned char) quot;
+  }
+  else 
+  {			/* the baud rate is too small. */
+	  return FALSE;
+  }
+  
+  return TRUE;
+}
+
+/***************************************************************************
+ * sab_baud:      Function to compute the best register value to achieve
+ *                a given baudrate.
+ *                
+ *
+ *     Parameters   : 
+ *                  port:    The port being used  (in only)
+ *                  encbaud: 2* the baudrate. We use the
+ *                           double value so as to support 134.5 (in only)
+ *                  bgr      Value of reg BGR for baudrate(output)
+ *                  ccr2     Value of reg CCR2 for baudrate (output)
+ *                  ccr4     Value of reg CCR4 for baudrate (output)
+ *                  truebaud The actual baudrate achieved (output).
+ *
+ *
+ *     Return value : Return TRUE if the vaudrate can be set, FALSE otherwise 
+ *
+ *     Prerequisite : The various ports must have been initialized
+ *
+ *     Remark       : Stolen from the Aurora ase driver.
+ *
+ *     Author       : fw
+ *
+ *     Revision     : Oct 9 2000, creation
+ ***************************************************************************/
+unsigned int 
+sab8253x_baud(sab_port_t *port, unsigned long encbaud,
+	      unsigned char *bgr, unsigned char *ccr2,
+	      unsigned char *ccr4, unsigned long *truebaudp)
+{
+	unsigned char	 bgr_std, bgr_enh, ccr2_std, ccr2_enh, ccr4_enh;
+	unsigned int		 ok_std, ok_enh;
+	unsigned long	 truebaud_std, truebaud_enh, truebaud,clkspeed;
+	
+	bgr_std = bgr_enh = 0;
+	ccr2_std = ccr2_enh = 0;
+	ccr4_enh = 0;
+	
+	/*
+	 * the port/chip/board structure will tell us:
+	 *  1) clock speed
+	 *  2) chip revision (to figure out if the enhanced method is
+	 *     available.
+	 */
+	
+	clkspeed = port->chip->c_cim ? port->chip->c_cim->ci_clkspeed :  port->board->b_clkspeed;
+	
+#ifdef NODEBUGGING
+	printk("With clk speed %ld, baud rate = %ld\n",clkspeed, encbaud);
+#endif
+	
+	ok_std = sab8253x_baudstd(encbaud, clkspeed, &bgr_std,
+				  &ccr2_std, &truebaud_std);
+#ifdef NODEBUGGING
+	printk("Std gives bgr = 0x%x, ccr2=0x%x for speed %ld\n",bgr_std,ccr2_std,truebaud_std);
+#endif
+	if(port->chip->c_revision >= SAB82532_VSTR_VN_3_2) 
+	{
+		ok_enh = sab8253x_baudenh(encbaud, clkspeed,
+					  &bgr_enh, &ccr2_enh, &truebaud_enh);
+#ifdef NODEBUGGING
+		printk("Enh gives bgr = 0x%x, ccr2=0x%x for speed %ld\n",bgr_enh,ccr2_enh,truebaud_enh);
+#endif
+	} 
+	else 
+		ok_enh = FALSE;
+	
+	/*
+	 * Did both methods return values?
+	 */
+	if (ok_std && ok_enh) 
+	{
+		/*
+		 * Find the closest of the two.
+		 */
+		if (ABSDIF((truebaud_enh<<1), encbaud) <
+		    ABSDIF((truebaud_std<<1), encbaud)) 
+		{
+			ok_std = FALSE;
+		}
+		else 
+		{
+			ok_enh = FALSE;
+		}
+	}
+	
+	/*
+	 * Now return the values.
+	 */
+	
+	if (ok_std || ok_enh) 
+	{
+		truebaud = ok_std ? truebaud_std : truebaud_enh;
+		
+		/*
+		 * If the true baud rate is off by more than 5%, then
+		 *  we don't support it.
+		 */
+		if (ROUND_DIV(ABSDIF((truebaud<<1), encbaud), encbaud) != 0) 
+		{
+			/*
+			 * We're not even in the right ballpark.  This
+			 *  test is here to deal with overflow conditions.
+			 */
+			return FALSE;
+		}
+		else if (ROUND_DIV(ABSDIF((truebaud<<1), encbaud) * 100,
+				   encbaud) >= 5) 
+		{
+			return FALSE;
+		}
+		
+		*truebaudp = truebaud;
+		
+		if (ok_enh) 
+		{
+			*ccr4 |= SAB82532_CCR4_EBRG;
+			*ccr2 = ccr2_enh;
+			*bgr = bgr_enh;
+#ifdef DEBUGGING
+			printk("Enhanced Baud at %ld, ccr4 = 0x%x, ccr2 = 9x%x, bgr = 0x%x\n",
+			       truebaud,*ccr4,*ccr2,*bgr);
+#endif
+		} 
+		else 
+		{
+			*ccr4 &= ~SAB82532_CCR4_EBRG;
+			*ccr2 = ccr2_std;
+			*bgr = bgr_std;
+#ifdef DEBUGGING
+			printk("Standard Baud at %ld, ccr4 = 0x%x, ccr2 = 9x%x, bgr = 0x%x\n",
+			       truebaud,*ccr4,*ccr2,*bgr);
+#endif
+		}
+		
+		return TRUE;
+	}
+	else 
+	{
+		return FALSE;
+	}
+}
+
+int Sab8253xCountTransmit(SAB_PORT *port)
+{
+	register RING_DESCRIPTOR *rd;
+	register int total;
+	register int count;
+	unsigned long flags;
+	RING_DESCRIPTOR *start;
+	
+	if(port->sabnext2.transmit == NULL)
+	{
+		return 0;
+	}
+	
+	save_flags(flags);
+	cli();
+	rd = port->sabnext2.transmit;
+	start = rd;
+	total = 0;
+	while(1)
+	{
+		count = rd->Count;
+		if((count & OWNER) == OWN_DRIVER)
+		{
+			break;
+		}
+		total += (count & ~OWNER);
+		if(rd->sendcrc)
+		{
+			total += (4 - rd->crcindex);
+		}
+		rd = rd->VNext;
+		if(rd == start)
+		{
+			break;
+		}
+	}
+	restore_flags(flags);
+	return total;
+}
+
+int Sab8253xCountTransmitDescriptors(SAB_PORT *port)
+{
+	register RING_DESCRIPTOR *rd;
+	register int total;
+	register int count;
+	unsigned long flags;
+	RING_DESCRIPTOR *start;
+	
+	if(port->sabnext2.transmit == NULL)
+	{
+		return 0;
+	}
+	
+	save_flags(flags);
+	cli();
+	rd = port->sabnext2.transmit;
+	start = rd;
+	total = 0;
+	while(1)
+	{
+		count = rd->Count;
+		if((count & OWNER) == OWN_DRIVER)
+		{
+			break;
+		}
+		++total;
+		rd = rd->VNext;
+		if(rd == start)
+		{
+			break;
+		}
+	}
+	restore_flags(flags);
+	return total;
+}
+
+int getccr0configS(struct sab_port *port)
+{
+	return port->ccontrol.ccr0;
+}
+
+int getccr1configS(struct sab_port *port)
+{
+	return port->ccontrol.ccr1;
+}
+
+int getccr2configS(struct sab_port *port)
+{
+	return port->ccontrol.ccr2;
+}
+
+int getccr3configS(struct sab_port *port)
+{
+	return port->ccontrol.ccr3;
+}
+
+int getccr4configS(struct sab_port *port)
+{
+	return port->ccontrol.ccr4;
+}
+
+int getrlcrconfigS(struct sab_port *port)
+{
+	return port->ccontrol.rlcr;
+}
+
+int getmodeS(struct sab_port *port)
+{
+	return port->ccontrol.mode;
+}
+
+void sab8253x_init_lineS(struct sab_port *port)
+{
+	unsigned char stat;
+	
+	if(port->chip->c_cim)
+	{
+		if(port->chip->c_cim->ci_type == CIM_SP502)
+		{
+			aura_sp502_program(port, SP502_OFF_MODE);
+		}
+	}
+
+	/*
+	 * Wait for any commands or immediate characters
+	 */
+	sab8253x_cec_wait(port);
+#if 0
+	sab8253x_tec_wait(port);	/* I have to think about this one
+					 * should I assume the line was
+					 * previously in async mode*/
+#endif
+	
+	/*
+	 * Clear the FIFO buffers.
+	 */
+	
+	WRITEB(port, cmdr, SAB82532_CMDR_RHR);
+	sab8253x_cec_wait(port);
+	WRITEB(port,cmdr,SAB82532_CMDR_XRES);
+	
+	
+	/*
+	 * Clear the interrupt registers.
+	 */
+	stat = READB(port, isr0);	/* acks ints */
+	stat = READB(port, isr1);
+	
+	/*
+	 * Now, initialize the UART 
+	 */
+	WRITEB(port, ccr0, 0);	  /* power-down */
+	WRITEB(port, ccr0, getccr0configS(port));
+	WRITEB(port, ccr1, getccr1configS(port));
+	WRITEB(port, ccr2, getccr2configS(port));
+	WRITEB(port, ccr3, getccr3configS(port));
+	WRITEB(port, ccr4, getccr4configS(port));	/* 32 byte receive fifo */
+	WRITEB(port, mode, getmodeS(port));
+	WRITEB(port, tic /* really rlcr */, getrlcrconfigS(port));
+	/* power-up */
+	
+	switch(port->ccontrol.ccr4 & SAB82532_CCR4_RF02)
+	{
+	case SAB82532_CCR4_RF32:
+		port->recv_fifo_size = 32;
+		break;
+	case SAB82532_CCR4_RF16:
+		port->recv_fifo_size = 16;
+		break;
+	case SAB82532_CCR4_RF04:
+		port->recv_fifo_size = 4;
+		break;
+	case SAB82532_CCR4_RF02:
+		port->recv_fifo_size = 2;
+		break;
+	default:
+		port->recv_fifo_size = 32;
+		port->ccontrol.ccr4 &= ~SAB82532_CCR4_RF02;
+		break;
+	}
+	
+	if(port->ccontrol.ccr2 & SAB82532_CCR2_TOE)
+	{
+		RAISE(port, txclkdir);
+	}
+	else
+	{
+		LOWER(port, txclkdir);
+	}
+	
+	SET_REG_BIT(port,ccr0,SAB82532_CCR0_PU);
+
+	if(port->chip->c_cim)
+	{
+		if(port->chip->c_cim->ci_type == CIM_SP502)
+		{
+			aura_sp502_program(port, port->sigmode);
+		}
+	}
+}
+
+/* frees up all skbuffs currently */
+/* held by driver */
+void Sab8253xFreeAllFreeListSKBUFFS(SAB_PORT* priv) /* empty the skbuffer list */
+/* either on failed open */
+/* or on close*/
+{
+	struct sk_buff* skb;
+	
+	if(priv->sab8253xbuflist == NULL)
+	{
+		return;
+	}
+	
+	DEBUGPRINT((KERN_ALERT "sab8253x: freeing %i skbuffs.\n", 
+		    skb_queue_len(priv->sab8253xbuflist)));
+	
+	while(skb_queue_len(priv->sab8253xbuflist) > 0)
+	{
+		skb = skb_dequeue(priv->sab8253xbuflist);
+		dev_kfree_skb_any(skb);
+	}
+	kfree(priv->sab8253xbuflist);
+	priv->sab8253xbuflist = NULL;
+}
+
+int Sab8253xSetUpLists(SAB_PORT *priv)
+{
+	if(priv->sab8253xbuflist)
+	{
+		if(priv->sab8253xc_rcvbuflist)
+		{
+			return 0;
+		}
+		else
+		{
+			return -1;
+		}
+		return 0;
+	}
+	else if(priv->sab8253xc_rcvbuflist)
+	{
+		return -1;
+	}
+	
+	priv->sab8253xbuflist = (struct sk_buff_head*) kmalloc(sizeof(struct sk_buff_head), GFP_KERNEL);
+	if(priv->sab8253xbuflist == NULL)
+	{
+		return -1;
+	}
+	priv->sab8253xc_rcvbuflist = (struct sk_buff_head*) kmalloc(sizeof(struct sk_buff_head), GFP_KERNEL);  
+	if(priv->sab8253xc_rcvbuflist == NULL)
+	{
+		kfree(priv->sab8253xbuflist);
+		return -1;
+	}
+	skb_queue_head_init(priv->sab8253xbuflist);
+	skb_queue_head_init(priv->sab8253xc_rcvbuflist);
+	return 0;
+}
+
+/* sets up transmit ring and one receive sk_buff */
+
+/* set up transmit and receive
+   sk_buff control structures */
+int Sab8253xInitDescriptors2(SAB_PORT *priv, int listsize, int rbufsize)
+{
+	RING_DESCRIPTOR *desc;
+	RING_DESCRIPTOR *xdesc;
+	
+	if(priv->dcontrol2.transmit != NULL)
+		
+	{
+		if(priv->dcontrol2.receive != NULL)
+		{
+			return 0;
+		}
+		return -1;
+	}
+	else if(priv->dcontrol2.receive != NULL)
+	{
+		return -1;
+	}
+	
+	priv->dcontrol2.transmit = (RING_DESCRIPTOR*) 
+		kmalloc(sizeof(RING_DESCRIPTOR) * listsize, GFP_KERNEL);
+	/* dcontrol2 is an historical
+	   artifact from when the code
+	   talked to an intelligent controller */
+	if(priv->dcontrol2.transmit == NULL)
+	{
+		return -1;
+	}
+	
+	priv->dcontrol2.receive = (RING_DESCRIPTOR*)
+		kmalloc(sizeof(RING_DESCRIPTOR), GFP_KERNEL); /* only one receive sk_buffer */
+	if(priv->dcontrol2.receive == NULL)
+	{
+		kfree(priv->dcontrol2.transmit);
+		priv->dcontrol2.transmit = NULL;
+		return -1;
+	}
+	
+	for(xdesc = priv->dcontrol2.transmit; 
+	    xdesc < &priv->dcontrol2.transmit[listsize - 1];
+	    xdesc = &xdesc[1])	/* set up transmit descriptors */
+	{
+		xdesc->HostVaddr = NULL;
+		xdesc->VNext = &xdesc[1];
+		xdesc->Count = 0 | OWN_DRIVER;
+		xdesc->crc = 0;
+		xdesc->sendcrc = 0;
+		xdesc->crcindex = 0;
+	}
+	xdesc->HostVaddr = NULL;
+	xdesc->VNext = priv->dcontrol2.transmit; /* circular list */
+	xdesc->Count = 0 | OWN_DRIVER;
+	xdesc->crc = 0;
+	xdesc->sendcrc = 0;
+	xdesc->crcindex = 0;
+	
+	desc = priv->dcontrol2.receive; /* only need one descriptor for receive */
+	desc->HostVaddr = NULL;
+	desc->VNext = &desc[0];
+	
+	desc = priv->dcontrol2.receive;
+	desc->HostVaddr = dev_alloc_skb(rbufsize);
+	if(desc->HostVaddr == NULL)
+	{
+		printk(KERN_ALERT "Unable to allocate skb_buffers (rx 0).\n");
+		printk(KERN_ALERT "Driver initialization failed.\n");
+		kfree(priv->dcontrol2.transmit);
+		kfree(priv->dcontrol2.receive);
+		priv->dcontrol2.transmit = NULL; /* get rid of descriptor ring */
+		priv->dcontrol2.receive = NULL; /* get rid of descriptor */
+		/* probably should do some deallocation of sk_buffs*/
+		/* but will take place in the open */
+		return -1;
+	}
+	skb_queue_head(priv->sab8253xbuflist, (struct sk_buff*) desc->HostVaddr);
+	desc->Count = rbufsize|OWN_SAB;	/* belongs to int handler */
+	desc->crc = 0;
+	desc->sendcrc = 0;
+	desc->crcindex = 0;
+	
+	/* setup the various pointers */
+	priv->active2 = priv->dcontrol2; /* insert new skbuff */
+	priv->sabnext2 = priv->dcontrol2; /* transmit from here */
+	
+	return 0;
+}
+
+/* loads program, waits for PPC */
+/* and completes initialization*/
+
+void Sab8253xCleanUpTransceiveN(SAB_PORT* priv)
+{
+	Sab8253xFreeAllFreeListSKBUFFS(priv);	
+	Sab8253xFreeAllReceiveListSKBUFFS(priv);
+	
+	/* these are also cleaned up in the module cleanup routine */
+	/* should probably only be done here */
+	if(priv->dcontrol2.receive)
+	{
+		kfree(priv->dcontrol2.receive);
+		priv->dcontrol2.receive = NULL;
+	}
+	if(priv->dcontrol2.transmit)
+	{
+		kfree(priv->dcontrol2.transmit);
+		priv->dcontrol2.transmit = NULL;
+	}
+	priv->active2 = priv->dcontrol2;
+	priv->sabnext2 = priv->dcontrol2; 
+}
+
+void Sab8253xFreeAllReceiveListSKBUFFS(SAB_PORT* priv) /* empty the skbuffer list */
+/* either on failed open */
+/* or on close*/
+{
+	struct sk_buff* skb;
+	
+	if(priv->sab8253xc_rcvbuflist == NULL)
+	{
+		return;
+	}
+	
+	DEBUGPRINT((KERN_ALERT "sab8253x: freeing %i skbuffs.\n", 
+		    skb_queue_len(priv->sab8253xc_rcvbuflist)));
+	
+	while(skb_queue_len(priv->sab8253xc_rcvbuflist) > 0)
+	{
+		skb = skb_dequeue(priv->sab8253xc_rcvbuflist);
+		dev_kfree_skb_any(skb);
+	}
+	kfree(priv->sab8253xc_rcvbuflist);
+	priv->sab8253xc_rcvbuflist = NULL;
+}
+
+/*
+ * This routine is called to set the UART divisor registers to match
+ * the specified baud rate for a serial port.
+ */
+
+void sab8253x_change_speedN(struct sab_port *port)
+{
+	unsigned long	flags;
+	unsigned char ccr2=0, ccr4=0, ebrg=0;
+	int bits = 8;
+	
+#ifdef DEBUGGING
+	printk("Change speed!  ");
+#endif
+	
+	if(!sab8253x_baud(port, (port->baud)*2, &ebrg, &ccr2, &ccr4, &(port->baud))) 
+	{
+		printk("Aurora Warning. baudrate %ld could not be set! Using 115200", port->baud);
+		port->baud = 115200;
+		sab8253x_baud(port, (port->baud*2), &ebrg, &ccr2, &ccr4, &(port->baud));
+	}
+	
+	if (port->baud)
+	{
+		port->timeout = (port->xmit_fifo_size * HZ * bits) / port->baud;
+		port->cec_timeout = port->tec_timeout >> 2;
+	}
+	else
+	{
+		port->timeout = 0;
+		port->cec_timeout = SAB8253X_MAX_CEC_DELAY;
+	}
+	port->timeout += HZ / 50;		/* Add .02 seconds of slop */
+	
+	save_flags(flags); 
+	cli();
+	sab8253x_cec_wait(port);
+	
+	WRITEB(port, bgr, ebrg);
+	WRITEB(port, ccr2, READB(port, ccr2) & ~(0xc0)); /* clear out current baud rage */
+	WRITEB(port, ccr2, READB(port, ccr2) | ccr2);
+	WRITEB(port, ccr4, (READB(port,ccr4) & ~SAB82532_CCR4_EBRG) | ccr4);
+	
+	if (port->flags & FLAG8253X_CTS_FLOW) 
+	{
+		WRITEB(port, mode, READB(port,mode) & ~(SAB82532_MODE_RTS));
+		port->interrupt_mask1 &= ~(SAB82532_IMR1_CSC);
+		WRITEB(port, imr1, port->interrupt_mask1);
+	} 
+	else 
+	{
+		WRITEB(port, mode, READB(port,mode) | SAB82532_MODE_RTS);
+		port->interrupt_mask1 |= SAB82532_IMR1_CSC;
+		WRITEB(port, imr1, port->interrupt_mask1);
+	}
+	WRITEB(port, mode, READB(port, mode) | SAB82532_MODE_RAC);
+	restore_flags(flags);
+}
+
+void sab8253x_shutdownN(struct sab_port *port)
+{
+	unsigned long flags;
+	
+	if (!(port->flags & FLAG8253X_INITIALIZED))
+	{
+		return;
+	}
+	
+	save_flags(flags); cli(); /* Disable interrupts */
+	
+	/*
+	 * clear delta_msr_wait queue to avoid mem leaks: we may free the irq
+	 * here so the queue might never be waken up
+	 */
+	wake_up_interruptible(&port->delta_msr_wait);
+	
+	/* Disable Interrupts */
+	
+	port->interrupt_mask0 = 0xff;
+	WRITEB(port, imr0, port->interrupt_mask0);
+	port->interrupt_mask1 = 0xff;
+	WRITEB(port, imr1, port->interrupt_mask1);
+	
+	LOWER(port,rts);
+	LOWER(port,dtr);
+	
+	/* Disable Receiver */	
+	CLEAR_REG_BIT(port,mode,SAB82532_MODE_RAC);
+	
+	/* Power Down */	
+	CLEAR_REG_BIT(port,ccr0,SAB82532_CCR0_PU);
+	
+	port->flags &= ~FLAG8253X_INITIALIZED;
+	restore_flags(flags);
+}
+
+int sab8253x_block_til_ready(struct tty_struct *tty, struct file * filp,
+			     struct sab_port *port)
+{
+	DECLARE_WAITQUEUE(wait, current);
+	int retval;
+	int do_clocal = 0;
+	unsigned long flags;
+	
+	/*
+	 * If the device is in the middle of being closed, then block
+	 * until it's done, and then try again.
+	 */
+	if (tty_hung_up_p(filp) ||
+	    (port->flags & FLAG8253X_CLOSING)) 
+	{
+		if (port->flags & FLAG8253X_CLOSING)
+		{
+			interruptible_sleep_on(&port->close_wait); /* finish up previous close */
+		}
+#ifdef SERIAL_DO_RESTART
+		if (port->flags & FLAG8253X_HUP_NOTIFY)
+		{
+			return -EAGAIN;
+		}
+		else
+		{
+			return -ERESTARTSYS;
+		}
+#else
+		return -EAGAIN;
+#endif
+	}
+	
+	/*
+	 * If this is a callout device, then just make sure the normal
+	 * device isn't being used.
+	 */
+	if (tty->driver.subtype == SERIAL_TYPE_CALLOUT) 
+	{
+		if (port->flags & FLAG8253X_NORMAL_ACTIVE) 
+		{
+			return -EBUSY;	/* async, sync tty or network driver active */
+		}
+		if ((port->flags & FLAG8253X_CALLOUT_ACTIVE) &&
+		    (port->flags & FLAG8253X_SESSION_LOCKOUT) &&
+		    (port->session != current->session))
+		{
+			return -EBUSY;
+		}
+		if ((port->flags & FLAG8253X_CALLOUT_ACTIVE) &&
+		    (port->flags & FLAG8253X_PGRP_LOCKOUT) &&
+		    (port->pgrp != current->pgrp))
+		{
+			return -EBUSY;
+		}
+		port->flags |= FLAG8253X_CALLOUT_ACTIVE; /* doing a callout */
+		return 0;
+	}
+	
+	/* sort out async vs sync tty, not call out */
+	/*
+	 * If non-blocking mode is set, or the port is not enabled,
+	 * then make the check up front and then exit.
+	 */
+	
+	if ((filp->f_flags & O_NONBLOCK) ||
+	    (tty->flags & (1 << TTY_IO_ERROR))) 
+	{
+		if (port->flags & FLAG8253X_CALLOUT_ACTIVE)
+		{
+			return -EBUSY;
+		}
+		port->flags |= FLAG8253X_NORMAL_ACTIVE;
+		return 0;
+	}
+	
+	if (port->flags & FLAG8253X_CALLOUT_ACTIVE) 
+	{
+		if (port->normal_termios.c_cflag & CLOCAL)
+		{
+			do_clocal = 1;
+		}
+	} 
+	else if (tty->termios->c_cflag & CLOCAL)
+	{
+		do_clocal = 1;
+	}
+	
+	/*
+	 * Block waiting for the carrier detect and the line to become
+	 * free (i.e., not in use by the callout).  While we are in
+	 * this loop, port->count is dropped by one, so that
+	 * sab8253x_close() knows when to free things.  We restore it upon
+	 * exit, either normal or abnormal.
+	 */
+	
+	/* The port decrement logic is probably */
+	/* broken -- hence if def'd out -- it does*/
+	retval = 0;
+	add_wait_queue(&port->open_wait, &wait); /* starts the wait but does not block here */
+	port->blocked_open++;
+	while (1) 
+	{
+		save_flags(flags);
+		cli();
+		if (!(port->flags & FLAG8253X_CALLOUT_ACTIVE) &&
+		    (tty->termios->c_cflag & CBAUD)) 
+		{
+			RAISE(port, dtr);
+			RAISE(port, rts);	/* maybe not correct for sync */
+			/*
+			 * ??? Why changing the mode here? 
+			 *  port->regs->rw.mode |= SAB82532_MODE_FRTS;
+			 *  port->regs->rw.mode &= ~(SAB82532_MODE_RTS);
+			 */
+		}
+		restore_flags(flags);
+		current->state = TASK_INTERRUPTIBLE;
+		if (tty_hung_up_p(filp) ||
+		    !(port->flags & FLAG8253X_INITIALIZED)) 
+		{
+#ifdef SERIAL_DO_RESTART
+			if (port->flags & FLAG8253X_HUP_NOTIFY)
+			{
+				retval = -EAGAIN;
+			}
+			else
+			{
+				retval = -ERESTARTSYS;	
+			}
+#else
+			retval = -EAGAIN;
+#endif
+			break;
+		}
+		if (!(port->flags & FLAG8253X_CALLOUT_ACTIVE) &&
+		    !(port->flags & FLAG8253X_CLOSING) &&
+		    (do_clocal || ISON(port,dcd))) 
+		{
+			break;
+		}
+#ifdef DEBUG_OPEN
+		printk("sab8253x_block_til_ready:2 flags = 0x%x\n",port->flags);
+#endif
+		if (signal_pending(current)) 
+		{
+			retval = -ERESTARTSYS;
+			break;
+		}
+#ifdef DEBUG_OPEN
+		printk("sab8253x_block_til_ready blocking: ttyS%d, count = %d, flags = %x, clocal = %d, vstr = %02x\n",
+		       port->line, port->count, port->flags, do_clocal, READB(port,vstr));
+#endif
+		schedule();
+	}
+	current->state = TASK_RUNNING;
+	remove_wait_queue(&port->open_wait, &wait);
+	port->blocked_open--;
+#ifdef DEBUG_OPEN
+	printk("sab8253x_block_til_ready after blocking: ttys%d, count = %d\n",
+	       port->line, port->count);
+#endif
+	if (retval)
+	{
+		return retval;
+	}
+	port->flags |= FLAG8253X_NORMAL_ACTIVE;
+	return 0;
+}
+
+/*
+ * sab8253x_wait_until_sent() --- wait until the transmitter is empty
+ */
+void sab8253x_wait_until_sent(struct tty_struct *tty, int timeout)
+{
+	struct sab_port *port = (struct sab_port *)tty->driver_data;
+	unsigned long orig_jiffies, char_time;
+	
+	if (sab8253x_serial_paranoia_check(port,tty->device,"sab8253x_wait_until_sent"))
+	{
+		return;
+	}
+	
+	orig_jiffies = jiffies;
+	/*
+	 * Set the check interval to be 1/5 of the estimated time to
+	 * send a single character, and make it at least 1.  The check
+	 * interval should also be less than the timeout.
+	 * 
+	 * Note: we have to use pretty tight timings here to satisfy
+	 * the NIST-PCTS.
+	 */
+	char_time = (port->timeout - HZ/50) / port->xmit_fifo_size;
+	char_time = char_time / 5;
+	if (char_time == 0)
+	{
+		char_time = 1;
+	}
+	if (timeout)
+	{
+		char_time = MIN(char_time, timeout);
+	}
+	while ((Sab8253xCountTransmit(port) > 0) || !port->all_sent) 
+	{
+		current->state = TASK_INTERRUPTIBLE;
+		schedule_timeout(char_time);
+		if (signal_pending(current))
+		{
+			break;
+		}
+		if (timeout && time_after(jiffies, orig_jiffies + timeout))
+		{
+			break;
+		}
+	}
+}
+
+void sab8253x_flush_buffer(struct tty_struct *tty)
+{
+	struct sab_port *port = (struct sab_port *)tty->driver_data;
+	unsigned long flags;
+	register RING_DESCRIPTOR *freeme;
+	
+	if(sab8253x_serial_paranoia_check(port, tty->device, "sab8253x_flush_buffer"))
+	{
+		return;
+	}
+	
+	if(port->sabnext2.transmit == NULL)
+	{
+		return;
+	}
+	
+	save_flags(flags); 
+	cli();			/* need to turn off ints because mucking
+				   with sabnext2 */
+#ifndef FREEININTERRUPT
+	freeme = port->active2.transmit;
+	do				/* just go all around */
+	{
+		if(freeme->HostVaddr)
+		{
+			skb_unlink((struct sk_buff*)freeme->HostVaddr);
+			dev_kfree_skb_any((struct sk_buff*)freeme->HostVaddr);
+			freeme->HostVaddr = NULL;
+		}
+		freeme->sendcrc = 0;
+		freeme->crcindex = 0;
+		freeme->Count = OWN_DRIVER;
+		freeme = (RING_DESCRIPTOR*) freeme->VNext;
+	}
+	while(freeme != port->active2.transmit);
+#else  /* buffers only from sabnext2.transmit to active2.transmit */
+	while((port->sabnext2.transmit->Count & OWNER) == OWN_SAB) /* clear out stuff waiting to be transmitted */
+	{
+		freeme = port->sabnext2.transmit;
+		if(freeme->HostVaddr)
+		{
+			skb_unlink((struct sk_buff*)freeme->HostVaddr);
+			dev_kfree_skb_any((struct sk_buff*)freeme->HostVaddr);
+			freeme->HostVaddr = NULL;
+		}
+		freeme->sendcrc = 0;
+		freeme->crcindex = 0;
+		freeme->Count = OWN_DRIVER;
+		port->sabnext2.transmit = freeme->VNext;
+	}
+#endif
+	port->sabnext2.transmit = port->active2.transmit; /* should already be equal to be sure */
+	sab8253x_cec_wait(port);
+	WRITEB(port,cmdr,SAB82532_CMDR_XRES);
+	restore_flags(flags);
+	
+	wake_up_interruptible(&tty->write_wait); /* wake up tty driver */
+	if ((tty->flags & (1 << TTY_DO_WRITE_WAKEUP)) &&
+	    tty->ldisc.write_wakeup)
+	{
+		(*tty->ldisc.write_wakeup)(tty);
+	}
+}
+

FUNET's LINUX-ADM group, linux-adm@nic.funet.fi
TCL-scripts by Sam Shen (who was at: slshen@lbl.gov)